当前位置: 首页 > news >正文

JavaSE——八股文

1. Object类有哪些方法?

  • hashCode
  • equals
  • clone
  • toString
  • notify/notifyAll
  • wait
  • finalize
@IntrinsicCandidate
public native int hashCode();public boolean equals(Object obj) {return (this == obj);
}@IntrinsicCandidate
protected native Object clone() throws CloneNotSupportedException;public String toString() {return getClass().getName() + "@" + Integer.toHexString(hashCode());
}@IntrinsicCandidatepublic final native void notify();@IntrinsicCandidate
public final native void notifyAll();public final void wait() throws InterruptedException {wait(0L);
}public final native void wait(long timeoutMillis) throws InterruptedException;@Deprecated(since="9")
protected void finalize() throws Throwable { }

(1)hashCode方法

@IntrinsicCandidate
public native int hashCode();
  • @IntrinsicCandidate 是 Java 虚拟机(JVM)中的一个注解,主要用于标记那些可能会被 JVM 进行特殊优化(通常是内联或使用特定的、更高效的本地代码实现)的底层方法。
  • native 关键字表示这个方法的实现不是用 Java 语言编写的,而是由 JVM 用 C、C++ 或汇编等本地语言实现的。JVM 负责提供这个方法的具体功能。
  • int 是返回值类型,hashCode() 方法返回一个整数,该整数通常用作哈希表(如 HashMap)中的哈希码。当你调用 hashCode() 这个方法时,它一定会给你返回一个 整数(integer)。这个整数的范围是 Java 中 int 类型的范围,即从 -2,147,483,648 到 2,147,483,647。
  • 相等的对象必须有相同的哈希码:这是 hashCode() 和 equals() 合约的铁律。如果 a.equals(b) 为 true,那么 a.hashCode() == b.hashCode() 必须为 true。违反这一点会导致 HashMap 等集合行为异常(比如 get() 找不到刚 put() 进去的对象)。

通过HashMap举例子。我们算出KEY的hashcode,再通过特定的取模算法,就能确定KEY在数组的索引下标。

(2)equals方法

public boolean equals(Object obj) {return (this == obj);
}

1,所有类都从Object类中继承equals()方法
2,Object中的equals方法是直接判断this和obj本身的值是否相等,即用来判断调用equals的对象和形参obj所引用的对象是否是同一对象,所谓同一对象就是指内存中同一块存储单元,如果this和obj指向的hi同一块内存对象,则返回true,如果this和obj指向的不是同一块内存,则返回false,注意:即便是内容完全相等的两块不同的内存对象,也返回false。
3,如果是同一块内存,则object中的equals方法返回true,如果是不同的内存,则返回false

4,如果希望不同内存但相同内容的两个对象equals时返回true,则我们需要重写父类的equals()方法(类似于String类中重写的toString()方法)

5,String类已经重写了object中的equals方法

(3)clone方法

要让一个类支持克隆,你需要:

  1. 实现 Cloneable 接口
  2. 重写 clone() 方法,并通常将其访问级别改为 public(以便外部调用)。
  3. 处理 CloneNotSupportedException(尽管在实现了 Cloneable 的类中,这个异常理论上不会抛出,但重写时语法上需要处理)。
  4. (可选)实现深拷贝Object 的 clone() 只做浅拷贝。如果你的对象包含可变对象的引用,你可能需要在重写的 clone() 方法中手动复制这些内部对象,以实现深拷贝。
public class MyClass implements Cloneable {private int value;private String name;private int[] data; // 假设这是一个需要深拷贝的数组// ... 构造函数、getter、setter ...@Overridepublic MyClass clone() {try {// 调用父类 Object 的 clone(),进行浅拷贝MyClass cloned = (MyClass) super.clone();// 对于需要深拷贝的字段,手动复制if (this.data != null) {cloned.data = this.data.clone(); // 数组的 clone() 会创建新数组}return cloned;} catch (CloneNotSupportedException e) {// 因为实现了 Cloneable,这个异常不应该发生throw new AssertionError(); // 或者根据需要处理}}
}// 使用
MyClass obj1 = new MyClass(10, "Test", new int[]{1, 2, 3});
MyClass obj2 = obj1.clone(); // 创建一个副本
  • 默认Object类的clone方法是浅拷贝
  • 如果想让clone方法变成深拷贝,需要自定义一个类实现Clonenable接口并重写clon

(4)toString方法

  • 声明:
    public String toString() {return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
  • 作用:
    • 返回对象的字符串表示形式
    • 主要用于调试、日志记录和信息展示。
  • 默认实现:
    • 返回类的全限定名 + @ + 对象哈希码的无符号十六进制表示。
    • 例如:com.example.MyClass@1a2b3c4d
  • 为什么需要重写:
    • 默认的字符串信息通常没有业务意义。
    • 重写后可以提供对象的有意义的、可读性强的信息。
  • 重写建议:
    • 包含对象的关键状态信息。
    • 格式清晰,易于理解。
    • 例如:"Person{name='John', age=30}"
  • 自动调用:
    • 当对象被用于字符串拼接 ("obj: " + obj) 或被 System.out.println(obj) 直接打印时,会自动调用 toString()

(5)wait方法

  • 声明:
    public final void wait() throws InterruptedException {wait(0L);
    }
    public final native void wait(long timeoutMillis) throws InterruptedException;
    public final void wait(long timeoutMillis, int nanos) throws InterruptedException { ... }
  • 作用:
    • 使当前线程进入等待状态 (WAITING/Timed Waiting),并释放该对象的监视器锁(monitor lock)。
    • 线程会一直等待,直到以下情况之一发生:
      1. 其他线程调用了该对象的 notify() 或 notifyAll() 方法。
      2. 等待时间 timeoutMillis 到期(对于带超时的版本)。
      3. 当前线程被其他线程中断 (interrupt())。
  • 关键点:
    • final: 不能被重写。
    • 必须在同步块中调用: 调用 wait() 前,当前线程必须拥有该对象的锁(即在 synchronized 块或方法内)。否则会抛出 IllegalMonitorStateException
    • 释放锁: 这是 wait() 的核心特性。它允许其他线程获取锁来修改对象状态。
    • 重新获取锁: 当线程被唤醒(或超时)后,它不会立即继续执行。它必须重新竞争该对象的锁,只有获得锁后才能从 wait() 调用中返回并继续执行。
  • 使用场景:
    • 生产者-消费者模式、线程间协调、条件等待。

(6)notify和notifyAll方法

  • 声明:
    @IntrinsicCandidate
    public final native void notify();
    @IntrinsicCandidate
    public final native void notifyAll();
  • 作用:
    • 唤醒在该对象监视器上等待的一个 (notify()) 或所有 (notifyAll()) 线程。
    • 被唤醒的线程不会立即执行。它们需要重新竞争该对象的锁,获得锁后才能继续。
  • 关键点:
    • final: 不能被重写。
    • @IntrinsicCandidate: 可被 JIT 优化。
    • 必须在同步块中调用: 和 wait() 一样,调用前必须持有对象的锁。
    • 不释放锁: 调用 notify()/notifyAll() 的线程不会立即释放锁。它通常会在完成当前同步块的逻辑后才释放锁。
    • notify() vs notifyAll():
      • notify(): 随机唤醒一个等待线程。如果多个线程等待不同的条件,使用 notify() 可能唤醒错误的线程,导致死锁或逻辑错误。通常更安全的做法是使用 notifyAll()
      • notifyAll(): 唤醒所有等待线程。虽然可能唤醒不需要的线程(“惊群效应”),但能确保满足条件的线程有机会运行。被唤醒的线程需要在循环中检查条件是否真正满足。
  • 使用场景:
    • 与 wait() 配合,实现线程间的精确通信和协作。

(7)finalize方法

  • 声明:
    @Deprecated(since="9")
    protected void finalize() throws Throwable { }
  • 作用:
    • 在垃圾回收器回收对象之前,由 JVM 调用此方法。
    • 理论上可用于执行对象销毁前的清理工作(如释放非 Java 资源:文件句柄、网络连接、本地内存等)。
  • 关键点:
    • @Deprecated: 从 Java 9 开始被标记为过时强烈不推荐使用
    • 不可靠:
      • 无法保证 finalize() 会被调用(程序可能在对象回收前就终止了)。
      • 无法保证调用的时机(可能在对象不可达后很久才调用)。
      • 甚至无法保证会被调用(JVM 退出时可能不执行)。
    • 性能开销: 启用 finalize() 会显著增加垃圾回收的开销和复杂性。
    • 危险finalize() 中抛出的未捕获异常会被忽略,可能导致资源清理失败。
  • 替代方案:
    • try-with-resources: 用于管理实现了 AutoCloseable 接口的资源(如 InputStreamOutputStreamConnection)。这是最推荐的方式。
    • 显式 close() 方法: 在不再需要资源时,手动调用其 close() 方法。
    • Cleaner 类 (Java 9+): 提供了一种更灵活、更安全的替代 finalize() 的机制,用于清理非堆资源。

2. equals方法和hashCode方法的关系。

相等的对象必须有相同的哈希码:这是 hashCode() 和 equals() 合约的铁律。如果 a.equals(b) 为 true,那么 a.hashCode() == b.hashCode() 必须为 true。违反这一点会导致 HashMap 等集合行为异常(比如 get() 找不到刚 put() 进去的对象)。

这是一个关于 hashCode()equals() 方法之间核心契约(Contract) 的极其重要的规则。理解它对于正确使用 Java 集合框架(尤其是基于哈希的集合如 HashMap, HashSet, Hashtable)至关重要。

我们来详细解释为什么这是“铁律”,以及违反它会导致什么灾难性的后果。

1. 这条规则的含义

这条规则包含两个层面:

  • 正向要求:如果两个对象通过 equals() 方法比较是相等的(a.equals(b) == true),那么它们的 hashCode() 方法必须返回相同的整数值(a.hashCode() == b.hashCode())。
  • 反向不要求:反过来不成立。如果两个对象的 hashCode() 相同(a.hashCode() == b.hashCode()),它们不一定相等(a.equals(b) 可能为 false)。这就是我们之前讨论的“哈希冲突”,是允许且正常的。

简单说:相等 ⇒ 同哈希码。同哈希码 ⇏ 相等。

2. 为什么是“铁律”?—— 以 HashMap 为例

HashMap 的工作原理完全依赖于这个契约。它的 put(K key, V value)get(Object key) 操作流程如下:

put 操作:

  1. 计算 key 的哈希码:int hash = key.hashCode();
  2. 根据这个哈希码,通过内部算法(如 hash & (table.length - 1))确定该键值对应该存放在内部数组的哪个“桶”(bucket)位置,记为 index
  3. 将 (key, value) 这个键值对放入 table[index] 处的链表(或红黑树)中。

get 操作:

  1. 计算传入的 key 的哈希码:int hash = key.hashCode();
  2. 根据这个哈希码,计算出它应该在哪个“桶”里,记为 index
  3. 关键步骤:遍历 table[index] 处的链表(或树)中的每一个
  4. 对于链表中的每一个键 kHashMap 会进行双重检查
    • 首先检查哈希码hash == k.hashCode()。如果哈希码不同,直接跳过。
    • 然后检查相等性:如果哈希码相同,再调用 key.equals(k)。只有当 equals() 也返回 true 时,才认为找到了匹配的键。

3. 违反契约的灾难性后果

假设我们有一个 Student 类,但我们错误地实现了 hashCode()equals()违反了“相等的对象必须有相同的哈希码”这条规则。

public class Student {private String name;private int age;public Student(String name, int age) {this.name = name;this.age = age;}// 错误的实现!违反了 hashCode-equals 契约!@Overridepublic boolean equals(Object obj) {if (this == obj) return true;if (obj == null || getClass() != obj.getClass()) return false;Student student = (Student) obj;// 只根据名字判断相等return name.equals(student.name);}@Overridepublic int hashCode() {// 错误!只根据年龄生成哈希码return Integer.hashCode(age);// 注意:这里没有考虑 name!}// ... toString(), getters 等
}

问题分析:

  • 两个 Student 对象,只要 name 相同,就被认为是 equals 的。
  • 但是,它们的 hashCode() 只依赖于 age

灾难场景:

import java.util.HashMap;public class DisasterExample {public static void main(String[] args) {HashMap<Student, String> studentMap = new HashMap<>();// 创建两个名字相同但年龄不同的学生Student alice1 = new Student("Alice", 20);Student alice2 = new Student("Alice", 21); // 名字相同,但年龄不同// 根据我们的 equals 方法,它们是相等的!System.out.println("alice1.equals(alice2): " + alice1.equals(alice2)); // 输出: true// 但是,它们的哈希码不同!(因为年龄不同)System.out.println("alice1.hashCode(): " + alice1.hashCode()); // 输出: 20System.out.println("alice2.hashCode(): " + alice2.hashCode()); // 输出: 21// **PUT 操作**// 将 alice1 作为键,存入学号 "S001"studentMap.put(alice1, "S001");// HashMap 内部:// 1. 计算 alice1.hashCode() -> 20// 2. 根据 20 计算出桶的位置,比如 index = 5// 3. 将 (alice1, "S001") 存入 table[5] 的链表中// **GET 操作 - 试图用 alice2 去查找**// 注意:alice2.equals(alice1) 是 true,它们逻辑上是同一个学生!String result = studentMap.get(alice2);System.out.println("Result: " + result); // 输出: null !!!! (灾难发生)// 为什么会是 null?// 1. HashMap 计算 alice2.hashCode() -> 21// 2. 根据 21 计算出桶的位置,比如 index = 7// 3. HashMap 去 table[7] 查找,但那里是空的!因为 (alice1, "S001") 被存到了 index=5!// 4. 所以,即使 alice1 和 alice2 在逻辑上是相等的,HashMap 也找不到它们。//    因为 `get` 操作从错误的“桶”开始查找。// **更糟糕的情况:PUT 同一个“逻辑对象”两次**studentMap.put(alice2, "S002"); // 这应该更新 alice1 的值,但...// HashMap 会:// 1. 计算 alice2.hashCode() -> 21 -> index = 7// 2. 在 table[7] 处,因为是空的,直接把 (alice2, "S002") 放进去。// 现在,逻辑上相等的键 alice1 和 alice2 分别存在于不同的桶中!// HashMap 的大小变成了 2,但实际上它只应该存储一个 "Alice"。System.out.println("Map size: " + studentMap.size()); // 输出: 2 (逻辑错误)}
}

4. 正确的做法

正确的 hashCode() 实现必须包含所有参与 equals() 比较的字段。

@Override
public int hashCode() {// 包含所有 equals 中用到的字段return Objects.hash(name, age); // 或者手动计算: name.hashCode() * 31 + age
}

这样,当 alice1.equals(alice2)true 时(意味着 name 相同,age 也必须相同),alice1.hashCode()alice2.hashCode() 也必然相等,HashMap 就能正常工作。

总结

“相等的对象必须有相同的哈希码”是 hashCode()equals() 之间的根本性契约HashMap 等集合类依赖这个契约来保证其正确性和效率:

  1. hashCode() 用于快速定位数据所在的“桶”(高效)。
  2. equals() 用于在同一个“桶”内精确识别是哪个对象(准确)。

如果这个契约被破坏,hashCode() 的“快速定位”功能就会失效,导致 HashMap 找不到它本应找到的对象,或者存储重复的逻辑对象,从而使整个集合的行为变得不可预测和错误。因此,每当重写 equals() 时,必须重写 hashCode(),并且确保它们使用相同的字段集。

http://www.dtcms.com/a/354881.html

相关文章:

  • 医院信息系统(HIS)的开发架构解析,代码示例
  • 面试tips--并发--进程与线程的区别线程通信方式总结
  • k8s集群1.20.9
  • 虚拟相机的最佳实践参考是什么
  • k8s是什么?
  • docker和k8s的区别
  • Android 开发 - 数据共享(数据共享、内容提供者实现、动态权限申请)
  • 面试记录7 c++软件开发工程师
  • Flask测试平台开发实战-第二篇
  • 面试之HashMap
  • 面试tips--JVM(3)--类加载过程
  • 【赵渝强老师】MySQL数据库的多实例环境
  • 前端Sentry数据分析与可视化:构建智能化监控仪表板
  • 大数据毕业设计选题推荐-基于大数据的痴呆症预测数据可视化分析系统-Spark-Hadoop-Bigdata
  • 重置 Windows Server 2019 管理员账户密码
  • 基于SamOut的音频Token序列生成模型训练指南
  • 【Rust】 3. 语句与表达式笔记
  • Flask测试平台开发实战-第一篇
  • 安科瑞三相智能安全配电装置在养老院配电系统中的应用
  • Flask测试平台开发,登陆重构
  • F010 Vue+Flask豆瓣图书推荐大数据可视化平台系统源码
  • 新型Zip Slip漏洞允许攻击者在解压过程中操纵ZIP文件
  • 大模型训练推理优化(5): FlexLink —— NVLink 带宽无损提升27%
  • Android Glide插件化开发实战:模块化加载与自定义扩展
  • 使用MySQL计算斐波那契数列
  • 三轴云台之闭环反馈技术篇
  • Vue + ECharts 中 Prop 数据被修改导致图表合并的问题及解决方案
  • Vibe Coding到底是什么:什么是 Vibe Coding?AI编程?
  • SpringCloud OpenFeign 远程调用(RPC)
  • Web网络开发 -- 常见CSS属性