在之前的文章中,我们已经对Java的基础数据类型、类、对象、继承、多态等核心概念有了一定的了解。
你可能听说过这样一句话:"在Java中,一切皆对象。"
Object类就是Java中所有类的"老祖宗",是类层次结构的根。
不管你创建什么类,它都直接或间接地继承自Object 类。
即使你没有显式地使用extends关键字,你的类也会默认继承Object。
今天,我们就来一起看看Object类。理解Object类和它的方法。
一、为什么万物皆Object?
在懒惰蜗牛:Day12 | Java继承详解里面,我们提到过Object类。
Java在设计之初就明确了"Java是一门纯粹面向对象编程(OOP)的语言"这个核心的基调。
"万物皆对象"是它的核心原则,所有类(除了基本类型包装器对应的class)都隐式或显式继承自Object,
这就建立了单一继承树。
既然万物皆对象,而Object又作为整个继承体系的根,那这个Object类怎么抽象出所有对象的基础行为?
Object类中的方法其实就定义了每个对象与生俱来的能力:

二、Object类源码
java
package java.lang;
import jdk.internal.vm.annotation.IntrinsicCandidate;
/**
* Class {@code Object} is the root of the class hierarchy.
* Every class has {@code Object} as a superclass. All objects,
* including arrays, implement the methods of this class.
*
* @see java.lang.Class
* @since 1.0
*/
public class Object {
@IntrinsicCandidate
public Object() {}
@IntrinsicCandidate
public final native Class<?> getClass();
@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;
public final void wait(long timeoutMillis, int nanos) throws InterruptedException {
if (timeoutMillis < 0) {
throw new IllegalArgumentException("timeoutMillis value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos > 0 && timeoutMillis < Long.MAX_VALUE) {
timeoutMillis++;
}
wait(timeoutMillis);
}
@Deprecated(since="9")
protected void finalize() throws Throwable { }
}
Object类中一共定义了12个方法(其中还包含了一个无参构造)。
getClass()、hashCode()、clone()、notify()、notifyAll()、wait()、wait(long timeoutMillis)都有native修饰符。
equals()、toString()、wait(long timeoutMillis, int nanos)没有native修饰符。
finalize()从Java 9开始被标记为Deprecated(过时),后续不再讨论。
native修饰的方法表示这个方法的实现不是用Java写的,而是由平台相关的原生代码(通常是 C/C++)实现的。
接下来,我们逐一的看看Object类中的各个方法。
1、toString()
java
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
toString方法返回的是对象的字符串表现形式。
从方法体来看,就是拼接了对象实例的类名称和对象的hash值的16进制。
java
package com.lazy.snail.day18;
/**
* @ClassName Day18Demo
* @Description TODO
* @Author lazysnail
* @Date 2025/6/3 16:31
* @Version 1.0
*/
public class Day18Demo {
public static void main(String[] args) {
Object o = new Object();
System.out.println(o.toString());
}
}
// 输出结果:java.lang.Object@4c873330
java.lang.Object是getClass().getName()的结果;
4c873330是Integer.toHexString(hashCode())的结果;
根据源码注释中API Note的描述可以得知。
一般情况下,toString方法返回一个"文本化表示"该对象的字符串。
返回结果应当是一个简洁但信息丰富的表达形式,便于人类阅读。
建议所有子类都重写此方法。
比如我们的学生管理系统中有一个Student类:
java
package com.lazy.snail.day18;
/**
* @ClassName Student
* @Description TODO
* @Author lazysnail
* @Date 2025/6/3 16:39
* @Version 1.0
*/
public class Student {
int id;
String name;
int age;
String gender;
public Student(int id, String name, int age, String gender) {
this.id = id;
this.name = name;
this.age = age;
this.gender = gender;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + ''' +
", age=" + age +
", gender='" + gender + ''' +
'}';
}
public static void main(String[] args) {
Student s1 = new Student(1, "懒惰蜗牛", 28, "男");
System.out.println(s1);
}
}
在Student类中重写了toString方法。(IDEA快捷键Alt + Insert)

再调用Student的toString方法时,输出为:Student{id=1, name='懒惰蜗牛', age=28, gender='男'}
对于在代码中调试debug,非常的有用,能够明确直观的识别某个对象。
2、getClass
在toString()中我们看到了getClass()方法。这个方法其实也是Object中定义的基本行为之一。
getClass()方法返回的是对象的运行时类。
java
package com.lazy.snail.day18;
/**
* @ClassName Day18Demo
* @Description TODO
* @Author lazysnail
* @Date 2025/6/3 16:31
* @Version 1.0
*/
public class Day18Demo {
public static void main(String[] args) {
Object o = new Object();
System.out.println(o.getClass());
Student s1 = new Student(1, "懒惰蜗牛", 28, "男");
System.out.println(s1.getClass());
}
}
运行输出:class java.lang.Object
class com.lazy.snail.day18.Student
3、hashCode
在toString()的方法体中还有一个hashCode()方法。
这也是Object中定义的基本行为之一。
返回的是一个对象的hash值。
java
package com.lazy.snail.day18;
/**
* @ClassName Day18Demo
* @Description TODO
* @Author lazysnail
* @Date 2025/6/3 16:31
* @Version 1.0
*/
public class Day18Demo {
public static void main(String[] args) {
Object o = new Object();
System.out.println(o.hashCode());
Student s1 = new Student(1, "懒惰蜗牛", 28, "男");
System.out.println(s1.hashCode());
}
}
运行输出:1283928880
2003749087
在Java应用程序的同一次执行期间,只要对象用于equals比较的信息没有被修改,对同一对象多次调用hashCode方法时,这个方法必须始终返回相同的整数值。
但这个整数值在不同次应用程序执行里不需要保持一致。
如果两个对象根据 equals(Object) 方法判断为相等,那么分别调用这两个对象的hashCode方法必须返回相同的整数值。
如果两个对象根据 equals(Object) 方法判断为不相等,那么分别调用这两个对象的hashCode方法时,并不要求必须返回不同的整数值。
4、equals
equals方法用来比较两个对象是否相等。
equals方法在非空对象引用上实现等价关系:
自反性:
csharp
x.equals(x); // true
对称性:
csharp
x.equals(y) == y.equals(x); // true
传递性:
csharp
if (x.equals(y) && y.equals(z))
x.equals(z); // true;
一致性:
多次调用 equals() 方法结果不变
csharp
x.equals(y) == x.equals(y); // true
和null的比较:
对任何不是null的对象x调用x.equals(null) 结果都是false
csharp
x.equals(null); // false;
equals和==
对于基本类型,==判断两个值是否相等,基本类型没有 equals() 方法。
对于引用类型,==判断两个变量是否引用同一个对象,而equals() 判断引用的对象是否等价。
java
package com.lazy.snail.day18;
/**
* @ClassName Book
* @Description TODO
* @Author lazysnail
* @Date 2025/6/3 20:11
* @Version 1.0
*/
public class Book {
int id;
String name;
public Book(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
Book book = (Book) o;
return id == book.id;
}
@Override
public int hashCode() {
return id;
}
public static void main(String[] args) {
Book book1 = new Book(1, "懒惰蜗牛学编程");
Book book2 = new Book(1, "懒惰蜗牛学Java");
System.out.println(book1 == book2);
System.out.println(book1.equals(book2));
}
}
==对比的是book1和book2引用的对象,不同对象则返回false。
Book重写了equal方法,当Book对象的id相同时,equals则返回true。
5、clone
创建并返回当前对象的一个副本。
一个类必须实现Cloneable接口,它的clone()方法才能被成功调用(特指 Object.clone() 的行为)。
否则,调用Object.clone()会抛出CloneNotSupportedException。
java
package com.lazy.snail.day18;
/**
* @ClassName Teacher
* @Description TODO
* @Author lazysnail
* @Date 2025/6/3 20:21
* @Version 1.0
*/
public class Teacher {
public static void main(String[] args) {
Teacher teacher = new Teacher();
try {
teacher.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
}

浅拷贝:
Object类的clone()方法默认执行浅拷贝。
它创建一个新对象,将原始对象中所有实例字段的值复制到新对象。
如果字段是对象引用,就复制引用本身,原始对象和克隆对象将共享这些被引用的对象。
java
package com.lazy.snail.day18;
/**
* @ClassName Teacher
* @Description TODO
* @Author lazysnail
* @Date 2025/6/3 20:21
* @Version 1.0
*/
public class Teacher implements Cloneable {
Student student;
public static void main(String[] args) {
Teacher t1 = new Teacher();
t1.student = new Student(1, "懒惰蜗牛", 28, "男");
try {
Teacher t2 = (Teacher) t1.clone();
t1.student.name = "蜗牛";
System.out.println(t2.student.name);
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
}
t2是t1浅拷贝而来,浅拷贝时对象中的引用是复制引用本身。
所以t2、t1中的student对象实际是共用的。
当t1中student对象的name属性修改后,t2.student.name也随之被修改(其实也不算被修改,因为本身就是同一个对象)。
深拷贝:
如果需要克隆对象和它引用的所有可变内部对象,就必须在覆盖clone方法时手动实现深拷贝逻辑。
Teacher中手动实现深拷贝逻辑:
java
@Override
public Teacher clone() {
try {
Teacher cloned = (Teacher) super.clone();
if (this.student != null) {
cloned.student = (Student) this.student.clone();
}
return cloned;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
同样的main方法运行结果变成了:"懒惰蜗牛"。
实际就是t1在拷贝的时候,如果t1中的student对象不为空,就把student也拷贝了一份。
所以现在t1和t2中的student对象是两个不同的对象,修改其中的属性不影响另一个。
wait、notify相关方法到并发编程、锁机制的时候再讲
结语
在本文中,我们一起从源码出发,逐步拆解了Object类中最基础、也最关键的几个方法,包括 toString()、getClass()、hashCode()、equals() 以及 clone()。
这些是我们日常开发中经常使用的对象操作方法。
就是这些看着像默认的方法,构成了每一个Java对象------它们定义了对象的身份、行为、比较方式和生命周期(包括复制与销毁)。
下一篇预告
Day19 | Java中的String
如果你觉得这系列文章对你有帮助,欢迎关注专栏,我们一起坚持下去!
更多文章请关注我的公众号《懒惰蜗牛工坊》