Java面试常见问题整理

null

记录一些常见的面试题,为以后面试做准备

目录

2019 - 12 -11

1.jdk,jre的区别是什么?jvm又是什么?

2.static关键字是什么意思?Java中是否可以覆盖(override)一个private或者是static的方法?

3.是否可以在static环境中访问非static变量?

4.Java中支持的数据类型有哪些?什么是自动拆装箱?

2019 - 12 -12

5.什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”?

6.Java中的方法覆盖(Overriding)和方法重载(Overload)是什么意思?

7.java中,什么是构造方法?什么是构造方法重载?什么是复制构造方法?

8.Java支持多继承吗?

9.接口和抽象类的区别是什么?

10.什么是值传递和引用传递

11.进程和线程的区别是什么?

12.在Java中创建线程有几种不同的方式?你喜欢哪一种?为什么?

2019 - 12 -13

13.概括的解释下线程的几种可用状态

14.同步方法和同步代码块的区别是什么呢?

15.在监视器(Monitor)的内部,是如何做线程同步的,程序应该做到哪种级别的同步?

2019 -12 -17

16.什么是死锁(deadlock)?

17.如何确保N个线程可以访问N个资源同时有不导致死锁?

18.Java集合类框架的基本接口有哪些?

19.为什么集合类没有实现Cloneable和Serializable?

20.什么是迭代器(Iterator)?

21.Iterator和ListIterator的区别是什么?

2019 -12 -18

22.快速失败(fail-fast)和安全失败(fail-safe)的区别是什么

23.Java中的HashMap的工作原理是什么?

24.hashcode()和equals()方法的重要性体现在什么地方?

25.HashMap和HashTable有什么区别?

26.数组(Array)和列表(ArrayList)有什么区别?什么时候应该使用Array而不是ArrayList?

27.ArrayList和LinkedList有什么区别?

28.Comparable和Comparator接口是干什么的?列出他们的区别。


1.jdk,jre的区别是什么?jvm又是什么?

JDK: Java Development ToolKit(Java开发工具包)。JDK是整个JAVA的核心,包括了Java运行环境(Java Runtime Envirnment),一堆Java工具(javac/java/jdb等)和Java基础的类库(即Java API 包括rt.jar)。可以让开发者开发,编译,执行Java应用程序

JRE:Java Runtime Enviromental(java运行时环境)。也就是我们说的JAVA平台,所有的Java程序都要在JRE下才能运行。包括JVM和JAVA核心类库和支持文件。与JDK相比,它不包含开发工具——编译器、调试器和其它工具。

JVM:Java Virtual Mechinal(JAVA虚拟机)。JVM是JRE的一部分,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。

2.static关键字是什么意思?Java中是否可以覆盖(override)一个private或者是static的方法?

在Java中变量的类型有类变量,成员变量,局部变量,用static修饰的变量即是类变量,他不属于类的具体实例,而是属于类的。

Java中的private方法不能被覆盖,因为private修饰的变量或者方法只在当前类中可以访问,如果是其他的类继承当前类是不能访问到private变量或者方法的,因此当然不能覆盖。

Java中的static方法也不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而static方法是编译时静态绑定的。static方法跟类的任何实例都不相关,所以概念上不适用。

3.是否可以在static环境中访问非static变量?

static变量在Java中是属于类的,他在所有的实例中的值都是一样的,当类被Java虚拟机载入的时候,会对static变量进行初始化。如果你的代码尝试不用实例来访问非static的变量,编译器会报错,因为这些变量还没有被创建出来,还没有跟任何实例绑定。所以不可以在static环境中访问非static变量

4.Java中支持的数据类型有哪些?什么是自动拆装箱?

Java中一共有两大类数据类型,一种是基本数据类型,一种是引用数据类型。其中基本数据类型有8种,它们分别是,byte,short,int,long,float,double,boolean,char,除了8种基本数据类型外,还有3种引用类型,它们分别是,类,接口,数组

自动装箱是Java编译器在基本数据类型和对应的包装类型之间做得一个转化。比如: 把int转化为Integerdouble转化为Double,等等。反之就是自动拆箱。

5.什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”?

Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字节码文件。Java被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。而这写能够实现,基本全靠Java虚拟机,Java虚拟机屏蔽了底层操作系统和硬件的差异,以此让Java编译后的字节码在Java虚拟机上运行的结果一致。值得注意的是,Java虽然是平台无关性的,但是Java虚拟机是平台有关的,正因为Java虚拟机平台有关性,才造就了Java语言的平台无关性。

6.Java中的方法覆盖(Overriding)和方法重载(Overload)是什么意思?

Java中的方法重载发生在同一个类里面,两个或者是多个方法的方法名相同但是参数不同的情况。与此相对,方法覆盖是说子类重新定义了父类的方法。方法覆盖必须有相同的方法名,参数列表和返回类型。覆盖者可能不会限制他所覆盖的方法的访问。

7.java中,什么是构造方法?什么是构造方法重载?什么是复制构造方法?

当新对象对创建的时候,构造方法会被调用。每一个类都有构造方法。在程序员没有给类提供构造方法的情况下,Java编译器会为这个类创建一个默认的构造方法。Java中构造方法重载和方法重载很类似。可以为一个类创建多个构造方法。每一个构造方法必须有它自己唯一的参数列表。Java不支持像C++中那样的复制构造方法,因为如果你不自己写构造方法的情况下,Java不会创建默认的复制构造方法。

8.Java支持多继承吗?

Java中类不支持多继承,只支持单继承,即一个类只有一个父类。这是基于安全性的考虑,如果子类继承的多个父类里面有相同的方法或者属性,子类将不知道具体要继承哪个。而接口是可以多实现,接口的作用是用来扩展对象的功能,一个子接口实现多个父接口,因为接口只定义方法,而没有具体的逻辑实现,多实现也要重新实现方法。

9.接口和抽象类的区别是什么?

接口中的所有方法隐含的都是抽象的。而抽象类这可以同时包含抽象和非抽象的方法。类可以实现多个接口,但是只能继承一个抽象类。类可以不实现抽象类和接口声明的所有方法,当然,在这种情况下,类也必须的声明是抽象的。在jdk8之前,接口之中可以定义变量和方法,变量必须是publicstaticfinal的,方法必须是publicabstract的。JDK8及以后,允许我们在接口中定义static方法和default方法,当定义一个实现类实现该接口后,由于default方法不是abstract的,所以可以在实现类中不重写。抽象类可以有privateprotectedpublic等修饰符

10.什么是值传递和引用传递

值传递(pass by value):是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果参数进行修改,将不会影响到实际参数

引用传递(pass by reference):是指在调用函数的时候将实际参数的地址直接传递到函数中,那么在函数中对参数的修改,将影响到实际参数。

区别:值传递会创建副本,引用传递不会在Java中不管是基本数据类型还是引用类型,都是值传递!关于此点请看 为什么java中只有值传递

11.进程和线程的区别是什么?

进程是执行着的程序,而线程是进程内部的一个执行序列。一个进程可以包含多个线程。线程又叫做轻量级进程。线程与进程的区别归纳:

  • 地址空间和其他资源:进程之间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
  • 通信:进程间通信IPC,线程之间可以直接读取数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
  • 调度和切换:线程上下文切换比进程上下文切换要快得多。
  • 在多线程OS中,进程不是一个可执行的实体。

12.在Java中创建线程有几种不同的方式?你喜欢哪一种?为什么?

有四种方式可以用来创建线程:

  • 继承Thread类,实现方法run()不可以抛异常 无返回值
  • 实现Runnable接口,实现方法run()不可以抛异常 无返回值
  • 实现Callable<T>接口,要覆盖的方法是public <T> call() 可以抛异常,可以有返回值
  • 使用Executor接口的ThreadPoolExecutor来创建线程池我一般通过实现Runnable接口,因为这种方式避免了单继承的局限,一个类可以继承多个接口。

13.概括的解释下线程的几种可用状态

  • 新建(new):初始状态,线程被创建,没有调用start()

  • 就绪(Runnable):线程调用了start(),此时位于可运行线程池中,等待线程被调度选中获取CPU的时间片。

  • 运行(Running):就绪状态的线程获得了CPU时间片,执行程序代码

  • 阻塞(Blocked):线程进入等待状态,线程因为某种原因,让出了CPU的使用权,停止运行,直到线程进入就绪状态,才有可能再次获得CPU时间片进入运行状态。

  • 死亡(dead):线程run()main()方法执行完毕,或者因为异常退出了run()方法,则线程结束生命周期。

    其中阻塞的情况分成三种

  • 等待阻塞:运行的线程执行了wait(),JVM会把当前线程放入等待队列

  • 同步阻塞:运行的线程在获取对象的同步锁时,如果该同步锁被其他线程占用了,JVM会把当前线程放入锁池中

  • 其他阻塞:运行的线程执行sleep(),join()或者发出IO请求时,JVM会把当前线程设置为阻塞状态,当sleep()执行完,join()线程终止,IO处理完毕线程再次恢复

14.同步方法和同步代码块的区别是什么呢?

同步方法默认用this或者当前类class对象作为锁。同步代码块可以选择以什么来加锁,比同步方法要更细腻度,我们可以选择只同步会发生同步问题的代码块而不是整个方法。同步方法使用关键字synchronized修饰方法,而同步代码块主要是修饰需要进行同步的代码,用synchronized(object){代码内容}进行修饰。

15.在监视器(Monitor)的内部,是如何做线程同步的,程序应该做到哪种级别的同步?

监视器和锁在Java虚拟机中是一块使用的。监视器监视一块同步代码块,确保一次只有一个线程执行同步代码块。每一个监视器都和一个对象引用相关联。线程在获取锁之前不允许执行同步代码!

16.什么是死锁(deadlock)?

所谓死锁就是指多个进程因为竞争资源而造成一种僵局(互相等待)。若无外力作用,这些进程都将无法向前推进。死锁产生的四个条件。

  • 互斥条件:进程要求对所分配的资源进行排他性控制,即在一段时间内,某个资源仅为一个进程所占有。此时如果有其他进程请求该资源,则请求进程只能等待。
  • 不剥夺条件:进程所获得资源在没有使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己释放(即主动释放)
  • 请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源又被其他进程所占有,此时请求进程被阻塞。但对自己已获得资源保持不放。
  • 循环等待条件:存在一种进程资源的循环等待链,链中每一个进程以获得的资源同时被链中下一个进程所请求。

17.如何确保N个线程可以访问N个资源同时有不导致死锁?

预防死锁,预先破坏产生死锁的四个条件,线程互斥基本不可避免,所以主要有破坏请求和保持条件,破坏不剥夺条件和破坏循环等待条件。其中实现简单的就是破坏循环等待条件。这个可以通过指定加锁顺序,即让所有的线程按照相同的顺序获取资源的锁,不先获得顺序靠前的锁,就无法获得后续的锁。则先获得锁的线程不会请求后获得的锁的线程需要的资源,因为后获得锁的线程还没有获得先获得锁的线程未释放的锁,更无法占用先获得锁的线程还没获得的顺序靠后的锁。

18.Java集合类框架的基本接口有哪些?

集合类接口指定了一组叫做元素的对象。集合类接口的每一种具体的实现类都可以选择以他自己的方式对元素进行保存和排序。有的集合类允许重复的键,有些不允许。
Java集合类里面最基本的接口有

  • Collection:代表一组对象,每一个对象都是他的子元素。
  • Set:不包含重复元素的Collection。
  • List:有顺序的Collection,并且可以包含重复元素。
  • Map:可以把键(key)映射到值(value)的对象,键不能重复。

19.为什么集合类没有实现Cloneable和Serializable?

克隆(cloning)或者序列化(serialization)的语义和含义是跟具体的实现相关的。因此,应该由集合类的具体实现类决定如何被克隆或者是序列化!

20.什么是迭代器(Iterator)?

Iterator接口提供了很多对集合元素进行迭代的方法。每一个集合类都包含了可以返回迭代器实例的迭代方法。迭代器可以在迭代过程中删除底层集合的元素,但是不可以直接调用集合的remove(Object Obj)删除,可以通过迭代器的remove()方法删除。

21.Iterator和ListIterator的区别是什么?

Iterator可用来遍历Set和Listen集合,但是ListIterator只能用来遍历List。
Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。
ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。

22.快速失败(fail-fast)和安全失败(fail-safe)的区别是什么

  • 快速失败:
    在使用迭代器遍历一个集合对象时,如果遍历过程中对集合的结构进行了修改(增加,删除),则会抛出ConcurrentModificationException
    原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个modCount变量。集合在被遍历期间如果结构发生变化,就会改变modCount的值每当迭代器使用next()/hashNext()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则就抛出异常,终止遍历。
    注意:这里的异常的抛出条件是检测到`modCount != expectedmmodCount值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用户检测并发修改的bug。
    场景:Java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)
  • 安全失败:
    采用安全失败机子的集合容器,再遍历时不是直接在集合内容上访问的,而是先复制原有的集合内容,在拷贝的集合上进行遍历。
    原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对元集合所作的修改并不能被迭代器检测到,所以不会触发ConcurrentModificationException
    缺点:基于拷贝内容的优点是避免了ConcurrentModificationException,但同样的,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在便利期间原集合发生的修改迭代器是不知道的。
    场景:java.util.concurrent包下都是安全失败,可以在多线程下并发使用,并发修改。

23.Java中的HashMap的工作原理是什么?

Java中的HashMap是以键值对的(key-value)的形式存储元素的。HashMap都需要一个Hash函数,它使用HashCode()equals()方法来向集合/从集合添加和检验元素。当调用put()方法的时候,HashMap会计算key的HashMap值,然后把键值对存储在集合中合适的索引上。如果key已经存在了,value会被更新成新值。HashMap的一些重要的特性是它的容量(capacity),负载因子(load factor)和扩容极限(threshold resizing)


hashmap是一个key-value键值对的数据结构,从结构上来讲在jdk1.8之前是用数组加链表的方式实现,jdk1.8加了红黑树,hashmap数组的默认初始长度是16,hashmap数组只允许一个key为null,允许多个value为null
hashmap的内部实现,hashmap是使用数组+链表+红黑树的形式实现的,其中数组是一个一个Node[]数组,我们叫他hash桶数组,它上面存放的是key-value键值对的节点。HashMap是用hash表来存储的,在hashmap里为解决hash冲突,使用链地址法,简单来说就是数组加链表的形式来解决,当数据被hash后,得到数组下标,把数据放在对应下表的链表中。
然后再说一下hashmap的方法实现
put方法,put方法的第一步,就是计算出要put元素在hash桶数组中的索引位置,得到索引位置需要三步,去put元素key的hashcode值,高位运算,取模运算,高位运算就是用第一步得到的值h,用h的高16位和低16位进行异或操作,第三步为了使hash桶数组元素分布更均匀,采用取模运算,取模运算就是用第二步得到的值和hash桶数组长度-1的值取与。这样得到的结果和传统取模运算结果一致,而且效率比取模运算高
jdk1.8中put方法的具体步骤,先判断hashmap是否为空,为空的话扩容,不为空计算出key的hash值i,然后看table[i]是否为空,为空就直接插入,不为空判断当前位置的key和table[i]是否相同,相同就覆盖,不相同就查看table[i]是否是红黑树节点,如果是的话就用红黑树直接插入键值对,如果不是开始遍历链表插入,如果遇到重复值就覆盖,否则直接插入,如果链表长度大于8,转为红黑树结构,执行完成后看size是否大于阈值threshold,大于就扩容,否则直接结束
get方法就是计算出要获取元素的hash值,去对应位置取即可。
扩容机制,hashmap的扩容中主要进行两部,第一步把数组长度变为原来的两倍,第二部把旧数组的元素重新计算hash插入到新数组中,在jdk1.8时,不用重新计算hash,只用看看原来的hash值新增的一位是零还是1,如果是1这个元素在新数组中的位置,是原数组的位置加原数组长度,如果是零就插入到原数组中。扩容过程第二部一个非常重要的方法是transfer方法,采用头插法,把旧数组的元素插入到新数组中。

hashmap大小为什么是2的幂次方
在计算插入元素在hash桶数组的索引时第三步,为了使元素分布的更加均匀,用取模操作,但是传统取模操作效率低,然后优化成h&(length-1),设置成2幂次方,是因为2的幂次方-1后的值每一位上都是1,然后与第二步计算出的h值与的时候,最终的结果只和key的hashcode值本身有关,这样不会造成空间浪费并且分布均匀
如果length不为2的幂,比如15。那么length-1的2进制就会变成1110。在h为随机数的情况下,和1110做&操作。尾数永远为0。那么0001、1001、1101等尾数为1的位置就永远不可能被entry占用。这样会造成浪费,不随机等问题。


24.hashcode()和equals()方法的重要性体现在什么地方?

Java中HashMap使用HashCode()equals方法来确定键值对的索引,当根据键获取值的时候也会用到这两个方法。如果没有正确的实现这两个方法,两个不同的键可能会有相同的hash值。而且,这两个方法也用来发现重复元素。所以这两个方法的实现对HashMap的精确性和正确性是至关重要的。

25.HashMap和HashTable有什么区别?

HashMap和HashTable都实现了Map接口,因此很多特性非常相似。但是,他们有以下不同点:
HashMap允许键和值是null,而HashTable不允许键或者是值为null。
HashTable是同步的,而HashMap不是。因此,HashMap更适合单线程环境,而HashTable适合与多线程环境。
HashMap提供了可供应迭代的键的集合,因此,HashMap是快速失败的。另一方面,HashTable提供了对键的列举(Enumeration)。

26.数组(Array)和列表(ArrayList)有什么区别?什么时候应该使用Array而不是ArrayList?

Array可以包含基本数据类型和对象类型,而ArrayList只能包含对象类型。
Array是固定大小的,ArrayList的大小是动态变化的。
ArrayList提供了更多的方法和特性,比如:addAll(),remove(),iterator()等等
对于基本数据类型,集合类使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式比较慢

27.ArrayList和LinkedList有什么区别?

ArrayList的实现用的是数组,LinkedList是基于链表实现。ArrayList适合查找,LinkedList适合增删!

ArrayList和LinkedList都实现了List接口,他们有以下的不同点。
ArrayList是基于索引的数据接口,他的底层是数组。他可以以O(1)时间复杂度对元素进行随机访问。与此对应,LinkedList是以元素进行随机访问。与此的语音,LinkedList是以元素列表的形式存储他的数据,每一个元素都和他的前一个和后一链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。相对于ArrayList,LinkedList的插入,添加,删除操作更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。
LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向了前一个元素,一个指向了后一个元素。

28.Comparable和Comparator接口是干什么的?列出他们的区别。

Java提供了只包含一个compareTo方法的Comparable接口。这个方法可以给两个对象排序。具体来说,它返回负数,0,正数来表明已经存在的对象小于,等于,大于输入对象。
Java提供了compare()和equals()两个方法的接口Comparator。compare()方法用来给两个输入参数排序,返回负数,0,正数来表明第一个参数是小于,等于,大于第二个参数的。equals()方法需要一个对象作为参数,它用来决定输入参数是否和comparator相等。只有当输入参数也是一个comparator并且输入参数和当前comparator的排序结果是相同的时候,这个方法才返回true。


Comparable & Comparator 都是用来实现集合中元素的比较、排序的,只是 Comparable 是在集合内部定义的方法实现的排序,Comparator 是在集合外部实现的排序,所以,如想实现排序,就需要在集合外定义 Comparator 接口的方法或在集合内实现 Comparable 接口的方法。 Comparator位于包java.util下,而Comparable位于包 java.lang下 Comparable 是一个对象本身就已经支持自比较所需要实现的接口(如 String、Integer 自己就可以完成比较大小操作,已经实现了Comparable接口) 自定义的类要在加入list容器中后能够排序,可以实现Comparable接口,在用Collections类的sort方法排序时,如果不指定Comparator,那么就以自然顺序排序, 这里的自然顺序就是实现Comparable接口设定的排序方式。 而 Comparator 是一个专用的比较器,当这个对象不支持自比较或者自比较函数不能满足你的要求时,你可以写一个比较器来完成两个对象之间大小的比较。 可以说一个是自已完成比较,一个是外部程序实现比较的差别而已。 用 Comparator 是策略模式(strategy design pattern),就是不改变对象自身,而用一个策略对象(strategy object)来改变它的行为。 比如:你想对整数采用绝对值大小来排序,Integer 是不符合要求的,你不需要去修改 Integer 类(实际上你也不能这么做)去改变它的排序行为,只要使用一个实现了 Comparator 接口的对象来实现控制它的排序就行了。

# JAVA  Interview 

标题:Java面试常见问题整理
作者:amore
地址:HTTPS://iamwaiting.cn/articles/2019/12/11/1576077816315.html
彧言:  正在加载今日诗词....

评论

  1. ???皮

  2. ??? 快更新!

取消