概要
这一章的复习主要涵盖 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()
方法都必须在同步环境中使用
synchronized
synchronized(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 |
通过对象实例访问
- 相同包下 通过对象引用 访问拥有包权限以上的属性
public
protected
default
- 不同包下 只能访问
public
通过自身和继承的属性访问
- 当前类中 拥有全部的权限
public
protected
default
- 导出类中 和 通过对象实例 的访问基本一致,区别在于
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
对象绑定在一起?