专栏首页做不甩锅的后端java1.8中Object类源码分析

java1.8中Object类源码分析

文章目录

    • 1.Object对象的源码
      • 1.1 registerNatives
      • 1.2 getClass
      • 1.3 hashCode
      • 1.4 equals
      • 1.5 clone
      • 1.6 toString
      • 1.7 notify
      • 1.8 notifyAll
      • 1.9 wait(long timeout, int nanos)
      • 1.10 wait(long timeout)
      • 1.11 wait()
      • 1.12 finalize()

1.Object对象的源码

Object类是一切类的超类,在类继承的树形结构上,Object是所有类的根节点。所有的对象,包括数据,都继承了Object类的方法。我们来看看Object类有哪些方法,分别的作是什么。

1.1 registerNatives

在Object类的最开始部分,有如下四行代码:

private static native void registerNatives();
static {
    registerNatives();
}

这个代码有static代码块,那么在一开始就会被加载,其目的是为该类中包含的除了registerNatives()方法以外的所有本地方法进行注册。 由于Object类中大量使用了native方法,一个Java程序要想调用一个本地方法,需要执行两个步骤:第一,通过System.loadLibrary()将包含本地方法实现的动态文件加载进内存;第二,当Java程序需要调用本地方法时,虚拟机在加载的动态文件中定位并链接该本地方法,从而得以执行本地方法。registerNatives()方法的作用就是取代第二步,让程序主动将本地方法链接到调用方,当Java程序需要调用本地方法时就可以直接调用,而不需要虚拟机再去定位并链接。 使用registerNatives()方法主要有如下三点好处:

  • 通过registerNatives方法在类被加载的时候就主动将本地方法链接到调用方,比当方法被使用时再由虚拟机来定位和链接更方便有效;
  • 如果本地方法在程序运行中更新了,可以通过调用registerNative方法进行更新;
  • Java程序需要调用一个本地应用提供的方法时,因为虚拟机只会检索本地动态库,因而虚拟机是无法定位到本地方法实现的,这个时候就只能使用registerNatives()方法进行主动链接。

此外,通过registerNatives()方法,在定义本地方法实现的时候,可以不遵守JNI命名规范。 此处是有关类加载器及JNI的根深层次的内容,此处暂不展开。

1.2 getClass

返回对象运行时的类。返回的Class对象是被static synchronized修饰的对象。

public final native Class<?> getClass();

实际的结果类型是 Class<? extends |X|>,X是对调用getClass的表达式的静态类型擦除。如下所示,代码中不需要强制转换:

Number n = 0; 
Class<? extends Number> c = n.getClass(); 

Class对象表示运行时此对象的类。

1.3 hashCode

 public native int hashCode();

返回对象的hash code,这个方法的好处是提供了对诸如HashMap等哈希表的支持。 通常hashcode的规则是:

  • 当在同一对象上被一个java应用程序多次调用的时候,hashCode方法必须始终如一地返回相同的整数。如果equals中使用的信息没有被修改。从应用程序的一次执行到相同的应用程序的一次执行,此整数不必保持一致。
  • 如果两个对象通过equals方法是相等的,那么对这两个对象中的每一此调用hashCode方法必须产生相同的结果。
  • 如果调用两个对象调用equals方法不相等,java.lang.Object#equals(java.lang.Object),那么这两个对象的hashCode的返回必须不一样。当然,程序员应该知道,为不想等的对象生成不同的hashCode结果可以提高HashTable的性能。

Object定义的hashCode方法确实为不同的对象返回了不同的hashCode值。这里通常是通过将内部地址转换为整数来实现,但是,Java&trade等编程语言通常不需要采用这种技术。

1.4 equals

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

用于表示其他对象是否等于此对象。{@equals}方法在非空对象引用上实现等价关系:

  • 具有自反性,对任何非空的引用,x.equals(x)应该返回True。
  • 具有对称性,对于任何非空的引用,当且仅当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。

类Object的equals方法在对象上实现了最有区别的等价关系,也就是说,对于任何非空引用值x和y,当且仅当x和y引用的是同一对象的时候,x==y返回true。 注意,每当重写hashCode方法的时候,通常都需要重写该方法,以便维护hashCode方法的常规约定,该方法申明相等的对象必须具有相同的hashCode。

1.5 clone

 protected native Object clone() throws CloneNotSupportedException;

创建并返回此对象的副本,copy的确切含义可能取决于对象的类别,一般来说,对于任何对象x的表达式:

x.clone() != x

如果为true,那么表达式:

x.clone().getClass() == x.getClass()

也将为true。这不是要给绝对的要求。 按照约定,应该通过clone调用来获得返回的对象。如果一个类及其所有超类也遵守这个约定,那么:

 x.clone().getClass() == x.getClass()

按照惯例,此方法返回的对象应独立于被clone的对象,为了实现这种独立性,可能需要修改由super.clone返回的对象的一个或者多个字段进行clone,然后再返回它。通常,这意味着复制构成的被clone的对象内部的深层结构的任何对象,并将这些对象的引用替换为对副本的引用。如果一个类只包含原始字段或者引用不可变对象,那么通常情况下super.clone需要修改。 类的方法clone执行特定的clone操作,首先,如果这个对象的类没有实现Cloneable,那么将抛出一个CloneNotSupportedException异常,需要注意的是,所有数组都被认为是实现了Cloneable接口。数组类型T[]的clone返回的是T[]。其中T是任何引用或者原始数据类型。否则,此方法将创建一个此对象的实例,并使用该对象的相应字段的内容来初始化其所有字段,就像通过赋值一样,字段内容本身不会被clone,此方法执行的是对象的浅拷贝,而不是深拷贝。 Object本身不实现Cloneable接口,因此如果直接调用Object的clone,则返回运行时异常。

1.6 toString

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

返回对象的字符串表现形式,通常,toString方法返回一个字符串,该字符串以文本的方式表示此对象,结果应该是一个简明但是信息丰富的表述,便于人阅读,建议子类对此方法进行重写。 Object的toString方法返回一个字符串,该字符串由对象所属的类的名称,@符号,以及hashcode无符号的十六进制表示组成。换句话说,此方法返回字符串等于:

getClass().getName() + '@' + Integer.toHexString(hashCode())

1.7 notify

 public final native void notify();

唤醒一个正在管程中等待的线程,如果线程正在等待该对象,则选择其中一个线程被唤醒,这种选择可能是随机的,这取决于具体的JVM实现。线程通过调用wait方法之后进入等待管程。 再当前线程放弃对该对象的锁定之前,唤醒的线程将无法继续,被唤醒的线程将以普通的形式与其他线程竞争。这些线程可能正在积极的对该对象上进行同步,例如,被唤醒的线程在成为下一个锁定此对象的线程时没有可靠的特权或劣势。 此方法只能由做为此对象管程所有者的线程调用,线程通过以下三种方式之一成为对象管程的所有者。

  • 通过执行该对象的同步实例方法。
  • 通过执行一个synchronized语句块或者方法在对象上同步。
  • 对于类型为Class的对象,执行这个类的同步静态方法。 只有一个线程一次可以获得对象管程的控制权限。

1.8 notifyAll

public final native void notifyAll();

唤醒在该对象管程中等待的所有线程,线程通过调用某个wait方法进入管程的等待队列。 当前线程放弃此对象的锁之前,唤醒的线程将无法继续工作。被唤醒的线程将以通常的方式与任何其他线程竞争。这些线程可能正积极地在该线程上进行同步,例如,被唤醒的线程在成为下一个锁定此对象的线程时没有可靠的特权和劣势。 此方法只能做为此对象的管程所有者线程调用,请参阅notify方法。了解线程成为管程所有者的方式。

1.9 wait(long timeout, int nanos)

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);
}

使当前线程等待,知道另外一个线程调用这个对象notify或者notifyAll方法。或者其他线程中断当前线程或者时间条件过期。 这个方法与wait方法类似,但是它可以更好的控制放弃前等待通知的时间,以纳秒为单位的实时量由以下公式给出:

1000000*timeout+nanos

在所有其他方面,此方法与一个参数的方法wait(long)执行相同的操作。特别是wait(0, 0)与wait(0)含义相同。 当前线程必须拥有次对象的管程,线程将释放此Monitor的所有权并等待,直到以下两种情况出现:

  • 另一个线程通过调用notify方法或者notifyAll方法通知等待此对象的Monitor线程唤醒。
  • timeout和nanos参数指定的超时时间已过时。 线程之后进入等待状态,直到他可以重新获得monitor并执行。 在一个版本中,中断和虚假的唤醒操作是可能的,这个方法应该在循环中使用。
synchronized (obj) {
      while (&lt;condition does not hold&gt;)
          obj.wait(timeout, nanos);
      ... // Perform action appropriate to condition
  }

此方法只能由做为此对象的monitor所有者线程调用,请参阅notify方法,了解线程成为Monitor所有者的方法的描述。

1.10 wait(long timeout)

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

使当前线程等待,知道另外一个线程调用这个对象notify或者notifyAll方法。或者其他线程中断当前线程或者时间条件过期。 当前线程必须拥有对象的monitor。 此方法会导致当前线程T将自己放入该对象的WaitSet中,然后放弃对此对象的锁,线程T出于线程调度的目的被禁用,并处于TIME_WAITING状态,直至以下四种情况之一发生:

  • 另外一个线程调用notify方法,而这个线程恰好被任意选择为要被唤醒的线程。
  • 另外一个线程调用notifyAll方法。
  • 其他的线程打断interrupt()这个线程。
  • 指定的过期时间失效,如果timeout为0,则不考虑时间,直是等待,直到受到通知。

之后从该对象的WaitSet中删除线程T,并重新启用线程调度。然后,它以通常的方法与其他线程竞争对象上的同步权,一旦它获得的对象的控制权,它对象的所有同步申明都将恢复到以前状态,也就是说,恢复到调用wait方法时的情况,T然后从wait方法的调用返回,因此,从Wait方法返回时,对象和线程T的同步状态与调用wait时完全相同。 线程也可以在没有通知、中断或者超时的情况下被唤醒,即所谓的假唤醒,虽然这种情况在实践中很少发生,但是程序必须通过测试本应导致线程被唤醒的条件,并在条件不满足的时候继续等待,以防止这种情况出现,换句话说,等待应该总是在循环中发生,就像下面这样:

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

关于此主题的更多信息,请参阅Doug Leas的《Java并发程序设计(第二版)》第3.2.3节,或者Joshua Bloch的《Effective Java Programming Language Guide》。 如果当前线程是interrupt()在当前线程之前或者等待期间被任何线程中断,则会抛出InterruptedException这个异常。在按照上述方法恢复此对象的锁定状态之前,不会引发此异常。 注意,{@code wait}方法将当前线程放入该对象的等待集中时,只解锁该对象;当线程等待时,当前线程可能同步的任何其他对象都保持锁定。 此方法只能由做为此对象Monitor的所有者线程调用,请参阅notify方法,了解此线程成为Monitor的方法描述。

1.11 wait()

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

使当前线程等待,知道另外一个线程调用这个对象notify或者notifyAll方法。换句话说,这个方法的行为与调用wait(0)一样。 当前线程必须拥有此对象的Monitor,线程释放此Monitor的所有权,并等待直到另外一个线程通过notify或者notifyAll方法通知等待此对象的monitor线程唤醒,然后线程进入等待状态,直到它可以重新获得monitor的权限并继续执行。 在另外一个参数版本中,中断和虚假的weakup是可能的,这个方法应该在循环中使用。

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

此方法只能由作为此对象Monitor所有者的线程调用。请参阅{@code notify}方法,了解线程成为监视器所有者的方式的描述。

1.12 finalize()

protected void finalize() throws Throwable { }

当垃圾回收确定不再有对对象的引用时,由对象上的垃圾回收器调用。子类重写{@code finalize}方法以释放系统资源或执行其他清理。 {@code finalize}的一般约定是,当Java&trade;虚拟机已经确定不再有任何方法可以让任何尚未终止的线程访问该对象,除非是由准备完成的其他对象或类的终结所采取的操作的结果,否则将调用它。{@code finalize}方法可以采取任何操作,包括使该对象再次对其他线程可用;{@code finalize}的通常目的是在对象被不可撤销地丢弃之前执行清理操作。例如,表示输入/输出连接的对象的finalize方法可能会执行显式I/O事务,以便在永久丢弃该对象之前断开连接。 Objecct的finalize方法不执行任何特殊的操作,它只是正常返回,Object的子类可以进行重写。 java编程语言不保证哪个线程将为任何给定对象调用finalize方法,但是可以保证调用finalize的线程在调用finalize时不会持有任何用户可见的同步锁,如果finalize方法引发未捕获的异常,则会忽略该异常并终止该对象的回收。 在为一个对象调用finalize方法之后,在java虚拟机再次确定没有任何方法可以让任何尚未终止的线程访问该对象之前,不会采取任何进一步的操作,包括其他对象或类可能进行的操作,这些对象或者类的操作是准备完成的点对象可能被丢弃。 {@code firalize}方法从来不会被Java虚拟机为任何给定对象多次调用。 {@code finalize}方法引发的任何异常都会导致该对象的终止终止,但会被忽略。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 多线程基础(一): 线程概念及生命周期

    什么是进程,相信大家都知道什么是进程却很难解释清楚。百科中的解释是:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调...

    冬天里的懒猫
  • java线程池(三):ThreadPoolExecutor源码分析

    在前面分析了Executors工厂方法类之后,我们来看看AbstractExecutorService的最主要的一种实现类,ThreadpoolExecutor...

    冬天里的懒猫
  • Callable and Future in Java(java中的Callable和Future)

    通常,我们有两种方式创建线程,一种方式是继承Thread类,另外一种方式是实现Runnable接口。然而,Runnable方式缺少的一个特性就是,当线程终止的时...

    冬天里的懒猫
  • java基础面试题

    参考:http://blog.csdn.net/jackfrued/article/details/44921941 说未经允许不转载,我只好参考了。 1.面向...

    Ryan-Miao
  • Java基础总结大全(1)

    一、基础知识: 1、JVM、JRE和JDK的区别: JVM(Java Virtual Machine):java虚拟机,用于保证java的跨平台的特性。 ...

    Java学习
  • 最全面试宝典-我的春招总结

    今年上半年经过接近俩个月的紧张面试,终于拿到了满意的offer。春招竞争之难,超乎我的想象。所以劝告各位学弟学妹,一定要把握秋招。俩个月里,基本上面的都是一些大...

    用户7557625
  • java面试题汇总一(会持续更新)

    不积跬步无以至千里,这里会不断收集和更新Java基础相关的面试题,目前已收集100题。

    叫我可儿呀
  • Java基础总结大全(1)

    一、基础知识: 1、JVM、JRE和JDK的区别: JVM(Java Virtual Machine):java虚拟机,用于保证java的跨平台的特性。 ...

    Java学习
  • JAVASE中的多线程小结,多生产多消费案例.

    因为线程任务已经被封装到Runnable接口中的run方法中,而这个run方法属于Runable接口的子类对象,所以要将这个子类对象作为参数传递给Thread类...

    帅的一麻皮
  • 一句话撸完重量级锁、自旋锁、轻量级锁、偏向锁、悲观、乐观锁等各种锁 ---- 不看后悔系列

    重量级锁?自旋锁?自适应自旋锁?轻量级锁?偏向锁?悲观锁?乐观锁?执行一个方法咋这么辛苦,到处都是锁。

    帅地

扫码关注云+社区

领取腾讯云代金券