JavaSourceLearning-java.lang.object

JavaSourceLearning-java.lang.object这年头没看过几段源码都不好意思说自己是开发了,卷起来!这个专栏主要是结合api文档和jdk源码调试开启源码的学习之旅。这是一场持久战,保持平和的心态坚持下去,权当是干饭后的甜点了。加入光荣的进化吧!

这年头没看过几段源码都不好意思说自己是开发了,卷起来!这个专栏主要是结合api文档和jdk源码调试开启源码的学习之旅。这是一场持久战,保持平和的心态坚持下去,权当是干饭后的甜点了。加入光荣的进化吧!干饭人!

Object

Object.class 位于 java.lang 包中,当我们创建一个类时,它将自动继承Object类。做个小测试,由下图可知,我们并没有显式的去继承Object(extends Object),但却可以调用Object类中的所有方法,断点调试进该方法,便能发现实际上调用的是父类Object中的方法。

image.png Object 类是所有类的父类,也就是说 java 中所有类都继承了 Object 类,子类可以使用 Object 的所有方法。

方法

hashCode()

public native int hashCode();

本地方法,返回对象的hash值。在程序执行过程中对同一对象多次调用hashCode()方法,返回值始终是相同的整数。但从应用程序的一次执行到同一应用程序的另一次执行,该整数不必保持一致。
如果两个对象的equals()结果返回为true,那么这两个对象调用hashCode()返回的结果必然相同。若两个对象的equals()调用结果为false,那么这两个对象调用hashCode()的结果没有必然关系,可能相等也可能不等,但建议为不相等的对象生成不同的hash结果。

equals()

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

参数:Object obj,要与调用equals方法对象进行比较的对象。
返回值:若obj与调用equals方法的对象相同则返回true,否则返回false。
equasl()用于判断其他对象是否与此对象“相等”。具有以下特性:

  • 自反性:对于任何非空值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方法实现了对象上最有区别的等价关系;也就是说,对于任何非空引用值x和y,当且仅当x和y引用同一对象(x==y的值为true)时,此方法才返回true。
    请注意,每当重写hashCode方法时,通常都需要重写该方法,以便维护hashCode方法的一般约定,即相等的对象必须具有相等的哈希代码。

clone()

protected native Object clone() throws CloneNotSupportedException;

参数:无
返回值:此实例的副本。
异常抛出:CloneNotSupportedException-如果对象的类不支持可克隆接口。重写clone方法的子类也可以引发此异常,以指示该实例无法被克隆。

创建并返回此对象的副本。“复制”的确切含义可能取决于对象的类别。一般目的是,对于任何对象x,表达式:
x.clone() != x 为true,且x.clone().getClass() == x.getClass()为true,但不是绝对的。一般情况下x.clone().equals(x)为true,但也不是绝对的。

按照惯例,返回的对象应该通过调用super.clone获得。如果一个类及其所有父类(对象除外)都遵守此约定,则x.clone().getClass()==x.getClass()。

Object类的clone方法执行特定的克隆操作。首先,如果这个对象的类没有实现Cloneable接口,那么将抛出CloneNotSupportedException异常。注意: 所有数组均视为实现了Cloneable接口,且clone方法返回值类型为T[],其中T可为任意引用类型或基础类型。此外,此方法将创建此对象的类的新实例,并使用此对象的相应字段的内容来初始化其所有字段,就像通过赋值一样; 字段的内容本身不会被克隆。因此,此方法执行此对象的“浅拷贝”,而不是“深拷贝”操作。 我们写一段简易的代码示例一下,ObjectTest实现了Cloneable接口,可以从代码看到是真真正正的克隆了一个对象,但是name是String类型的, 它只是一个引用, 指向一个真正的String对象,那么对它的拷贝有两种方式:

  1. 直接将源对象中的name的引用值拷贝给新对象的name字段;
  2. 根据原ObjectTest对象中的name指向的字符串对象创建一个新的相同的字符串对象,将这个新字符串对象的引用赋给新拷贝的Person对象的name字段。 这两种拷贝方式分别叫做 浅拷贝 和 深拷贝 。通过如下代码简单验证:clone方法执行的是浅拷贝。如果想要实现深拷贝,就需要通过覆盖Object中的clone方法的方式来实现了。
public class ObjectTest implements Cloneable{
    private String name;

    public ObjectTest(String name){
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    protected ObjectTest clone() throws CloneNotSupportedException {
        return (ObjectTest)super.clone();
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        ObjectTest o = new ObjectTest("zhangsan");
        ObjectTest o1 = o.clone();
        System.out.println(o == o1 ? "同一对象" : "不同对象");
        System.out.println(o.getName() == o1.getName() ? "浅拷贝" : "深拷贝");
    }
}
结果为:
不同对象
浅拷贝

toString()

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

参数:无
返回值:返回一个代表该对象的字符串。如debug.langtest.ObjectTest@4554617c
getClass().getName() 返回该对象的类名。
Integer.toHexString(hashCode()) 调用Integer类中 toHexString()方法传入本地方法hashCode()方法返回的哈希值作为参数。进入toHexString()方法。

public static String toHexString(int i) {
    return toUnsignedString0(i, 4);
}

该方法返回结果为: toUnsignedString0(i, 4)方法的返回值,我们进一步进入该方法。

 /**
 * Convert the integer to an unsigned number.
 */
 private static String toUnsignedString0(int val, int shift) {
    // assert shift > 0 && shift <=5 : "Illegal shift value";
    int mag = Integer.SIZE - Integer.numberOfLeadingZeros(val);
    int chars = Math.max(((mag + (shift - 1)) / shift), 1);
    char[] buf = new char[chars];

    formatUnsignedInt(val, shift, buf, 0, chars);

    // Use special constructor which takes over "buf".
    return new String(buf, true);
}
/**
*该方法返回无符号整型i的最高非零位前面0的个数
*/
public static int numberOfLeadingZeros(int i) {
    // HD, Figure 5-6
    if (i == 0)
        return 32;
    int n = 1;
    if (i >>> 16 == 0) { n += 16; i <<= 16; }
    if (i >>> 24 == 0) { n +=  8; i <<=  8; }
    if (i >>> 28 == 0) { n +=  4; i <<=  4; }
    if (i >>> 30 == 0) { n +=  2; i <<=  2; }
    n -= i >>> 31;
    return n;
}

toUnsignedString0(int val, int shift)该方法作用为将传入数值转换为规定进制,并且返回该进制数的字符串,val为传入数据,shift为log2(所需进制数)例如方法中传入的4,表示要转换为十六进制shift=log2(16)=4;我们进入方法内部,先看一下 Integer.numberOfLeadingZeros(val) 方法,该方法返回无符号整型i的最高非零位前面0的个数。
举个栗子,假设 i 为777,则对应二进制位为:
0000 0000 0000 0000 0000 0011 0000 1001
首先,i右移16,结果为0,说明i<2^16-1,且最高非零位前至少有16个0.然后将i左移16位,即:
0000 0011 0000 1001 0000 0000 0000 0000
i右移24位,结果为3 != 0。i右移28位,结果为0,说明最高非零位前至少有16+4个0。继续将i左移4位,即:
0011 0000 1001 0000 0000 0000 0000 0000
i右移30位,结果为0,说明最高非零位前至少有16+4+2个0,这时用二分法排查只剩最后2位,将i左移2位,即:
1100 0010 0100 0000 0000 0000 0000 0000
i右移31位结果为1,n-= i结果为16+4+2+1-1=22

回到toUnsignedString0()方法中,也就是说mag为val的二进制的有效位数。chars为var转换为指定进制后的长度,Math.max(((mag + (shift – 1)) / shift), 1)效果等价于mag/shift向上取整。继续往下,buf为char[3]数组,再调用formatUnsignedInt(var,shift,buf,0,chars).

static int formatUnsignedInt(int val, int shift, char[] buf, int offset, int len) {
    int charPos = len;
    int radix = 1 << shift;
    int mask = radix - 1;
    do {
        buf[offset + --charPos] = Integer.digits[val & mask];
        val >>>= shift;
    } while (val != 0 && charPos > 0);

    return charPos;
}

我们继续用777这个神奇的数字举例,那么方法调用为formatUnsignedInt(777,4,buf,0,3) 方法内charPos=3,radix=16,mask=15,进入循环体:
val & mask = 9, buf[2] = 9, val >>>= 4为48
继续循环,val & mask = 0, buf[1] = 0, val >>>= 4为3
继续循环, val & mask = 3, buf[0] = 3, val >>>= 4为0,退出循环,返回0,buf[]={3,0,9}

该方法的核心就是将十进制转换成二进制或八进制或十六进制,不是使用的除数取余法,而是采用更加快捷的位运算。详情可参考此篇博客

兜兜转转之间,hash值777转换为了字符串格式的十六进制“309”。最终返回”全类名@309″代表该对象。
一般来说,toString()方法返回一个文本类型的字符串以代表改对象。返回值应简洁但包含必要信息以便于阅读。并建议所有的子类重写该方法。

wait()、notify()和notifyAll()

wait()方法:

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

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

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(0),而wait(long timeout, int nanos)只是提供了更高的精度。

wait()方法的作用是使当前线程等待,直到另一个线程调用此对象的notify()方法或notifyAll()方法,或者经过指定的时间。
需调用该方法,则当前线程必须拥有此对象的监视器。
调用wait()方法将导致当前线程被放置到调用对象(obj)的等待集合中(有可能多个线程在obj这个对象上等待)并且导致当前线程放弃在这个对象上的所有同步请求(即:此时这个线程将不会再占有对象obj的对象监视器,也就是其他想通过 synchronized(obj){} 来获得对象obj的对象监视器线程就将有可能获得obj对象的监视器,其同步代码就就会被执行了)。

总结一下:当调用 obj.wait()方法时发生三件事:

  1. 把当前线程放置到对象obj的等待集合中
  2. 释放当前线程中 synchronized(obj){} 这个代码块中对 obj 对象的监视器的占用,从而使得其他请求 obj 对象同步(通过synchronized(obj){}代码块)的线程有机会获得obj对象监视器使其同步代码块得以执行。
  3. 使得当前线程处于 sleep 状态,直到有下面几种情况发生:

wait方法返回的机制:

  • 其他线程调用这个对象的notify()方法,而线程T恰好被任意选择为要唤醒的线程。或者其他线程为此对象调用notifyAll()方法。
  • 另一个线程中断了线程T。(异常返回,其他的线程对该线程调用了interrupt方法,使得当前线程收到了 InterruptedException ,从而使得线程唤醒,)
  • wait(long timeout),如果超过timeout时间,则等待就被返回了,此时的线程也被唤醒了。

然后从该对象的等待集中删除线程T,并重新启用线程调度。然后,它就可以正常与其他线程竞争在对象上同步的权利;一旦它获得锁,它对对象的所有同步声明都将恢复到原来的状态——也就是说,恢复到调用wait方法时的状态。线程T然后从wait方法的调用返回。因此,从wait方法返回时,对象和线程T的同步状态与调用wait方法时完全相同。

除了被唤醒,中断,或达到超时时间之外,一个线程仍然可以唤醒,即所谓的虚假唤醒,虽然这种情况实际发生的概率很低,但是应用程序还是应该通过检测条件是否满足(不满足时继续等待)来处理这种情况.换句话说,wait() 方法应该总是写在循环里面.如下所示:

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

notify()

public final native void notify();

notify()方法为本地方法,作用为唤醒一个等待该锁的线程,如果有多个线程竞争该锁,则在多个线程之间唤醒任意一个,到底决定唤醒他们中的哪一个线程是不确定的况且这还要取决于具体的实现。

因为notify()方法是唤醒单一的随机线程,所以它可以用于实现互斥排他锁(针对于多个线程在做相似的任务)。使用notify()实现notifyAll()方法

notifyAll()

public final native void notifyAll();

该方法会唤醒所有等待当前对象的监视器的所有线程。被唤醒的线程将会按照通常的方式完成运行—就像其他线程一样。

但是,在我们允许他们的运行继续之前,总是先快速检查一下该线程继续进行所必需的条件—因为还有一些情况时,线程在没有收到任何通知的情况下被唤醒。

notify、notifyAll 的区别:

  • 唤醒线程的数量:当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争。
  • 线程的可互换性:如果所有等待的线程都是可互换的(它们唤醒的顺序无关紧要),我们应该使用 notify() 。一个常见的例子是线程池。但是我们应该将 notifyAll() 用于等待线程可能具有不同目的并且应该能够并发运行的其他情况。一个例子是对共享资源的维护操作,其中多个线程在访问资源之前等待操作完成。

finalize()

protected void finalize() throws Throwable { }

当垃圾收集器确定不再有对对象的引用时,由垃圾收集器在对象上调用。子类需重写 finalize() 方法以回收系统资源或执行其他清理。
finalize()的一般约定是,当JVM已确定,任何尚未终止的线程都无法再通过任何方式访问此对象,除非由于某个其他对象或类的终结而采取的操作已准备好进行回收。finalize方法可以执行任何操作,包括使此对象再次可供其他线程使用;但是,finalize的通常用途是在对象被不可撤销地丢弃之前执行清理操作。例如,表示输入/输出连接的对象的finalize方法可能会执行显式I/O事务,以在永久丢弃该对象之前断开连接。Object类对象的finalize方法不执行特殊操作;它只是正常返回。对象的子类可以重写此方法。
Java编程语言不保证哪个线程将调用任何给定对象的finalize方法。但是,可以保证调用finalize的线程在调用finalize时不会持有任何用户可见的同步锁。如果finalize方法引发了未捕获的异常,则该异常将被忽略,并且该对象的终结将终止。finalize方法在后续分析GC的时候还会具体分析其原理。(先把坑挖上)

该博客仅为初学者自我学习的记录,粗浅之言,如有不对之处,恳请指正。

今天的文章JavaSourceLearning-java.lang.object分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/16468.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注