2014-03-01 62 views
2

我剛剛開始討論Java中的泛型,並且遇到了一些奇怪的行爲。這是簡化的版本,但我有一個基類,它被多個類傳入一個通用函數。在這個函數內部,我調用了一個有幾個版本的方法,它們將不同的基類或派生類作爲參數。Java多態通用調用

如果我有以下幾點:

public class A{}; 
public class B extends A{}; 
public class C extends A{}; 
public class Runner 
{ 
    public static void Run(ClassA a){Do Something}; 
    public static void Run(ClassB b){Do Something}; 
    public static void Run(ClassC c){Do Something}; 
} 
void SomeRandomCall<B extends ClassA>(B b) 
{ 
    Runner.Run(b); 
} 
SomeRandomCall<ClassB>(new ClassB()); 

我在Runner.Run被調用Run(ClassA a),而不是運行(ClassB b)功能調試尋找。給定這兩個函數,不應該調用Run(ClassB b),因爲提供了類型特定的函數?

如果不是這種情況,我將如何能夠擁有一個通用函數來調用能夠調用帶有基類和派生類的簽名的函數?

+0

「泛型」?你的意思是「繼承」吧? – Astrobleme

+0

'void SomeRandomCall (B b)'應該是什麼?爲什麼B(我是一個類)在'<>'(什麼表示泛型)? – exception1

+1

「公共類B擴展A {}」中的「B」;「與「void SomeRandomCall (B b)」中的「B」完全無關。它可能是「void SomeRandomCall (Z z)」 – aliteralmind

回答

2

由於您的縮寫對我有點困惑,我做了一個小的,可運行的示例。我假設你在SomeRandomCallB是一個泛型類型,而不是B類

這就是:

public class Main { 

    public static class A{}; 
    public static class B extends A{}; 
    public static class C extends A{}; 
    public static class Runner{ 
     public static void Run(A a){System.out.println("A");}; 
     public static void Run(B b){System.out.println("B");}; 
     public static void Run(C c){System.out.println("C");}; 
    } 

    static <T extends A> void SomeRandomCall(T x){ 
     Runner.Run(x); 
    } 

    public static void main(String[] args) { 
     B b = new B(); 
     new Runner().Run(b); 
    } 
} 

輸出是:B.

這就是你所期望的,這是好。在編譯時,Java編譯器爲方法選擇候選,它們是Run(A a)Run(B b)。在運行時,參數的類型是B,因此B被打印。

但是,請注意下面的例子:

public static void main(String[] args) { 
     A ab = new B(); 
     new Runner().Run(ab); 
    } 

現在該怎麼辦?現在輸出爲A.原因是在編譯時ab的類型爲A,所以我們只有一個候選方法來執行:Run(A a)。運行期間的實際類型ab已不再重要,因爲我們只有一個可以執行的候選。輸出,獨立是否abABC,是A

1

好吧,由於A擴展了B,因此B類型的對象可以作爲參數傳遞給接受參數的類型爲A的參數。這裏也是一樣。由於run(A a)run(B b)之前存在,所以前者被執行。因此,你的錯誤(如果你正在考慮它)。

對於進一步的闡述考慮這個下面的例子:

Programmer一個是一個超類(如A)。 A Java ProgrammerProgrammer的子類,因此是C++ programmer(如BC)。

現在,有三個門,在增加距離的順序:

  1. 對於程序員。
  2. 適用於Java程序員。
  3. 對於C++程序員。

由於第一扇門離您最近並且符合條件,因此請立即輸入。你不想花更多的精力去第二扇門。這裏也是一樣。

1
void SomeRandomCall<T extends SomeClass>(T t) 

被編譯,就像它是

void SomeRandomCall(Someclass t) 

這就是爲什麼

EnclosingClass.<ClassB>SomeRandomCall(new ClassB()); 

將導致A因爲ClassB爲編譯時類型不再是在方法內部可見。

這也是可見的,如果你通過javap -c -s(在Main下面的例子)看一下類(即反編譯)

static <T extends Main$A> void SomeRandomCall(T); 
    Signature: (LMain$A;)V 
    Code: 
     0: aload_0 
     1: invokestatic #18     // Method Main$Runner.Run:(LMain$A;)V 
     4: return 

它的簽名是void SomeRandomCall(A),它總是會調用Run(A)

另請參見Overloading in Java and multiple dispatch爲什麼編譯時類型與重載方法有關係。如果可以


public static class Runner{ 
    public static void Run(A a){System.out.println("A");}; 
    public static void Run(B b){System.out.println("B");}; 
    public static void Run(C c){System.out.println("C");}; 
} 

public static void main(String[] args) { 
    B b = new B(); 
    Runner.Run(b); 
    A a = b; 
    Runner.Run(a); 
} 

編譯器將只選擇正確的重載方法。例如。上面會反編譯到

public static void main(java.lang.String[]); 
    Signature: ([Ljava/lang/String;)V 
    Code: 
     0: new   #29     // class Main$B 
     3: dup 
     4: invokespecial #31     // Method Main$B."<init>":()V 
     7: astore_1 
     8: aload_1 
     9: invokestatic #32     // Method Main$Runner.Run:(LMain$B;)V 
     12: aload_1 
     13: astore_2 
     14: aload_2 
     15: invokestatic #18     // Method Main$Runner.Run:(LMain$A;)V 
     18: return 

每次調用Run將使用不同的重載版本(由數字標識invokestatic後)

+0

所以預期是正確的,但我必須路過/鑄造B,爲地方之前,我到達SomeRandomCall。我將在週一重新檢查我的代碼,如果找不到投射,我會再次發佈。 – Theopile

+0

@Theopile它作爲'B'到達'SomeRandomCall',但'SomeRandomCall'內的參數將始終爲'A',因爲這是簽名中方法參數的類型,並且對'Run(A)'的調用是硬編碼的,方法。它不能動態調用'Run(B)'。這需要一個只接受'B'的不同方法。如果您希望能夠調用所需的方法,請查看[double dispatch](http://c2.com/cgi/wiki?DoubleDispatchExample)[patterns](http://en.wikipedia.org/wiki/Visitor_pattern) 。你不可能使用單一的方法來攪拌3種不同的東西。 – zapl

+0

我試圖避免發送,但它絕對工作,謝謝。 – Theopile