繼承
繼承概念
為什麼要用到繼承?當多個類別有相同的屬性與方法,可以把這些相同的屬性與方法抽出來做為父類別,子類別繼承父類別就擁有那些相同的屬性與方法,解決程式碼重覆。
以下A類別與B類別都有3個相同屬性與方法。
1
2
3
4
5
6
7
8
9
10
class A {
private String s1;
private String s2;
private String s3;
private String sa;
public void method1() {}
public void method2() {}
public void method3() {}
public void methodA() {}
}
1
2
3
4
5
6
7
8
9
10
class B {
private String s1;
private String s2;
private String s3;
private String sb;
public void method1() {}
public void method2() {}
public void method3() {}
public void methodB() {}
}
把相同屬性與方法抽出來變成父類別。
1
2
3
4
5
6
7
8
class Parent {
private String s1;
private String s2;
private String s3;
public void method1() {}
public void method2() {}
public void method3() {}
}
A類別與B類別繼承Parent,就可以跟之前一樣,各自擁有s1,s2,s3屬性與method1,method2,method3方法,A類別與B類別又各自保有跟其它類別不相同的屬性與方法,相同的抽取出來作為父類別,不同的作為子類別。
1
2
3
4
class A extends Parent{
private String sa;
public void methodA() {}
}
1
2
3
4
class B extends Parent{
private String sb;
public void methodB() {}
}
繼承關係階層圖
先把滑鼠移到子類別名。
按下ctrl + h
Memory Layout
使用Object Layout core,可以看出物件記憶體內部屬性。
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
import org.openjdk.jol.info.ClassLayout;
public class Test {
public static void main(String[] args) {
System.out.println(ClassLayout.parseInstance(new A()).toPrintable());
System.out.println(ClassLayout.parseInstance(new B()).toPrintable());
}
}
class Parent {
private String s1;
private String s2;
private String s3;
public void method1() {}
public void method2() {}
public void method3() {}
}
class A extends Parent{
private String sa;
public void methodA() {}
}
class B extends Parent{
private String sb;
public void methodB() {}
}
執行結果可以看出A物件與B物件裡面有3個屬性(s1,s2,s3)來自Parent類別,只有一個屬性是自己的。
inherit.A object internals: OFF SZ TYPE DESCRIPTION VALUE 0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0) 8 4 (object header: class) 0x01003410 12 4 java.lang.String Parent.s1 null 16 4 java.lang.String Parent.s2 null 20 4 java.lang.String Parent.s3 null 24 4 java.lang.String A.sa null 28 4 (object alignment gap) Instance size: 32 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total inherit.B object internals: OFF SZ TYPE DESCRIPTION VALUE 0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0) 8 4 (object header: class) 0x0108c460 12 4 java.lang.String Parent.s1 null 16 4 java.lang.String Parent.s2 null 20 4 java.lang.String Parent.s3 null 24 4 java.lang.String B.sb null 28 4 (object alignment gap) Instance size: 32 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
取得子類別所有方法
使用以下程式碼取得A類別所有方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
// 參數是「package.類別名」
getClzMethod(Class.forName("inherit.A"));
}
public static void getClzMethod(Class<?> clz) {
Class<?> clazz = clz;
System.out.println("Methods in class: " + clazz.getName());
for (Method method : clazz.getDeclaredMethods()) {
System.out.println(method);
}
System.out.println("------------------");
// parent
Class<?> superclass = clazz.getSuperclass();
System.out.println("Methods in Parent: " + clazz.getName());
for (Method method : superclass.getDeclaredMethods()) {
System.out.println(method);
}
}
}
Methods in class: inherit.A
public void inherit.A.methodA()
------------------
Methods in Parent: inherit.A
public void inherit.Parent.method1()
public void inherit.Parent.method2()
public void inherit.Parent.method3()
由以上執行結果,可以看出A類有methodA()。
其它method1()、method2()、method3(),都來自父類別。
呼叫誰的方法,就用誰的屬性
父類別有一個屬性i = 20
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Parent {
private String s1;
private String s2;
private String s3;
int i = 20; // 父類別屬性i
// 父類別方法
public void method1() {
// 印出父類別屬性i
System.out.println("i = " + i);
}
public void method2() {
}
public void method3() {
}
}
子類別i = 10,並且覆寫method1()。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class A extends Parent {
int i = 10; // 子類別屬性i
private String sa;
@Override
public void method1() {
// 印出子類別屬性i,如果子類別沒有屬性i,就用父類別的屬性i
System.out.println("i = " + i);
}
public void methodA() {
// 使用super.呼叫父類別的method()
super.method1();
}
}
請問執行子類別的methodA(),會印出什麼?
1
2
3
4
5
6
public class Test {
public static void main(String[] args){
A test = new A();
test.methodA();
}
}
i = 20
執行結果是印出父類別屬性i = 20,而不是子類別i = 10。
請問執行子類別覆寫的method1(),會印出什麼?
1
2
3
4
5
6
public class Test {
public static void main(String[] args){
A test = new A();
test.method1();
}
}
i = 10
執行結果為10。
由此可證,呼叫父類別的方法,使用的是父類別屬性,呼叫子類別的方法,使用的是子類別屬性,除非子類別沒這個屬性,就會從父類別找有沒有這個屬性。
覆寫
以上A類別有覆寫method1()方法,什麼是覆寫?就是父類別原本有的方法,子類別寫一模一樣的方法覆蓋過去。
1
2
3
4
@Override
public void method1() {
System.out.println("i = " + i);
}
使用之前取得子類別所有方法的程式碼。
執行結果如下:
Methods in class: inherit.A
public void inherit.A.method1()
public void inherit.A.methodA()
------------------
Methods in Parent: inherit.A
public void inherit.Parent.method1()
public void inherit.Parent.method2()
public void inherit.Parent.method3()
可以看出子類別有method1()與methodA()。
父類別也有method1(),那二邊都有method1(),要執行誰的?
根據等號=右邊是誰,就執行誰的方法,除非子類別沒有那個方法,才執行父類別的方法。
1
2
A test = new A();
test.method1(); // 執行A類別下的method11()
使用Debug工具,列出物件所有屬性
把斷點設在new 子類別()的下一行,滑鼠移到變數,本例是變數是test,會顯示物件所有屬性,包含父類別屬性,相同屬性,會用父類別名.屬性來區分。下圖中是用Parent.i,告知這是父類別的i屬性。
不用透過super取得父類別的屬性與方法
父類別的屬性與方法,子類別可以直接使用,不用透過super。
以下程式碼,子類別直接用n1,n2,method1(),沒有透過super使用父類別屬性方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 父類別
class Father {
int n1;
int n2;
private int n3;
public void method1() {}
private void method2() {}
}
// 子類別
class Child extends Father{
public void show() {
System.out.println(n1);
System.out.println(n2);
method1();
}
}
父類別與子類別都有一樣的方法或屬性,就會用super來指定是要用父類別的,還是子類別的,若沒有寫super,預設用子類別。
子類別無法存取父類別private屬性與方法。
父類別私有屬性或方法,透過父類別public方法取得
即便子類別繼承父類別,但是private屬性與方法沒辦法存取。
父類別提供public的方法,給子類呼叫。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Father {
int n1;
int n2;
private int n3;
public void method1() {}
private void method2() {}
// 取得私有屬性
public int getN3() {
return n3;
}
// 呼叫私有方法
public void callMethod2() {
method2();
}
}
子類別透過public方法,取得private屬性與呼叫private方法。
1
2
3
4
5
6
class Child extends Father{
public void show() {
System.out.println(getN3());
callMethod2();
}
}