【Java 面试突击 · 02】大厂高频面试题:从并发容器与内存优化到Java8新特性及反射机制解析
目录
2. ConcurrentHashMap是如何保证线程安全的?
4. 为什么JDK9要将String的底层实现由char[]改成byte[]?
5. String, StringBuffer 和 StringBuilder 的区别?
7. final, finally, finalize 的区别?
1. 重载和重写有什么区别?
【回答话术】
这两个概念都属于多态,但发生的时间和作用对象不同:
-
重载(Overload):
- 作用对象:同一个类中。
- 核心特征:方法名相同,参数列表不同(参数个数、类型、顺序)。与返回值类型和访问修饰符无关。
- 发生时间:编译时多态(静态绑定)。
-
重写(Override):
- 作用对象:父子类之间(继承关系)。
- 核心特征:子类方法签名必须与父类方法完全一致。子类方法的访问权限不能比父类更严格(如父类是protected,子类不能是private)。
- 发生时间:运行时多态(动态绑定),依赖于对象的实际类型。
2. ConcurrentHashMap是如何保证线程安全的?
【回答话术】
ConcurrentHashMap 在不同版本的实现机制有所不同,面试时最好能区分说明:
-
JDK 1.7 版本:
- 采用了分段锁(Segment)机制。将数据分为多个段(Segment),每个段是一个独立的哈希表。
- 操作时锁住的是具体的段,而不是整个表,从而减少了锁的竞争,提高了并发度。
-
JDK 1.8 版本:
- 废除了 Segment,改用 CAS + synchronized。
- 利用 Node 数组 + 链表 + 红黑树的结构。在插入或更新时,使用 CAS 保证原子性;当发生哈希冲突需要加锁时,只对链表的头节点或红黑树的根节点加
synchronized锁,锁粒度更细,性能更好。
3. 了解Java的包装类类型吗?为什么需要包装类?
【回答话术】
-
包装类类型:
- Java 为 8 种基本数据类型(byte, short, int, long, float, double, char, boolean)分别提供了对应的包装类(Byte, Short, Integer, Long, Float, Double, Character, Boolean)。
-
为什么需要包装类:
- 泛型支持:Java 的泛型不支持基本数据类型,集合(如
List<T>)只能存储对象,因此必须使用包装类。 - 集合存储:集合类(如 ArrayList, HashMap)要求存储对象,基本数据类型无法直接存入。
- 方法参数:很多工具类方法(如
Integer.parseInt())要求参数是对象。 - 空值表示:包装类可以为
null,而基本数据类型有默认值(如 int 默认为 0),这在数据库映射(表示空字段)时非常关键。
- 泛型支持:Java 的泛型不支持基本数据类型,集合(如
4. 为什么JDK9要将String的底层实现由char[]改成byte[]?
【回答话术】
主要是为了节省内存空间。
- 背景:在 JDK 8 及之前,String 底层使用
char[]存储,char占 2 个字节(UTF-16 编码)。但在实际应用中,大部分字符串(如英文、数字)只需要 1 个字节(Latin-1 编码)就能表示。 - 优化:JDK 9 将底层改为
byte[],并引入了一个coder标识位。- 如果字符串只包含 Latin-1 字符(单字节),则用
byte[]存储,节省一半内存。 - 如果包含需要双字节表示的字符(如中文),则回退到双字节模式。
- 如果字符串只包含 Latin-1 字符(单字节),则用
- 效果:在大多数应用场景下,String 占用的堆内存减少了约 50%,显著降低了 GC 压力。
5. String, StringBuffer 和 StringBuilder 的区别?
【回答话术】
这三个类都用于处理字符串,但侧重点不同:
表格
| 特性 | String | StringBuffer | StringBuilder |
|---|---|---|---|
| 可变性 | 不可变(每次修改生成新对象) | 可变 | 可变 |
| 线程安全 | 线程安全(不可变即安全) | 线程安全(方法加了 synchronized) | 非线程安全(无锁,性能高) |
| 适用场景 | 字符串内容极少变化,或作为常量。 | 多线程环境下,需要频繁修改字符串。 | 单线程环境下,需要频繁修改字符串(推荐)。 |
总结:操作次数少用 String,单线程大量操作用 StringBuilder,多线程大量操作用 StringBuffer。
6. Java创建对象有几种方式?
【回答话术】
主要有以下 4 种方式:
- new 关键字:最常见的方式,调用构造器创建对象。
- Class.newInstance():通过反射机制创建对象(调用无参构造器)。
- Constructor.newInstance():更强大的反射方式,可以调用有参构造器,且可以访问私有构造器。
- clone() 方法:通过克隆(深拷贝或浅拷贝)创建对象,不调用任何构造器。
- 反序列化:通过
ObjectInputStream从字节流中恢复对象。
7. final, finally, finalize 的区别?
【回答话术】
这三个词长得像,但功能完全不同:
-
final(关键字):用于修饰类、方法、变量。
- 修饰类:不能被继承。
- 修饰方法:不能被重写。
- 修饰变量:如果是基本类型,值不可变;如果是引用类型,引用地址不可变(但对象内部属性可变)。
-
finally(代码块):用于异常处理。无论是否发生异常,
finally块中的代码都会被执行(常用于释放资源)。 -
finalize()(方法):是
Object类的一个方法。在垃圾回收器准备回收对象之前,会调用此方法。注意:该方法已被废弃(deprecated),不推荐使用,因为无法保证何时被调用,且性能开销大。
8. Java支持多继承吗?
【回答话术】
- 类层面:不支持。Java 中一个类只能直接继承一个父类(单继承)。这是为了避免“菱形继承”带来的歧义问题(如 C++ 中的虚继承问题)。
- 接口层面:支持。一个接口可以继承多个接口。
- 实现层面:Java 8 引入了接口的默认方法(default method),一个类可以实现多个接口,这在功能上实现了类似多继承的效果,但避免了父类方法冲突的问题(如果冲突,必须在子类中显式重写该方法)。
9. 介绍一下Java 8的新特性?
【回答话术】
Java 8 是一个里程碑版本,核心新特性包括:
- Lambda 表达式:简化代码,支持函数式编程风格(如
(a, b) -> a + b)。 - Stream API:用于集合的批量操作(过滤、映射、归约),支持函数式风格,代码更简洁。
- 接口默认方法:接口中可以有具体实现的方法(
default关键字),允许在不破坏现有实现类的情况下升级接口。 - Optional 类:用于解决空指针异常(NullPointerException),提供了一种优雅的判空方式。
- 新的日期时间 API (JSR 310):引入了
java.time包(如LocalDate,LocalTime),解决了旧日期类(Date,SimpleDateFormat)线程不安全和设计混乱的问题。
10. 什么是反射?反射有哪些应用场景呢?
【回答话术】
-
什么是反射:
反射(Reflection)是指在运行时(Runtime)分析类的能力,以及操作对象的能力。通过反射,可以在程序运行时获取类的完整构造(属性、方法、构造器),甚至可以调用私有方法或修改私有属性。 -
应用场景:
- 框架开发:如 Spring(依赖注入、AOP)、MyBatis(ORM 映射)大量使用反射来动态创建对象和调用方法。
- 通用工具类:如 JSON 序列化/反序列化工具(Jackson, Gson),通过反射读取对象属性。
- 动态代理:Java 的动态代理机制(
java.lang.reflect.Proxy)依赖反射来生成代理类。 - IDE 和调试工具:IDE 的代码提示、调试器查看对象属性等都依赖反射机制。
-
缺点:破坏封装性、性能开销较大(比直接调用慢)、安全性问题。
更多推荐


所有评论(0)