JDK源码阅读-Object类

概述

Object类是类层次结构的根类,可以作为各种的通用持有者。它是每个java类的基类,如果没有明确指出基类,Object就被认为是当前定义的类的基类。包括arrays在内的所有对象,都实现Object类的方法。Object类属于java.lang包,在这个类中有很多native(本地)方法。其类图如下(JDK8):

构造方法

Object类没有显示的构造方法,只有编译器默认提供的构造方法。

字段

Object没有字段。

方法

Object只包含12个方法,但这些方法都十分重要。
访问限制级别可分为:

  • public: equals(Object), hashCode(), notify(), notifyAll(), toString(), wait(), wait(long), wait(long, int)
  • protected: clone(), finalize()
  • private: registerNatives()

是否为final可分为:

  • final: getClass(), notify(), notifyAll(), wait(), wait(long), wait(long, int)registerNatives()(类私有方法自动成为final)。这些方法不能被子类重写。
  • 非final: hashCode(), equals(Object), clone(), toString(), finalize()。这些方法可以被子类重写,且必须满足通用的约定,否则其他依赖于这些约定的类就无法与该类正常工作。

是否为native可分为:

  • native:registerNatives(), getClass(), hashCode(), clone(), notify(), notifyAll(), wait(long)。这些方法是用C/C++在动态库中实现的,然后通过JNI(java Native Inteface)调用。Java语言本身不能对操作系统底层进行访问与操作,但可以通过JNI接口来调用其他语言来实现对底层的访问,JNI已加入Java标准。
  • 非native:equals(Object), toString(), wait(), wait(long, int)finalize()

registerNatives()方法

其主要作用是将C/C++中的方法映射到Java中的native方法,实现方法命名的解耦。在类初始化时调用static块,执行registerNatives方法。

private static native void registerNatives();
static {
    registerNatives();
}
复制代码

getClass()方法

返回包含对象信息的类对象(Class类型的实例),并且返回的类对象是被此类的静态同步(static synchronized)方法锁定的**(啥意思?)**。类型类Class表示一个类型的类,因为一切皆对象,类型也不例外,因此所有的类型类都是Class类的实例。

public final native Class<?> getClass();
复制代码

与线程有关的方法

包括wait,notifynotifyAll,暂不分析,先占个坑,之后补上。

finalize()方法

类似C++的析构函数,当GC(Garbage Collector)确定不存在对该对象的更多引用时,由对象的GC调用此方法,用于释放资源。一般不建议使用此方法来释放非内存资源。它不同于C++的析构函数,析构函数在对象作用域调用,执行时间点确定。而此方法,是在内存不足,GC发生时进行调用,由于GC是不确定随机的,所以无法确定此方法的执行时间。

protected void finalize() throws Throwable {}
复制代码

equals(Object)方法

equals方法用于两个对象是否相等。Object的equals方法很简单,只用两个对象的引用是否相等来判断是否是同一个对象。Java规范要求equals方法具备以下特性:

  1. 自反性:对于任何非空引用,x.equals(x)应该返回true;
  2. 对称性:对于任何引用x和y,当且仅当y.equals(x)返回true,x.equals(y)也应该返回true;
  3. 传递性:对于任何引用x、y和z,如果x.equals(y)返回true,y.equals(z)返回true,x.equals(z)也应该返回true;
  4. 一致性:如果x和y引用的对象没有发生变化,反复调用x.equals(y)应该返回同样的结果;
  5. 对于任意非空引用x,x.equals(null)应该返回false。

Object类的equals实现为:

public boolean equals(Object obj) {
    return (this == obj); // 大佬都不嫌麻烦用括号扩起,习惯使然!
}
复制代码

然而在大多数情况下,只将引用作为判断对象的唯一标准,太严格且没有实际意义。例如对于大多数实体类,如Book类,我们对比两个Book类实例时,关注的是这两个实例的状态(书名、价格、作者等)是否相等。如果两者的状态都相等,即使不是同一个引用,我们也判定这两个Book实例相等。下面实现一个判断两个Book实例是否相等的equals方法:

public class Book {
    private String name; // 书名
    private int price; // 价格
    private String writer; // 作者

    @Override // 1. 带注解,防止入参类型错误
    public boolean equals(Object otherObject){
        // 2. 检测两个实例引用是否相同,优化需要。
        if (this == otherObject){
            return true;
        }

        // 3. 检测被比较对象是否为空,必须
        if (otherObject == null){
            return false;
        }

        // 4. 比较this和入参是否同属于一个类
        if (getClass() != otherObject.getClass()){
            return false;
        }

        // 5. 将otherObject转换为相应的类类型变量,为后续比较具体域状态做准备
        Book other = (Book) otherObject;

        // 6. 比较所有的域。有对象域可能都为null,因此不能使用name.equals(other.name)这种方法
        return Objects.equals(name, other.name)
                && price == other.price
                && Objects.equals(writer, other.writer);
    }
}
复制代码

在代码的第4步中,使用getClass()来判断两个对象是否同属于一个类,其使用场景为:equals的定义在每个子类中有所改变。如果所有的子类都拥有统一的语义,如所有的子类都使用父类的equals方法,则使用instanceof代替getClass()

if (!(otherObject instanceof Book)) {
    return false;
}
复制代码

equals在使用时,要格外注意当父类实现了equals方法,子类之间equals方法的实现方式要满足对称性的要求。
另外值得注意的是,如果重写equals方法,则必须同时重新定义hashCode()方法,以便用户可以将对象插入到散列表中。

hashCode()方法

Object类中的hashCode方法是native方法,返回一个整型数值(也可以是负数),无具体的实现方式。由于该方法是在Object中定义的,因此java中每个对象都会有一个默认的散列码(hash code),其值可能是对象的存储地址。

public native int hashCode();
复制代码

hashCode方法具体分析考虑之后结合集合再整理,先占个坑。TODO...

toString()方法

Object的该方法返回了类的名称加上'@',再加上此类哈希码的16进制表示,如:I[@1a46e30,其中I[表示一个整型数组。

public String toString(){
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
复制代码

一个好的习惯,应该是为每个类,特别是模型类重写一个toString()方法,以便用户能够通过这个方法获得对象状态的必要信息。一个反面例子就是数组没有重写toString(),而是直接继承了Object的toString()方法,因此数组对象调用toString()方法,打印的字符串都是类似I[@1a46e30这种形式的,鸡肋!所以无奈只能使用静态方法Arrays.toString(arr)或者Arrays.deepToString()方法。

绝对多数的toString方法都遵循这样的格式:类的名字,随后是一对方括号括起来的阈值。

下面是Person类中toString方法的实现:

public String toString() {
    return getClass().getName()
        + "[name=" + name
        + ", age=" + age
        + "]";
}
复制代码

toString在项目被频繁使用,特别是只要对象与一个字符串通过操作符"+"连接起来,或作为System.out.println()的入参,则编译器都会默认调用对象的toString方法。

待续...

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值