每日5题Java面试系列(6):基础篇(无参构造函数、equals与hashCode方法、Integer、为什么重写equals一定要重写HashCode方法)

系列介绍

欢迎来到"Java面试基础篇"系列!本系列旨在帮助Java开发者系统性地准备面试,每天精选5道经典面试题,涵盖Java基础、进阶、框架等各方面知识。坚持学习21天,助你面试通关!
历史面试题:
每日5题Java面试系列(1)
每日5题Java面试系列(2)
每日5题Java面试系列(3)
每日5题Java面试系列(4)
每日5题Java面试系列(5)

1. 为什么建议自定义一个无参构造函数

作为Java程序员,我认为无参构造函数的设计是类设计的重要考量点,其背后有深刻的工程实践意义:

核心原因分析:

  1. 框架兼容性:
    • 反射机制(如Spring、Hibernate)通常依赖无参构造创建实例
    • 序列化/反序列化(Jackson、Kryo)需要无参构造重建对象
    • JPA规范要求实体类必须有无参构造

  2. 继承体系安全:
    • 子类默认调用父类无参构造
    • 若无则必须显式调用super(有参),增加维护成本
    • 典型案例:自定义异常类继承RuntimeException

  3. 设计模式适配:
    • 工厂模式、原型模式等创建型模式通常需要无参构造
    • 代理生成(CGLib)依赖无参构造实例化

  4. 不可变对象构建:
    • 可通过无参构造+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则必须相同)
应用场景精确匹配哈希表快速定位

深度解析:

  1. 哈希表工作原理:
    • HashMap先通过hashCode定位桶,再用equals确认

    • 良好的hashCode应均匀分布,减少碰撞

  2. 性能优化关键:
    • 万级数据的HashSet.contains()比ArrayList快1000倍

    • 不当的hashCode会导致哈希退化为链表

  3. 分布式系统影响:
    • 分布式缓存(如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

原理分析:

  1. Integer缓存机制:
    • Java对-128~127的Integer值进行缓存(IntegerCache)

    • 自动装箱时优先返回缓存对象

  2. 字节码层面:

    Integer a1 = Integer.valueOf(100); // 使用缓存
    Integer a2 = Integer.valueOf(100); // 返回同一对象
    
  3. JVM规范依据:
    • JLS 5.1.7规定装箱值可以(但不必须)被缓存

    • 实现优化:减少小整数对象创建

扩展知识:

  1. 缓存范围可配置:

    -Djava.lang.Integer.IntegerCache.high=500
    
  2. 其他缓存类型:
    • Byte, Short, Long: -128~127

    • Character: 0~127

    • Boolean: TRUE/FALSE常量

  3. 工程实践建议:
    • 包装类型比较始终使用equals()

    • 明确需要对象比较时使用new Integer()

    • 性能敏感场景优先使用基本类型

4. 为什么重写equals()就一定要重写hashCode方法

这个问题涉及Java对象模型的根本契约,是设计不可变对象的关键:

技术契约要求:
根据Java规范(Object类文档):

  1. 一致性:对象相等则hashCode必须相等
  2. 非反向:hashCode相等对象不一定相等
  3. 不变性:对象状态不变时hashCode应不变

违反后果示例:

Set<Student> students = new HashSet<>();
students.add(new Student(1, "Alice")); // hash=1001
students.contains(new Student(1, "Alice")); // 可能返回false
// 因为新对象hashCode不同,直接定位到错误哈希桶

深度分析:

  1. 哈希表工作原理:
    • 操作流程:hashCode()定位桶 → equals()确认元素

    • 不一致的hashCode导致元素"消失"

  2. 并发集合影响:
    • ConcurrentHashMap的分段锁基于hashCode

    • 不一致hashCode可能导致锁粒度异常

  3. 分布式系统扩展:
    • 数据分片可能依赖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)中尤为重要,因为实体对象的标识比较是领域模型的基础操作。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值