阿里的面试题里面有个题很奇妙:你知道Object类里面有哪些方法吗?
绝大部分猿类都知道 有hashcode 、equals 、clone、toString
只有部分人会回答有 wait和notify,notifyall,finalize
我原来也不清楚wait和notify居然是在Object里面定义的~~~~囧
由此会衍生很多问题出来:比如在日常工作中会重写哪些方法?为什么重写equals一定要重写hashcode?深clone和浅clone说下你的认识?wait和notify为什么要定义在Object中,wait和notify是怎么使用的?wait和sleep的区别?finalize的效果和使用场景?
然后我们来一一看下这些问题。
1、一般会重写的的方法:
hashcode、equals、tostring
2、为什么重写equals方法一定要重写hashcode
首先我们看下equals的javadoc里面的说明:
* @return {@code true} if this object is the same as the obj
* argument; {@code false} otherwise.
含义:只有当equals前后的两个对象是同一个对象的时候,才返回true,否则就是false。
也就是说,我们如果我们不重写hashcode,两个你认为相同的obj,可能出现不同的hashcode,而hashmap和set之类的数据结构都是依赖hashcode来进行的,
如果不重写hashcode就有可能碰到这个情况,当equals为true的时候,hashcode必须完全相同。当hashcode不同的时候,equals必须为false。
如果不覆盖equals,那么默认equals比较的引用的内存地址,如果覆盖了,你就需要自己去保证这些内容的一致。
还有一系列看上去很唬人实际上很好理解的原则在里面:
/*
* 重写equals必须注意:
* 1 自反性:对于任意的引用值x,x.equals(x)一定为true
* 2 对称性:对于任意的引用值x 和 y,当x.equals(y)返回true,y.equals(x)也一定返回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
**/
3、深clone和浅clone
Object里面有个clone方法是浅clone,clone出来的类,新的对象和原来的对象并不是同一个引用,但是如果类中属性存在引用类型,那么新的类的对应属性也会指向这个引用。
也就是,你如果修改了新对象的引用对象的内容,原来的对象也会发生改变。
如果我们需要clone出来一个完全不同的类,可以通过实现cloneable的protected的clone方法,来确定,
可以通过自己拼接一份新的对象返回,不过对于嵌套层次比较深的类,这是个很浩大的工程。当然也有简单的做法,把对象序列化,然后再反序列化回来其实就是一个新的了。
这种clone之后完全不同的两个对象的情况,就是深clone了。
4、wait和notify为什么要定义在Object中,wait和notify是怎么使用的
wait和notify为什么不在Thread中而是在Object中,其实这个地方稍微有点绕。
我们就假设一种情况吧:A对象,100个线程,线程Thread[1] 持有A对象的锁,其它99个都想使用A,然后就在等待锁。A用完了,怎么通知其他99个?我们是Thread[1]持有其它99个线程的,然后分别去通信通知他们还是通过Object对象直接告诉其他99好呢?
这个很明显吧,我们并不能确定外围有多少个线程,我们只能确定,Object都可以作为锁,然后为什么不用对象本身来管理这些消息通知呢?我们只要在其中一个线程使用完成锁之后,对象把锁丢地上说:兄弟们我的钥匙扔地上了,你们看着办啊。。而不是之前拿钥匙的人,因为这个人没法认识其他所有人,但是凡是关心这个对象的,这个对象肯定都是知道的。
所以这个地方的wait和notify其实Object中定义的~比较口语化,我也不知道怎么更加优雅的表达,那我们就抄一段放这里吧:
5、sleep和wait的区别
sleep是Thread类的静态方法,它生效的也仅仅是当前线程,而不能用 某线程.sleep() .
wait是持有该对象锁的线程主动放弃锁,交出monitor。
sleep本身并不释放所和资源,而只是等待一段时间。wait是交出了锁和资源,后面如果再想获取,需要看自己的实现了。
6、finalize
Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。finalize方法在Object类中是protected方法,也就是你可以自己去重写这个方法,但是除非很特殊的情况,一般没必要自己去重写。
这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在 Object 类中定义的,因此所有的类都继承了它。子类覆盖 finalize() 方法以整理系统资源或者执行其他清理工作。finalize() 方法是在垃圾收集器删除对象之前对这个对象调用的。
当垃圾回收器(garbage colector)决定回收某对象时,就会运行该对象的finalize()方法。值得C++程序员注意的是,finalize()方法并不能等同与析构函数。Java中是没有析构函数的。C++的析构函数是在对象消亡时运行的。由于C++没有垃圾回收,对象空间手动回收,所以一旦对象用不到时,程序员就应当把它delete()掉。所以析构函数中经常做一些文件保存之类的收尾工作。但是在Java中很不幸,如果内存总是充足的,那么垃圾回收可能永远不会进行,也就是说filalize()可能永远不被执行,显然指望它做收尾工作是靠不住的。
那么finalize()究竟是做什么的呢?它最主要的用途是回收特殊渠道申请的内存。Java程序有垃圾回收器,所以一般情况下内存问题不用程序员操心。但有一种JNI(Java Native Interface)调用non-Java程序(C或C++),finalize()的工作就是回收这部分的内存。
也就是说,finalize并不保证执行以后就会把内存释放掉,而是会到执行后的下一次垃圾回收才有机会被回收掉。