115个Java面试题和答案——终极列表(上)

    |     2015年4月18日   |   Java面试题   |     0 条评论   |    1435

这篇整理 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面试题和答案——终极列表(上)
本文链接地址:https://ai.zhousir.top/?p=351
回复 取消