簡體   English   中英

Java最佳實踐:轉換對象與接口

[英]Java best practice: casting objects vs interfaces

假設我們有以下玩具界面:

interface Speakable
{
    public abstract void Speak();
}

interface Flyer
{
    public abstract void Fly();
}

我們有一個實現兩個接口的類:

class Duck implements Speakable, Flyer
{
    public void Speak()
    {
        System.out.println("quack quack don't eat me I taste bad.");
    }

    public void Fly()
    {
        System.out.println("I am flying");
    }
}

在這一點上,我看到了在Duck上調用方法的不同方法,我無法確定哪一個是最佳實踐。 考慮這種情況:

public class Lab 
{
        private static void DangerousSpeakAndFly(Object x)
        {
            Speakable temp  = (Speakable) x;
            temp.Speak();
            Flyer temp2= (Flyer) x;
            temp2.Fly();
        }

        public static void main(String[] args) 
        {
            Duck daffy= new Duck();
            DangerousSpeakAndFly(daffy);
        }
}

這個程序將按預期運行,因為傳入函數的對象恰好可以轉換為FlyerSpeakable ,但是當我看到這樣的代碼時,我感到畏縮,因為它不允許編譯時類型檢查,並且由於它可以拋出緊密耦合意外的異常,例如當一個不同類型的對象(不能轉換為任何一個或一個接口)作為參數傳入時,或者如果Duck實現更改,那么它不再實現Flyer

我看到Java代碼一直都是這樣編寫的,有時候是在教科書中(例如O'Reilly的“Head First Design Patterns”第300頁)所以我必須要有一個我缺少的優點。

如果我要編寫類似的代碼,我會盡量避免向下轉換為無法保證的類型或接口。 例如,在這種情況下,我會做這樣的事情:

interface SpeakingFlyer extends Flyer, Speakable
{

}

class BuzzLightyear implements SpeakingFlyer
{
    public void Speak()
    {
        System.out.println("My name is Buzz");
    }
    public void Fly()
    {
        System.out.println("To infinity and beyond!");
    }
}

這將允許我這樣做:

private static void SafeSpeakAndFly(SpeakingFlyer x)
{
    x.Speak();
    x.Fly();
}

public static void main(String[] args) 
{
    BuzzLightyear bly= new BuzzLightyear();
    SafeSpeakAndFly(bly);
}

這是不必要的矯枉過正嗎? 這樣做有什么陷阱?

我覺得這個設計將SafeSpeakAndFly()函數與其參數分離,並且由於編譯時類型檢查而阻止了令人討厭的錯誤。

為什么第一種方法在實踐中如此廣泛地使用而后者不是?

我看到Java代碼一直都是這樣編寫的,有時候是在教科書中(例如O'Reilly的“Head First Design Patterns”第300頁)所以我必須要有一個我缺少的優點。

這本書最初發表於2004年,我認為Java當時並不支持Generics。 因此,不安全的鑄造是當時非常常用的。 可能,如果我沒有Java中參數多態的支持,我首先要檢查參數是否是我想要將其轉換為的類型的實例,然后執行實際的轉換:

private static void dangerousSpeakAndFly(Object x) {
    if (x instanceof Speakable) {
        Speakable temp  = (Speakable) x;
        temp.Speak();
    }
    if (x instanceof Flyer) {
        Flyer temp2= (Flyer) x;
        temp2.Fly();
    }
}

但是,擁有泛型可以讓我們這樣做:

private static <T extends Speakable & Flyer> void reallySafeSpeakAndFly(T x) {
    x.Speak();
    x.Fly();
}

在這里,編譯器可以確保我們沒有傳遞一些沒有實現SpeakableFlyer東西,並且可以在編譯時檢測到這些Speakable嘗試。

為什么第一種方法在實踐中如此廣泛地使用而后者不是?

我想,你可能已經看過很多遺留代碼了。 :)

您可以強制參數與SpeakableFlyer同時使用類型交集創建方法泛型:

private <T extends Speakable & Flyer> static void DangerousSpeakAndFly(T x) { 
    // use any of `Speakable` or `Flyer` methods of `x`
}

因此,您不需要轉換或創建其他接口。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM