1 hashCode
hashCode
的一般约定为:
- 同一个程序中多次调用返回相同的值。不会为
equals
方法比较对象提供信息。 - 如果两个对象
equals
结果为true
,那么hashCode
的结果也相等。 - 如果两个对象
equals
结果为false
,hashCode
结果可能相等也可能不相等。保证不相等对象的哈希值不同,有助于提高哈希表的性能。
2 equals
用于比较是否和传入对象相等。
equals
的等价条件是:
- 自反性。任何非
null
引用x
有,x.equals(x)
返回true
。 - 对称性。任何非
null
引用x
和y
有,x.equals(y)
返回ture
当且仅当y.equals(x)
也返回`true。 - 传递性。任何非
null
引用x
、y
和z
有,如果x.equals(y)
且y.equals(z)
,那么x.equals(z)
。 - 一致性。任何非
null
引用x
、y
,如果没有修改对象,那么多次调用x.equals(y)
返回的结果是一致的。 - 任何非
null
引用。x.equals(null)
是false
。
Object
类对象比较特别,x.equals(y)
为true
当且仅当x == y
为true
。
在重写equals
方法时,需要重写hashCode
方法。为的是保证hashCode
的第1条特性能够成立。
3 clone
克隆结果的特性
-
x.clone != x
为true
。 -
x.clone.getClass() == x.getClass()
为true
,如果拷贝的过程中,调用了super.clone
(除Object
类之外)。 -
x.clone.equals(x)
为true
,如果重写了equals
方法。
克隆原理
通常,克隆对象应该独立于被克隆对象。为了达到这样的目的,可能需要在返回克隆对象前,修改克隆对象的字段,也就是说:
- 可变对象。需要将可变对象的引用替换为拷贝的引用。
- 不可变对象(初等类型或不可变引用)。克隆过程中就不需要修改
super.clone
的结果,一般来说虚拟机会创建新的对象。如下代码,super.clone
返回的结构已经将a
和b
完成了。
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
方法执行的特定操作。
- 如果对象的类没有实现接口
Cloneable
,那么会抛出CloneNotSupportedException
。 - 所有数组被认为是默认实现了接口
Cloneable
,数组类型T[]
的clone
方法的返回类型是T[]
,其中T
是任何引用或初等类型。否则,此方法将创建此对象类的新实例,并使用此对象相应字段的内容初始化其所有字段;字段的内容本身不会被克隆。由此可见,此方法执行此对象的“浅拷贝”,而不是“深拷贝”操作。
Object
类没有实现Cloneable
接口,所以调用clone
方法会抛CloneNotSupportedException
。
4 notify
唤醒等待获取对象监控器(monitor)的一个线程。如果有多个线程在等待,则被唤醒线程是不确定的。
被唤醒线程不能立即执行,而是等到当前线程释放对象锁才可以执行。被唤醒的线程需要与任何其他线程竞争,这其中不乏主动基于此对象同步的线程。
该方法只能由对象monitor拥有者线程来调用。线程成为monitor拥有者方式有:
-
synchronized
实例方法。 -
synchronized
语句块。 -
synchronized
静态方法。即Class
实例的方法。
Java对象的实现由Monitor Object、Monitor Lock和Monitor Condition三部分组成。[2]
5 wait
使当前线程等待,直到另一个线程调用notify
方法或notifyAll
方法、或者超时。
当前线程必须用于对象monitor。
此方法使当前线程被放入这个对象的等待集中,然后释放所有这个对象上的同步声明。线程进入休眠状态,直到:
- 其他线程调用对象的
notify
,线程恰好被选中。 - 其他线程调用对象的
notifyAll
。 - 其他线程调用的
interrupt
方法。 - 用尽超时时间。
线程唤醒后,会先从等待集合中移除,并重新进入调度行列。被唤醒线程与其他线程同步以常规方式竞争对象同步的权限。一旦获取了对象所有权,所有的对象同步声明将恢复为原状(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
一般约定:
- 当不再有任何存活线程以任何方式访问该对象时,则调用finalize,除非已经被某个对象或类执行
finalize
时调用过。
finalize
方法的用途:
- 使此对象对其他线程再次可用。
- 在对象被回收之前执行清理操作。例如,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);
}
网友评论