一:前情导读
所有类的根类是Object,提供的方法不多但是都很精妙。本文的目的就是阐述这些方法都具有什么作用,尽可能解决工作亦或是面试过程中遇到的问题。
二:对象运行时类
做任何反射操作(方法获取执行、属性获取修改/赋值、权限修改…)都需要Class对象,也就是对象运行时类的支持。获取该对象的方法共有三个:
1
Object提供的getClass()方法,该方法访问权限为public,使用final、native修饰
2
Class提供的forName()静态方法,该方法具有不同的重载,并且都是调用forName()本地方法实现,其中还使用到了类加载器确定
3 类的隐藏属性class
三:一致性比较
判断两个对象是否相等的equals()与==有什么区别,又是怎么实现对两个对象的内容进行的比较?
Object中提供的equals()方法默认实现为==使用堆地址比较,自定义类中需要使用到equals()方法比较对象内容一致需要手动重写实现,下面看一下String类中对该方法的实现
- 首先使用堆地址比较,堆地址一致那么两个对象一定相等
- 类型判断,两个类型不一致的对象一定不一致
- 长度与字节大小的内容比较,判断内容的一致性
四:克隆本质分析
4.1 字符串
原始数值修改数值
输出结果可能会有人惊讶,或许有人知道结果但是能解释清楚么?恐怕也是语焉不详,如下图所示:
别忘了String字符串有两种实例化方式。第一种字面常量,第二种实例化对象。字面常量的时候存储地址在常量池中,每个常量对应不同内存地址,修改字面量就意味着指向不同内存地址。如果是使用new实例化的字符串对象呢?结果也一致,字符串是不可修改final类,修改后返回的也是一个新的对象,与以前对象没有任何关联。
4.2 引用类型对象
13
结局很刺激,在这里必须一举粉碎Java值传递还是引用传递的争论。在基本类型与常量上Java采用类似于值传递,因为其字面量改变会引发内存地址的修改。引用类型采用引用传递也就是内存地址传递,修改是相互影响的,如下图所示:
修改前后内存地址不变,那么也就意味着取用的数据都是来源于同一内存地址对象,自然相互影响。在这里强调一下,数组也是特殊的一种对象
4.3 clone克隆
完成深度克隆可以采用Object提供的clone(),满足下列两点要求即可:
- 实现Cloneable接口,空实现接口与Serializeable一致,标志接口
- 重写clone()方法,实现克隆逻辑
克隆对象的获取是通过new重新实例化对象创建,自然在堆内存中的地址是不一致的,也就是两个对象互不影响
4.4 多层引用
克隆后的书
朕的大清亡了么?你个骗子,不是说已经深度克隆了么,稍安勿躁,请看如下内存示意图:
最外层对象的引用地址确实是分开了互不影响,但是架不住里面的狗贼还是勾勾搭搭呀。想要一步到位的解决这些红杏出墙的骚操作,那只能再修改clone()
4.5 副本变化
使用==进行赋值的时候就是相当于在内存地址开辟一个副本,俩崽子都是指向同一位置,再来看如下问题:
1
3
副本的交换变化是不会影响到原对象内存地址变化,内存示意图如下所示:
4.6 克隆总结
说归说,笑归笑,知识学到位很重要。其实说白了很么克隆那都是假的,只是使用不同手段达到内存地址引用不一致就行了。当然不是我针对clone(),是在座所有的深度克隆方式都一个逼样
五:垃圾回收拯救
finalize()是一个普遍都熟悉的方法,GC的时候如果对象根据可达性算法分析不可达
后会自动调用该方法进行可达性重连接
。听起来很牛逼,但是不靠谱是必须的,具有以下几个特点:
- 不确定性:执行不确定性,因为需要在一个队列中等待执行,且执行线程优先级低于GC线程,如果垃圾回收到该对象还未执行方法重新创建连接则不再执行。结果不确定性,与上述理由相差无几,垃圾回收这个对象,但是对象执行finalize()方法未进行完创建新连接也必须被回收
- 一次性:该方法想在GC被自动调用只需要重写该方法即可,但是需要说明一点就是这个方法在整个对象生命周期内只能被执行一次
六:哈希值与打印
Object类提供的本地方法hashCode()可获取哈希值。针对哈希值强调一点:同一对象哈希值一定一致,哈希值一致不一定是同一对象
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
对象打印的时候都会自动调用toString()方法
,经常打印出来就是一串哈希值,因为Object类提供的toString()方法就是返回对象哈希值。当然因为String等类重写了toString()方法,所以返回的就是内容。如果需要打印自定义对象内容,则必须在对象所属类中重写toString()方法实现
七:线程沉睡与唤醒
个人认为这是Object最重要的一组方法wait()/notify()、notifyAll()。wait()让线程进入等待并释放锁资源,notify()唤醒任意一个对象上等待的线程,notifyAll()则是唤醒对象上所有等待的线程。但需要强调如下几点:
- 调用wait()/notify()、notifyAll()方法的对象都必须是
线程拥有对象的监视器的对象
,通俗点按照个人理解:必须是synchronized上锁对象
,不要用currentThread调用方法,这是经常犯错也是最骚的操作,如果线程不拥有对象监视器则会报错IllegalMonitorStateException
- notify()是
随机
唤醒的一个线程,注意随机
- wait()方法还需要注意的一点就是线程会释放锁资源,这也是和Thrad类的sleep()方法的区别
平时日常使用的wait()方法实现只是调用了wait(0),也就是说真正执行的方法也就是上述代码的第一个本地方法。时间参数设定代表超过时间范围后没有其它线程调用notify()/notifyAll()方法,该对象上线程也会自动重新准备就绪抢夺CPU资源