美文网首页JVM
Java Object 方法简析

Java Object 方法简析

作者: Xun_Moo | 来源:发表于2018-07-10 21:30 被阅读0次

    Java中,Object类提供了一下方法

    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;
    

    1. getClass

    这是一个final且native方法,即不允许子类修改,且由非Java语言实现.
    返回某个运行时对象的class<? extend |X|>.

     Number n = 0.0;
    Class<? extends Number> nClass = n.getClass();
    System.out.println(nClass.equals(Double.class)); //true
    
    Number n2 = 0;
    Class<? extends Number> n2Class = n.getClass();
    System.out.println(nClass.equals(Integer.class)); //true
    

    应该注意的是,返回的是运行时的类对象。举个例子,当你将一个Number对象初始化为小数的时候,Java默认它为double类型,此时你getClass获取到的class对象就是Double。当你将它初始化为整数的时候,Java默认它为Integer类型,此时你获取到的class即为Integer。

    2. hashCode

    该函数返回对象的哈希值,常用于哈希表中。这也是一个native方法。
    该方法有着如下的约定:

    1. 在同一个Java程序的运行过程中,在对象不修改“equals”方法中所用信息的前提下,该对象返回的hashCode始终是相同的。同时,不同Java程序中对象的hashCode不保持相同。
    2. 若通过equals方法比较得到两个对象是相等的话,那么这两个对象的hashCode也必须是一致的。所以若重写了equals方法,hashCode方法一定要跟着重写。
    3. 不相等的对象不保证hashCode一定不相等。但为不相等的对象赋予不相等的hashCode可以提高hashTable的性能。
      Object类中定义的hashCode方法为不同的对象产生不同的hashCode,一般是通过将对象的内部地址换算成整数。不过Java中定义的各种类型一般会重写hashCode方法。比如Integer中返回的是该对象的value值:
    @Override
    public int hashCode() {
      return Integer.hashCode(value);
    }
    public static int hashCode(int value) {
      return value;
    }
    

    而在String中,缓存了hash值,节省了重复计算的开销。

    public int hashCode() {
      int h = hash;
      if (h == 0 && value.length > 0) {
        char val[] = value;
    
        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
         }
        hash = h;
      }
      return h;
    }
    

    3. equals

    该方法用于判断两个对象是否相等。Object提供的默认方法中,通过判断两个对象的内存地址是否相等,也就是说调用X.equals(Y)的时候,会判断X、Y是否指向同一个对象。

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

    该方法的实现对于所有非null的对象,必须具备以下特性:

    1. 自反性,即X.equals(X)必须为true
    2. 对称性,即X.equals(Y)和Y.equals(X)的计算结果必须一致
    3. 传递性,即若X.equals(Y)=true且Y.equals(Z)=true,则X.equals(Z)必须为true
    4. 一致性,即当equals方法需要的信息没有被改变的情况下,无论执行多少次,X.equals(Y)的结果都必须是相同的
    5. 对于任何非null的对象,X.equals(null)结果必须为false

    值得注意的是,一旦重写equals方法,则必须重写hashCode方法,确保相同的对象有相同的哈希码。这一点在hashCode方法介绍中也有提到。

    4. clone

    该方法创造并返回对象的一个拷贝。通常情况下,拷贝的定义满足以下约束,即对象的拷贝与原对象不是同一个对象,但与原对象数据和类型完全相同。类比一下,文件和文件的复印件内容和类型是一模一样的(忽略印刷错误等),但两者在物理意义上是两件物品。不过这个定义不是绝对的,一般根据需求和场景定义

    x.clone() != x //true
    x.clone().getClass() == x.getClass() //true
    x.clone().equals(x) //true
    

    有两点值得注意:

    1. Object没有实现Cloneable接口,所以调用Object类对象的clone方法的时候会抛出CloneNotSupportedException异常
    2. 数组默认的拷贝是“浅拷贝”操作。在Java中,所有数组被视为实现Cloneable接口,返回类型是T [],其中T是任何引用或基本类型。此方法会创建此对象的类的新实例,并使用该对象的相应字段的内容通过赋值初始化其所有字段,而这些字段的内容本身不会被克隆。

    5. toString

    该方法返回一个代表着该对象信息的字符串,这个字符串需要做到简洁易读懂。Object中默认返回“对象的类名@该对象哈希码的16进制表示”

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

    建议所有的Object子类都重写该方法,在调试或者记录log的时候非常有用。

    6. notify

    唤醒一个在此对象监视器上等待的线程,若有多个等待的线程,则随机唤醒其中某一个。
    线程被唤醒后并不能马上进行处理,而是要等待当前线程释放对象锁之后才可以继续处理,同时被唤醒的线程还需要与其他在该对象同步的其他线程竞争锁。
    一个线程若要成为对象监视器的所有者,有以下3种方法:

    1. 执行对象的同步实例方法
    2. 使用synchronized内置锁
    3. 对于Class类型的对象,执行同步静态方法
      值得注意的是,notify方法只能被作为此对象监视器所有者的线程调用,且一次只能有一个线程拥有对象的监视器。若当前线程不是此对象监视器所有者的话会抛出IllegalMonitorStateException异常

    7. notifyAll

    功能和注意点同notify方法,区别是该方法用于唤醒在此对象监视器上等待的所有线程。当所有线程被唤醒后,需要等待当前线程释放锁,并同其他线程竞争。

    8. wait(long timeout)

    让一个线程进入等待状态,直到被notify/notifyAll方法唤醒,或者过了规定的时间
    这个方法会致使当前线程(简称T)被加入到等待集合中并释放其所拥有的资源和锁。出于线程调度的目的,线程T将不可用并进入休眠状态,直到发生以下四件事中的任意一件

    1. 其他某个线程调用此对象的notify方法,碰巧选中并唤醒了T
    2. 其他某个线程调用此对象的notifyAll方法
    3. 其他某个线程调用Thread.interrupt方法中断线程T
    4. 时间到了参数设置的超时时间。如果timeout参数为0,则不会超时,会一直进行等待,即wait(0)等同于wait()

    值得注意的是,一个线程也可能在没有被唤醒、中断或超时的情况下醒过来,这种称之为“虚假唤醒”。虽然这种情况在实践中很少发生,但是应用程序有必要对应该导致该线程被唤醒的条件进行测试,若条件不满足,则继续等待。如下所示:

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

    9. wait(long timeout, int nanos)

    这个方法的表现形式和wait(long timeout)是一致的,只不过超时时间计算方式如下:1000000*timeout+nanos,其中nanos表示额外的时间,单位为毫微秒。
    于是,我们有wait(0,0)=wait(0)=wait(0)

    10. wait

    与其他两个wait方法的唯一不同,是该方法会让线程一直等待,知道被notify方法或notifyAll方法唤醒。
    再次提醒,调用以上与线程等待和唤醒相关的方法(notify/notifyAll/wait)的时候,当前线程必须是此对象的监视器所有者,否则会抛IllegalMonitorStateException异常

    11. finalize

    该方法会在对象被垃圾回收机制回收之前调用,用于释放资源或执行其他清理。Object默认不做任何操作,子类可以根据自身需求重写。
    Java不保证哪个线程将会调用任何给定对象的finalize方法。 但是保证在调用finalize时,调用该方法的线程不会持有任何用户可见的同步锁。 如果finalize方法抛出未捕获的异常,则会忽略该异常并结束该对象当前执行的finalize操作。

    相关文章

      网友评论

        本文标题:Java Object 方法简析

        本文链接:https://www.haomeiwen.com/subject/ypyxpftx.html