Java Object 方法简析
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方法。
该方法有着如下的约定:
- 在同一个Java程序的运行过程中,在对象不修改“equals”方法中所用信息的前提下,该对象返回的hashCode始终是相同的。同时,不同Java程序中对象的hashCode不保持相同。
- 若通过equals方法比较得到两个对象是相等的话,那么这两个对象的hashCode也必须是一致的。所以若重写了equals方法,hashCode方法一定要跟着重写。
- 不相等的对象不保证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的对象,必须具备以下特性:
- 自反性,即X.equals(X)必须为true
- 对称性,即X.equals(Y)和Y.equals(X)的计算结果必须一致
- 传递性,即若X.equals(Y)=true且Y.equals(Z)=true,则X.equals(Z)必须为true
- 一致性,即当equals方法需要的信息没有被改变的情况下,无论执行多少次,X.equals(Y)的结果都必须是相同的
- 对于任何非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
有两点值得注意:
- Object没有实现Cloneable接口,所以调用Object类对象的clone方法的时候会抛出CloneNotSupportedException异常
- 数组默认的拷贝是“浅拷贝”操作。在Java中,所有数组被视为实现Cloneable接口,返回类型是T [],其中T是任何引用或基本类型。此方法会创建此对象的类的新实例,并使用该对象的相应字段的内容通过赋值初始化其所有字段,而这些字段的内容本身不会被克隆。
5. toString
该方法返回一个代表着该对象信息的字符串,这个字符串需要做到简洁易读懂。Object中默认返回“对象的类名@该对象哈希码的16进制表示”
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
建议所有的Object子类都重写该方法,在调试或者记录log的时候非常有用。
6. notify
唤醒一个在此对象监视器上等待的线程,若有多个等待的线程,则随机唤醒其中某一个。
线程被唤醒后并不能马上进行处理,而是要等待当前线程释放对象锁之后才可以继续处理,同时被唤醒的线程还需要与其他在该对象同步的其他线程竞争锁。
一个线程若要成为对象监视器的所有者,有以下3种方法:
- 执行对象的同步实例方法
- 使用synchronized内置锁
- 对于Class类型的对象,执行同步静态方法
值得注意的是,notify方法只能被作为此对象监视器所有者的线程调用,且一次只能有一个线程拥有对象的监视器。若当前线程不是此对象监视器所有者的话会抛出IllegalMonitorStateException异常
7. notifyAll
功能和注意点同notify方法,区别是该方法用于唤醒在此对象监视器上等待的所有线程。当所有线程被唤醒后,需要等待当前线程释放锁,并同其他线程竞争。
8. wait(long timeout)
让一个线程进入等待状态,直到被notify/notifyAll方法唤醒,或者过了规定的时间
这个方法会致使当前线程(简称T)被加入到等待集合中并释放其所拥有的资源和锁。出于线程调度的目的,线程T将不可用并进入休眠状态,直到发生以下四件事中的任意一件
- 其他某个线程调用此对象的notify方法,碰巧选中并唤醒了T
- 其他某个线程调用此对象的notifyAll方法
- 其他某个线程调用Thread.interrupt方法中断线程T
- 时间到了参数设置的超时时间。如果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操作。