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());
}

 @IntrinsicCandidate
 public 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 ...

    @Override
    public 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 契约!
    @Override
    public 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);
    }

    @Override
    public 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()); // 输出: 20
        System.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(),并且确保它们使用相同的字段集。

Logo

惟楚有才,于斯为盛。欢迎来到长沙!!! 茶颜悦色、臭豆腐、CSDN和你一个都不能少~

更多推荐