Java基础(九):Object核心类深度剖析

发布于:2025-08-19 ⋅ 阅读:(10) ⋅ 点赞:(0)

Java基础系列文章

Java基础(一):初识Java——发展历程、技术体系与JDK环境搭建

Java基础(二):八种基本数据类型详解

Java基础(三):逻辑运算符详解

Java基础(四):位运算符详解

Java基础(五):流程控制全解析——分支(if/switch)和循环(for/while)的深度指南

Java基础(六):数组全面解析

Java基础(七): 面向过程与面向对象、类与对象、成员变量与局部变量、值传递与引用传递、方法重载与方法重写

Java基础(八):封装、继承、多态与关键字this、super详解

Java基础(九):Object核心类深度剖析

一、Object 类是什么?

  • 位置:java.lang.Object
    在这里插入图片描述
  • 所有类(数组、集合、自定义类不包括基本类型)都直接或间接继承自 Object
  • 如果你写的类没有显式 extends,编译器会自动加上 extends Object
  • 定义了一组最基本的方法(如 equals、hashCode、toString 等),保证任何对象都具备这些能力

类结构示意:

public class Object {
    private static native void registerNatives();
    static {
        registerNatives();
    }

    public final native Class<?> getClass();
    public native int hashCode();
    public boolean equals(Object obj) { return this == obj; }
    protected native Object clone() throws CloneNotSupportedException;
    public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
    public final native void notify();
    public final native void notifyAll();
    public final native void wait(long timeout) throws InterruptedException;
    public final void wait(long timeout, int nanos) throws InterruptedException;
    public final void wait() throws InterruptedException;
    protected void finalize() throws Throwable { }
}

二、Object类方法全解析

1、public final native Class<?> getClass()

  • 作用:返回对象的运行时类(Class对象)
    • 因为Java有多态现象,所以一个引用数据类型的变量的编译时类型与运行时类型可能不一致
    • 如果需要查看这个变量实际指向的对象的类型,需要用getClass()方法
      public static void main(String[] args) {
      	Object obj = new Person();
      	System.out.println(obj.getClass());//运行时类型
      }
      // 输出结果
      class com.atguigu.java.Person
      
  • 关键特性
    • native方法:通过JVM本地方法实现
    • 应用场景:反射、类型检查
    • .class语法区别:getClass()动态获取,MyClass.class静态获取

2、public boolean equals(Object obj)

  • 作用:比较对象内容是否逻辑相等(默认实现为==地址比较,也就是否指向同一个对象)
    在这里插入图片描述
  • 对于File、String、Integer、Date,用equals()方法进行比较时
    • 是比较类型及内容不考虑引用的是否是同一个对象
    • 原因:在这些类中重写了Object类的equals()方法
  • 重写要求
    • 自反性:x.equals(x)返回true
    • 对称性:x.equals(y)y.equals(x)
    • 传递性:x.equals(y) 且 y.equals(z)x.equals(z)
    • 一致性:多次调用结果不变
    • 非空性:x.equals(null)返回false

正确重写示例:

@Override
public boolean equals(Object o) {
	// 如果是同一个对象,直接返回true
    if (this == o) return true;
    
    // 如果传入的对象是null或者不是同一个类,直接返回false
    if (o == null || getClass() != o.getClass()) return false;
    
    // 如果传入的对象是同一个类,所有属性的做比较
    Person person = (Person) o;
    return age == person.age && Objects.equals(name, person.name);
}

3、public native int hashCode()

  • 作用:返回对象的哈希码(32位整数),用于在哈希表等数据结构中快速查找对象
  • 契约规则
    1. 同一对象多次调用必须返回相同值
    2. equals()相等的对象必须有相同哈希码
    3. 不相等的对象不要求哈希码不同(但不同能提升哈希表性能)
    4. 参与 HashMap/HashSet 等作为键/元素时,此契约极其关键

重写规范:

@Override
public int hashCode() {
    return Objects.hash(name, age); // 使用JDK工具类
}

3.1、常用的哈希码的算法

  • Object类的hashCode方法(默认的hashCode算法)
    • 基于对象的内存地址转换为整数作为哈希值
    • 由于每个对象的内存地址都不一样,所以哈希码也不一样
  • String类重写hashCode方法
    • 通过遍历字符串中的每个字符的ASCII值
    • 利用质数 31 进行加权计算,生成基于字符序列的哈希值
    public int hashCode() {
        int h = 0;
        for (int i = 0; i < value.length; i++) {
            h = 31 * h + value[i];
        }
        return h;
    }
    
  • Integer类重写hashCode方法
    • 直接使用Integer对象里所包含的那个整数的数值
    public int hashCode() {
        return value;
    }
    
  • Date类重写hashCode方法
    • 返回 Date 对象的毫秒数(自1970年1月1日以来的毫秒数)作为哈希值
    public int hashCode() {
    	// ht 是一个 64 位的长整型(long),它表示时间戳
        long ht = this.getTime();
        // 对低 32 位和高 32 位的哈希值进行异或,得到最终的哈希值
        return (int) ht ^ (int) (ht >> 32);
    }
    

3.2、JDK工具类Objects.hash方法

  • 该方法通过遍历每个元素,使用31 * 当前结果(初始值1) + 元素哈希(null为0)的方式计算并返回一个综合所有元素内容的哈希码
// Objects类方法
public static int hash(Object... values) {
    return Arrays.hashCode(values);
}
// Arrays类方法
public static int hashCode(Object a[]) {
    if (a == null){
        return 0;
	}
    int result = 1;

    for (Object element : a){
    	// 31这个数字是为了尽量的减少hash冲突
        result = 31 * result + (element == null ? 0 : element.hashCode());
	}
    return result;
}

为什么使用 31?

  • 31 是一个奇质数,使用质数有助于减少哈希冲突,使得不同对象组合出相同哈希值的概率更低
  • 31 可以被 JVM 优化:31 * i 等价于 (i << 5) - i,这种位运算在某些情况下 JVM 会自动优化,提高计算效率
  • 经验验证​:经过大量实践和实验表明,31 在哈希分布性和计算性能之间取得了较好的平衡,因此被广泛采用(如 Java 的 List、String、HashMap等哈希计算都用了 31)

3.3、hashCode作用及与equal的区别

hashCode主要作用

  • 主要用于哈希表(如 HashMap、HashSet、Hashtable等)中快速定位对象
  • 哈希码是一个整数,用于快速确定对象在哈希表中可能存储的“桶(bucket)”位置,从而提升查找、插入、删除等操作的效率
  • 好的 hashCode 实现应尽量保证:相等的对象必须有相同的 hashCode,不同对象尽量有不同的 hashCode(减少哈希冲突

两者的关联规则(非常重要!)​​

  • 一致性
    • 在对象未被修改的情况下,多次调用 hashCode()应返回相同的值
  • equals 为 true ⇒ hashCode 必须相同
    • 如果 a.equals(b) == true,那么 ​a.hashCode() 必须等于 b.hashCode()
    • 这是强制要求!如果不遵守,对象放入 HashMap/HashSet 后可能找不到
  • hashCode 相同 ⇏ equals 为 true
    • 如果 a.hashCode() == b.hashCode(),​不代表 a.equals(b) 一定为 true
    • 即:不同对象可能有相同的哈希码,这就是哈希冲突,很常见

总结一句话

  hashCode() 用于快速定位对象(提高哈希表效率),equals() 用于精确判断对象是否逻辑相等;两者必须配合使用,且遵循“equals为true时hashCode必须相同”的规则。​​

3.4、HashSet插入对象(示例)

  • User实体类,重写equal方法和hashCode方法
public class User {
    private String name;
    private Integer age;
    
    public User(String name,Integer age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        System.out.println("equals...");
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return age == user.age && Objects.equal(name, user.name);
    }

    @Override
    public int hashCode() {
        System.out.println("hashCode...");
        return Objects.hashCode(name, age);
    }
}
  • HashSet插入两个属性相同的对象
public class Test {
    public static void main(String[] args) {
        Set<User> set = new HashSet<>();
        User u1  = new User("张三",3);
        User u2  = new User("张三",3);
        set.add(u1);
        set.add(u2);
        System.out.println("set size:"+ set.size());
    }
}
  • 输出结果
    • 插入u1只需要调用hashCode获取对应下标位置
    • 插入u2调用hashCode发现对应位置有数据也就是u1,然后会去调用equal比较,内容相同则表示同一个数据不插入,则集合数据只有一个
hashCode...
hashCode...
equals...
set size:1

4、protected native Object clone() throws CloneNotSupportedException

  • 作用:创建对象的副本(浅拷贝
    • 浅拷贝:复制基本类型字段,引用类型复制指针(开发常用Convert、BeanUtil等第三方工具)
    • 深拷贝:需手动递归克隆引用字段(使用Fast、Jackson、Gson序列化工具)
  • 访问修饰符: protected,表示只有该类本身、子类或同包中的类可以调用此方法
  • Cloneable是一个标记接口(marker interface)​,它不包含任何方法。它存在的意义仅仅是告诉 JVM,这个类的对象是允许被克隆的
    • ⚠️ ​重要:如果一个类没有实现 Cloneable接口,却调用了 clone()方法,就会抛出 CloneNotSupportedException

正确使用clone()方法步骤:

  1. 实现 Cloneable接口​
  2. 重写 clone()方法,并将访问权限改为 public(通常做法)​​
  3. 在重写的 clone()方法中调用 super.clone()​
public class Person implements Cloneable {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Getter & Setter 略...

    @Override
    public Person clone() throws CloneNotSupportedException {
        return (Person) super.clone(); // 调用 Object 的 clone() 方法
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }
}

5、public String toString()

  • 作用:返回对象的字符串表示(默认:类名@十六进制哈希码
    在这里插入图片描述

重写建议:

@Override
public String toString() {
    return "Person{" +
           "name='" + name + '\'' +
           ", age=" + age +
           '}';
}
// 输出:Person{name='Alice', age=30}

6、wait() / notify() / notifyAll() / wait(long timeout) / wait(long timeout, int nanos)

  • 作用:实现线程间通信(必须在synchronized块内使用)
方法 作用 是否释放锁 备注
wait() 让当前线程等待,直到其他线程调用 notify()或 notifyAll() 无限期等待
wait(long timeout) 最多等指定的毫秒时间,超时自动唤醒(避免永久阻塞) 也会被notify()或notifyAll()提前唤醒
wait(long timeout, int nanos) 同上,只是超时时间设置精确到纳秒 很少使用
notify() 唤醒一个等待该对象的线程(具体哪个不确定,由 JVM 决定)
被唤醒线程从​等待状态进入锁的竞争状态,需要重新获取对象锁才能运行
可能唤醒不合适的线程
notifyAll() 唤醒所有等待线程
所有被唤醒的线程都会尝试重新去获取对象的锁,但只有一个线程能获取到锁并继续执行,其余线程继续阻塞
推荐使用,更安全

简单示例:

class SharedResource {
    private boolean ready = false;
	
	// 消费者​ 线程发现数据没准备好,就调用 wait()​等待,直到被叫醒获取锁才能执行
    public synchronized void consume() throws InterruptedException {
        while (!ready) {
            System.out.println("消费者:数据未准备好,等待中...");
            wait(); // 等待生产者通知
        }
        System.out.println("消费者:数据已消费");
        ready = false; // 重置状态
    }
	
	// 生产者​ 线程准备好数据后,调用 notify()或 notifyAll()​叫醒消费者
    public synchronized void produce() {
        // 模拟生产
        ready = true;
        System.out.println("生产者:数据已准备好,通知消费者");
        notifyAll(); // 唤醒所有等待的消费者线程
    }
}

现代Java开发中,这些方法现在通常被java.util.concurrent包中的高级并发工具(如CountDownLatch、CyclicBarrier、Semaphore、Future和 CompletableFuture等)取代,因为它们更安全、更易用且功能更强大。


7、protected void finalize() throws Throwable

  • 作用:用于在对象被垃圾回收器回收之前,​提供一个清理资源的最后机会
  • 可以在子类中重写 finalize()方法,用于执行一些 ​清理操作,比如:
    • 关闭文件句柄
    • 释放非 Java 资源(如本地方法分配的资源)
    • 执行一些对象销毁前的逻辑

重要注意事项:

  1. 不保证一定会被调用​
    • Java ​不保证 finalize()方法一定会被执行。如果程序结束或者垃圾回收器没有运行,这个方法可能永远不会被调用
    • 垃圾回收本身就不保证及时性,finalize()的调用更没有时间保证
  2. 不推荐依赖 finalize()做资源释放​
    • 因为调用时机不确定,可能导致资源长时间未释放,造成内存泄漏或资源浪费
    • 更推荐使用 ​try-with-resources​ 或手动调用 close() 方法来管理资源(如实现AutoCloseable接口)
  3. 性能开销大​
    • 使用 finalize()的对象在垃圾回收时需要额外的处理步骤,会影响垃圾回收性能
    • Java 9 开始,finalize()方法已被 ​标记为 deprecated(已废弃)​,官方不建议再使用它

网站公告

今日签到

点亮在社区的每一天
去签到