首页 文章 精选 留言 我的

精选列表

搜索[面试],共4912篇文章
优秀的个人博客,低调大师

java面试-数据库索引全面解析

索引是什么? 数据库中查找操作非常普遍,索引就是提升查找速度的一种手段。 索引分类 B+树索引它就是传统意义上的索引,它是最常用、最有效的索引。 哈希索引哈希索引是一种自适应的索引,数据库会根据表的使用情况自动生成哈希索引,我们人为是没办法干预的。 全文索引用于实现关键词搜索。但它只能根据空格分词,因此不支持中文。若要实现搜索功能,可选择lucene。 RTree索引在mysql很少使用,仅支持geometry数据类型;相对于BTREE,RTREE的优势在于范围查找。 B+树索引 数据库以页为存储单元,一个页是8K(8192Byte),一页可以存放N条记录。页在B+树中分为:数据页和索引页。B+树的高一般为2-4层,因此查找某一键值的行记录只需2-4次IO,效率较高。 聚集索引 和 非聚集索引 不管是聚集索引还是非聚集索引,它们的逻辑结构都一棵是B+树,它们的唯一区别在于: 聚集索引的数据页存放的是完整的记录;也就是说,聚集索引决定了表的物理存储顺序; 非聚集索引的数据页只存指向记录的地址信息,它真正的数据已经在聚集索引中存储了。 联合索引 和 覆盖索引 联合索引当查询条件涉及多列时,可以使用联合索引。 覆盖索引只需通过辅助索引就能获取要查询的信息,而无需再次通过聚集索引查询具体的记录信息。由于覆盖索引并不包含整行的记录,因此它的大小远远小于聚集索引。它比较适合做一些统计操作。 MyISAM索引实现 主键索引在主键索引中,索引页中存放的是主键和指向数据页的偏移量;数据页中存放的是主键和该主键所属行记录的地址空间。 辅助索引在MyISAM中,主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key可以重复。 综上所述,在MyISAM中,索引文件和数据文件分开存放,不管是主键索引还是辅助索引,都属于非聚集索引。 InnoDB索引实现 主键索引索引页仍然存放主键和和指向数据页的偏移量,但数据页存放的是完整的记录。也就是在InnoDB中,数据和主键索引是存放在一起的。 辅助索引索引节点存放的内容一样,仍然是键值信息和指向数据页的偏移量;但数据页中存放的是键值信息和该键值对应的主键。然后通过主键查询主键索引就能找到该条记录。 综上所述: 聚集索引这种实现方式使得按主键的搜索十分高效,但是辅助索引搜索需要检索两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。 InnoDB的辅助索引也会包含主键列,所以,如果主键定义的比较大,其他索引也将很大。如果想在表上定义 、很多索引,则争取尽量把主键定义得小一些。InnoDB 不会压缩索引。 索引的优点 第一,通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。 第二,可以大大加快数据的检索速度,这也是创建索引的最主要的原因。 第三,可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。 第四,在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。 第五,通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。 索引的缺点 第一,创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。 第二,索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。 第三,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。 哪些情况需要加索引? 在经常需要搜索的列上,可以加快搜索的速度; 在作为主键的列上,强制该列的唯一性和组织表中数据的排列结构; 在经常用在连接的列上,这些列主要是一些外键,可以加快连接的速度; 在经常需要根据范围进行搜索的列上创建索引,因为索引已经排序,其指定的范围是连续的; 在经常需要排序的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间; 在经常使用在WHERE子句中的列上面创建索引,加快条件的判断速度。 哪些情况不需要加索引? 第一,对于那些在查询中很少使用或者参考的列不应该创建索引。这是因为,既然这些列很少使用到,因此有索引或者无索引,并不能提高查询速度。相反,由于增加了索引,反而降低了系统的维护速度和增大了空间需求。 第二,对于那些只有很少数据值的列也不应该增加索引。这是因为,由于这些列的取值很少,例如人事表的性别列,在查询的结果中,结果集的数据行占了表中数据行的很大比例,即需要在表中搜索的数据行的比例很大。增加索引,并不能明显加快检索速度。 第三,对于那些定义为text, image和bit数据类型的列不应该增加索引。这是因为,这些列的数据量要么相当大,要么取值很少。第四,当修改性能远远大于检索性能时,不应该创建索引。这是因为,修改性能和检索性能是互相矛盾的。当增加索引时,会提高检索性能,但是会降低修改性能。当减少索引时,会提高修改性能,降低检索性能。因此,当修改性能远远大于检索性能时,不应该创建索引。

优秀的个人博客,低调大师

java面试-数据库三大范式

第一范式 第一范式(1NF)要求数据库表的每一列都是不可分割的基本数据项,同一列中不能有多个值。 若某一列有多个值,可以将该列单独拆分成一个实体,新实体和原实体间是一对多的关系。 在任何一个关系数据库中,第一范式(1NF)是对关系模式的基本要求,不满足第一范式(1NF)的数据库就不是关系数据库。 第二范式 满足第二范式(2NF)必须先满足第一范式(1NF)。 第二范式要求实体中没一行的所有非主属性都必须完全依赖于主键;即:非主属性必须完全依赖于主键。 完全依赖:主键可能由多个属性构成,完全依赖要求不允许存在非主属性依赖于主键中的某一部分属性。 若存在哪个非主属性依赖于主键中的一部分属性,那么要将发生部分依赖的这一组属性单独新建一个实体,并且在旧实体中用外键与新实体关联,并且新实体与旧实体间是一对多的关系。 第三范式 满足第三范式必须先满足第二范式。 第三范式要求:实体中的属性不能是其他实体中的非主属性。因为这样会出现冗余。即:属性不依赖于其他非主属性。 如果一个实体中出现其他实体的非主属性,可以将这两个实体用外键关联,而不是将另一张表的非主属性直接写在当前表中。

优秀的个人博客,低调大师

java-面试-Java并发容器大合集

概述 java.util包中的大部分容器都是非线程安全的,若要在多线程中使用容器,你可以使用Collections提供的包装函数:synchronizedXXX,将普通容器变成线程安全的容器。但该方法仅仅是简单地给容器使用同步,效率很低。因此并发大师Doug Lea提供了java.util.concurrent包,提供高效的并发容器。并且为了保持与普通的容器的接口一致性,仍然使用util包的接口,从而易于使用、易于理解。 PS:问题:synchronizedXXX究竟对容器做了什么从而能达到线程安全的目的? 类图 List和Set JUC包中List接口的实现类:CopyOnWriteArrayList CopyOnWriteArrayList是线程安全的ArrayList JUC包中Set接口的实现类:CopyOnWriteArraySet、ConcurrentSkipListSet CopyOnWriteArraySet是线程安全的Set,它内部包含了一个CopyOnWriteArrayList,因此本质上是由CopyOnWriteArrayList实现的。 ConcurrentSkipListSet相当于线程安全的TreeSet。它是有序的Set。它由ConcurrentSkipListMap实现。 Map ConcurrentHashMap:线程安全的HashMap。采用分段锁实现高效并发。 ConcurrentSkipListMap:线程安全的有序Map。使用跳表实现高效并发。 Queue ConcurrentLinkedQueue:线程安全的无界队列。底层采用单链表。支持FIFO。 ConcurrentLinkedDeque:线程安全的无界双端队列。底层采用双向链表。支持FIFO和FILO。 ArrayBlockingQueue:数组实现的阻塞队列。 LinkedBlockingQueue:链表实现的阻塞队列。 LinkedBlockingDeque:双向链表实现的双端阻塞队列。 CopyOnWrite容器(写时复制容器) CopyOnWrite容器包括:CopyOnWriteArrayList和CopyOnWriteArraySet。 PS:CopyOnWriteArraySet有CopyOnWriteArrayList实现。 特性 适用于读操作远远多于写操作,并且数据量较小的情况。 修改容器的代价是昂贵的,因此建议批量增加addAll、批量删除removeAll。 CopyOnWrite容器是如何实现线程安全的? 使用volatile修饰数组引用:确保数组引用的内存可见性。 对容器修改操作进行同步:从而确保同一时刻只能有一条线程修改容器(因为修改容器都会产生一个新的容器,增加同步可避免同一时刻复制生成多个容器,从而无法保证数组数据的一致性) 修改时复制容器:确保所有修改操作都作用在新数组上,原本的数组在创建过后就用不变化,从而其他线程可以放心地读。 新增方法 CopyOnWriteArrayList: // 添加集合中不存在的元素 int addAllAbsent(Collection<? extends E> c) // 该元素若不存在则添加 boolean addIfAbsent(E e) CopyOnWriteArraySet:木有新增! 迭代 CopyOnWriteArrayList拥有内部类:COWIterator,它是ListIterator的子类。 当调用iterator函数时返回的是COWIterator对象。 COWIterator不允许修改容器,你若调用则会抛出UnsupportedOperationException。 优点 读操作无需加锁,从而高效。 缺点 数据一致性问题 由于迭代的是容器当前的快照,因此在迭代过程中容器发生的修改并不能实时被当前正在迭代的线程感知。 内存占用问题 由于修改容器都会复制数组,从而当数组超大时修改容器效率很低。 PS:因此写时复制容器适合存储小容量数据。 ConcurrentHashMap java.util包中提供了线程安全的HashTable,但这家伙只是通过简单的同步来实现线程安全,因此效率低。只要有一条线程获取了容器的锁之后,其他所有的线程访问同步函数都会被阻塞。因此同一时刻只能有一条线程访问同步函数。而ConcurrentHashMap采用了分段锁机制实现高效的并发访问。 分段锁原理 ConcurrentHashMap由多个Segment构成,每个Segment都包含一张哈希表。每次操作只将操作数据所属的Segment锁起来,从而避免将整个锁住。 数据结构 ConcurrentHashMap内部包含了Segment数组,而每个Segment又继承自ReentrantLock,因此它是一把可重入的锁。 Segment内部拥有一个HashEntry数组,它就是一张哈希表。HashEntry是单链表的一个节点,HashEntry数组存储单链表的表头节点。 新增API V putIfAbsent(K key, V value) ConcurrentSkipListMap 它是一个有序的Map,相当于TreeMap。 TreeMap采用红黑树实现排序,而ConcurrentHashMap采用跳表实现有序。 跳表的由来 作用:存储有序序列,并且实现高效的查找与插入删除。 存储有序序列最简单的办法就是使用数组,从而查找可以采用二分搜索,但插入删除需要移动元素较为低效。 因此出现了二叉搜索树,用来解决插入删除移动元素的问题。但二叉搜索树在最坏情况下会退化成一条单链表,搜索的效率降为O(n)。 为了避免二叉搜索树的退化,出现了二叉平衡树,它在每次插入删除节点后都会重新调整树形,使得它仍然保持平衡,从而保证了搜索效率,也保证了插入删除的效率。 此外,根据平衡算法的不同,二叉平衡树又分为:B+树、B-树、红黑树。 但平衡算法过于复杂,因此出现跳表。 跳表介绍 跳表是条有序的单链表,它的每个节点都有多个指向后继节点的引用。 它有多个层次,上层都是下层的子集,从而能跳过不必要的节点,提升搜索速度。 它通过空间来换取时间。 如查找19的过程: ConcurrentSkipListSet 它是一个有序的、线程安全的Set,相当于线程安全的TreeSet。 它内部拥有ConcurrentSkipListMap实例,本质上就是一个ConcurrentSkipListMap,只不过仅使用了Map中的key。 ArrayBlockingQueue 概要 ArrayBlockingQueue是一个 数组实现的 线程安全的 有限 阻塞队列。 数据结构 ArrayBlockingQueue继承自AbstractQueue,并实现了BlockingQueue接口。 ArrayBlockingQueue内部由Object数组存储元素,构造时必须要指定队列容量。 ArrayBlockingQueue由ReentrantLock实现队列的互斥访问,并由notEmpty、notFull这两个Condition分别实现队空、队满的阻塞。 ReentrantLock分为公平锁和非公平锁,可以在构造ArrayBlockingQueue时指定。默认为非公平锁。 新增API // 在队尾添加指定元素,若队已满则等待指定时间 boolean offer(E e, long timeout, TimeUnit unit) // 获取并删除队首元素,若队为空则阻塞等待 E take() // 添加指定元素,若队已满则一直等待 void put(E e) // 获取队首元素,若队为空,则等待指定时间 E poll(long timeout, TimeUnit unit) 队满、队空阻塞唤醒的原理 队满阻塞:当添加元素时,若队满,则调用notFull.await()阻塞当前线程;当移除一个元素时调用notFull.signal()唤醒在notFull上等待的线程。 队空阻塞:当删除元素时,若队为空,则调用notEmpty.await()阻塞当前线程;当队首添加元素时,调用notEmpty.signal()唤醒在notEmpty上等待的线程。 LinkedBlockingQueue 概要 LinkedBlockingQueue是一个 单链表实现的、线程安全的、无限 阻塞队列。 数据结构 LinkedBlockingQueue继承自AbstractQueue,实现了BlockingQueue接口。 LinkedBlockingQueue由单链表实现,因此是个无限队列。但为了方式无限膨胀,构造时可以加上容量加以限制。 LinkedBlockingQueue分别采用读取锁和插入锁控制读取/删除 和 插入过程的并发访问,并采用notEmpty和notFull两个Condition实现队满队空的阻塞与唤醒。 队满队空阻塞唤醒的原理 队满阻塞:若要插入元素,首先需要获取putLock;在此基础上,若此时队满,则调用notFull.await(),阻塞当前线程;当移除一个元素后调用notFull.signal()唤醒在notFull上等待的线程;最后,当插入操作完成后释放putLock。 队空阻塞:若要删除/获取元素,首先要获取takeLock;在此基础上,若队为空,则调用notEmpty.await(),阻塞当前线程;当插入一个元素后调用notEmpty.signal()唤醒在notEmpty上等待的线程;最后,当删除操作完成后释放takeLock。 PS:API和ArrayBlockingQueue一样。 LinkedBlockingDeque 概要 它是一个 由双向链表实现的、线程安全的、 双端 无限 阻塞队列。 数据结构 ConcurrentLinkedQueue 概述 它是一个由单链表实现的、线程安全的、无限 队列。 数据结构 它仅仅继承了AbstractQueue,并未实现BlockingQueue接口,因此它不是阻塞队列,仅仅是个线程安全的普通队列。 特性 head、tail、next、item均使用volatile修饰,保证其内存可见性,并未使用锁,从而提高并发效率。 PS:它究竟是怎样在不使用锁的情况下实现线程安全的?

优秀的个人博客,低调大师

java面试-Java并发编程(二)——重排序

当我们写一个单线程程序时,总以为计算机会一行行地运行代码,然而事实并非如此。 什么是重排序? 重排序指的是编译器、处理器在不改变程序执行结果的前提下,重新排列指令的执行顺序,以达到最佳的运行效率。 重排序分类 重排序分为:编译器重排序 和 处理器重排序。 数据依赖 编译器和处理器并不会随意的改变指令的执行顺序,因为有些指令之间是有依赖关系的,若改变了他们的执行顺序,就会出现错误的结果。因此,编译器和处理器只会对没有依赖关系的指令进行重排序。 数据依赖:若相邻的两条指令访问同一个变量,并且其中有一条指令执行写操作,那么这样的两条指令之间存在数据依赖。对于有数据依赖关系的指令,不会发生重排序。 数据依赖关系总结一下为以下三种情况: 指令 示例 读后写 a=b;b=1; 写后写 a=1;a=2; 写后读 a=1;b=a; as-if-serial 在单线程开发中,程序员不需要知道指令是如何重排序的,只要简单地认为指令是按照顺序依次执行的即可。这就是as-if-serial的语义,即:貌似是串行的。

优秀的个人博客,低调大师

java-面试-Java并发编程(七)——Executors

Executors框架简介 Executor框架便是Java 5中引入的,其内部使用了线程池机制,它在java.util.cocurrent 包下,通过该框架来控制线程的启动、执行和关闭,可以简化并发编程的操作。因此,在Java 5之后,通过Executor来启动线程比使用Thread的start方法更好,除了更易管理,效率更好(用线程池实现,节约开销)外,还有关键的一点:有助于避免this逸出。 Executor框架包括:线程池,Executor,Executors,ExecutorService,CompletionService,Future,Callable等。 Executor类 Executor接口中之定义了一个方法execute(Runnable command),该方法接收一个Runable实例,它用来执行一个任务,任务即一个实现了Runnable接口的类。 ExecutorService类 ExecutorService接口继承自Executor接口,它提供了更丰富的实现多线程的方法,比如,ExecutorService提供了关闭自己的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。 ExecutorService的生命周期包括三种状态:运行、关闭、终止。创建后便进入运行状态,当调用了shutdown()方法时,便进入关闭状态,此时意味着ExecutorService不再接受新的任务,但它还在执行已经提交了的任务,当所有已经提交了的任务执行完后,便到达终止状态。如果不调用shutdown()方法,ExecutorService会一直处在运行状态,不断接收新的任务,执行新的任务,服务器端一般不需要关闭它,保持一直运行即可。 Executors类 Executors提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。 public static ExecutorServicenewFixedThreadPool(int nThreads) 创建固定数目线程的线程池。 newFixedThreadPool与cacheThreadPool差不多,也是能reuse就用,但不能随时建新的线程; 任意时间点,最多只能有固定数目的活动线程存在,此时如果有新的线程要建立,只能放在另外的队列中等待,直到当前的线程中某个线程终止直接被移出池子; 和cacheThreadPool不同,FixedThreadPool没有IDLE机制(可能也有,但既然文档没提,肯定非常长,类似依赖上层的TCP或UDP IDLE机制之类的),所以FixedThreadPool多数针对一些很稳定很固定的正规并发线程,多用于服务器; 从方法的源代码看,cache池和fixed 池调用的是同一个底层 池,只不过参数不同:fixed池线程数固定,并且是0秒IDLE(无IDLE)cache池线程数支持0-Integer.MAX_VALUE(显然完全没考虑主机的资源承受能力),60秒IDLE。 public static ExecutorServicenewCachedThreadPool() 创建一个可缓存的线程池,调用execute将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有60秒钟未被使用的线程。 缓存型池子通常用于执行一些生存期很短的异步型任务; 注意,放入CachedThreadPool的线程不必担心其结束,超过TIMEOUT不活动,其会自动被终止。 缺省timeout是60s。 public static ExecutorServicenewSingleThreadExecutor() 创建一个单线程化的Executor。 用的是和cache池和fixed池相同的底层池,但线程数目是1-1,0秒IDLE(无IDLE) public staticScheduledExecutorServicenewScheduledThreadPool(int corePoolSize)创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。Timer类存在以下缺陷: Timer类不管启动多少定时器,但它只会启动一条线程,当有多个定时任务时,就会产生延迟。如:我们要求一个任务每隔3S执行,且执行大约需要10S,第二个任务每隔5S执行,两个任务同时启动。若使用Timer我们会发现,第而个任务是在第一个任务执行结束后的5S才开始执行。这就是多任务的延时问题。 若多个定时任务中有一个任务抛异常,那所有任务都无法执行。 Timer执行周期任务时依赖系统时间。若系统时间发生变化,那Timer执行结果可能也会发生变化。而ScheduledExecutorService基于时间的延迟,并非时间,因此不会由于系统时间的改变发生执行变化。综上所述,定时任务要使用ScheduledExecutorService取代Timer。 Executor执行任务 在Java 5之后,任务分两类:一类是实现了Runnable接口的类,一类是实现了Callable接口的类。两者都可以被ExecutorService执行,但是Runnable任务没有返回值,而Callable任务有返回值。并且Callable的call()方法只能通过ExecutorService的submit(Callable task) 方法来执行,并且返回一个 Future,是表示任务等待完成的 Future。 Callable接口类似于Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable 不会返回结果,并且无法抛出经过检查的异常而Callable又返回结果,而且当获取返回结果时可能会抛出异常。Callable中的call()方法类似Runnable的run()方法,区别同样是有返回值,后者没有。 当将一个Callable的对象传递给ExecutorService的submit方法,则该call方法自动在一个线程上执行,并且会返回执行结果Future对象。同样,将Runnable的对象传递给ExecutorService的submit方法,则该run方法自动在一个线程上执行,并且会返回执行结果Future对象,但是在该Future对象上调用get方法,将返回null。 Executor执行Runnable任务 通过Executors的以上四个静态工厂方法获得 ExecutorService实例,而后调用该实例的execute(Runnable command)方法即可。一旦Runnable任务传递到execute()方法,该方法便会自动在一个线程上执行。 // 获取ExecutorService实例 ExecutorService executorService = Executors.newCachedThreadPool(); // 提交任务 executorService.execute( new Runnable(){ public void run(){ //…… } } ); 1 2 3 4 5 6 7 8 9 Executor执行Callable任务 // 创建线程池 ExecutorService executorService = Executors.newCachedThreadPool(); // 提交任务 Future<String> future = executorService.submit( new Callable<String>{ public String call(){ // …… } } ); // 获取执行结果 if ( future.isDone ) { String result = future.get(); } // 关闭线程池 executorService.shutdown(); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Callable<String>表示call函数返回值为String类型; 如果Future的返回尚未完成,则get()方法会阻塞等待,直到Future完成返回,可以通过调用isDone()方法判断Future是否完成了返回。 ThreadPoolExecutor类 该类用于构造自定义的线程池。构造方法如下: public ThreadPoolExecutor (int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue) 1 corePoolSize:线程池中所保存的核心线程数,包括空闲线程。线程池认为这是一个最合理的值,它会尽量使得线程数量维持在这个值上下。 maximumPoolSize:池中允许的最大线程数。 keepAliveTime:线程池中的空闲线程所能持续的最长时间。 unit:持续时间的单位。 workQueue:任务执行前保存任务的队列,仅保存由execute方法提交的Runnable任务。 当试图通过excute方法讲一个Runnable任务添加到线程池中时,按照如下顺序来处理: 如果线程池中的线程数量少于corePoolSize,即使线程池中有空闲线程,也会创建一个新的线程来执行新添加的任务; 如果线程池中的线程数量大于等于corePoolSize,但缓冲队列workQueue未满,则不再创建新的线程,并将新任务放到workQueue中,按照FIFO的原则依次等待执行(线程池中有线程空闲出来后依次将缓冲队列中的任务交付给空闲的线程执行); 如果线程池中的线程数量大于等于corePoolSize,且缓冲队列workQueue已满,但线程池中的线程数量小于maximumPoolSize,则会创建新的线程来处理被添加的任务; 如果线程池中的线程数量等于了maximumPoolSize,有4种才处理方式(该构造方法调用了含有5个参数的构造方法,并将最后一个构造方法为RejectedExecutionHandler类型,它在处理线程溢出时有4种方式,这里不再细说,要了解的,自己可以阅读下源码)。 另外,当线程池中的线程数量大于corePoolSize时,如果里面有线程的空闲时间超过了keepAliveTime,就将其移除线程池,这样,可以动态地调整线程池中线程的数量。

优秀的个人博客,低调大师

Java继承中的几道面试

第一题: 1 /* 2 看程序写结果: 3 A:访问成员变量的原则:就近原则。 4 B:this和super的问题: 5 this 访问本类的成员 6 super 访问父类的成员(可以理解为的) 7 C:子类的所有构造方法执行前默认先执行父类的无参构造方法。 8 D:一个类的初始化过程: 9 成员变量进行初始化过程如下: 10 默认初始化 11 显示初始化 12 构造方法初始化 13 14 输出的结果是: 15 fu 16 zi 17 30 18 20 19 10 20 */ 21 class Fu { 22 public int num = 10; 23 public Fu() { 24 System.out.println("fu"); 25 } 26 } 27 28 class Zi extends Fu { 29 public int num = 20; 30 public Zi() { 31 System.out.println("zi"); 32 } 33 public void show() { 34 int num = 30; 35 System.out.println(num); //30 36 System.out.println(this.num); //20 37 System.out.println(super.num); //10 38 } 39 } 40 class ExtendsTest { 41 public static void main(String[] args) { 42 Zi z = new Zi(); 43 z.show(); 44 } 45 } 第二题: 1 /* 2 看程序写结果: 3 A:一个类的静态代码块,构造代码块,构造方法的执行流程: 4 静态代码块 > 构造代码块 > 构造方法 5 B:静态的内容是随着类的加载而加载, 6 即:静态代码块的内容会优先执行。 7 C:构造代码块 8 在类中方法外出现(即在类中的成员位置),可以把多个构造方法方法中相同的代码存放到一起,用于对对象进行初始化, 9 每次调用构造方法都执行,并且在构造方法前执行。 10 C:子类的所有的构造方法默认都会去访问父类的无参构造方法。 11 12 输出结果是: 13 静态代码块Fu 14 静态代码块Zi 15 构造代码块Fu 16 构造方法Fu 17 构造代码块Zi 18 构造方法Zi 19 */ 20 class Fu { 21 static { 22 System.out.println("静态代码块Fu"); 23 } 24 25 { 26 System.out.println("构造代码块Fu"); 27 } 28 29 public Fu() { 30 System.out.println("构造方法Fu"); 31 } 32 } 33 34 class Zi extends Fu { 35 static { 36 System.out.println("静态代码块Zi"); 37 } 38 39 { 40 System.out.println("构造代码块Zi"); 41 } 42 43 public Zi() { 44 System.out.println("构造方法Zi"); 45 } 46 } 47 48 class ExtendsTest2 { 49 public static void main(String[] args) { 50 Zi z = new Zi(); 51 } 52 } 第三题: 1 /* 2 看程序写结果: 3 A:成员变量的问题 4 int x = 10; //成员变量x是基本类型 5 Student s = new Student(); //成员变量s是引用类型 6 B:一个类的初始化过程 7 先进行成员变量的初始化: 8 默认初始化 9 显示初始化 10 构造方法初始化 11 C:子父类的初始化(分层初始化) 12 先进行父类初始化,然后进行子类初始化。 13 14 结果: 15 YXYZ 16 17 问题: 18 虽然子类中的构造方法默认有一个 super(); 19 但初始化的时候,不是按照那个顺序进行的。 20 而是按照分层初始化进行的。 21 super(); 它仅仅表示要先初始化父类数据,再初始化子类数据。 22 */ 23 24 class X { 25 //成员变量(引用类型) 26 Y b = new Y(); 27 //无参构造方法 28 X() { 29 System.out.print("X"); 30 } 31 } 32 33 class Y { 34 //无参构造方法 35 Y() { 36 System.out.print("Y"); 37 } 38 } 39 40 public class Z extends X { 41 //成员变量(引用类型) 42 Y y = new Y(); 43 //无参构造方法 44 Z() { 45 //super(); //它仅仅表示要先初始化父类数据,再初始化子类数据。 46 System.out.print("Z"); 47 } 48 public static void main(String[] args) { 49 new Z(); 50 } 51 } 我的GitHub地址: https://github.com/heizemingjun 我的博客园地址: http://www.cnblogs.com/chenmingjun 我的蚂蚁笔记博客地址: http://blog.leanote.com/chenmingjun Copyright ©2018 黑泽明军 【转载文章务必保留出处和署名,谢谢!】

资源下载

更多资源
优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。