Object类简介

1 hashCode

hashCode的一般约定为:

  1. 同一个程序中多次调用返回相同的值。不会为equals方法比较对象提供信息。
  2. 如果两个对象equals结果为true,那么hashCode的结果也相等。
  3. 如果两个对象equals结果为falsehashCode结果可能相等也可能不相等。保证不相等对象的哈希值不同,有助于提高哈希表的性能。

2 equals

用于比较是否和传入对象相等。
equals的等价条件是:

  1. 自反性。任何非null引用x有,x.equals(x)返回true
  2. 对称性。任何非null引用xy有,x.equals(y)返回ture当且仅当y.equals(x)也返回`true。
  3. 传递性。任何非null引用xyz有,如果x.equals(y)y.equals(z),那么x.equals(z)
  4. 一致性。任何非null引用xy,如果没有修改对象,那么多次调用x.equals(y)返回的结果是一致的。
  5. 任何非null引用。x.equals(null)false

Object类对象比较特别,x.equals(y)true当且仅当x == ytrue

在重写equals方法时,需要重写hashCode方法。为的是保证hashCode的第1条特性能够成立。

3 clone

克隆结果的特性

  1. x.clone != xtrue
  2. x.clone.getClass() == x.getClass()true,如果拷贝的过程中,调用了super.clone(除Object类之外)。
  3. x.clone.equals(x)true,如果重写了equals方法。

克隆原理
通常,克隆对象应该独立于被克隆对象。为了达到这样的目的,可能需要在返回克隆对象前,修改克隆对象的字段,也就是说:

  1. 可变对象。需要将可变对象的引用替换为拷贝的引用。
  2. 不可变对象(初等类型或不可变引用)。克隆过程中就不需要修改super.clone的结果,一般来说虚拟机会创建新的对象。如下代码,super.clone返回的结构已经将ab完成了。
public class Main implements Cloneable{
    private int a;
    private String b;
    @Override
    public Main clone() throws CloneNotSupportedException {
       return (Main) super.clone();
    }
}

Object类的clone方法
Object类的clone方法执行的特定操作。

  1. 如果对象的类没有实现接口Cloneable,那么会抛出CloneNotSupportedException
  2. 所有数组被认为是默认实现了接口Cloneable,数组类型T[]clone方法的返回类型是T[],其中T是任何引用或初等类型。否则,此方法将创建此对象类的新实例,并使用此对象相应字段的内容初始化其所有字段;字段的内容本身不会被克隆。由此可见,此方法执行此对象的“浅拷贝”,而不是“深拷贝”操作。

Object类没有实现Cloneable接口,所以调用clone方法会抛CloneNotSupportedException

4 notify

唤醒等待获取对象监控器(monitor)的一个线程。如果有多个线程在等待,则被唤醒线程是不确定的。

被唤醒线程不能立即执行,而是等到当前线程释放对象锁才可以执行。被唤醒的线程需要与任何其他线程竞争,这其中不乏主动基于此对象同步的线程。

该方法只能由对象monitor拥有者线程来调用。线程成为monitor拥有者方式有:

  1. synchronized实例方法。
  2. synchronized语句块。
  3. synchronized静态方法。即Class实例的方法。

Java对象的实现由Monitor Object、Monitor Lock和Monitor Condition三部分组成。[2]

5 wait

使当前线程等待,直到另一个线程调用notify方法或notifyAll方法、或者超时。

当前线程必须用于对象monitor。

此方法使当前线程\text{T}被放入这个对象的等待集中,然后释放所有这个对象上的同步声明。线程\text{T}进入休眠状态,直到:

  1. 其他线程调用对象的notify,线程\text{T}恰好被选中。
  2. 其他线程调用对象的notifyAll
  3. 其他线程调用\text{T}interrupt方法。
  4. 用尽超时时间。

线程唤醒后,会先从等待集合中移除,并重新进入调度行列。被唤醒线程与其他线程同步以常规方式竞争对象同步的权限。一旦获取了对象所有权,所有的对象同步声明将恢复为原状(quo ante),即调用wait时的状态。

线程也可以在不被通知、中断或超时的情况下被唤醒,即所谓的“虚假唤醒”。虽然,这种情况在实践中很少发生,但应用程序必须通过测试本应导致线程被唤醒的条件,并在条件不满足时继续等待来防范这种情况。换句话说,等待应该总是以循环的形式出现,就像这样:

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

如果当前线程在等待之前或等待期间被任何线程中断,则抛出InterruptedException。只是在对象的锁状态恢复之前,不会抛出。也就是说,如果当前线程被中断时,对象monitor被其他线程拥有,当前线程只有获得对象monitor才能够抛出异常

注意,wait方法在将当前线程放入此对象的等待集中时,只解锁此对象;在线程等待期间,当前线程可能同步的任何其他对象都将保持锁定状态。

6 finalize

当垃圾回收器确定对象不再被引用时,垃圾回收器调用对象的这个方法。子类重写finalize方法,用于释放系统资源或执行其他清理工作。

关于finalize一般约定:

  1. 当不再有任何存活线程以任何方式访问该对象时,则调用finalize,除非已经被某个对象或类执行finalize时调用过。

finalize方法的用途:

  1. 使此对象对其他线程再次可用。
  2. 在对象被回收之前执行清理操作。例如,I/O连接对象的finalize方法可能会执行显式I/O事务,以便在对象被回收之前断开连接。

Java并不保证哪个线程将调用对象的finalize方法,但是,可以保证的是调用finalize的线程在调用finalize时不会持有任何用户可见的同步锁。如果finalize方法执行时抛出未捕获的异常,则将忽略该异常,并终止执行该对象的finalization。

调用对象finalize方法之后,虚拟机不会做任何动作,直到它再次确定不再有任何活跃线程以任何方法引用该对象,包括其他即将终止的对象或类可能执行的操作,这时,对象可能会被丢弃。

对于一个对象,finalize方法不会被虚拟机调用多次,也就是说最多一次。

代码实例:

static class Test {
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("Finalized!!!");
    }
}

public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(() -> {
        try {
            Test test = new Test();
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
        }
        System.out.println(Thread.currentThread().getName() + " finished");
    });

    thread.start();
    thread.join(); // 如果注释掉,则不会执行finalize

    System.gc();

    TimeUnit.SECONDS.sleep(10);
}

参考资料

  1. Object
  2. 关于synchronized的Monitor Object机制的研究
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容