[java复习] 继承

概要

这一章的复习主要涵盖 Object 的源码解析,继承的性质,继承初始化。这里整理的都是个人觉得比较重要重新认真分析的东西,不一定全部为基础,如果有不准确的地方,还望大佬从评论区指出,感激不尽。

文章中的所有的自定义的测试都在自己的 java-review 仓库中,本文对应的链接:

根类 Object

  • java 中采用的是单继承,其中 java.lang.Object 是所有类的根类,包括数组
  • 自定义一个类的时候,其实已经继承 Object 这个类,拥有 Object 类内部的默认权限以上的所有方法

registerNatives()

1
2
3
4
5
6
7
8
// java 12 source

private static native void registerNatives();

static {
registerNatives();
}
}
  • java 底层由 c++ 实现,registerNatives() 的作用就是将 java 类中方法注册为 native 方法,是 java 类方法 和 底层方法 建立映射
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// openjdk 6

static JNINativeMethod methods[] = {
{"hashCode", "()I", (void *)&JVM_IHashCode},
{"wait", "(J)V", (void *)&JVM_MonitorWait},
{"notify", "()V", (void *)&JVM_MonitorNotify},
{"notifyAll", "()V", (void *)&JVM_MonitorNotifyAll},
{"clone", "()Ljava/lang/Object;", (void *)&JVM_Clone},
};

JNIEXPORT void JNICALL
Java_java_lang_Object_registerNatives(JNIEnv *env, jclass cls)
{
(*env)->RegisterNatives(env, cls,
methods, sizeof(methods)/sizeof(methods[0]));
}

getClass()

1
2
3
4
// java 12 source

/* Return the runtime class of this Object */
public final native Class<?> getClass();
  • Object # getClass 方法使用了 final 进行锁定,不能够被子类重写 @Override
  • 返回这个类对应的 Class 对象实例的引用,类的所有的实例返回的都是同一个 Class 对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// test

private static void classOfObject() {
Object obj1 = new Object();
Object obj2 = new Object();
Class clazz1 = obj1.getClass();
Class clazz2 = obj2.getClass();

System.out.println("obj1 == obj2: " + (obj1 == obj2));
System.out.println("lazz1 == clazz2: " + (clazz1 == clazz2));
System.out.println("clazz1.equals(clazz2): " + clazz1.equals(clazz2));

/* Output:

obj1 == obj2: false
lazz1 == clazz2: true
clazz1.equals(clazz2): true
*///:~
}

hashCode()

1
2
3
4
// java 12 source

/* Return a hash value for the Object. */
public native int hashCode();
  • Object # hashCode 同样是的 native 方法,用来获取对象的 Hash 值,可以进行重写

equals(Object obj)

1
2
3
4
5
6
// java 12 source

/* Indicates whether some other object is "equal to" this one. */
public boolean equals(Object obj) {
return (this == obj);
}

Object # equals 方法的作用是判断两个对象是否相等,正确的 equals() 和 hashCode() 的关系应该为

  • 两个不同的对象的 hashCode() 的值相同,但 equals() 两个对象必须不同
  • 两个对象的 hashCode() 不同 equals() 比较两个对象

因此,像 Hash 容器就先进行比较两个对象的 Hash 值,如果相同,再通过 equals() 来比较两个对象

正确的 equals()方法必须满足下列 5 个条件

  1. 自反性。对任意 x,x.equals(x) 一定返回 true
  2. 对称性。对任意 x 和 y,如果 y.equals(x) 返回 true,则 x.equals(y) 也返回 true.
  3. 传递性。对任意 x、y、z,如果有 x.equals(y) 返回 ture,y.equals(z) 返回true,则 x.equals(z) 一定返回 true
  4. 一致性。对任意 x 和 y,如果对象中用于等价比较的信息没有改变,那么无论调用 x.equals(y) 多少次,返回的结果应该保持一致,要么一直是 true,要么一直是 false.
  5. 对任何不是 null 的,x.equals(null) 一定返回 false.

clone()

1
2
3
4
// java 12 source

/* Creates and returns a copy of this object. */
protected native Object clone() throws CloneNotSupportedException;
  • Object # clone 方法用来返回一个当前的拷贝,是一个 protect native 方法,所以 Object 对象实例以及没有重写 Object # clone 的类,都是没有权限直接调用 clone() 方法的

测试直接调用 Object # clone()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// test

private static void SuperCloneTest() {
SuperClone obj = new SuperClone();
try {
System.out.println(obj == obj.clone());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}

/*

java.lang.CloneNotSupportedException: com.example.review._extends._Object$SuperClone
at java.base/java.lang.Object.clone(Native Method)
at com.example.review._extends._Object$SuperClone.clone(_Object.java:93)
at com.example.review._extends._Object.SuperCloneTest(_Object.java:65)
at com.example.review._extends._Object.main(_Object.java:87)
*///:~
}

Object # clone 内部并没有完整实现,需要子类重写这个方法才能进行使用,直接调用会抛出 java.lang.CloneNotSupportedException 异常

toString()

1
2
3
4
5
6
// java 12 source

/* Returns a string representation of the object. */
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
  • Object # toString() 默认返回 ClassName + @ + 十六进制的 Hash 值

在 String 环境中的对象实例,会直接调用调用对象实例的 toString() 方法拼接字符串,这一部分在前面写的 字符串 一文中有详细的介绍

wait()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// java 12 source

/* Causes the current thread to wait until it is awakened, typically by being notified or interrupted */
public final void wait() throws InterruptedException {
wait(0L);
}

/* Causes the current thread to wait until it is awakened, typically
* by being notified or interrupted, or until a
* certain amount of real time has elapsed. */
public final native void wait(long timeoutMillis) throws InterruptedException;

/* Causes the current thread to wait until it is awakened, typically
* by being notified or interrupted or until a
* certain amount of real time has elapsed. */
public final void wait(long timeoutMillis, int nanos) throws InterruptedException {
if (timeoutMillis < 0) {
throw new IllegalArgumentException("timeoutMillis value is negative");
}

if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}

if (nanos > 0 && timeoutMillis < Long.MAX_VALUE) {
timeoutMillis++;
}

wait(timeoutMillis);
}
  1. Object # wait 会释放掉当前线程持有的锁所以 synchronized 方法在 wait 状态是可以为被其他线程调用的,而 sleep() 和 yield() 都不会释放锁,详细的部分会放到线程一部分里面来讲 再留一个坑

  2. Object # wait 方法的作用是让调用当前对象方法的线程进入等待状态,都为 final 不能被修改

    • wait(long timeoutMillis) 方法
    • wait(long timeoutMillis, int nanos) 方法指定超时时间为 timeoutMillis 毫秒 + nanos 毫微秒 (0 <= nanos <= 999999)

以 毫微秒 为单位
$$ TimeOut = 1000000 * timeoutMillis + nanos $$

notify()

1
2
3
4
5
6
7
// java 12 source

/* Wakes up a single thread that is waiting on this object's monitor. */
public final native void notify();

/* Wakes up all threads that are waiting on this object's monitor. */
public final native void notifyAll();
  • Object # notify 方法的作用就是将通过调用 obj.wait() 进入 wait 状态的线程唤醒
    • notify() 只会随机唤醒一个线程
    • notifyAll() 会唤醒在这个对象上的等待线程中的一个

测试不在同步环境中中使用 wait() 和 notify()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// test

private static void notifyTest() {
_Object obj = new _Object();
log("notify test", '-');
obj.notify();
}

/*
测试 wait() 方法不在同步中使用
wait() 方法必须在 synchronized 环境中使用,synchronized 关键字 或者 synchronized(obj) 同步块

*/
private static void waitTest() {
_Object obj = new _Object();
log("wait test", '-');
try {
obj.wait(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log("wait() end...");

/*

---------- wait test ----------
Exception in thread "main" java.lang.IllegalMonitorStateException
at java.base/java.lang.Object.wait(Native Method)
at com.example.review._extends._Object.waitTest(_Object.java:87)
at com.example.review._extends._Object.waitAndNotifyTest(_Object.java:148)
at com.example.review._extends._Object.main(_Object.java:159)
*///:~
}

不管是 notify() 还是 wait() 方法都必须在同步环境中使用

  • synchronized
  • synchronized(obj) {}

否则就会抛出 java.lang.IllegalMonitorStateException 异常

测试同步环境中使用 wait() 和 notify()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// test

private static void waitSyncTest() {
_Object obj = new _Object();
log("wait Sync test", '>', 1);
new Thread() {
@Override
public void run() {
try {
for (int i = 1; i <= 5; i++) {
sleep(1000);
log("sleep " + i + " s");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
log("another thread -----> notify obj thread", -1);
synchronized (obj) {
obj.notify();
}
}
}.start();

synchronized (obj) {
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log("quit wait status", '<', -1);

/*

>>>>>>>>>> wait Sync test >>>>>>>>>>

sleep 1 s
sleep 2 s
sleep 3 s
sleep 4 s
sleep 5 s

another thread -----> notify obj thread
*///:~
}
  1. obj # wait 方法让主线程进入 wait 状态,
  2. 主线程进入 wait状态前,new 一个新的线程并启动,**5** 秒后调用 obj # notify 方法,唤醒在 obj 对象上等待的主线程

finalize() [已废弃]

1
2
3
4
5
// java 12 source

/* @deprecated The finalization mechanism is inherently problematic */
@Deprecated(since="9")
protected void finalize() throws Throwable { }
  • Object # finalize 方法在方法要被回收的时候被执行

这里使用了 WeakReference 弱引用来进行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// test

private static void finalizeTest() {
FinalizeTest finalizeTest = new FinalizeTest();
WeakReference<FinalizeTest> weaks = new WeakReference<>(finalizeTest);
System.out.println("---------- ref is not null ---------- ");
System.gc();
System.out.println("---------- ref is null ---------- ");
finalizeTest = null;
System.gc();

/* Output:

---------- ref is not null ----------
---------- ref is null ----------
FinalizeTest, hash = 34801522
*///:~
}

WeakReference 弱引用引用的类型如果没有其他指向了该应用的强引用,进行垃圾回收的时候该对象就会被回收,此时就会回调对象的 finalize() 方法

权限控制

子类会继承父类的属性和方法,子类是否可见 取决于父类的权限设置

Permission class package child class any class
public + + + +
default + + +
protected + +
private +

如果子类的方法重写 @Override 了父类的方法,那么 子类中该方法的访问级别 不允许低于父类的访问级别

这是为了确保可以使用父类实例的地方都可以使用子类实例,也就是确保满足 里氏替换原则

权限控制测试

层次结构

1
2
3
4
5
6
7
8
9
permission
│
├─ pack1
│ CurPackTest.java
│ Permission.java
│
└─ pack2
ChildPerm.java
OtherPackTest.java

ps: 使用了 //! 注释掉的为保存的语句,也就是没有权限的测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// java 12 source

/* 1. class 同一个类中测试 */
package permission.pack1;

public class Permission {

public int pubVal;
int defVal;
protected int proVal;
private int priVal;

private void test() {
this.pubVal = 1;
this.proVal = 1;
this.defVal = 1;
this.priVal = 1;
}

}


/* 2. package 同一个包中的测试 */
package permission.pack1;

public class CurPackTest {

public void test() {
Permission permission = new Permission();
permission.pubVal = 1;
permission.defVal = 1;
permission.proVal = 1;
//! permission.priVal = 1; // private param can be used
}

}

/* 3. child 子类中的测试 */
package permission.pack2;

public class ChildPerm extends Permission {

public void setParentParam(int i) {
super.pubVal = i;
//! super.defVal = i; // child class cannot use default param
//! super.priVal = i; // child class cannot use private param
super.proVal = i;
}

// static 静态方法(例如 main) 只能通过对象实例的引用来访问
public static void staticMethodTest() {
Permission permission = new ChildPerm();
permission.pubVal = 1;
//! permission.proVal = 1; // protected cannot be used
//! permission.defVal = 1; // default cannot be used
//! permission.priVal = 1; // private cannot be used
}

}


/* 4. any class 不同包任意类中的测试 */
package permission.pack2;

public class OtherPackTest {

public void test(){
Permission permission = new Permission();
permission.pubVal = 1;
//! permission.defVal = 1; // default cannot be used
//! permission.proVal = 1; // protected cannot be used
//! permission.priVal = 1; // private cannot be used
}

}

通过对象实例访问

  1. 相同包下 通过对象引用 访问拥有包权限以上的属性
    • public
    • protected
    • default
  1. 不同包下 只能访问
    • public

通过自身和继承的属性访问

  1. 当前类中 拥有全部的权限
    • public
    • protected
    • default
  1. 导出类中 和 通过对象实例 的访问基本一致,区别在于 protected 继承权限修饰符 (即子类的内部)
    • 子类中可以直接通过 super. 关键字来访问父类的继承权限的成员和方法,并 不是通过对象实例引用来访问!

继承类的初始化

这一部分在 {post_link 初始化 } 一文中有详细的解释,这里简单说明一下类初始化顺序

  1. 按照从 父类-> 子类,按照声明顺序从上-> 下,初始化父类和子类的中的 静态 成员/域
  2. 按照从 父类-> 子类,按照声明顺序从上-> 下,初始化父类和子类的中的 非静态 成员/域
  3. 按照从 父类-> 子类,调用构造器完成舒适化

初始化的位置

  1. 声明的位置
  2. 构造器中
  3. 调用之前(惰性初始化)
  4. 使用实例初始化

方法和成员变量的继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// test

/* 测试类 - 父类 */
public class SuperClass {

public int value = 0;

void printValues() {
System.out.println("super class printValues:");
System.out.println(" value: " + value);
System.out.println("this.value: " + this.value);
}

void method() {
System.out.println("super method()");
}

public void perm() {
System.out.println("super perm()");
}

}

/* 测试类 - 子类 */
public class SubClass extends SuperClass {

protected int value = 1; // 继承类中的成员变量没有权限要求

void printValues() {
System.out.println("sub class printValues:");
System.out.println(" value: " + value);
System.out.println(" this.value: " + this.value);
System.out.println("super.value: " + super.value);
System.out.println();

System.out.println("sub ----------> super printValues:");
super.printValues();
}

void method() {
System.out.println("sub method()");
System.out.println();

System.out.println("sub ----------> super method:");
super.method();
}

//! protected void perm() {} // 重写方法的权限不能低于被重写的方法的权限

}

为了区分和容易记住,将

  • 父类的 value 置为 0
  • 子类的 value 置为 1

方法的覆盖(重写)

方法覆盖的规范

  1. 权限控制。 当子类没有权限继承到父类的方法的时候,子类的同名方法就相当于另外增加了一个方法
    • private 父类方法私有
    • default 默认权限且不在同一个包内时,子类对其不可见
  1. final。 使用了 final 修饰的方法是不能被子类重写的

  2. 异常。 子类重写方法的抛出异常类型 必须是父类抛出的异常类型的子类

  3. 协变返回类型。 子类重写的方法返回类型,可以是 父类方法返回类型的子类

重写方法的调用

首先让 父类引用 指向一个 子类实例对象,测试的两种情况

  1. 直接通过这个父类引用去调用重写的方法
  2. 将引用向下转型指子类型的引用,再去调用重写方法

ps: 在子类的方法中通过 super 又调用了父类的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// test

private static void extendsSuperAndSubMethod() {
SuperClass superClass = new SubClass();
System.out.println("------------ superClass.method() ------------");
superClass.method();
System.out.println();

System.out.println("------------ ((SubClass)superClass).method() ------------");
((SubClass)superClass).method();

/* Output:

------------ superClass.method() ------------
sub method()

sub ----------> super method:
super method()

------------ ((SubClass)superClass).method() ------------
sub method()

sub ----------> super method:
super method()
*///:~
}

不管是父类引用,还是子类引用,通过引用去调用被重写的这个方法,被调用的 总是子类重写的方法

成员变量的

成员变量不同于方法的覆盖重写,后面的测试中可以大体看的出来,被

继承的成员变量调用

首先让 父类引用 指向一个 子类实例对象,测试的三种情况

  1. 类内部调用成员变量(因为上面已经总结过重写的方法默认调用的为子类方法,这里就不分开测了)
    • value 不加任何修饰
    • this.value 通过 this 调用
    • super.value 通过 super 调用父类的成员变量
    • super.printValue() 调用父类的 printValue 方法,打印父类的上面的两种调用的结果
  1. 通过父类引用调用同名的成员变量
  2. 通过向下转型为子类引用,调用同名的成员变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// test

private static void extendsSuperAndSubMember() {
SuperClass superClass = new SubClass();
System.out.println("------------ method call remember ------------");
superClass.printValues();

System.out.println();
System.out.println("------------ instance call remember ------------");
System.out.println(" superClass.value: " + superClass.value);
System.out.println("((SubClass) superClass).value: " +((SubClass) superClass).value);

/* Output:

------------ method call remember ------------
sub class printValues:
value: 1
this.value: 1
super.value: 0

sub ----------> super printValues:
super class printValues:
value: 0
this.value: 0

------------ instance call remember ------------
superClass.value: 0
((SubClass) superClass).value: 1
*///:~
}

从上面的结果可以看出,通过引用不同的调用 父类和子类的重名的成员变量 和 重写的方法 的效果是不同的

  • 父类类型的引用调用的是 父类的成员变量
  • 子类类型的引用调用的是 子类的成员变量

另外 static 属性也是如此,大概是和类的 Class 对象绑定在一起?