Java基础(九):Object核心类深度剖析
Java基础系列文章
Java基础(一):初识Java——发展历程、技术体系与JDK环境搭建
Java基础(二):八种基本数据类型详解
Java基础(三):逻辑运算符详解
Java基础(四):位运算符详解
Java基础(五):流程控制全解析——分支(if/switch)和循环(for/while)的深度指南
Java基础(六):数组全面解析
Java基础(七): 面向过程与面向对象、类与对象、成员变量与局部变量、值传递与引用传递、方法重载与方法重写
Java基础(八):封装、继承、多态与关键字this、super详解
Java基础(九):Object核心类深度剖析
目录
- 一、Object 类是什么?
- 二、Object类方法全解析
- 1、public final native Class<?> getClass()
- 2、public boolean equals(Object obj)
- 3、public native int hashCode()
- 3.1、常用的哈希码的算法
- 3.2、JDK工具类Objects.hash方法
- 3.3、hashCode作用及与equal的区别
- 3.4、HashSet插入对象(示例)
- 4、protected native Object clone() throws CloneNotSupportedException
- 5、public String toString()
- 6、wait() / notify() / notifyAll() / wait(long timeout) / wait(long timeout, int nanos)
- 7、protected void finalize() throws Throwable
一、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
- 因为Java有
- 关键特性
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) {// 如果是同一个对象,直接返回trueif (this == o) return true;// 如果传入的对象是null或者不是同一个类,直接返回falseif (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位整数)
,用于在哈希表等数据结构中快速查找对象 - 契约规则
- 同一对象多次调用必须返回相同值
equals()
相等的对象必须有相同哈希码- 不相等的对象
不要求
哈希码不同(但不同能提升哈希表性能) - 参与
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; }
- 直接使用Integer对象里所包含的那个
Date
类重写hashCode方法- 返回 Date 对象的
毫秒数
(自1970年1月1日以来的毫秒数)作为哈希值
public int hashCode() {// ht 是一个 64 位的长整型(long),它表示时间戳long ht = this.getTime();// 对低 32 位和高 32 位的哈希值进行异或,得到最终的哈希值return (int) ht ^ (int) (ht >> 32); }
- 返回 Date 对象的
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;}@Overridepublic 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);}@Overridepublic 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()方法步骤:
- 实现 Cloneable接口
- 重写 clone()方法,并将访问权限改为 public(通常做法)
- 在重写的 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 略...@Overridepublic Person clone() throws CloneNotSupportedException {return (Person) super.clone(); // 调用 Object 的 clone() 方法}@Overridepublic 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 资源(如本地方法分配的资源)
- 执行一些对象销毁前的逻辑
重要注意事项:
- 不保证一定会被调用
- Java 不保证 finalize()方法一定会被执行。如果程序结束或者垃圾回收器没有运行,这个方法可能永远不会被调用
- 垃圾回收本身就不保证及时性,finalize()的调用更没有时间保证
- 不推荐依赖 finalize()做资源释放
- 因为调用时机不确定,可能导致资源长时间未释放,造成内存泄漏或资源浪费
- 更推荐使用
try-with-resources
或手动调用close()
方法来管理资源(如实现AutoCloseable
接口)
- 性能开销大
- 使用 finalize()的对象在垃圾回收时需要额外的处理步骤,会影响垃圾回收性能
Java 9
开始,finalize()方法已被 标记为deprecated(已废弃)
,官方不建议再使用它