前言
五一回家又断更了一个放假时间了~~~
只有光头才能变强
回顾前面:
ThreadLocal 就是这么简单 https://mp.weixin.qq.com/s?__biz=MzI4Njg5MDA5NA==&mid=2247484118&idx=1&sn=da3e4c4cfd0642687c5d7bcef543fe5b&chksm=ebd743d7dca0cac19a82c7b29b5b22c4b902e9e53bd785d066b625b4272af2a6598a0cc0f38e#rd
多线程三分钟就可以入个门了! https://mp.weixin.qq.com/s?__biz=MzI4Njg5MDA5NA==&mid=2247484186&idx=1&sn=2a7b937e6d3b1623aceac199d3e402f9&chksm=ebd7421bdca0cb0d6206db8c7f063c884c3f0b285975c8e896fde424660b4ccb88da1549f32c#rd
多线程基础必要知识点! 看了学习多线程事半功倍 https://mp.weixin.qq.com/s?__biz=MzI4Njg5MDA5NA==&mid=2247484194&idx=1&sn=ed1241fcba5d3e85b6d900d8667f04f6&chksm=ebd74223dca0cb35fe16a267c88ac9e5159825b27c278fb165a8c50d681e1340b73cfd69ae0d#rd
Java 锁机制了解一下 https://mp.weixin.qq.com/s?__biz=MzI4Njg5MDA5NA==&mid=2247484198&idx=1&sn=4d8e372165bb49987a6243f17153a9b4&chksm=ebd74227dca0cb31311886f835092c9360d08a9f0a249ece34d4b1e49a31c9ec773fa66c8acc#rd
AQS 简简单单过一遍 https://mp.weixin.qq.com/s?__biz=MzI4Njg5MDA5NA==&mid=2247484202&idx=1&sn=dbf9e94d2486ee0baa43e043a2363231&chksm=ebd7422bdca0cb3dc0451e09d139b72558b1cfa3593a6bcc1716ae9d1bd443804d194a303985#rd
Lock 锁子类了解一下 https://mp.weixin.qq.com/s?__biz=MzI4Njg5MDA5NA==&mid=2247484206&idx=1&sn=9722748c0308b3e56220be1c9d939ad7&chksm=ebd7422fdca0cb39ac7825e565ac4e7ed7fd77638da1a931f916d3b6c06ef50beb5c085510bf#rd
之前花了点之间时间去搞多线程的基础知识了, 难呀难呀难呀.... 打算还写一篇线程池的就暂时将多线程系列停止了...
今天中午在逛简书的时候发现一些大厂也会问 Object 对象里面有什么方法(也算是一个知识点吧),Object 我还没去认真复习过, 于是这篇主要看看 Object 对象有什么要注意的地方~
那么接下来就开始吧, 如果文章有错误的地方请大家多多包涵, 不吝在评论区指正哦~
一, Object 对象简介
声明: 本文都是使用 JDK1.8
我们学 Java 的知道, Java 是一门面向对象的语言. 无论在 Java 中出现什么, 都可以认为它是对象(除了八大基本数据类型. 当然了, 八大基本数据类型也能装箱成为对象):
而 Object 就是这些对象的最高级别的, 所有的 Java 对象都隐式地继承了 Object 对象(不用显示写 extends 继承)
所有的 Java 对象都拥有 Object 默认的方法.
那么我们看看 Object 有什么方法:
其实就可以归纳成几个:
registerNatives()[底层实现, 不研究]
- hashCode()
- equals(Object obj)
- clone()
- toString()
- notify()
- notifyAll()
wait(long timeout)[还有重载了两个]
finalize()
Object 一共有 11 个方法, 其中一个为底层的实现 registerNatives(), 其中两个 wait()和
wait(long timeout, int nanos)
重载方法.
所以我们真正需要看的就是 8 个方法
还有一个属性:
public final native Class<?> getClass();
二, equals 和 hashCode 方法
equals 和 hashCode 方法可以说是面试的重点题了, 配合着 String 可以说在面试题中哪都有它们的存在.
首先, 我们来看看 equals 和 hashCode 在 Object 中原生的实现吧:
- hashCode:
- public native int hashCode();
- equals:
- public boolean equals(Object obj) {
- return (this == obj);
- }
看上去都非常简单:
hashCode()由 native 方法底层实现了.
equals()就直接 == 判断是否相等了.
想要更加清晰它们究竟是做什么的, 我们来读读它的注释:
根据注释我们可以总结以下的要点:
重写 equals()方法, 就必须重写 hashCode()的方法
equals()方法默认是比较对象的地址, 使用的是 == 等值运算符
hashCode()方法对底层是散列表的对象有提升性能的功能
同一个对象 (如果该对象没有被修改): 那么重复调用 hashCode() 那么返回的 int 是相同的!
hashCode()方法默认是由对象的地址转换而来的
equals()方法还有 5 个默认的原则:
自反性 --->调用 equals()返回的是 true, 无论这两个对象谁调用 equals()都好, 返回的都是 true
一致性 --->只要对象没有被修改, 那么多次调用还是返回对应的结果!
传递性 --->x.equals(y)和 y.equals(z)都返回 true, 那么可以得出: x.equals(z)返回 true
对称性 --->x.equals(y)和 y.equals(x)结果应该是相等的.
传入的参数为 null, 返回的是 false
为啥说 hashCode()以散列表为底层带来性能的提升是很容易理解的. 我们再来回顾一下 HashMap 的插入:
如果 hash 值都不相等, 那么可以直接判断该 key 是不相等的了!
2.1equals 和 hashCode 方法重写
equals()方法默认是比较对象的地址, 使用的是 == 等值运算符. 但是按我们正常开发来说, 比较的是对象地址是没有意义的.
一般地, 如果我们有两个 Address 对象, 只要这两个对象的省号, 城市号, 街道号相等, 我们就认为这两个对象相等了!
2.2String 实现的 equals 和 hashCode 方法
我们在初学的时候可能就听过了: String 已经实现了 equals 和 hashCode 方法了.
这也就是为什么, 我们可以直接使用 String.equals()来判断两个字符串是否相等!
下面我们就来看看它的实现吧:
三, toString 方法
接下来我们看看 toString 方法, 也十分简单:
toString 方法主要是用来标识该对象的:
从上面的结果我们都可以看出来: 得出的结果我们并不能看到什么东西~
于是我们一般都重写 toString(), 那么打印出的结果就很方便我们调试了!
- @Override
- public String toString() {
- return "Address{" +
- "provinceNo=" + provinceNo +
- ", cityNo=" + cityNo +
- ", streetNo=" + streetNo +
- '}';
- }
下面的结果看起来就好多了:
四, clone 方法
我们也来看看它的顶部注释:
看了上面的注释我们可以总结以下的要点:
clone 方法用于对象的克隆, 一般想要克隆出的对象是独立的(与原有的对象是分开的)
深拷贝指的是该对象的成员变量 (如果是可变引用) 都应该克隆一份, 浅拷贝指的是成员变量没有被克隆一份
下面我们来看一下浅拷贝: 拷贝了 Employee 对象, 但是其成员变量 hireday 没有被克隆出去, 所以指向的还是同一个 Date 对象!
4.1clone 用法
那么我们如何克隆对象呢? 无论是浅拷贝还是深拷贝都是这两步:
克隆的对象要实现 Cloneable 接口
重写 clone 方法, 最好修饰成 public
浅拷贝: 仅仅拷贝了 Person 对象, 而 date 没有拷贝!
- public class Person implements Cloneable {
- // 可变的成员变量
- private Date date;
- @Override
- public Object clone() throws CloneNotSupportedException {
- return super.clone();
- }
- }
深拷贝: 不仅拷贝了 Person 对象, 也拷贝了 date 成员变量
- public class Person implements Cloneable {
- // 可变的成员变量
- public Date date;
- @Override
- public Object clone() throws CloneNotSupportedException {
- // 拷贝 Person 对象
- Person person = (Person) super.clone();
- // 将可变的成员变量也拷贝
- person.date = (Date) date.clone();
- // 返回拷贝的对象
- return person;
- }
- }
4.2clone 疑问进一步学习 protected
不知道有没有人跟我有相同的疑问:
我只想要浅拷贝, 能不能直接调用该对象. clone()来实现?
比如我现在有个 Address 对象:
- public class Address {
- private int provinceNo;
- private int cityNo;
- private int streetNo;
- public Address() {
- }
- public Address(int provinceNo, int cityNo, int streetNo) {
- this.provinceNo = provinceNo;
- this.cityNo = cityNo;
- this.streetNo = streetNo;
- }
- }
下面的代码你们认为如何?
- Address address = new Address(1, 2, 3);
- address.clone();
我们都知道:
protected 修饰的类和属性, 对于自己, 本包和其子类可见
可能会想: clone()方法是定义在 Object 类上的 (以 protected 来修饰), 而我们自定义的 Address 对象隐式继承着 Object(所有的对象都是 Object 的子类), 那么子类调用 Object 以 protected 来修饰 clone() 是完全没问题的
但是, IDE 现实告诉我, 这编译就不通过了!
出现错误的原因我立马就想到: 是不是我对 protected 修饰符出现了偏差?
protected 修饰的类和属性, 对于自己, 本包和其子类可见, 这句话本身是没有错的. 但是还需要补充: 对于 protected 的成员或方法, 要分子类和超类是否在同一个包中. 与基类不在同一个包中的子类, 只能访问自身从基类继承而来的受保护成员, 而不能访问基类实例本身的受保护成员.
上面的代码就错在: Address 与 Object 不是在同一个包下的, 而 Address 直接访问了 Object 的 clone 方法. 这是不行的.
下面我截两张图再来给你们看看(看完图再看上面的描述, 就能理解了):
图片来源和更多的展开阅读: https://blog.csdn.net/wangyanguiyiyang/article/details/49800493
五, wait 和 notify 方法
wait 和 notify 方法其实就是 Java 给我们提供让线程之间通信的 API.
按照惯例我们还是来看注释怎么说吧:
wait 方法:
notify 方法:
notifyAll()方法:
看完上面的注释我们可以总结以下的要点:
无论是 wait,notify 还是 notifyAll()都需要由监听器对象 (锁对象) 来进行调用
简单来说: 他们都是在同步代码块中调用的, 否则会抛出异常!
notify()唤醒的是在等待队列的某个线程 (不确定会唤醒哪个),notifyAll() 唤醒的是等待队列所有线程
导致 wait()的线程被唤醒可以有 4 种情况
该线程被中断
wait()时间到了
被 notify()唤醒
被 notifyAll()唤醒
调用 wait()的线程会释放掉锁
其实总结完上面的并不会有比较深刻的印象, 可以尝试着回答几个问题来加深对 wait()和 notify()的理解.
5.1 为什么 wait 和 notify 在 Object 方法上?
从一开始我们就说了: wait()和 notify()是 Java 给我们提供线程之间通信的 API, 既然是线程的东西, 那什么是在 Object 类上定义, 而不是在 Thread 类上定义呢?
因为我们的锁是对象锁[要是忘记的同学可回顾: Java 锁机制了解一下 https://mp.weixin.qq.com/s?__biz=MzI4Njg5MDA5NA==&mid=2247484198&idx=1&sn=4d8e372165bb49987a6243f17153a9b4&chksm=ebd74227dca0cb31311886f835092c9360d08a9f0a249ece34d4b1e49a31c9ec773fa66c8acc#rd ] , 每个对象都可以成为锁. 让当前线程等待某个对象的锁, 当然应该通过这个对象来操作了.
锁对象是任意的, 所以这些方法必须定义在 Object 类中
5.2notify 方法调用后, 会发生什么?
上面已经说了, notify 会唤醒某个处于等待队列的线程.
但是要注意的是:
notify 方法调用后, 被唤醒的线程不会立马获得到锁对象. 而是等待 notify 的 synchronized 代码块执行完之后才会获得锁对象
5.3sleep 和 wait 有什么区别?
Thread.sleep()与 Object.wait()二者都可以暂停当前线程, 释放 CPU 控制权.
主要的区别在于 Object.wait()在释放 CPU 同时, 释放了对象锁的控制.
而 Thread.sleep()没有对锁释放
参考资料:
- https://blog.csdn.net/lingzhm/article/details/44940823
- http://www.cnblogs.com/dolphin0520/p/3920385.html
- https://www.cnblogs.com/eer123/p/7880789.html
- https://www.jianshu.com/p/f4454164c017
六, finalize()方法
finalize()方法将在垃圾回收器清除对象之前调用, 但该方法不知道何时调用, 具有不定性
一般我们都不会重写它~
一个对象的 finalize()方法只会被调用一次, 而且 finalize()被调用不意味着 gc 会立即回收该对象, 所以有可能调用 finalize()后, 该对象又不需要被回收了, 然后到了真正要被回收的时候, 因为前面调用过一次, 所以不会调用 finalize(), 产生问题.
参考资料:
https://segmentfault.com/q/1010000000094660
进阶的资料:
- https://www.cnblogs.com/Smina/p/7189427.html
- http://www.importnew.com/23913.html
- https://zhuanlan.zhihu.com/p/29522201
- https://zhuanlan.zhihu.com/p/25698745
七, 总结
总的来说也算是把 Object 看了一遍了, 不至于一下子把它的方法给忘了~~~ 在学习的过程中也遇到过问题, 最明显的是对 protected 修饰符又加深了一次理解.
参考资料:
Java 核心技术卷一
如果文章有错的地方欢迎指正, 大家互相交流. 习惯在微信看技术文章, 想要获取更多的 Java 资源的同学, 可以关注微信公众号: Java3y. 为了大家方便, 刚新建了一下 qq 群: 742919422, 大家也可以去交流交流. 谢谢支持了! 希望能多介绍给其他有需要的朋友
文章的目录导航:
https://zhongfucheng.bitcron.com/post/shou-ji/wen-zhang-dao-hang
来源: https://www.cnblogs.com/Java3y/p/8985368.html