Object
字数: 0 字 时长: 0 分钟
1. Object 类
1.1 Object 类概述
在 Java 中,经常会听到一句话:
万物皆对象。
更准确地说,Java 中除了基本数据类型之外,我们平时定义和使用的大多数类型最终都与 Object 类有关。
Object 类位于:
java.lang.Object它是 Java 类继承体系中的根类。
如果一个类没有显式指定父类:
public class Person {
}实际上可以理解为:
public class Person extends Object {
}因此,我们自己定义的类都可以使用 Object 中提供的方法:
Person person = new Person();
System.out.println(person.toString());
System.out.println(person.hashCode());
System.out.println(person.getClass());数组本身也是对象:
int[] array = {1, 2, 3};
System.out.println(array.getClass());
System.out.println(array.hashCode());
System.out.println(array.toString());直接打印数组时,经常会看到类似:
[I@1b6d3586这样的结果,也是因为数组对象使用了 Object 中默认的 toString() 实现。
Object 主要提供了下面这些方法:
getClass()
hashCode()
equals()
clone()
toString()
wait()
wait(long timeout)
wait(long timeout, int nanos)
notify()
notifyAll()
finalize()大致可以分为六类:

对象比较
├── equals()
└── hashCode()
对象拷贝
└── clone()
对象转字符串
└── toString()
线程等待与通知
├── wait()
├── notify()
└── notifyAll()
获取类型信息
└── getClass()
对象终结
└── finalize()下面分别来看这些方法。
1.2 对象比较
1.2.1 hashCode()
hashCode() 用于返回对象的哈希值。
Object 中的方法定义如下:
public native int hashCode();native 表示该方法的具体实现并不是普通 Java 代码,而是由 JVM 提供底层实现。
需要注意:
如果两个对象通过
equals()判断相等,那么它们的hashCode()必须相等。
也就是说:
equals() 相等
↓
hashCode() 必须相等但是反过来并不一定成立:
hashCode() 相等
↓
两个对象不一定 equals()因为不同对象可能发生哈希冲突。
如果一个类重写了 equals(),通常也应该同时重写 hashCode()。
例如:
@Override
public int hashCode() {
return Objects.hash(name, age);
}equals() 和 hashCode() 的关系在 HashMap、HashSet 等集合中尤其重要,后面的集合笔记还会详细介绍。
1.2.2 equals()
equals() 用于判断两个对象是否相等。
Object 中默认实现如下:
public boolean equals(Object obj) {
return this == obj;
}可以看到,Object.equals() 默认直接使用:
==也就是说,如果子类没有重写 equals(),默认比较的是:
两个引用是否指向同一个对象。
例如:
Person person1 = new Person();
Person person2 = new Person();
System.out.println(person1 == person2);
System.out.println(person1.equals(person2));由于两个对象是分别 new 出来的,如果 Person 没有重写 equals(),结果都是:
false
false但是在实际开发中,我们经常希望按照对象中的字段判断两个对象是否“相等”。
例如有一个 Person4 类,我们规定:
姓名和年龄相同,就认为是同一个人。
这时就可以重写 equals():
public class Person4 {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public boolean equals(Object obj) {
// 如果两个引用本身就是同一个对象
if (this == obj) {
return true;
}
// 判断 obj 是否为 Person4
if (obj instanceof Person4) {
Person4 p = (Person4) obj;
// 根据 name 和 age 判断两个对象是否相等
return Objects.equals(this.name, p.getName())
&& this.age == p.getAge();
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}这里不仅重写了 equals(),同时也重写了 hashCode(),保证两者的约定一致。
像 String、Integer 等常用类,也都重写了 equals(),因此它们通常比较的是对象中的实际内容,而不是单纯比较对象地址。
1.3 对象拷贝 clone()
Object 中提供了 clone() 方法,用于创建当前对象的副本:
protected native Object clone()
throws CloneNotSupportedException;clone() 是一个 native 方法。
默认情况下,Object.clone() 执行的是浅拷贝(Shallow Copy)。
需要注意的是:
一个类如果想正常调用
clone(),通常需要实现Cloneable接口。
例如:
public class Person implements Cloneable {
private String name;
private int age;
@Override
protected Object clone()
throws CloneNotSupportedException {
return super.clone();
}
}然后:
Person person1 = new Person();
// 创建 person1 的副本
Person person2 = (Person) person1.clone();Cloneable 是一个比较特殊的标记接口,本身没有定义任何方法。
如果对象没有实现 Cloneable,直接调用 Object.clone(),会抛出:
CloneNotSupportedException默认 clone() 只进行浅拷贝。
例如对象中存在引用类型字段:
Person
├── name
├── age
└── Address浅拷贝后,两个 Person 对象中的 Address 引用仍然可能指向同一个对象。
深拷贝和浅拷贝后面如果涉及对象复制,可以再单独展开。
1.4 对象转字符串 toString()
toString() 用于返回对象的字符串表示。
Object 中默认实现如下:
public String toString() {
return getClass().getName()
+ "@"
+ Integer.toHexString(hashCode());
}因此,如果一个类没有重写 toString():
Person person = new Person();
System.out.println(person);输出结果可能类似:
com.kerwim.Person@3d075dc0其中:
com.kerwim.Person表示对象所属的类。
而:
3d075dc0是 hashCode() 转换成的十六进制字符串。
这样的结果通常没有太多业务意义,因此实际开发中经常会重写 toString()。
例如:
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}这时打印:
System.out.println(person);就可以得到类似:
Person{name='kerwim', age=18}比默认输出直观很多。
这项工作也可以直接交给 IntelliJ IDEA。
例如:
Generate
↓
toString()IDE 就可以自动生成对应代码。
现在很多项目还会使用 Lombok:
@Data
public class Person {
private String name;
private int age;
}@Data 会自动生成:
getter
setter
equals()
hashCode()
toString()等常见方法。
数组也是对象,因此直接打印数组时:
int[] array = {1, 2, 3};
System.out.println(array);可能会得到:
[I@1b6d3586因为数组没有像 String 那样重写 toString()。
所以打印数组内容通常应该使用:
System.out.println(Arrays.toString(array));1.5 多线程调度 wait、notify、notifyAll
Object 还提供了一组和线程等待、通知有关的方法:
wait()
notify()
notifyAll()为什么这些方法定义在 Object 中?
因为 Java 中的普通对象都可以作为:
锁对象
Monitor用于线程之间的等待与通知。
这里先简单看一个例子:
public class WaitNotifyDemo {
public static void main(String[] args) {
// 创建一个锁对象
Object lock = new Object();
// 线程 1
new Thread(() -> {
synchronized (lock) {
System.out.println("线程1:我要等待");
try {
// 当前线程进入等待状态
lock.wait();
} catch (InterruptedException e) {
// 恢复线程的中断状态
Thread.currentThread().interrupt();
}
System.out.println("线程1:我被唤醒了");
}
}).start();
// 线程 2
new Thread(() -> {
synchronized (lock) {
System.out.println("线程2:我要唤醒线程");
// 唤醒一个等待在 lock 上的线程
lock.notify();
System.out.println("线程2:已经执行 notify()");
}
}).start();
}
}大致过程为:
线程 1
↓
获得 lock 锁
↓
lock.wait()
↓
进入等待状态
↓
释放 lock
线程 2
↓
获得 lock 锁
↓
lock.notify()
↓
通知等待线程
线程 1
↓
重新竞争 lock
↓
获得锁之后继续执行这里特别需要注意:
wait()、notify()、notifyAll()必须在当前线程已经持有对应对象 Monitor 的情况下调用。
所以通常要配合:
synchronized (lock)使用。
wait()
public final void wait()
throws InterruptedException调用之后,当前线程会进入等待状态,直到:
其他线程调用 notify()
或者
其他线程调用 notifyAll()
或者
线程被中断notify()
public final native void notify();用于唤醒在这个对象 Monitor 上等待的一个线程。
如果有多个线程正在等待,具体哪个线程被唤醒并不由程序直接指定。
notifyAll()
public final native void notifyAll();用于唤醒在这个对象 Monitor 上等待的所有线程。
这些线程被唤醒之后,还需要重新竞争锁。
wait(long timeout)
public final native void wait(long timeout)
throws InterruptedException;最长等待 timeout 毫秒。
例如:
lock.wait(1000);最多等待:
1000 mswait(long timeout, int nanos)
public final void wait(
long timeout,
int nanos)
throws InterruptedException可以在毫秒的基础上再指定纳秒参数。
wait/notify 属于比较典型的线程通信机制,后面的 JUC 笔记中还会详细介绍。
1.6 获取类型信息 getClass()
Object 中还提供了:
public final native Class<?> getClass();用于获取对象在运行时对应的 Class 对象。
例如:
public class GetClassDemo {
public static void main(String[] args) {
Person person = new Person();
// 获取 person 在运行时对应的 Class 对象
Class<? extends Person> clazz =
person.getClass();
// 获取完整类名
System.out.println(clazz.getName());
}
}假设 Person 位于:
com.kerwim.ObjAndClass那么输出结果:
com.kerwim.ObjAndClass.Person还可以继续获取更多类的信息:
Person person = new Person();
Class<?> clazz = person.getClass();
// 完整类名
System.out.println(clazz.getName());
// 简单类名
System.out.println(clazz.getSimpleName());
// 当前 Class 对象
System.out.println(clazz);getClass() 是 Java 反射机制的重要入口之一。
这里只先了解:
对象
↓
getClass()
↓
Class 对象
↓
获取类的运行时信息反射相关内容会在 反射.md 中详细介绍。
1.7 垃圾回收 finalize()
Object 曾经提供:
protected void finalize()
throws Throwable这个方法最初的设计目的是:
当垃圾回收器准备回收一个对象时,可以在对象真正被回收之前执行一些清理操作。
例如以前可能看到这样的写法:
@Override
protected void finalize()
throws Throwable {
System.out.println("对象准备被回收");
super.finalize();
}但是 finalize() 存在很多问题:
什么时候执行无法确定
不保证一定会执行
会增加 GC 负担
可能导致对象回收延迟
容易产生资源泄漏等问题因此,从 Java 9 开始:
finalize()已经被标记为废弃。
现在的 Java 项目中:
不要再依赖 finalize() 释放资源。
例如文件、数据库连接、网络连接等资源,应该通过:
try-with-resources
AutoCloseable
close()等更加明确的方式释放。

所以对于 finalize(),现在主要知道它的历史作用即可:
早期 Java
↓
对象被 GC 前尝试执行清理逻辑
现代 Java
↓
finalize() 已废弃
↓
不推荐继续使用具体的对象回收机制、垃圾回收器以及 GC,会在 JVM 部分详细介绍。
1.8 Object 方法总结
最后把 Object 中比较重要的方法简单归纳一下:
| 方法 | 作用 |
|---|---|
equals() | 判断两个对象是否相等 |
hashCode() | 获取对象的哈希值 |
toString() | 获取对象的字符串表示 |
clone() | 创建对象副本 |
getClass() | 获取对象运行时对应的 Class |
wait() | 让当前线程进入等待状态 |
notify() | 唤醒一个等待线程 |
notifyAll() | 唤醒所有等待线程 |
finalize() | 对象终结方法,已经废弃 |
其中实际开发中最常见的通常是:
equals()
hashCode()
toString()
getClass()而:
wait()
notify()
notifyAll()主要会在线程通信和并发编程中遇到。
Object 可以看作 Java 对象体系的基础,几乎所有普通 Java 类最终都会继承它,因此理解 Object 中这些方法,对后面的集合、反射、并发以及 JVM 学习都会有帮助。