Java中Object类的方法解析

Object中共有11个方法

1.equals(Object obj)

equals()方法主要用来比对两个对象是否相等,但是在Object中的实现是比较内存地址是否相等,而在在String、Integer中被进行了重写。

Object中

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

String中

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = length();
        if (n == anotherString.length()) {
            int i = 0;
            while (n-- != 0) {
                if (charAt(i) != anotherString.charAt(i))
                        return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

2.hashCode()

hashCode()方法是native方法

hashCode()方法主要用来获取对象的哈希值,可以用在HashMap中

  • 在java程序执行过程中,在一个对象没有被改变的前提下,无论这个对象被调用多少次,hashCode方法都会返回相同的整数值。对象的哈希码没有必要在不同的程序中保持相同的值。
  • 如果2个对象使用equals方法进行比较并且相同的话,那么这2个对象的hashCode方法的值也必须相等。
  • 如果根据equals方法,得到两个对象不相等,那么这2个对象的hashCode值不需要必须不相同。但是,不相等的对象的hashCode值不同的话可以提高哈希表的性能(哈希碰撞)。

String的hashCode()的计算方法:s[0]31^(n-1) + s[1]31^(n-2) + … + s[n-1]

public int hashCode() {
    int h = hash;
    final int len = length();
    if (h == 0 && len > 0) {
        for (int i = 0; i < len; i++) {
            h = 31 * h + charAt(i);
        }
        hash = h;
    }
    return h;
}

比如”zg”的hashCode = 12231^1 + 103 = 3885, “zgjy”的hashCode = 12231^3 + 103 31^2 + 106 31^1+ 121 = 3736892 (‘z’的ascii码为122,’g’的ascii为103,’j’的ascii码为106,’y’的ascii码为121)

如果2个对象的equals方法相等,那么他们的hashCode值也必须相等,反之,如果2个对象hashCode值相等,但是equals不相等,这样会影响性能,所以还是建议2个方法都一起重写。

3.getClass()

getClass()是一个final方法,不允许子类重写,并且也是一个native方法。
返回当前运行时对象的Class对象,注意这里是运行时,比如以下代码中n是一个Number类型的实例,但是java中数值默认是Integer类型,所以getClass方法返回的是java.lang.Integer:

Number n = 0;
Class<? extends Number> c= n.getClass();//java.lang.Integer

4.clone()

创建并返回此对象的副本。精确的意思“复制”的类型可能取决于对象的类。一般
情况下,对于任何对象x,表达式为:

x.clone() != x
结果为true
x.clone().getClass() == x.getClass()
结果为true
x.clone().equals(x)

结果为true (这个不做绝对要求)

由于Object本身没有实现Cloneable接口,所以不重写clone方法并且进行调用的话会发生CloneNotSupportedException异常。

5.toString()

Object对象的默认实现,即输出类的名字@实例的哈希码的16进制。

6.notify()

notify方法是一个native方法,并且也是final的,不允许子类重写。

唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果所有的线程都在此对象上等待,那么只会选择一个线程。选择是任意性的,并在对实现做出决定时发生。一个线程在对象监视器上等待可以调用wait方法。

直到当前线程放弃对象上的锁之后,被唤醒的线程才可以继续处理。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争。例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。

notify方法只能被作为此对象监视器的所有者的线程来调用。一个线程要想成为对象监视器的所有者,可以使用以下3种方法:

执行对象的同步实例方法
使用synchronized内置锁
对于Class类型的对象,执行同步静态方法
一次只能有一个线程拥有对象的监视器。

如果当前线程不是此对象监视器的所有者的话会抛出IllegalMonitorStateException异常

注意点:

因为notify只能在拥有对象监视器的所有者线程中调用,否则会抛出IllegalMonitorStateException异常

7.notifyAll()

跟notify()一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。

同样,如果当前线程不是对象监视器的所有者,那么调用notifyAll同样会发生IllegalMonitorStateException异常。

8.wait()

该方法一直等待,没有超时概念,也是一个native方法,并且是final的,不允许子类重写。

Consumer consumer = new Consumer();
consumer.wait();

直接调用wait方法会发生IllegalMonitorStateException异常,这是因为调用wait方法需要当前线程是对象监视器的所有者。

跟notify和notifyAll方法一样,当前线程必须是此对象的监视器所有者,否则还是会发生IllegalMonitorStateException异常。

9.wait(long millis)

wait(long millis)方法会让当前线程等待直到另外一个线程调用对象的notify或notifyAll方法,或者超过参数设置的timeout超时时间。

10.wait(long millis, int nanos)

跟wait(long timeout)方法类似,多了一个nanos参数,这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)。 所以超时的时间还需要加上nanos毫秒。

11.finalize()[1]

方法主要涉及到GC的回收。在堆里面存放着Java世界中几乎所有的对象实例, 垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象之中哪些还“存活”着, 哪些已经“死去”(即不可能再被任何途径使用的对象)。

  1. 引用计数,此时添加一个引用计数器,如果对象被引用计数+1,如果引用失效则计数-1,如果引用计数为0,则此对象不可使用。客观的讲引用计数的效率很高,也有一些比较著名的应用案例,例如微软公司的COM(Component Object Model)技术、使用ActionScript3的FlashPlayer、Python语言和在游戏脚本领域被广泛应用的Squirrel中都使用了引用计数算法进行内存管理。但是如果两个对象循环引用就可能存在问题
    public class ReferenceCountingGC {

    public Object instance = null;
    private static final int _1MB = 1024 1024;
    /*

    • 这个成员属性的唯一意义就是占点内存,以便能在GC日志中看清楚是否被回收过
      /
      private static byte[] bigSize = new byte[2
      _1MB];

      public static void testGC() {
      ReferenceCountingGC objA = new ReferenceCountingGC();
      ReferenceCountingGC objB = new ReferenceCountingGC();
      objA.instance = objB;
      objB.instance = objA;
      objA = null;
      objB = null; //假设在这行发生GC,objA和objB是否能被回收?
      System.gc();
      }

      public static void main(String[] args){
      testGC();
      }

}

  1. 可达性分析,在主流的商用程序语言(Java、C#,甚至包括前面提到的古老的Lisp)的主流实现中,都是称通过可达性分析(ReachabilityAnalysis)来判定对象是否存活的。这个算法的基本思路就是通过一系列的
    称为”GCRoots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(ReferenceChain),当一个对象到GCRoots没有任何引用链相连(用图论的话来说,就是从GCRoots到这个对象不可达)时,则证明此对象是不可用的。如下图所示,对象object5、object6、object7虽然互相有关联,但是它们到GCRoots是不可达的,所以它们将会被判定为是可回收的对象。
    image

在Java语言中,可作为GCRoots的对象包括下面几种:

虚拟机栈(栈帧中的本地变量表)中引用的对象。

方法区中类静态属性引用的对象。

方法区中常量引用的对象。

本地方法栈中JNI(即一般说的Native方法)引用的对象。

对象生存还是死亡

如果对象判断为不可达,并非“非死不可”,这时候它们处于缓刑状态,而要真正的死亡需要经过两次标记,如果对象在进行可达性分析后发现没有与GCRoots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”。

如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会放置在一个叫做F-Queue的队列之中,并在稍后由一个由虚拟机自动建立的、低优先级的Finalizer线程去执行它。这里所谓的“执行”是指虚拟机会触发这个方法,但并不承诺会等待它运行结束,这样做的原因是,如果一个对象在finalize()方法中执行缓慢,或者发生了死循环(更极端的情况),将很可能会导致F-Queue队列中其他对象永久处于等待,甚至导致整个内存回收系统崩溃。finalize()方法是对象逃脱死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模的标记,如果对象要在finalize()中成功拯救自己——只要重新与引用链上的任何一个对象建立关联即可,譬如把自己(this关键字)赋值给某个类变量或者对象的成员变量,那在第二次标记时它将被移除出“即将回收”的集合;如果对象这时候还没有逃脱,那基本上它就真的被回收了。从代码清单中我们可以看到一个对象的finalize()被执行,但是它仍然可以存活。

public class FinalizeGC implements Cloneable{
public static FinalizeGC finalizeGC = null;


public void isAlive() {
    System.out.println(" yes, i am still alive:)");
}

@Override
protected void finalize() throws Throwable {
    super.finalize();
    System.out.println(" finalize mehtod executed!");
    FinalizeGC.finalizeGC = this;
}

public static void main(String[] args) throws Throwable {
    finalizeGC = new FinalizeGC();
    //对象 第一次 成功 拯救 自己
    finalizeGC = null;
    System.gc();
    //因为finalize()优先级很低,所以暂停0.5秒等待它
    Thread.sleep(500);
    if (finalizeGC != null) {
        finalizeGC.isAlive();
    } else {
        System.out.println(" no, i am dead:(");
    }
    //下面这段代码与上面的完全相同,但是这次自救却失败了
    finalizeGC = null;
    System.gc();
    //因为finalize()优先级很低,所以暂停0.5秒以等待它
    Thread.sleep(500);
    if (finalizeGC != null) {
        finalizeGC.isAlive();
    } else {
        System.out.println(" no, i am dead:(");
   }
    finalizeGC.isAlive();
 }
}

参考文献


  • 周志明. 深入理解Java虚拟机:JVM高级特性与最佳实践(第2版) 机械工业出版社.
  • 坚持原创技术分享,您的支持将鼓励我继续创作!