概要
这一章的复习主要涵盖 Object 的源码解析,继承的性质,继承初始化。这里整理的都是个人觉得比较重要重新认真分析的东西,不一定全部为基础,如果有不准确的地方,还望大佬从评论区指出,感激不尽。
文章中的所有的自定义的测试都在自己的 java-review 仓库中,本文对应的链接:
根类 Object
java中采用的是单继承,其中java.lang.Object是所有类的根类,包括数组- 自定义一个类的时候,其实已经继承
Object这个类,拥有Object类内部的默认权限以上的所有方法
registerNatives()
1 | // java 12 source |
java底层由c++实现,registerNatives()的作用就是将java类中方法注册为native方法,是java类方法 和 底层方法 建立映射
1 | // openjdk 6 |
getClass()
1 | // java 12 source |
Object # getClass方法使用了final进行锁定,不能够被子类重写@Override- 返回这个类对应的
Class对象实例的引用,类的所有的实例返回的都是同一个Class对象
1 | // test |
hashCode()
1 | // java 12 source |
Object # hashCode同样是的native方法,用来获取对象的Hash值,可以进行重写
equals(Object obj)
1 | // java 12 source |
Object # equals 方法的作用是判断两个对象是否相等,正确的 equals() 和 hashCode() 的关系应该为
- 两个不同的对象的
hashCode()的值相同,但equals()两个对象必须不同 - 两个对象的
hashCode()不同equals()比较两个对象
因此,像 Hash 容器就先进行比较两个对象的 Hash 值,如果相同,再通过 equals() 来比较两个对象
正确的 equals()方法必须满足下列 5 个条件
- 自反性。对任意
x,x.equals(x)一定返回true - 对称性。对任意
x和y,如果y.equals(x)返回true,则x.equals(y)也返回true. - 传递性。对任意
x、y、z,如果有x.equals(y)返回ture,y.equals(z)返回true,则x.equals(z)一定返回true - 一致性。对任意
x和y,如果对象中用于等价比较的信息没有改变,那么无论调用x.equals(y)多少次,返回的结果应该保持一致,要么一直是true,要么一直是false. - 对任何不是
null的,x.equals(null)一定返回false.
clone()
1 | // java 12 source |
Object # clone方法用来返回一个当前的拷贝,是一个protect native方法,所以Object对象实例以及没有重写Object # clone的类,都是没有权限直接调用clone()方法的
测试直接调用 Object # clone()
1 | // test |
Object # clone 内部并没有完整实现,需要子类重写这个方法才能进行使用,直接调用会抛出 java.lang.CloneNotSupportedException 异常
toString()
1 | // java 12 source |
Object # toString()默认返回ClassName+@+十六进制的 Hash 值
在 String 环境中的对象实例,会直接调用调用对象实例的 toString() 方法拼接字符串,这一部分在前面写的 字符串 一文中有详细的介绍
wait()
1 | // java 12 source |
Object # wait会释放掉当前线程持有的锁所以synchronized方法在wait状态是可以为被其他线程调用的,而sleep()和yield()都不会释放锁,详细的部分会放到线程一部分里面来讲 再留一个坑Object # wait方法的作用是让调用当前对象方法的线程进入等待状态,都为final不能被修改wait(long timeoutMillis)方法wait(long timeoutMillis, int nanos)方法指定超时时间为timeoutMillis毫秒 +nanos毫微秒(0 <= nanos <= 999999)
以 毫微秒 为单位
$$ TimeOut = 1000000 * timeoutMillis + nanos $$
notify()
1 | // java 12 source |
Object # notify方法的作用就是将通过调用obj.wait()进入wait状态的线程唤醒notify()只会随机唤醒一个线程notifyAll()会唤醒在这个对象上的等待线程中的一个
测试不在同步环境中中使用 wait() 和 notify()
1 | // test |
不管是 notify() 还是 wait() 方法都必须在同步环境中使用
synchronizedsynchronized(obj) {}
否则就会抛出 java.lang.IllegalMonitorStateException 异常
测试同步环境中使用 wait() 和 notify()
1 | // test |
obj # wait方法让主线程进入wait状态,- 主线程进入
wait状态前,new一个新的线程并启动,**5**秒后调用obj # notify方法,唤醒在obj对象上等待的主线程
finalize() [已废弃]
1 | // java 12 source |
Object # finalize方法在方法要被回收的时候被执行
这里使用了 WeakReference 弱引用来进行测试
1 | // test |
WeakReference 弱引用引用的类型如果没有其他指向了该应用的强引用,进行垃圾回收的时候该对象就会被回收,此时就会回调对象的 finalize() 方法
权限控制
子类会继承父类的属性和方法,子类是否可见 取决于父类的权限设置
| Permission | class | package | child class | any class |
|---|---|---|---|---|
public |
+ | + | + | + |
default |
+ | + | + | |
protected |
+ | + | ||
private |
+ |
如果子类的方法重写 @Override 了父类的方法,那么 子类中该方法的访问级别 不允许低于父类的访问级别
这是为了确保可以使用父类实例的地方都可以使用子类实例,也就是确保满足 里氏替换原则
权限控制测试
层次结构
1 | permission |
ps: 使用了 //! 注释掉的为保存的语句,也就是没有权限的测试
1 | // java 12 source |
通过对象实例访问
- 相同包下 通过对象引用 访问拥有包权限以上的属性
publicprotecteddefault
- 不同包下 只能访问
public
通过自身和继承的属性访问
- 当前类中 拥有全部的权限
publicprotecteddefault
- 导出类中 和 通过对象实例 的访问基本一致,区别在于
protected继承权限修饰符 (即子类的内部)- 子类中可以直接通过
super.关键字来访问父类的继承权限的成员和方法,并 不是通过对象实例引用来访问!
- 子类中可以直接通过
继承类的初始化
这一部分在 {post_link 初始化 } 一文中有详细的解释,这里简单说明一下类初始化顺序
- 按照从 父类-> 子类,按照声明顺序从上-> 下,初始化父类和子类的中的 静态 成员/域
- 按照从 父类-> 子类,按照声明顺序从上-> 下,初始化父类和子类的中的 非静态 成员/域
- 按照从 父类-> 子类,调用构造器完成舒适化
初始化的位置
- 声明的位置
- 构造器中
- 调用之前(惰性初始化)
- 使用实例初始化
方法和成员变量的继承
1 | // test |
为了区分和容易记住,将
- 父类的
value置为0 - 子类的
value置为1
方法的覆盖(重写)
方法覆盖的规范
- 权限控制。 当子类没有权限继承到父类的方法的时候,子类的同名方法就相当于另外增加了一个方法
private父类方法私有default默认权限且不在同一个包内时,子类对其不可见
final。 使用了final修饰的方法是不能被子类重写的异常。 子类重写方法的抛出异常类型 必须是父类抛出的异常类型的子类
协变返回类型。 子类重写的方法返回类型,可以是 父类方法返回类型的子类
重写方法的调用
首先让 父类引用 指向一个 子类实例对象,测试的两种情况
- 直接通过这个父类引用去调用重写的方法
- 将引用向下转型指子类型的引用,再去调用重写方法
ps: 在子类的方法中通过 super 又调用了父类的方法
1 | // test |
不管是父类引用,还是子类引用,通过引用去调用被重写的这个方法,被调用的 总是子类重写的方法
成员变量的
成员变量不同于方法的覆盖重写,后面的测试中可以大体看的出来,被
继承的成员变量调用
首先让 父类引用 指向一个 子类实例对象,测试的三种情况
- 类内部调用成员变量(因为上面已经总结过重写的方法默认调用的为子类方法,这里就不分开测了)
value不加任何修饰this.value通过this调用super.value通过super调用父类的成员变量super.printValue()调用父类的printValue方法,打印父类的上面的两种调用的结果
- 通过父类引用调用同名的成员变量
- 通过向下转型为子类引用,调用同名的成员变量
1 | // test |
从上面的结果可以看出,通过引用不同的调用 父类和子类的重名的成员变量 和 重写的方法 的效果是不同的
- 父类类型的引用调用的是 父类的成员变量
- 子类类型的引用调用的是 子类的成员变量
另外 static 属性也是如此,大概是和类的 Class 对象绑定在一起?