115个Java面试题和答案——终极列表(上)
这篇整理 Java 面试里常见的题型,方便雇主考查面向对象和 Java 基础。全文分上下两篇:本篇(上)覆盖 OOP 特性、Java 常见问题、线程、集合、垃圾收集;第二篇 再谈异常、Applet、Swing、JDBC、RMI、Servlet 和 JSP。
目录仍指向原文锚点。上篇对应 ImportNew 第一篇。
| 章节 | 题号 | 锚点 |
|---|---|---|
| 面向对象编程(OOP) | — | #oop |
| 常见的 Java 问题 | 1–10 | #general |
| Java 线程 | 11–17 | #thread |
| Java 集合类 | 18–34 | #collection |
| 垃圾收集器 | 35–42 | #gc |
一、面向对象编程(OOP)
Java 是支持并发、基于类、面向对象的语言。面向对象开发的好处:
-
代码按模块开发,更易维护和修改。 -
代码复用。 -
可靠性和灵活性更好。 -
可理解性更强。
四个关键特性:封装、继承、多态、抽象。
封装
封装让对象能藏起内部状态和行为,只暴露少量方法给外界改数据。Java 有三种修饰符:public、private、protected,分别决定同包、跨包能看到什么。好处:
-
藏属性,保护内部状态。 -
行为可以单独改或扩展,可用性和可维护性更好。 -
减少对象之间的不良交互,模块化更干净。
更多细节见 这份封装文档。
多态
多态是语言给不同底层数据类型提供同一套接口的能力。作用在多态类型上的操作,也可以作用到其它类型的值上。
继承
继承让对象从基类拿到字段和方法,从而复用代码,也能在不改原类的前提下给现存类加新特性。
抽象
抽象是把想法从具体实例里抽出来,按功能而不是按实现细节来建类。Java 允许只暴露接口、不含方法实现的抽象类。目的就是把行为和实现拆开。
抽象和封装的不同
二者互补。抽象关心对象做什么,封装关心怎么做。通常靠隐藏内部状态来封装,所以封装可以看成提供抽象的一种策略。
二、常见的 Java 问题
1. 什么是 Java 虚拟机?为什么 Java 被称作「平台无关的编程语言」?
JVM 是能执行 Java 字节码的虚拟机进程。源文件先编译成字节码,再由 JVM 执行。
Java 被设计成同一份程序能跑在任意平台,不必为每个平台重写或重编译。JVM 知道底层硬件的指令长度和其它特性,把这件事扛下来。
2. JDK 和 JRE 的区别是什么?
JRE(Java 运行时环境)是要执行 Java 程序的那套 JVM,也包含执行 applet 需要的浏览器插件。JDK 是完整开发包,内含 JRE、编译器以及 JavaDoc、调试器等,用来开发、编译、执行应用程序。
3. static 关键字是什么意思?能否覆盖 private 或 static 方法?
static 表示成员变量或方法可以在没有实例的情况下访问。static 方法不能被覆盖:覆盖靠运行时动态绑定,static 是编译期静态绑定,跟任何实例无关,概念上就不适用。
4. 能否在 static 环境中访问非 static 变量?
static 变量属于类,所有实例共享一份,类被 JVM 载入时初始化。不用实例去碰非 static 变量,编译器会报错:那些变量还没创建,也没绑到任何实例上。
5. Java 支持的数据类型有哪些?什么是自动拆装箱?
八种基本类型:
-
byte -
short -
int -
long -
float -
double -
boolean -
char
自动装箱是编译器在基本类型和对应包装类之间做的转换,例如 int → Integer、double → Double;反过来就是拆箱。
6. 方法覆盖(Overriding)和方法重载(Overloading)是什么意思?
重载:同一个类里多个方法同名但参数不同。覆盖:子类重新定义父类方法,必须同名、同参数列表、同返回类型;覆盖方不能收紧被覆盖方法的访问权限。
7. 什么是构造函数?构造函数重载?复制构造函数?
创建新对象时会调构造函数。每个类都有;程序员不写时,编译器生成默认构造。构造函数也可以重载,每个必须有自己唯一的参数列表。Java 不像 C++ 那样支持复制构造函数:你不写的话,Java 也不会自动生成一份复制构造。
8. Java 支持多继承么?
不支持。每个类只能继承一个类,但可以实现多个接口。
9. 接口和抽象类的区别是什么?
-
接口里的方法默认都是抽象的;抽象类可以同时有抽象方法和具体方法。 -
类可以实现多个接口,只能继承一个抽象类。 -
实现接口必须实现全部方法;继承抽象类可以不实现全部,但自己也得声明为抽象。 -
抽象类可以在不提供接口方法实现的情况下实现接口。 -
接口里的变量默认 final;抽象类可以有非 final 变量。 -
接口成员默认 public;抽象类可以是 private、protected 或 public。 -
接口绝对抽象,不能实例化。抽象类也不能 new,但如果有 main 仍可被调用。
也可参考 JDK 8 里抽象类和接口的区别。
10. 什么是值传递和引用传递?
值传递:传的是对象的一份副本,改副本不影响源对象。
引用传递:传的不是实际对象,而是引用;外面通过引用做的修改,所有持有该引用的地方都能看见。(Java 方法参数本身仍是值传递,传引用类型时传递的是引用的副本。)
三、Java 线程
11. 进程和线程的区别是什么?
进程是正在执行的应用程序;线程是进程内部的一条执行序列。一个进程可以有多条线程。线程也叫轻量级进程。
12. 创建线程有几种方式?你喜欢哪一种?为什么?
-
继承 Thread 类 -
实现 Runnable 接口 -
用 Executor 框架创建线程池
更受欢迎的是实现 Runnable:不必再继承 Thread。若业务类已经继承了别的类,Java 又没有多继承,就只能走接口。线程池也高效,容易实现和使用。
13. 概括线程的几种可用状态。
-
就绪(Runnable):准备运行,不一定立刻执行。 -
运行中(Running):正在执行线程代码。 -
等待中(Waiting):阻塞,等外部处理结束。 -
睡眠中(Sleeping):被强制睡眠。 -
I/O 阻塞(Blocked on I/O):等 I/O 完成。 -
同步阻塞(Blocked on Synchronization):等锁。 -
死亡(Dead):执行完毕。
14. 同步方法和同步代码块的区别是什么?
每个对象一把锁。线程用 synchronized 拿这把锁。可以加在方法上(粗粒度)或代码块上(细粒度)。
15. 在监视器(Monitor)内部如何做线程同步?程序应该做哪种级别的同步?
监视器和锁在 JVM 里配套使用。监视器盯着一块同步代码,保证一次只有一条线程执行。每个监视器关联一个对象引用。线程没拿到锁,就不许进同步代码。
16. 什么是死锁(deadlock)?
两个进程都在等对方执行完才能继续,结果两边都陷入无限等待。
17. 如何确保 N 个线程访问 N 个资源同时又不导致死锁?
指定获取锁的顺序,并强制所有线程按同一顺序加锁、按同一顺序释放。顺序一致就不会环等待。
四、Java 集合类
18. Java 集合类框架的基本接口有哪些?
-
Collection:一组对象,每个都是子元素。 -
Set:不包含重复元素的 Collection。 -
List:有顺序,可以重复。 -
Map:键映射到值,键不能重复。
19. 为什么集合类没有实现 Cloneable 和 Serializable 接口?
集合接口只规定一组元素。每种具体实现可以用自己的方式保存和排序;有的允许重复键,有的不允许。克隆和序列化的语义跟具体实现相关,所以该由实现类自己决定怎么 clone / serialize。(原文第 20 题后半段补了这条理由。)
20. 什么是迭代器(Iterator)?
Iterator 提供迭代集合元素的方法。每个集合类都有能返回迭代器的方法。迭代过程中可以删除底层集合的元素。
21. Iterator 和 ListIterator 的区别是什么?
-
Iterator 能遍历 Set 和 List;ListIterator 只能遍历 List。 -
Iterator 只能向前;ListIterator 可以向前也可以向后。 -
ListIterator 实现了 Iterator,还支持增加、替换、取前一个/后一个索引等。
22. 快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?
安全失败的迭代器基于底层集合的拷贝,源集合被改也不受影响。java.util 下的集合都是快速失败,java.util.concurrent 下的是安全失败。快速失败会抛 ConcurrentModificationException,安全失败不会。
23. HashMap 的工作原理是什么?
以键值对存储。靠 hash 函数,用 hashCode() 和 equals() 完成添加和检索。put() 时计算 key 的 hash,放到合适索引;key 已存在则更新 value。重要参数:容量(capacity)、负载因子(load factor)、扩容阈值(threshold)。
24. hashCode() 和 equals() 的重要性体现在什么地方?
HashMap 用它们确定键值对索引,按键取值时也用。实现不对,两个不同 key 可能得到相同 hash,被当成相等;查重也靠它们。对正确性至关重要。
25. HashMap 和 Hashtable 有什么区别?
-
都实现 Map,很多特性相似。 -
HashMap 允许 key、value 为 null;Hashtable 都不允许。 -
Hashtable 同步,HashMap 不同步:单线程用 HashMap,多线程才考虑 Hashtable。 -
HashMap 提供可迭代的键集合,快速失败;Hashtable 提供 Enumeration。 -
一般认为 Hashtable 是遗留类。
26. 数组(Array)和 ArrayList 有什么区别?什么时候该用 Array?
-
Array 能装基本类型和对象;ArrayList 只能装对象。 -
Array 大小固定;ArrayList 动态。 -
ArrayList 方法更多:addAll、removeAll、iterator 等。 -
集合对基本类型靠自动装箱,写起来省事;处理固定大小的基本类型时,数组更快。
27. ArrayList 和 LinkedList 有什么区别?
-
ArrayList 底层数组,随机访问 O(1);LinkedList 双向链表,查找 O(n)。 -
LinkedList 插入、删除通常更快,不必像数组那样搬元素、改索引。 -
LinkedList 更占内存:每个结点两个引用(前驱、后继)。
也可参考 ArrayList vs. LinkedList。
28. Comparable 和 Comparator 接口是干什么的?列出区别。
Comparable 只有一个 compareTo(),返回负/零/正,表示小于/等于/大于已有对象。
Comparator 有 compare() 和 equals()。compare() 给两个参数排序;equals() 判断另一个对象是否是「排序结果相同」的 comparator,是才返回 true。
29. 什么是 Java 优先级队列(PriorityQueue)?
基于优先级堆的无界队列,元素按自然顺序排;创建时也可以传入比较器。不允许 null(没有自然顺序)。不是线程安全的。入队出队 O(log n)。
30. 你了解大 O 符号么?能给出不同数据结构的例子么?
大 O 描述元素增多时,算法规模或性能在最坏情况下长什么样,也可以描述内存消耗。集合类就是数据结构,用时间、内存、性能上的大 O 来选实现。数据量大时特别有用。
31. 如何权衡无序数组还是有序数组?
有序数组查找 O(log n),无序是 O(n)。有序数组插入是 O(n),因为大的元素要后移;无序插入是 O(1)。
32. Java 集合类框架的最佳实践有哪些?
-
按需求选类型:大小固定且事先知道,用 Array 而不是 ArrayList。 -
能估计元素个数就设初始容量,避免反复 rehash / 扩容。 -
总是用泛型:类型安全、可读、健壮,还能避免运行时 ClassCastException。 -
用 JDK 不可变类当 Map 的键,可避免自己实现 hashCode / equals。 -
面向接口编程,而不是面向实现。 -
底层集合为空时,返回长度 0 的集合或数组,不要返回 null。
33. Enumeration 和 Iterator 的区别有哪些?
Enumeration 大约快一倍,内存更少。但 Iterator 更安全:其它线程不能改正在遍历的集合。Iterator 还允许删除底层元素,Enumeration 做不到。
34. HashSet 和 TreeSet 有什么区别?
HashSet 用哈希表,无序;add / remove / contains 是 O(1)。TreeSet 用树,有序;这几个方法是 O(log n)。
五、垃圾收集器(Garbage Collectors)
35. Java 中垃圾回收有什么目的?什么时候进行?
识别并丢弃应用不再使用的对象,释放并重用资源。
36. System.gc() 和 Runtime.gc() 会做什么?
提示 JVM 进行垃圾回收。立刻开始还是延迟,由 JVM 自己决定。
37. finalize() 什么时候被调用?析构的目的是什么?
释放对象占用的内存之前,GC 会调 finalize()。一般建议在这里释放对象持有的资源。
38. 对象引用被置为 null,GC 会立即释放内存吗?
不会。下一个回收周期里,这个对象才是可回收的。
39. Java 堆的结构是什么样子的?什么是永久代(Perm Gen)?
堆是运行时数据区,所有类实例和数组都在堆上分配,JVM 启动时创建,由 GC 回收。
堆里既有存活对象(应用还能访问,不会被收)也有死亡对象(已经不可访问,但还没被收走)。死亡对象在被收走之前一直占着堆。
40. 串行(serial)收集器和吞吐量(throughput)收集器的区别是什么?
吞吐量收集器使用并行版本的新生代收集器,面向中等和大规模数据。串行收集器对大多数小应用(现代处理器上大约 100M 内存)就够了。
41. 在 Java 中,对象什么时候可以被垃圾回收?
当对象对当前使用它的应用程序变得不可触及,就可以被回收。
42. JVM 的永久代中会发生垃圾回收么?
通常说永久代不走常规新生代回收;永久代满了或超过临界值会触发 Full GC。看 GC 日志会发现永久代也会被回收,所以永久代大小设对了才能少 Full GC。参考 Java 8:从永久代到元数据区。(译者注:Java 8 已移除永久代,换成名为元数据区的 native 内存。)
原文:javacodegeeks 翻译:ImportNew.com – miracle1919 译文:http://www.importnew.com/10980.html。
一句话总结:上篇 42 题:OOP 四件套打底,再把 JVM、线程、集合和 GC 的对比题答清楚。
转载请注明来源:115个Java面试题和答案——终极列表(上)







