Day18 | 深入理解Object类

在之前的文章中,我们已经对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

如果你觉得这系列文章对你有帮助,欢迎关注专栏,我们一起坚持下去!

更多文章请关注我的公众号《懒惰蜗牛工坊》

相关推荐
jiayong231 小时前
Maven NUL文件问题 - 解决方案实施报告
java·maven
未秃头的程序猿1 小时前
🔒 从单机到分布式:三大锁机制深度剖析与实战指南
java·后端
大猫子的技术日记1 小时前
[百题重刷]前缀和 + Hash 表:缓存思想, 消除重复计算
java·缓存·哈希算法
得物技术1 小时前
# 一、项目概览 Dragonboat 是纯 Go 实现的(multi-group)Raft 库。 为应用屏蔽 Raft 复杂性,提供易于使用的 NodeH
后端
4***14902 小时前
Rust系统工具开发实践指南
开发语言·后端·rust
s***35302 小时前
Spring Boot3.x集成Flowable7.x(一)Spring Boot集成与设计、部署、发起、完成简单流程
java·spring boot·后端
大头an2 小时前
Spring Boot CLI 从入门到企业级实战(上下篇)
后端
s***4532 小时前
SpringBoot返回文件让前端下载的几种方式
前端·spring boot·后端
元Y亨H2 小时前
一文读懂计算机系统核心流量术语
后端