Object 类介绍
1. Java中唯一没有父类的类,Java中所有的类从根本上都继承自这个类。
Object常见方法
1.
public native int hashCode();
该方法调用本地JNI返回该对象的内存地址。Object.hashCode的通用约定(摘自《Effective Java》第45页)
1. 在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,那么,对该对象调用hashCode方法多次,它必须始终如一地返回 同一个整数。在同一个应用程序的多次执行过程中,这个整数可以不同,即这个应用程序这次执行返回的整数与下一次执行返回的整数可以不一致。
2. 如果两个对象根据equals(Object)方法是相等的,那么调用这两个对象中任一个对象的hashCode方法必须产生同样的整数结果。
3. 如果两个对象根据equals(Object)方法是不相等的,那么调用这两个对象中任一个对象的hashCode方法,不要求必须产生不同的整数结果。然而,程序员应该意识到这样的事实,对于不相等的对象产生截然不同的整数结果,有可能提高散列表(hash table)的性能。
2.
public boolean equals(Object obj) { return (this == obj); }
== 为比较两个对象的内存地址,默认equals方法会比较两个Object的内存地址是否相等来判断两个对象是否相等。
Java内规定,hashCode方法的结果需要与equals方法一致。也就是说,如果两个对象的hashCode相同,那么两个对象调用equals方法的结果需要一致。修改equals方法时,同时修改hashCode方法有助于提高 散列表等借助了hashCode()方法的数据结构的性能。
扩展:
在重写equals方法的时候,需要遵守下面的通用约定:
1、自反性。
对于任意的引用值x,x.equals(x)一定为true。
2、对称性。
对于任意的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)也一定返回true。
3、传递性。
对于任意的引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也一定返回true。
4、一致性。
对于任意的引用值x和y,如果用于equals比较的对象没有被修改的话,那么,对此调用x.equals(y)要么一致地返回true,要么一致的返回false。
5、对于任意的非空引用值x,x.equals(null)一定返回false。
重写hashCode方法的大致方式:
a、把某个非零常数值,比如说17(最好是素数),保存在一个叫result的int类型的变量中。
b、对于对象中每一个关键域f(值equals方法中考虑的每一个域),完成一些步骤:
1、为该域计算int类型的散列吗c:
1)、如果该域是boolean类型,则计算(f?0:1)。
2)、如果该域是byte、char、short或者int类型,则计算(int)f。
3)、如果该域是float类型,则计算Float.floatToIntBits(f)。
4)、如果该域是long类型,则计算(int)(f ^ (f>>>32))。
5)、如果该域是double类型,则计算Double.doubleToLongBits(f)得到一个long类型的值,然后按照步骤4,对该long型值计算散列值。
6)、如果该域是一个对象引用,并且该类的equals方法通过递归调用equals的方式来比较这个域,则同样对这个域递归调用hashCode。如果要求一个更为复杂的比较,则为这个域计算一个“规范表示”,然后针对这个范式表示调用hashCode。如果这个域的值为null,则返回0(或者其他某个常数)
7)、如果该域是一个数组,则把每一个元素当做单独的域来处理。也就是说,递归地应用上述规则,对每个重要的元素计算一个散列码,然后根据步骤下面的做法把这些散列值组合起来。
2、按照下面的公式,把步骤1中计算得到的散列码C组合到result中:
result = 31*result+c。
c、返回result。
d、写完hashCode方法之后,问自己“是否相等的实例具有相等的散列码”。如果不是的话,找出原因,并修改。
3.
protected native Object clone() throws CloneNotSupportedException;
1. 代码实现,对于clone过程中遇到的所有非基本类型变量,均需要实现相应的clone方法。
2. 放入流中,将该对象序列化放入流中。
4.
public final void wait() throws InterruptedException { wait(0); }
wait方法,Thread A持有对象Object A的锁时(通过synchronized方法),调用了Object A的wait方法,则此时Thread A会进入到Object A的 等待池 中进行等待。 等待池 中的线程不会去竞争锁。
5.
public final native void notify();
notify方法,Thread B持有Object A的锁时,调用Object A的notify方法,会 随机唤醒一个 处于 等待池 中的线程(Thread A),此时这个被唤醒的线程会被移动到 锁池 中。处于 锁池 的线程会去竞争锁。
6.
public final native void notifyAll();
notifyAll方法,Thread B持有Object A的锁时,调用Object A的notify方法,会 唤醒所有 等待池 中的线程(Thread A),此时所有被唤醒的线程会被移动到 锁池 中。处于 锁池 的线程会去竞争锁。
总结:
4、5、6这三个方法的使用均需要当前使用对象持有锁才能调用,否则会抛出相应的异常。
一般而言我们通常使用notifyAll方法而不是notify方法。因为notify仅唤醒一个线程去执行,若唤醒的线程拿到锁执行完并释放后,由于不满足条件没有再次进行notify/notifyAll,通知其他出于wait状态的线程。则其他线程则仍然处于wait状态,进而陷入了死锁。
问题的本质在于 其他的线程在等待池中等待notify/notifyAll,而不是在锁池等待锁。
后续文章再详细介绍锁相关的内容。
7.
protected void finalize() throws Throwable { }
一旦GC准备好释放对象占用的存储空间,首先会去调用finalize()方法进行一些必要的清理工作。只有到下一次再进行垃圾回收动作的时候,才会真正释放这个对象所占用的内存空间。
finalize方法被调用并不代表GC会立刻回收该对象,所以有可能执行该方法后,GC没有回收。而到了下一次真正需要回收时,会由于finalize方法已经被调用而产生一些其他问题。与C++的析构函数有所不同。由于执行时机的不可预知,因此不推荐使用。
后续文章具体介绍java的引用类型