日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務,提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

JAVA 編程五年多,我自以為已經(jīng)熟諳 Overload 和 Override 背后的工作機制。當開始思考和記錄下面這些案例時,才意識到我對它們的了解并不像自己想象的那樣。為了讓內(nèi)容更有趣,下面會把它們列為一系列謎題,同時也提供了答案。如果你能不偷看做出所有答案,我會對你刮目相看。

1. 單一分派

給定下面兩個類:

class Parent {
 void print(String a) { log.info("Parent - String"); }
 void print(Object a) { log.info("Parent - Object"); }
}
class Child extends Parent {
 void print(String a) { log.info("Child - String"); }
 void print(Object a) { log.info("Child - Object"); }
}

 

下面代碼打印結果是什么?

String string = "";
Object stringObject = string;
// 打印結果是什么?
Child child = new Child();
child.print(string);
child.print(stringObject);
Parent parent = new Child();
parent.print(string);
parent.print(stringObject);

 

答案:

child.print(string); // Prints: "Child - String"
child.print(stringObject); // Prints: "Child - Object"
parent.print(string); // Prints: "Child - String"
parent.print(stringObject); // Prints: "Child - Object"

child.print(string) 和 parent.print(string) 是 Java 面向?qū)ο蟪绦蚓幊痰慕炭茣绞纠U{(diào)用的方法取決于實例的“實際”類型,而非“聲明”類型。也就是說,不論把變量定義為 Child 還是Parent,因為實際的實例類型是Child 都會調(diào)用 Child::print。

第二組輸出結果更為棘手。stringObject 和 string 是完全相同的字符串,唯一的區(qū)別在于 string 聲明為 String,而 stringObject 聲明為 Object。Java 不支持雙重分派(double-dispatch),因此在處理方法參數(shù)時,參數(shù)的“聲明”類型優(yōu)先于“實際”類型。即使參數(shù)“實際”類型是 String 還是會調(diào)用 print(Object)。

2. 隱式 Override

給定下面兩個類:

class Parent {
 void print(Object a) { log.info("Parent - Object"); }
}
class Child extends Parent {
 void print(String a) { log.info("Child - String"); }
}

 

下面代碼打印結果是什么?

String string = "";
Parent parent = new Child();
parent.print(string);

 

答案:

parent.print(string); // Prints: "Parent - Object"

 

實例的“實際”類型是 Child,聲明的參數(shù)類型是 String。代碼中確實有一個 Child::print(String)方法。前面的例子中 parent.print(string) 調(diào)用的正是這個方法,但這里不是。

檢查子類 Override 前,Java 似乎要先選擇調(diào)用哪個方法。這種情況下,實例聲明的類型是Parent,而 Parent 中唯一匹配的方法是 Parent::print(Object)。接著,Java 會檢查Parent::print(Object) 有沒有潛在的 Override 方法,結果沒有找到。因此,最后執(zhí)行的就是這個方法。

3. 顯式 Override

給定下面兩個類:

class Parent {
 void print(Object a) { log.info("Parent - Object!"); }
 void print(String a) { throw new RuntimeException(); }
}
class Child extends Parent {
 void print(String a) { log.info("Child - String!"); }
}

 

下面代碼打印結果是什么?

String string = "";
Parent parent = new Child();
parent.print(string);

 

答案:

parent.print(string); // Prints: "Child - String!"

 

這個例子與前面的唯一區(qū)別在于添加了一個新的 Parent::print(String) 方法。實際上這個方法從來沒有執(zhí)行。如果運行,會拋出一個異常。然而,正是因為它的存在讓 Java 執(zhí)行了一個不同的方法。

運行時在分析 parent.print(String) 的時候,找到了一個可以匹配的 Parent::print(String) 方法,然后看到這個方法被 Child::print(String) 覆寫。

通常認為如果不調(diào)用,只是添加一個新方法絕不會改變系統(tǒng)行為。上面的例子顯然是另一種情況。

4. 帶有歧義的參數(shù)

給定下面的類:

class Foo {
 void print(Cloneable a) { log.info("I am cloneable!"); }
 void print(Map a) { log.info("I am Map!"); }
}

 

下面代碼打印結果是什么?

HashMap cloneableMap = new HashMap();
Cloneable cloneable = cloneableMap;
Map map = cloneableMap;
// 打印結果是什么?
Foo foo = new Foo();
foo.print(map);
foo.print(cloneable);
foo.print(cloneableMap);

 

答案:

foo.print(map); // Prints: "I am Map!"
foo.print(cloneable); // Prints: "I am cloneable!"
foo.print(cloneableMap); // 編譯失敗

 

與單一分派類似,參數(shù)聲明的類型優(yōu)先于實際類型。如果傳入?yún)?shù)有多個方法可以匹配,Java 會拋出編譯錯誤,要求明確指定調(diào)用哪個方法。

5. 多重繼承:接口

給定下面的接口:

interface Father {
 default void print() { log.info("I am Father!"); }
}
interface Mother {
 default void print() { log.info("I am Mother!"); }
}
class Child implements Father, Mother {}

 

下面代碼打印結果是什么?

new Child().print();

 

與前面的例子類似,這個示例也不能編譯通過。具體來說,由于 Father 和 Mother 中存在互相沖突的默認方法,Child 類無法編譯通過。需要在 Child 類定義中明確指定 Child::print。

6. 多重繼承:類與接口

給定下面的類和接口:

class ParentClass {
 void print() { log.info("I am a class!"); }
}
interface ParentInterface {
 default void print() { log.info("I am an interface!"); }
}
class Child extends ParentClass implements ParentInterface {}

 

下面代碼打印結果是什么?

new Child().print();

 

答案:

new Child().print(); // Prints: "I am a class!"

 

說明:如果類和接口在繼承時發(fā)生沖突,繼承類優(yōu)先。

7. Override 傳遞

給定下面的類:

class Parent {
 void print() { foo(); }
 void foo() { log.info("I am Parent!"); }
}
class Child extends Parent {
 void foo() { log.info("I am Child!"); }
}

 

下面代碼打印結果是什么?

new Child().print();

 

答案:

new Child().print(); // Prints: "I am Child!"

 

Override 對傳遞調(diào)用也有效。看過 Parent 定義的人可能認為 Parent::print 總是調(diào)用Parent::foo。但是如果該方法被 Override,那么 Parent::print 將調(diào)用 Override 版本的 foo()。

8. 私有 Override

給定下面的類:

class Parent {
 void print() { foo(); }
 private void foo() { log.info("I am Parent!"); }
}
class Child extends Parent {
 void foo() { log.info("I am Child!"); }
}

 

下面代碼打印結果是什么?

new Child().print();

 

答案:

new Child().print(); // Prints: "I am Parent!"

 

現(xiàn)在 Parent.foo() 聲明變?yōu)?private,除此之外,這個示例與前面的例子完全相同。無論 foo() 在子類中是否實現(xiàn)了其它版本,也不管調(diào)用 print() 的實例類型如何,當 Parent.print() 調(diào)用 foo()時,foo() 都會硬編碼為 Parent.foo()。

通常認為,把一個方法從 public 改為 private,只要編譯成功,僅僅是一次重構。上面的例子證明這種想法是錯的:即使編譯成功,系統(tǒng)行為也可能出現(xiàn)意料之外的變化。

解決方法,可以為方法加上 @Override 注解。一旦改變基類方法的可見性,就會引起編譯錯誤。

9. 靜態(tài) Override

給定下面的類:

class Parent {
 static void print() { log.info("I am Parent!"); }
}
class Child extends Parent {
 static void print() { log.info("I am Child!"); }
}

 

下面代碼打印結果是什么?

Child child = new Child();
Parent parent = child;
parent.print();
child.print();

 

答案:

parent.print(); // Prints: "I am Parent!"
child.print(); // Prints: "I am Child!"

 

Java 不支持靜態(tài)方法 Override。如果父類和子類中定義了相同的靜態(tài)方法,僅根據(jù)聲明類型決定調(diào)用哪個方法,不考慮實例類型。

這與非靜態(tài)方法的處理完全相反:非靜態(tài)方法會依據(jù)實例類型決定調(diào)用哪個方法,忽略聲明類型。因此,把方法改為靜態(tài)方法或者反向操作需要格外小心。即使沒有編譯錯誤,系統(tǒng)行為也可能出現(xiàn)意料之外的變化。

這也是給方法加上 @Override 注解的另一個重要原因。上面的例子中,如果為 Child::print 加上注解會報告一個編譯錯誤:靜態(tài)方法不能 Override。

不僅如此,最佳實踐也要求不使用實例調(diào)用靜態(tài)方法:不僅可能導致類似上面的意外結果,在重構出現(xiàn)問題時也無法告警。示例中這樣調(diào)用 static 方法時,像 Intellij 這樣的 IDE 會給出警告,最好給予足夠的重視并處理。

10. 靜態(tài)鏈接

給定下面的類:

class Parent {
 void print() { staticMethod(); instanceMethod(); }
 static void staticMethod() { log.info("Parent::staticMethod"); }
 void instanceMethod() { log.info("Parent::instanceMethod"); }
}
class Child extends Parent {
 static void staticMethod() { log.info("Child::staticMethod"); }
 void instanceMethod() { log.info("Child::instanceMethod"); }
}

 

下面代碼打印結果是什么?

Child child = new Child();
child.print();

 

答案:

Parent::staticMethod
Child::instanceMethod

 

這個例子組合了之前討論的各個主題。即使調(diào)用方法在父類中,Override 也會生效。然而,對于靜態(tài)方法,即使變量聲明的類型是 Child 仍然調(diào)用的是 Parent::staticMethod。

11. 總結

如果說有什么值得注意的話,那就是處理繼承非常棘手,而且很容易出錯。如果試圖在繼承上耍小聰明,總有一天會吃苦頭。還是建議循規(guī)蹈矩,用最佳實踐來保護自己:

  1. 用@override 注解標記所有 Override 方法
  2. 始終用類而非實例調(diào)用靜態(tài)方法
  3. 在 IDE 中設置警告或者 lint 錯誤,強制執(zhí)行上面的規(guī)則檢查有問題的代碼
  4. 能用組合就不要繼承

分享到:
標簽:Java Overload
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數(shù)有氧達人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定