2012-03-28 193 views
4

請注意,所有代碼都是一個簡化示例,目的是僅傳達我的問題的核心思想。它應該都經過輕微的編輯後編譯並運行。使用Java進行反射投射和重載方法調度

我有幾個類都實現了一個通用接口。

public interface Inter{} 
public class Inter1 implements Inter{} 
public class Inter2 implements Inter{} 

在一個單獨的I類有類型間,我使用來存儲和移除Inter1和Inter2類型,基於用戶輸入的列表。

java.util.ArrayList<Inter> inters = new java.util.ArrayList<Inter>(); 

我也有一個重載方法家族,它處理每個實現如何相互作用,以及2個「Inter」的默認實現。

void doSomething(Inter in1, Inter in2){ 
    System.out.println("Inter/Inter");  
} 
void doSomething(Inter1 in1, Inter1 in2){ 
    System.out.println("Inter1/Inter11");  
} 
void doSomething(Inter2 in1, Inter1 in2){ 
    System.out.println("Inter2/Inter1");  
} 

的方法是週期性地調用像這樣:

for(int i = 0; i < inters.size() - 1; i++){ 
    for(int o = i+1; o < inters.size(); o++){ 
     Inter in1 = inters.get(i); Inter in2 = inters.get(o); 

     doSomething(in1.getClass().cast(in1), in2.getClass().cast(in2)); 

     System.out.println("Class 1: " + in1.getClass().getName()); 
     System.out.println("Class 2: " + in2.getClass().getName()); 
    } 
} 

從該輸出的一個例子是:

Inter/Inter 
Class 1: Inter 
Class 2: Inter 
Inter/Inter 
Class 1: Inter 
Class 2: Inter1 
Inter/Inter 
Class 1: Inter1 
Class 2: Inter1 

綜觀輸出,很顯然,doSomething的(國際IN1, Inter in2)被調用,即使在應該調用其他方法的情況下也是如此。有趣的是,輸出的類名是正確的。

爲什麼java在運行時使用反射來確定類類型時有靜態方法重載? 有什麼辦法讓Java來做到這一點?我知道我可以使用反射和Class.getMethod()和method.invoke()來獲得我想要的結果,但是在使用cast的時候會更加簡單。

我意識到之前已經提出過有關類似概念的問題,但所有的答案都是信息性的,沒有人滿意我。 Double dispatch看起來好像會起作用,但這意味着需要重新編寫大量的代碼,因爲我經常使用這種類型的東西。

回答

6

它看起來對我來說,我們在談論什麼事情上:

doSomething(in1.getClass().cast(in1), in2.getClass().cast(in2)); 

根據您的驚喜,正在輸出的類型總是Inter,看來你是一個有點困惑這裏發生了什麼。特別是,您似乎認爲in1.getClass().cast(in1)in2.getClass().cast(in2)應該由於它們的運行時類型不同而導致不同的過載。但是,這是錯誤的。

方法重載分辨率靜態地發生。這意味着它發生在方法的兩個參數的聲明類型的基礎上。由於in1in2都被聲明爲Inter,所選的方法顯然是void doSomething(Inter in1, Inter in2)

這裏的外賣是in1被宣佈爲Inter。這意味着in1.getClass()與用於靜態分析的Inter.class基本相同 - getClass只是返回Class<? extends Inter>。因此,演員陣容毫無用處,你只會得到第一個超載。

+0

謝謝,我希望不是這樣,因爲它相當煩人。我只是不禁覺得像這樣的東西應該是動態的。它使一切變得更加複雜......噢,我想我只需要使用反射API,就像看起來那樣混亂。 – Adno 2012-03-28 06:48:20

1

The Java Language Specification(JLS)in section 15。12 Method Invocation Expression詳細解釋了編譯器按照選擇正確方法調用的過程。

在那裏,你會注意到這是一個編譯時間任務。該JLS說,在第15.12.2:

此步驟使用方法和的類型參數的表達式 以找到既方便和適用 有可能會超過方法的名一種這樣的方法,在這種情況下,選擇最具體的一種。

就你而言,這意味着由於你傳遞了兩個Integer類型的對象,所以最具體的方法就是接收這個對象的方法。

要驗證此編譯時間性質,您可以執行以下測試。

聲明這樣的類並編譯它。

public class ChooseMethod { 
    public void doSomething(Number n){ 
    System.out.println("Number"); 
    } 
} 

聲明第二個類,調用第一個類的方法並編譯它。

public class MethodChooser { 
    public static void main(String[] args) { 
    ChooseMethod m = new ChooseMethod(); 
    m.doSomething(10); 
    } 
} 

如果您調用main,則輸出爲Number

現在,向ChooseMethod類添加更具體的第二種方法,然後重新編譯它(但不要重新編譯其他類)。

public void doSomething(Integer i) { 
System.out.println("Integer"); 
} 

如果再次運行main,輸出仍爲Number

基本上,因爲它是在編譯時決定的。如果您重新編譯MethodChooser類(具有主類的類),並再次運行該程序,則輸出將爲Integer。因此,如果要強制選擇其中一個重載方法,則參數的類型必須與編譯時的參數類型一致,而不僅僅是在運行時,您似乎期望這個練習。