例子
- base類
public class Base {
public static int s;
private int a;
static {
System.out.println("基類靜態(tài)代碼塊拉背, s: "+s);
s = 1;
}
{
System.out.println("基類實例代碼塊, a: "+a);
a = 1;
}
public Base(){
System.out.println("基類構(gòu)造方法, a: "+a);
a = 2;
}
protected void step(){
System.out.println("base s: " + s +", a: "+a);
}
public void action(){
System.out.println("start");
step();
System.out.println("end");
}
}
- 子類
public class Child extends Base {
public static int s;
private int a;
static {
System.out.println("子類靜態(tài)代碼塊弛随, s: "+s);
s = 10;
}
{
System.out.println("子類實例代碼塊竣蹦, a: "+a);
a = 10;
}
public Child(){
System.out.println("子類構(gòu)造方法沉御, a: "+a);
a = 20;
}
protected void step(){
System.out.println("child s: " + s +", a: "+a);
}
}
- main方法
public static void main(String[] args) {
System.out.println("---- new Child()");
Child c = new Child();
System.out.println("\n---- c.action()");
c.action();
Base b = c;
System.out.println("\n---- b.action()");
b.action();
System.out.println("\n---- b.s: " + b.s);
System.out.println("\n---- c.s: " + c.s);
}
- 輸出結(jié)果
---- new Child()
基類靜態(tài)代碼塊, s: 0
子類靜態(tài)代碼塊, s: 0
基類實例代碼塊, a: 0
基類構(gòu)造方法, a: 1
子類實例代碼塊, a: 0
子類構(gòu)造方法, a: 10---- c.action()
start
child s: 10, a: 20
end---- b.action()
start
child s: 10, a: 20
end---- b.s: 1
---- c.s: 10
類的加載
- 一個類的主要信息
(1)類變量(靜態(tài)變量)
(2)類初始化代碼
定義靜態(tài)變量時的賦值語句
靜態(tài)初始化代碼塊
先執(zhí)行父類的昵观,再執(zhí)行子類的
父類執(zhí)行時碳抄,子類靜態(tài)變量的值也是有的愉老, 是默認(rèn)值。對于默認(rèn)值剖效,我們之前說過嫉入, 數(shù)字型變量都是0,boolean是false璧尸, char是'\u0000'咒林,引用型變量是null。
(3)類方法(靜態(tài)方法)
(4) 實例變量
(5)實例初始化代碼
定義實例變量時的賦值語句
實例初始化代碼塊
構(gòu)造方法
(6)實例方法
(7)父類信息引用
- 類加載過程包括:
(1)分配內(nèi)存保存類的信息
(2)給類變量賦默認(rèn)值
(3)加載父類
(4)設(shè)置父子關(guān)系
(5)執(zhí)行類初始化代碼
內(nèi)存
(1)棧存放函數(shù)的局部變量
(2)堆存放動態(tài)分配的對象
(3)方法區(qū)放類的信息
(4)例子加載后內(nèi)存示意圖
對象創(chuàng)建的過程
new Child()就是創(chuàng)建Child對象
(1)分配內(nèi)存
(2)對所有實例變量賦默認(rèn)值
(3)執(zhí)行實例初始化代碼
分配的內(nèi)存包括本類和所有父類的實例變量爷光,但不包括任何靜態(tài)變量垫竞。實例初始化代碼的執(zhí)行從父類開始,先執(zhí)行父類的蛀序,再執(zhí)行子類的欢瞪。但在任何類執(zhí)行初始化代碼之前,所有實例變量都已設(shè)置完默認(rèn)值
每個對象除了保存類的實例變量之外徐裸,還保存著實際類信息的引用遣鼓。
Child c = new Child();會將新創(chuàng)建的Child對象引用賦給變量c,而Base b = c;會讓b也引用這個Child對象
創(chuàng)建和賦值后重贺,內(nèi)存布局
方法調(diào)用
- c.action()代碼的執(zhí)行過程
(1)查看c的對象類型骑祟,找到Child類型,在Child類型中找action方法气笙,發(fā)現(xiàn)沒有次企,到父類中尋找
(2)在父類Base中找到了方法action,開始執(zhí)行action方法
(3)action先輸出了start潜圃,然后發(fā)現(xiàn)需要調(diào)用step()方法缸棵,就從Child類型開始尋找step方法
(4)在Child類型中找到了step()方法,執(zhí)行Child中的step()方法谭期,執(zhí)行完后返回action方法
(5)繼續(xù)執(zhí)行action方法蛉谜,輸出end
尋找要執(zhí)行的實例方法的時候,是從對象的實際類型信息開始查找的崇堵,找不到的時候型诚,再查找父類類型信息。 - b.action()
這句代碼的輸出和c.action是一樣的鸳劳,這稱之為動態(tài)綁定狰贯, 而動態(tài)綁定實現(xiàn)的機制,就是根據(jù)對象的實際類型查找要執(zhí)行的方法, 子類型中找不到的時候再查找父類涵紊。這里傍妒,因為b和c指向相同的對象, 所以執(zhí)行結(jié)果是一樣的摸柄。 - 虛方法表
所謂虛方法表颤练,就是在類加載的時候,為每個類創(chuàng)建一個表驱负, 這個表包括該類的對象所有動態(tài)綁定的方法及其地址嗦玖, 包括父類的方法,但一個方法只有一條記錄跃脊, 子類重寫了父類方法后只會保留子類的宇挫。
對Child類型來說,action方法指向Base中的代碼酪术,toString方法指向Object中的代碼器瘪,而step()指向本類中的代碼。
這個表在類加載的時候生成绘雁,當(dāng)通過對象動態(tài)綁定方法的時候橡疼,只需要查找這個表就可以了,而不需要挨個查找每個父類庐舟。
- 變量訪問
對變量的訪問是靜態(tài)綁定的欣除,無論是類變量還是實例變量。代碼中演示的是類變量:b.s和c.s继阻,通過對象訪問類變量,系統(tǒng)會轉(zhuǎn)換為直接訪問類變量Base.s和Child.s废酷。
例子中的實例變量都是private的瘟檩,不能直接訪問,如果是public的澈蟆,則b.a訪問的是對象中Base類定義的實例變量a墨辛,而c.a訪問的是對象中Child類定義的實例變量a。