[英]Doubts about the use of polymorphism, and also about how is polymorphism related to casting?
我向在大學學習該學科的學生講授 Java 編程語言的基礎知識。
今天其中一個讓我對她的問題感到非常困惑,所以我告訴她給我一天的時間來思考這個問題,我會盡可能准確地給她一個答案。
她告訴我,當她在考試中使用關鍵字instanceof
時,老師非常生氣。
此外,她說老師說如果她使用這個詞,就沒有辦法證明多態性是如何工作的。
我想了很多試圖找到一種方法來證明在某些情況下我們需要使用instanceof
,並且即使我們使用它,該方法中仍然存在一些多態性。
所以這是我做的例子:
public interface Animal
{
public void talk();
}
class Dog implements Animal {
public void talk() {
System.out.println("Woof!");
}
}
public class Cat implements Animal
{
public void talk() {
System.out.println("Meow!");
}
public void climbToATree() {
System.out.println("Hop, the cat just cimbed to the tree");
}
}
class Hippopotamus implements Animal {
public void talk() {
System.out.println("Roar!");
}
}
public class Main {
public static void main(String[] args) {
//APPROACH 1
makeItTalk(new Cat());
makeItTalk(new Dog());
makeItTalk(new Hippopotamus());
//APPROACH 2
makeItClimbToATree(new Cat());
makeItClimbToATree(new Hippopotamus());
}
public static void makeItTalk(Animal animal) {
animal.talk();
}
public static void makeItClimbToATree(Animal animal) {
if(animal instanceof Cat) {
((Cat)animal).climbToATree();
}
else {
System.err.println("That animal cannot climb to a tree");
}
}
}
我的結論如下:
第一種方法(方法 1)是一個簡單的演示,演示如何對接口進行編程,而不是實現。 我認為多態性是清晰可見的,在方法makeItTalk(Animal animal)
的參數中,以及調用方法 talk 的方式中,使用動物 object。(這部分沒問題)
第二部分是讓我感到困惑的部分。 她在考試中的某個時候使用了instanceof
(我不知道他們的考試是什么樣子的),這沒有被正確接受,因為老師說,你沒有證明多態性。
為了幫助她理解什么時候可以使用instanceof
,我想告訴她,當她需要調用的方法不在接口中,而只是在其中一個實現類中時,她可以使用它。
如您所見,只有貓才能爬到樹上,讓河馬或狗爬到樹上是不合邏輯的。 我認為這可能是何時使用instanceof
的一個例子
但是方法 2 中的多態性呢?
您在那里看到了多少種多態性(僅方法 2)?
你認為這條線有某種類型的多態性嗎?
((Cat)animal).climbToATree();
我認為確實如此,因為為了實現這種類型的 Casting,對象需要具有 IS-A 關系,在某種程度上是多態性。
你怎么看,對嗎?
如果是的話,你會如何用你自己的話來解釋,鑄造依賴於多態性?
instanceof
方法被認為不好的原因很簡單。 貓不是唯一可能會爬樹的Animal
。
如果您需要添加考拉 class,會發生什么情況。 然后你的簡單if
變成一個不那么簡單的or
。 那么,當您添加另一個 class 時會發生什么? 和另一個。 還有一個。 這就是為什么instanceof
被認為不好的主要原因。 因為它將實現耦合到具體的 class,而不是為被調用者打開它來確定要做什么。
如果對不能攀爬的動物調用,只需實現makeItClimbToATree()
方法即可拋出CantClimbTreesException
。 這樣你就可以兩全其美了。 易於實現,易於擴展。
恕我直言, instanceof
只有 1 個真正有效的用途:在測試用例中測試從方法返回的實例與預期的返回類型匹配(在非類型安全語言中)。
基本上,任何其他用途很可能會被重構掉或以不同的方式設計,以消除對其使用的需求。
另一種看待它的方式是:多態性允許您從代碼中消除幾乎所有的條件語句。 您無法擺脫的唯一條件(至少所有條件)是在 object 創建方法中(例如在必須根據運行時參數選擇 class 的工廠中)。 幾乎任何其他條件都可以用多態性代替。 因此,根據定義,任何進行條件執行的東西都是反多態的。 這並不是說它不好( Good 和 Good Enough之間存在巨大差異),但在學術討論中,它不是多態的......
永遠不要忘記 60/60 規則。 您總開發時間的 60% 將用於維護您編寫的代碼,而其中 60% 的時間將用於添加新功能。 讓維護更輕松,您的生活也會更輕松。 這就是為什么instanceof
不好的原因。 它使初始設計更容易,但使長期維護復雜化(無論如何這更昂貴)......
在您上面的示例中,無需調用
makeItClimbToATree (new Hippopotamus ());
它可以很容易地避免,如果 makeItClimbToATree 不期望動物,而是更具體的東西,它真的能夠爬樹。 允許動物的必要性,因此使用 instanceof,是不可見的。 如果您在動物列表中管理動物,它會更加明顯。
雖然 ircmaxells 的解釋開始很好,但在介紹考拉和其他爬樹者時,他沒有看到隱藏在海葵中的第二個擴展:動物的不同能力,如 seaAnemoneHider、winterSleeping、blueEyed、bugEating 等等,等等. 您最終會得到 boolean 而不是 boolean,不斷重新編譯基礎 class,以及破壞擴展客戶類,這需要再次重新編譯,並且無法以類似的方式。
客戶 A 需要客戶 B 聲明 NotBugEatingException,以將您的行為納入基礎 class。
引入您自己的接口並結合 instanceof,是一種更簡潔、更靈活的方法。 客戶 A 可能會定義 DiveLikeAPenguin 和客戶 B 吹號,兩者都不知道對方,兩者都不會影響 Animal class 並且不會引起無用的重新編譯。
import java.util.*;
interface Animal {
public void talk ();
}
interface TreeClimbing {
public void climbToATree ();
}
class Dog implements Animal {
public void talk () { System.out.println("Woof!"); }
}
class Cat implements Animal, TreeClimbing {
public void talk () { System.out.println("Meow!"); }
public void climbToATree () { System.out.println ("on top!"); }
}
public class TreeCriterion {
public static void main(String[] args) {
List <Animal> animals = new ArrayList <Animal> ();
animals.add (new Cat ());
animals.add (new Dog ());
discuss (animals);
upTheTree (animals);
}
public static void discuss (List <Animal> animals) {
for (Animal a : animals)
a.talk ();
}
public static void upTheTree (List <Animal> animals) {
for (Animal a : animals) {
if (a instanceof TreeClimbing)
((TreeClimbing) a).climbToATree ();
}
}
}
我們不需要第三只動物,狗和貓就足夠了。 我將它們設為默認可見而不是公開,以使整個示例適合單個文件。
你認為這條線有某種類型的多態性嗎?
((Cat)animal).climbToATree();
不,尤其是,因為Cat
在示例中是葉子 class 。
我認為確實如此,因為為了實現這種類型的 Casting,對象需要具有 IS-A 關系,在某種程度上是多態性。
多態性需要 IS-A 關系,但反之則不然。
多態性是指您基於抽象接口調度(可能)不同的方法。 如果你沒有那個調度,那么它就沒有使用多態性。 在您的示例中,使用instanceof
強制轉換為沒有子類的 class ,您無需調度。
(當然,Java 中“做多態性”的方法不止一種。你可以使用接口,使用抽象類,或者使用帶有子類的具體類……或者未來可能編寫的假設子類來實現它。接口(以及基於接口的調度)通常是最好的方法,因為它們可以將 API 與 class 的身份完全分開。)
另外,像這樣使用instanceof
通常是設計不佳和/或建模不佳的標志。 具體來說,它硬連線了只有貓才能攀爬的假設,如果我們將其他動物包括在 model / 程序中,這將是微不足道的證偽。 如果發生這種情況,您的代碼將中斷。
我很驚訝沒有人寫任何關於后期裝訂的文章。 Java 中的多態性 = 后期結合。 當我們最終知道它的類型時,被調用的方法將附加到 object 上。 在您的示例中:
if(animal instanceof Cat) {
((Cat)animal).climbToATree();
}
您在 Cat object 上調用climbToATree()
,因此編譯器接受它。 在運行時,無需檢查調用 object 的類型,因為climbToATree()
僅屬於Cat
。 因此,這些代碼行中沒有多態性。
關於鑄造與多態性有關,它不是。 如果強制轉換是合法的,強制轉換只會限制兩個對象中共享的字段。 你可以這樣做:
class A {
int getInt() {}
}
class B extends A {
int getInt() {}
}
// in main
A a = new B();
A b = (A)a;
b.getInt(); // This would still call class B's getInt();
強制轉換本身沒有添加任何值, getInt() 在運行時綁定到a
的運行時類型,即 class B。
也許我錯過了重點並且沒有得到考試問題的上下文,但是Animal
是否可以爬樹應該是實現Animal
的 class 的一部分。 例如,如果Animal
是一個接口,您可以有一個方法boolean isCapableOfClimbing()
然后每個實現 class 將能夠指示其能力。
然后可以使用一種試圖使動物攀爬的方法。 對於試圖讓動物爬樹的方法來說,檢查它是否是特定 class 的實例是沒有意義的,因為那時該方法指定了應該在實現 class 中指定的內容。 一個簡單的方法不應該為它正在使用的 class 提供行為。
至於您何時使用instanceof
的問題,幾乎總是使用它的地方是如果覆蓋 class 的equals()
方法,因為它只接受Object
並且您通常必須確保它是相同類型的它可以被強制轉換,然后進行有意義的比較。
類似下面的代碼呢? 它通過將爬樹分離為您可以在動物上實現或不實現的另一個接口來解決普遍性問題。 它更適合這個問題:爬樹並不是所有動物的固有屬性,只是其中一部分。 至少對我來說,它看起來比拋出NotImplementedException
更加清晰和優雅。
public interface Animal {
public void talk();
}
public interface AnimalCanClimbTrees extends Animal {
public void climbToATree();
}
public class Dog implements Animal {
public void talk() {
System.out.println("Woof!");
}
}
/* Animal is probably not needed, but being explicit is never bad */
public class Cat implements Animal, AnimalCanClimbTrees
{
public void talk() {
System.out.println("Meow!");
}
public void climbToATree() {
System.out.println("Hop, the cat just cimbed to the tree");
}
}
class Hippopotamus implements Animal {
public void talk() {
System.out.println("Roar!");
}
}
public class Main {
public static void main(String[] args) {
//APPROACH 1
makeItTalk(new Cat());
makeItTalk(new Dog());
makeItTalk(new Hippopotamus());
//APPROACH 2
makeItClimbToATree(new Cat());
makeItClimbToATree(new Hippopotamus());
}
public static void makeItTalk(Animal animal) {
animal.talk();
}
public static void makeItClimbToATree(Animal animal) {
if(animal instanceof AnimalCanClimbTrees) {
((AnimalCanClimbTrees)animal).climbToATree();
}
else {
System.err.println("That animal cannot climb to a tree");
}
}
}
instanceof
運算符與多態無關。 它僅用於查看 object 是否是特定 class 的實例。 您會看到此運算符在equals()
方法中被大量使用,因為該方法將通用Object
作為參數:
public class Cat implements Animal{
@Override
public boolean equals(Object obj){
if (obj == null || !obj instanceof Cat){
//obj is null or not a "Cat", so can't be equal
return false;
}
if (this == obj){
//it's the same instance so it must be equal
return true;
}
Cat catObj = (Cat)obj; //cast to "Cat"
return this.getName().equals(catObj.getName()); //compare the two objects
}
}
如果 class 沒有實現方法,那么它應該拋出異常。 我相信您應該拋出的“官方”異常是UnsupportedOperationException
。 為了“正確”,我認為Animal
接口應該有一個public void climbToATree();
方法。 Dog
和Hippo
類中的climbToATree()
方法應該拋出UnsupportedOperationException
,因為它們無法實現此方法。 但是,如果您經常拋出此異常,那么您的 object model 可能有問題,因為我認為這不是常見的事情。
另請注意,在 Java 中將@Override
注釋與多態編程一起使用是有幫助的(但不是必需的)。 如果帶有此注解的方法未覆蓋父方法、未實現抽象方法或(在 Java 6 中)未實現接口方法,這將導致引發編譯錯誤。 這可以幫助捕捉您在方法簽名中犯的任何錯誤。 例如:
public String tostring(){
return "foobar";
}
如果沒有注釋,程序將成功編譯並運行。 但這不是你的本意,你想重寫 toString(),但你不小心把名字拼錯了!!
多態和 OOP 方法是將方法 makeItClimbToATree 放在 Animal 接口上:
public interface Animal{
public void talk();
public void makeItClimbToATree();
}
然后,Animal 的實現者將為該方法提供行為,對於 Cat 之外的所有方法都可以拋出異常。 這是多態的,因為您通過單一方法對 Animal 的不同實現進行操作。
使用 instanceOf 運算符的 function 被認為是“壞的” OOP,因為它需要了解所有實現類型才能確定方法的行為。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.