系列介绍
欢迎来到"Java面试基础篇"系列!本系列旨在帮助Java开发者系统性地准备面试,每天精选5道经典面试题,涵盖Java基础、进阶、框架等各方面知识。坚持学习21天,助你面试通关!
历史面试题:
每日5题Java面试系列(1)
每日5题Java面试系列(2)
每日5题Java面试系列(3)
每日5题Java面试系列(4)
每日5题Java面试系列(5)
1. 为什么建议自定义一个无参构造函数
作为Java程序员,我认为无参构造函数的设计是类设计的重要考量点,其背后有深刻的工程实践意义:
核心原因分析:
-
框架兼容性:
• 反射机制(如Spring、Hibernate)通常依赖无参构造创建实例
• 序列化/反序列化(Jackson、Kryo)需要无参构造重建对象
• JPA规范要求实体类必须有无参构造 -
继承体系安全:
• 子类默认调用父类无参构造
• 若无则必须显式调用super(有参),增加维护成本
• 典型案例:自定义异常类继承RuntimeException -
设计模式适配:
• 工厂模式、原型模式等创建型模式通常需要无参构造
• 代理生成(CGLib)依赖无参构造实例化 -
不可变对象构建:
• 可通过无参构造+Builder模式实现灵活构建
• 例如:new User().setName("Alice").setAge(30)
实践建议:
// 推荐做法
public class Order {
private Long id;
private String number;
// 显式声明无参构造(即使不公开)
protected Order() {}
// 业务构造方法
public Order(Long id, String number) {
this.id = id;
this.number = number;
}
}
特殊场景例外:
• 工具类(应私有化构造)
• 必须属性注入的领域对象(DDD聚合根)
• 明确不需要反射创建的类
2. 有了equals,为什么还需要hashCode方法
这个问题触及Java对象模型的核心契约,理解这点对设计高质量类至关重要:
本质区别:
| 维度 | equals() | hashCode() |
|---|---|---|
| 用途 | 逻辑相等比较 | 散列值计算 |
| 性能 | O(n)复杂度(可能全量比较) | O(1)复杂度 |
| 契约 | 自反、对称、传递、一致 | 一致性(equals为true则必须相同) |
| 应用场景 | 精确匹配 | 哈希表快速定位 |
深度解析:
-
哈希表工作原理:
• HashMap先通过hashCode定位桶,再用equals确认• 良好的hashCode应均匀分布,减少碰撞
-
性能优化关键:
• 万级数据的HashSet.contains()比ArrayList快1000倍• 不当的hashCode会导致哈希退化为链表
-
分布式系统影响:
• 分布式缓存(如Redis)可能依赖hashCode分片• 不一致的hashCode导致数据路由错误
典型案例:
// 错误示范 - 导致HashMap无法正确工作
class Employee {
String id;
@Override
public boolean equals(Object o) { /* 比较id */ }
// 未重写hashCode
}
// 正确实现
@Override
public int hashCode() {
return Objects.hash(id); // 保证相同id产生相同hash
}
3. Integer a1=100; Integer a2=100; a1==a2的运行结果及原因
这个问题考察对Java内存模型和自动装箱的深入理解:
运行结果: true
原理分析:
-
Integer缓存机制:
• Java对-128~127的Integer值进行缓存(IntegerCache)• 自动装箱时优先返回缓存对象
-
字节码层面:
Integer a1 = Integer.valueOf(100); // 使用缓存 Integer a2 = Integer.valueOf(100); // 返回同一对象 -
JVM规范依据:
• JLS 5.1.7规定装箱值可以(但不必须)被缓存• 实现优化:减少小整数对象创建
扩展知识:
-
缓存范围可配置:
-Djava.lang.Integer.IntegerCache.high=500 -
其他缓存类型:
• Byte, Short, Long: -128~127• Character: 0~127
• Boolean: TRUE/FALSE常量
-
工程实践建议:
• 包装类型比较始终使用equals()• 明确需要对象比较时使用new Integer()
• 性能敏感场景优先使用基本类型
4. 为什么重写equals()就一定要重写hashCode方法
这个问题涉及Java对象模型的根本契约,是设计不可变对象的关键:
技术契约要求:
根据Java规范(Object类文档):
- 一致性:对象相等则hashCode必须相等
- 非反向:hashCode相等对象不一定相等
- 不变性:对象状态不变时hashCode应不变
违反后果示例:
Set<Student> students = new HashSet<>();
students.add(new Student(1, "Alice")); // hash=1001
students.contains(new Student(1, "Alice")); // 可能返回false
// 因为新对象hashCode不同,直接定位到错误哈希桶
深度分析:
-
哈希表工作原理:
• 操作流程:hashCode()定位桶 → equals()确认元素• 不一致的hashCode导致元素"消失"
-
并发集合影响:
• ConcurrentHashMap的分段锁基于hashCode• 不一致hashCode可能导致锁粒度异常
-
分布式系统扩展:
• 数据分片可能依赖hashCode• 持久化到Redis等缓存时可能作为key
正确实现模式:
class Product {
private String sku;
private String name;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Product)) return false;
Product p = (Product) o;
return Objects.equals(sku, p.sku);
}
@Override
public int hashCode() {
return Objects.hash(sku); // 保证与equals使用相同字段
}
}
Lombok最佳实践:
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
class OrderItem {
@EqualsAndHashCode.Include
private Long id;
private Integer quantity;
// 其他字段不参与equals/hashCode
}
这些原则在领域驱动设计(DDD)中尤为重要,因为实体对象的标识比较是领域模型的基础操作。
1241

被折叠的 条评论
为什么被折叠?



