分析Java中的類(lèi)加載問(wèn)題
一、Java類(lèi)的加載順序
引用1個(gè)網(wǎng)上的經(jīng)典例子,并做稍許改動(dòng),以便大家更好地理解。
public class Animal {
private int i = test();
private static int j = method();
static {
System.out.println("a");
}
Animal(){
System.out.println("b");
}
{
System.out.println("c");
}
public int test(){
System.out.println("d");
return 1;
}
public static int method(){
System.out.println("e");
return 1;
}
}
public class Dog extends Animal{
{
System.out.println("h");
}
private int i = test();
static {
System.out.println("f");
}
private static int j = method();
Dog(){
System.out.println("g");
}
public int test(){
System.out.println("i");
return 1;
}
public static int method(){
System.out.println("j");
return 1;
}
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println();
Dog dog1 = new Dog();
}
}
執(zhí)行這段main程序,會(huì)輸出什么?
答案是
eafjicbhig
icbhig
為了方便大家一個(gè)個(gè)細(xì)節(jié)去理解, 我換一種方式去提問(wèn)。
Q: 什么時(shí)候會(huì)進(jìn)行靜態(tài)變量的賦值和靜態(tài)代碼塊的執(zhí)行?
A:
- 第一次創(chuàng)建某個(gè)類(lèi)或者某個(gè)類(lèi)的子類(lèi)的實(shí)例
- 訪(fǎng)問(wèn)類(lèi)的靜態(tài)變量、調(diào)用類(lèi)的靜態(tài)方法
- 使用反射方法forName
- 調(diào)用主類(lèi)的main方法(本例子的第一次靜態(tài)初始化其實(shí)屬于這個(gè)情況,調(diào)用了Dog的main方法)
注: 類(lèi)初始化只會(huì)進(jìn)行一次, 上面任何一種情況觸發(fā)后,之后都不會(huì)再引起類(lèi)初始化操作。
Q:初始化某個(gè)子類(lèi)時(shí),也會(huì)對(duì)父類(lèi)做靜態(tài)初始化嗎?順序呢?
A:如果父類(lèi)之前沒(méi)有被靜態(tài)初始化過(guò),那就會(huì)進(jìn)行, 且順序是先父類(lèi)再子類(lèi)。 后面的非靜態(tài)成員初始化也是如此。
所以會(huì)先輸出eafj。
Q: 為什么父類(lèi)的method不會(huì)被子類(lèi)的method重寫(xiě)?
A: 靜態(tài)方法是類(lèi)方法,不會(huì)被子類(lèi)重寫(xiě)。畢竟類(lèi)方法調(diào)用時(shí),是必定帶上類(lèi)名的。
Q: 為什么第一個(gè)輸出的是e而不是a?
A: 因?yàn)轭?lèi)變量的顯示賦值代碼和靜態(tài)代碼塊代碼按照從上到下的順序執(zhí)行。
Animal的靜態(tài)初始化過(guò)程中,method的調(diào)用在static代碼塊之前,所以先輸出e再輸出a。
而Dog的靜態(tài)初始化過(guò)程中,method的調(diào)用在static代碼塊之后,因此先輸出f,再輸出j
Q: 沒(méi)有在子類(lèi)的構(gòu)造器中調(diào)用super()時(shí),也會(huì)進(jìn)行父類(lèi)對(duì)象的實(shí)例化嗎?
A: 會(huì)的。會(huì)自動(dòng)調(diào)用父類(lèi)的默認(rèn)構(gòu)造器。 super()主要是用于需要調(diào)用父類(lèi)的特殊構(gòu)造器的情況。
因此會(huì)先進(jìn)行Animal的對(duì)象實(shí)例化,再進(jìn)行Dog的對(duì)象實(shí)例化
Q: 構(gòu)造方法、成員顯示賦值、非靜態(tài)代碼塊(即輸出c和h的那2句)的順序是什么?
A:
1.成員顯示賦值、非靜態(tài)代碼塊(按定義順序)
2.構(gòu)造方法
因此Animal的實(shí)例化過(guò)程輸出icb(如果對(duì)輸出i有疑問(wèn),見(jiàn)下面一題)
接著進(jìn)行Dog的實(shí)例化,輸出hig
Q: 為什么Animal實(shí)例化時(shí), i=test()中輸出的是i而不是d?
A:因?yàn)槟阏嬲齽?chuàng)建的是Dog子類(lèi),Dog子類(lèi)中的test()方法由于簽名和父類(lèi)test方法一致,因此test方法被重寫(xiě)了。
此時(shí)即使在父類(lèi)中調(diào)用,也還是用使用子類(lèi)Dog的方法。除非你new的是Animal。
Q: 同上題, 如果test方法都是private或者final屬性, 那么上題的情況會(huì)有變化嗎??
A:
因?yàn)閜rivate和final方法是不能被子類(lèi)重寫(xiě)的。
所以Animal實(shí)例化時(shí),i=test輸出d。
總結(jié)一下順序:
1.父類(lèi)靜態(tài)變量顯式賦值、父類(lèi)靜態(tài)代碼塊(按定義順序)
2.子類(lèi)靜態(tài)變量顯式賦值、子類(lèi)靜態(tài)代碼塊(按定義順序)
3.父類(lèi)非靜態(tài)變量顯式賦值(父類(lèi)實(shí)例成員變量)、父類(lèi)非靜態(tài)代碼塊(按定義順序)
4.父類(lèi)構(gòu)造函數(shù)
5.子類(lèi)非靜態(tài)變量(子類(lèi)實(shí)例成員變量)、子類(lèi)非靜態(tài)代碼塊(按定義順序)
6.子類(lèi)構(gòu)造函數(shù)。
二、類(lèi)加載過(guò)程
Q:類(lèi)加載的3個(gè)必經(jīng)階段是:
A:
1.加載(類(lèi)加載器讀取二進(jìn)制字節(jié)流,生成java類(lèi)對(duì)象)
2.鏈接(驗(yàn)證,分配靜態(tài)域初始零值)
3.初始化(前面的題目講的其實(shí)就是初始化時(shí)的順序)
更詳細(xì)的如下:

三、被動(dòng)引用中和類(lèi)靜態(tài)初始化的關(guān)系
Q:new某個(gè)類(lèi)的數(shù)組時(shí),會(huì)引發(fā)類(lèi)初始化嗎?
像下面輸出什么
public class Test {
static class A{
public static int a = 1;
static{
System.out.println("initA");
}
}
public static void main(String[] args) {
A[] as = new A[5];
}
}
A:
new數(shù)組時(shí),不會(huì)引發(fā)類(lèi)初始化。
什么都不輸出。
Q:引用類(lèi)的final靜態(tài)字段,會(huì)引發(fā)類(lèi)初始化嗎?
像下面輸出什么?
public class Test {
static class A{
public static final int a = 1;
static{
System.out.println("initA");
}
}
public static void main(String[] args) {
System.out.println("A.a=" + A.a);
}
}
A: 不會(huì)引發(fā)。
不會(huì)輸出initA。 去掉final就會(huì)引發(fā)了。
(注意這里必須是基本類(lèi)型常量, 如果是引用類(lèi)型產(chǎn)量,則會(huì)引發(fā)類(lèi)初始化)
Q:子類(lèi)引用了父類(lèi)的靜態(tài)成員,此時(shí)子類(lèi)會(huì)做類(lèi)初始化嘛?
如下會(huì)輸出什么
public class Test {
static class A{
public static int a = 1;
static{
System.out.println("initA");
}
}
static class B extends A{
static {
System.out.println("initB");
}
}
public static void main(String[] args) {
System.out.println("B.a=" + B.a);
}
}
A:
子類(lèi)不會(huì)初始化。
打印initA,卻不會(huì)打印initB。
四、類(lèi)加載器雙親委派
類(lèi)加載時(shí)的雙親委派模型,不知道能怎么出題。。。反正就記得優(yōu)先去父類(lèi)加載器中看類(lèi)是否能加載。

Bootsrap不是ClassLoader的子類(lèi),他是C++編寫(xiě)的。
而ExtClassLoader和AppClassLoader都是繼承自ClassLoader的
Q:java中, 是否類(lèi)和接口的包名和名字相同, 那么就一定是同一個(gè)類(lèi)或者接口?
A:錯(cuò)誤。
1個(gè)jvm中, 類(lèi)和接口的唯一性由二進(jìn)制名稱(chēng)以及它的定義類(lèi)加載器共同決定。
因此2個(gè)不同的加載器加載出來(lái)相同的類(lèi)或接口時(shí), 實(shí)際上是不同的。
以上就是分析Java中的類(lèi)加載問(wèn)題的詳細(xì)內(nèi)容,更多關(guān)于Java 類(lèi)加載的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
spring中的@Value讀取配置文件的細(xì)節(jié)處理過(guò)程
這篇文章主要介紹了spring中的@Value讀取配置文件的細(xì)節(jié)處理過(guò)程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09
Java跳過(guò)證書(shū)訪(fǎng)問(wèn)HTTPS詳細(xì)代碼示例
在訪(fǎng)問(wèn)HTTPS網(wǎng)站時(shí),Java會(huì)默認(rèn)檢查SSL證書(shū)是否有效,如果證書(shū)無(wú)效,則會(huì)阻止訪(fǎng)問(wèn),這篇文章主要給大家介紹了關(guān)于Java跳過(guò)證書(shū)訪(fǎng)問(wèn)HTTPS的相關(guān)資料,需要的朋友可以參考下2024-02-02
Java Scala數(shù)據(jù)類(lèi)型與變量常量及類(lèi)和對(duì)象超詳細(xì)講解
本文內(nèi)容主要分為3節(jié),依次講解:Scala的數(shù)據(jù)類(lèi)型有哪些? 變量常量如何使用? 類(lèi)和對(duì)象如何理解? 受限于博主的大腦容量,大概是無(wú)法做到事無(wú)巨細(xì)的,不過(guò)其實(shí)也沒(méi)必要那么"細(xì)",抓住主要脈絡(luò),加上大量的練習(xí),融會(huì)貫通只不過(guò)是時(shí)間的問(wèn)題2022-12-12
SpringBoot一個(gè)請(qǐng)求的處理全過(guò)程分享
本文詳細(xì)介紹了SpringBoot請(qǐng)求處理的全過(guò)程,包括過(guò)濾器鏈、攔截器鏈、路徑映射、參數(shù)綁定、Controller方法執(zhí)行、返回值處理、異常解析和視圖解析渲染等步驟,同時(shí),文中還列舉了請(qǐng)求處理過(guò)程中常見(jiàn)的問(wèn)題及解決方案2024-12-12
sql查詢(xún)返回值使用map封裝多個(gè)key和value實(shí)例
這篇文章主要介紹了sql查詢(xún)返回值使用map封裝多個(gè)key和value實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
JAVA時(shí)間戳-Calendar類(lèi)使用(包括set,get,add方法)
這篇文章主要介紹了JAVA時(shí)間戳-Calendar類(lèi)使用(包括set,get,add方法),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-04-04
Java 8 lambda表達(dá)式引入詳解及實(shí)例
這篇文章主要介紹了Java 8 lambda表達(dá)式引入詳解及實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-05-05

