2017-06-17 150 views
0

我對泛型非常瞭解。根據我的理解,當調用Generic類或方法時,必須提供Type參數。例如:類型轉換爲Generic

List<String> strings = new ArrayList<String>(); 

但是我在理解下面的代碼時遇到了一些困難。

public static <T extends Employee> T findById(@NonNull factory, int employeeId) { 
    return (T) factory.findEmployeeById(employeeId); 
} 

員工有很多子類型。例如:

public class Programmer extends Employee {} 

public class Engineer extends Employee {} 

通過使用這種方法findById,我能成功地實施類似下面。

Programmer employee = EmployeeUtils.findById(factory, 2); 

findById上述方法,怎麼會有人知道什麼是T類型?只有暗示它是Employee的子類型。它無法知道它是程序員還是工程師。 Java編譯器如何成功地在運行時將類型轉換爲具體的子類(ProgrammerEngineer)?

如果您想推薦Java泛型,請指導我進一步閱讀。

+2

「但是我在理解下面的代碼時遇到了一些困難」,這可能是因爲這個代碼是泛型的常見但濫用的使用:它不是類型安全的。 –

回答

1

How can Java compiler successfully do the type cast at the runtime to Programmer (or Engineer)?

Java編譯器不做轉換。

演員只是對編譯器說的一種方式:「我比你更瞭解,相信我。」編譯器仍然會試圖阻止你進行強制轉換,它可以確定它是絕對不安全的(例如將String轉換爲Integer),但是通過強制轉換,你需要遠離編譯器負責類型安全。如果你知道 - 你的代碼/系統的語義 - 它應該是安全的。

這種特殊模式對於從存儲庫中檢索異構實體是很常見的;但它不是安全的。

負責方法的調用者只調用具有「正確類型的ID」的方法。問題在於代碼中沒有指示它可能會失敗。

這種模式總讓我感到厭煩的原因是它隱藏了問題發生的實際位置。回想一下,Java泛型基本上只是casts的一個縮寫。這意味着,該方法「真的」看起來是這樣的:

public static Employee findById(@NonNull factory,int employeeId) { 
    // There is actually no cast here! 
    return factory.findEmployeeById(employeeId); 
} 

和呼叫網站看起來是這樣的:

// The cast is here instead! 
SpecificEmployeeType e = (SpecificEmployeeType) findById(someId); 

(儘量明確地寫這樣的代碼,並與通用比較字節碼版本)

因此,雖然您可以在findById方法中禁止警告,但實際的異常發生在呼叫站點。所以,警告 - 表示可能在那裏發生問題 - 是在錯誤的地方。

如果您沒有泛型明確地寫入,您可以確切地看到問題實際發生的位置。

人們經常說他們想要以這種方式使用泛型,因爲它更「更清潔」:您不需要在每個調用站點都進行明確的轉換或抑制。我個人認爲,想要那裏有額外的東西:它使它看起來像有危險的事情發生在那裏(這是!),所以你知道你需要格外小心。


還值得指出的是,你不應添加@SuppressWarnings("unchecked")到你的代碼 - 無論是在findById方法,或者在調用的地方 - 除非你可以絕對肯定的是,中投是安全的。

與鑄造一樣,警告抑制是對編譯器無法證明的事情負責的一種方式。當然,通過壓制警告,你只是忽略了編譯器試圖提供的幫助。與任何警示性建議一樣,您可以自由地忽略它,但如果沒有充分理解後果,這樣做是蠻荒的。

1

在這種情況下編譯器不檢查類型轉換。當您的編譯器選項相應地設置時,您應該會收到一條警告,告知您未經檢查的轉換爲T

該檢查將在運行時執行。請看下面的代碼:

public class GenericTest { 
    public abstract static class Employee {} 

    public static class Programmer extends Employee {} 

    public static class Engineer extends Employee {} 

    public static void main(String[] args) { 
     Programmer p = getEmployee(); 
    } 

    public static <T extends Employee> T getEmployee() { 
     return (T) new Engineer(); 
    } 
} 

此代碼將警告Type safety: Unchecked cast from GenericTest.Engineer to T編譯。在運行時,會出現一個ClassCastException:

java.lang.ClassCastException: GenericTest$Engineer cannot be cast to GenericTest$Programmer

當你這樣做演員,你應該確保該類型是在運行時正確,否則醜陋異常都會飛。

泛型在編譯時被刪除。因此,當您分析編譯的字節碼時,您會看到該方法根據其簽名返回Employee。其實上面的例子中的編譯的字節代碼幾乎相同,從下面的代碼(具有相同的類)編寫的字節碼:

public static void main(String[] args) { 
    Programmer p = (Programmer) getEmployee(); 
} 

public static Employee getEmployee() { 
    return new Engineer(); 
} 

希望這將幫助您瞭解在運行時會發生什麼。

進一步的閱讀考慮Oracle Tutorial for Generics

+0

我添加了surpresswarning註釋以隱藏未經檢查的強制轉換。你能否更詳細地解釋退貨聲明中的T是什麼? –

+0

@ Steve.NayLinAung我已經更新了我的答案。希望對你有幫助 – SilverNak

0

返回類型T將爲僱員。 以下代碼確認了此操作。

public class GenericTest { 
    public abstract static class Employee { 
    } 

    public static class Programmer extends Employee { 
    } 

    public static class Engineer extends Employee { 
     void testMethod(){ 
      System.out.println("Engineer method"); 
     } 
    } 

    public static void main(String[] args) { 
     getEmployee().testMethod(); 
    } 

    public static <T extends Employee> T getEmployee() { 
     return (T) new Engineer(); 
    } 
} 

如果我們嘗試運行代碼,我們得到了一個編譯錯誤

Error:(28, 22) java: cannot find symbol symbol: method engineerMethod() location: class GenericTest.Employee

要修正這個錯誤,你需要一個「抽象方法」或「法」添加到類的抽象類這個:

public abstract static class Employee { 
    void testMethod(){}; 
}