JDK源码学习系列——java.lang.Object类

Java源码学习笔记–Object类

欢迎👏👏👏关注我的微信公众号:人人都是Java程序员,公众号不定期更新。

本源码的版本如下:.
java version “1.8.0_201”.
Java™ SE Runtime Environment (build 1.8.0_201-b09).
Java HotSpot™ 64-Bit Server VM (build 25.201-b09, mixed mode).

方法摘要

public final native Class<?> getClass();    //返回当前对象的运行时类对象
public native int hashCode();	//返回当前对象的哈希值
public boolean equals(Object obj);	//判断两个对象是否相等
protected native Object clone() throws CloneNotSupportedException;	//创建并返回当前对象的一份拷贝
public String toString();	//类返回对象的字符串表示形式
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 { };//该方法的作用是实例被垃圾回收器回收的时候触发的操作 (最后的挣扎)

getClass()方法

public final native Class<?> getClass();

  返回当前对象的运行时类对象。类对象由static synchronized关键字锁定来保证唯一。
  需要注意的是,虽然Object类中getClass() 方法声明是:public final native Class<?> getClass();返回的是一个 Class<?>,但是如下是能通过编译的: Class<? extends String> c = "".getClass();也就是说类型为T的变量getClass方法的返回值类型其实是Class<? extends T>而非getClass方法声明中的Class<?>。

hashCode()方法

public native int hashCode()

  返回当前对象的哈希值,哈希值主要用在HashMap等需要哈希值的地方。

哈希值应当满足:

  • 在程序执行期间,只要不改变产生哈希值的字段,那么无论对象调用多少次该方法,返回结果一定相同。如果改变了产生哈希值的字段,那么返回结果可能不同,会造成内存泄露(因为该对象无法找到)。
  • 通过equals调用返回true的两个对象的哈希值一定相同。
  • 通过equasl返回false的两个对象的哈希值并不是是完全不同的。也就是说它们有可能相同。但考虑到哈希表的性能,最好保证它们不同。

为什么要设计这个方法?

  我们知道在Java 中有几种集合类,比如 List,Set,Map。List集合存放的元素是有序可重复的,Set 存放的元素则是无序不可重复的,而 Map 集合存放的是键值对。
  判断一个元素是否相等可以通过 equals 方法。但每增加一个元素,我们就需要通过 equals 方法判断集合中的每一个元素是否重复。如果集合中有一百万个元素了,但我们新加入一个元素时,那就需要进行一百万次equals方法的调用,这显然效率很低。
  于是,Java 的集合设计者就采用了哈希表来实现。这样一来,当集合要添加新的元素时,先调用这个元素的 hashCode 方法,就一下子能定位到它应该放置的物理位置上。

  • 如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;
  • 如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了;不相同的话,也就是发生了Hash key相同导致冲突的情况,那么就在这个Hash key的地方产生一个链表,将所有产生相同HashCode的对象放到这个单链表上去,串在一起(很少出现)。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。

equals(Object obj)方法

public boolean equals(Object obj) {
    return (this == obj);
}

  Object类的默认实现是比较两个对象的内存地址是否相等。

面试高频题:==和equals 区别?

  ==运算符用于比较基本类型的值是否相同,或者比较两个对象的引用是否相等(也就是比较两个对象指向的内存地址是否相同)。而 equals 用于比较两个对象是否相等(包含内存中保存两个相等的数据,两个对象分别指向这两个内存地址的情形)。

equals方法必须遵循以下原则:

  • 自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true。
  • 对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true。
  • 传递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。
  • 一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改。
  • 对于任何非空引用值 x,x.equals(null) 都应返回 false。

  如果重写了equals方法,通常必须重写hashCode方法。通过重写 equals 方法,我们自定义两个对象相等的标尺为对象的属性都相等,则对象相等,否则不相等。如果不重写 equals 方法,那么始终是调用 Object 类的equals 方法,也就是用 == 比较两个对象在栈内存中的引用地址是否相等。

重写equals方法的注意点:

重写时用 instanceof 关键字是做不到对称性的要求的。什么时候使用 instanceof 运算符,什么时候使用 getClass() 有如下建议:

  • 如果子类能够拥有自己的相等概念,则对称性需求将强制采用 getClass 进行检测。
  • 如果有超类决定相等的概念,那么就可以使用 instanceof 进行检测,这样可以在不同的子类的对象之间进行相等的比较。
    代码模板如下:
@Override
    public boolean equals(Object otherObject) {
        //1、判断比较的两个对象引用是否相等,如果引用相等那么表示是同一个对象,那么当然相等
        if(this == otherObject){
            return true;
        }
        //2、如果 otherObject 为 null,直接返回false,表示不相等
        if(otherObject == null ){//对象为空或者不是Person类的实例
            return false;
        }
        //3、比较 this 和 otherObject 是否是同一个类(注意下面两个只能使用一种)
        //3.1:如果 equals 的语义在每个子类中所有改变,就使用 getClass 检测
        if(this.getClass() != otherObject.getClass()){
            return false;
        }
        //3.2:如果所有的子类都有统一的定义,那么使用 instanceof 检测
        if(!(otherObject instanceof Person)){
            return false;
        }
        //4、将 otherObject 转换成对应的类类型变量
        Person other = (Person) otherObject;
        //5、最后对对象的属性进行比较。使用 == 比较基本类型,使用 equals 比较对象。如果都相等则返回true,否则返回false
        //   使用 Objects 工具类的 equals 方法防止比较的两个对象有一个为 null而报错,因为 null.equals() 是会抛异常的
        return Objects.equals(this.pname,other.pname) && this.page == other.page;
        //6、注意如果是在子类中定义equals,则要包含 super.equals(other)
        //return super.equals(other) && Objects.equals(this.pname,other.pname) && this.page == other.page;

    }

clone()方法

protected native Object clone() throws CloneNotSupportedException;

  创建并返回当前对象的一份浅拷贝(仅拷贝数值类型数据和引用对象地址值),使用时往往需要重写为public形式。需要注意的是,要求被克隆的对象所属的类实现Cloneable接口,否则抛出CloneNotSupportedException。

浅拷贝与深拷贝

  在浅拷贝中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。在Java语言中,通过覆盖Object类的clone()方法可以实现浅克隆。

  在深拷贝中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深拷贝将原型对象的所有引用对象也复制一份给克隆对象。简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。

  在Java语言中,如果需要实现深拷贝,可以通过覆盖Object类的clone()方法实现,也可以通过序列化(Serialization)等方式来实现。如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深拷贝。

  序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深拷贝。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。

  Java语言提供的Cloneable接口和Serializable接口的代码非常简单,它们都是空接口,这种空接口也称为标识接口,标识接口中没有任何方法的定义,其作用是告诉JRE这些接口的实现类是否具有某个功能,如是否支持克隆、是否支持序列化等。

toString() 方法

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

  Object对象的默认实现是输出类的名字@实例的哈希码的16进制。通常,toString方法返回一个“以文本形式表示”此对象的字符串。 结果应该是简洁易懂的表示形式,便于人们阅读。 建议所有子类都重写此方法。

notify()、notifyAll()方法

public final native void notify();
public final native void notifyAll();

  notify()方法唤醒正在此对象监视器上等待的单个线程。如果有任何线程正在等待这个对象,则选择其中一个线程被唤醒。这个选择是任意的,由JVM来决定。线程通过调用一个wait方法来等待对象的监视器。此方法应仅由此对象监视器的所有者所在的线程调用。线程通过以下三种方式之一成为对象监视器的所有者:

  • 通过执行该对象的同步实例方法。
  • 通过执行在对象上同步的同步语句的主体。
  • 对于类对象,通过执行该类的同步静态方法。
    如果当前线程不是该对象监视器的所有者则抛出IllegalMonitorStateException。

wait()方法

public final native void wait(long timeout) throws InterruptedException;

  此方法导致当前线程(称为T)将自己放入此对象的等待集中,然后放弃此对象上的任何和所有同步声明。线程T在线程调度时被禁用,并处于休眠状态,直到发生以下四种情况之一:

  • 其他一些线程调用此对象的notify方法,而线程T恰好被任意选择为要唤醒的线程。
  • 其他一些线程调用此对象的notifyAll方法。
  • 其他一些线程中断线程T。
  • 指定的实际时间已经过了,或多或少。但是,如果超时为零,则不考虑实时,线程只是等待,直到得到通知。

  然后从该对象的等待集中删除线程T,并重新启用线程调度。然后它以通常的方式与其他线程竞争对象上的同步权;一旦它获得了对对象的控制,它对对象的所有同步声明就会恢复到原来的状态——也就是说,恢复到调用wait方法时的状态。然后线程T从wait方法的调用返回。因此,从wait方法返回时,对象和线程T的同步状态与调用wait方法时的同步状态完全相同。
  线程也可以在没有通知、中断或超时的情况下唤醒,这就是所谓的伪唤醒。虽然这种情况在实践中很少发生,但是应用程序必须通过测试导致线程被唤醒的条件来防范这种情况,如果条件不满足,则继续等待。换句话说,等待应该总是在循环中发生,就像这个:

synchronized (obj) {
         while (<condition does not hold>)
             obj.wait(timeout);
         ... // Perform action appropriate to condition
}

  如果设置时间小于零,会抛出IllegalArgumentException错误;如果当前线程没有获得对象监视器则抛出IllegalMonitorStateException错误;如果任何线程在当前线程等待通知之前或在当前线程等待通知期间中断当前线程,则抛出InterruptedException 。当抛出此异常时,当前线程的中断状态将被清除。
  以下两个方法和上面大致相同,就不做过多介绍了:

    public final void wait(long timeout, int nanos) throws InterruptedException {
        if (timeout < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos > 0) {
            timeout++;
        }

        wait(timeout);
    }

  跟之前的两个wait方法一样,只不过该方法一直等待,没有超时时间这个概念。wait方法和notify方法会一起使用的,wait方法阻塞当前线程,notify方法唤醒当前线程

 public final void wait() throws InterruptedException {
        wait(0);
    }

finalize()方法

protected void finalize() throws Throwable { }

  类对象的finalize方法不执行任何特殊操作;它只是正常返回。对象的子类可以覆盖此定义。子类重写finalize方法以处置系统资源或执行其他清理。
  Java编程语言并不保证哪个线程将为任何给定对象调用finalize方法。但是,可以保证调用finalize的线程在调用finalize时不会持有任何用户可见的同步锁。如果finalize方法抛出未捕获的异常,则忽略该异常并终止该对象的终结。
  对象的finalize方法被调用后,没有采取进一步的行动(进一步的行动指的是将某个引用指向该对象),直到Java虚拟机再次确定,不再有任何方法可以访问这个对象的线程还没有死,包括其他对象或类的可能的行动准备完成,此时对象可能被丢弃。
  对于任何给定对象,Java虚拟机都不会多次调用finalize方法,只有一次机会。
  finalize方法抛出的任何异常都会导致此对象的终止,但在其他情况下会被忽略。
  finalizer 方法的调用时机由 sun 和 JVM 开发商共同决定:确定不再有任何未死亡的线程通过任何方法调用来访问或使用该对象时。(即确定对象的任何方法都不(再)会被调用时,调用其 finalize 方法)

整体代码结构

/*
 * Copyright (c) 1994, 2012, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package java.lang;

/**
 *类Object是等级最高的类,是所有类的父类。包括数组在内的所有对象都继承了该类。
 */
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 {
        if (timeout < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos > 0) {
            timeout++;
        }

        wait(timeout);
    }

    public final void wait() throws InterruptedException {
        wait(0);
    }

    protected void finalize() throws Throwable { }
}

参考文档

1、JDK1.8源码(一)——java.lang.Object类
2、Class Object
3、java.lang.Object学习
4、Java提高篇——对象克隆(复制)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章