2013-04-11 4 views
14

Ran in un problema interessante; la seguente classe compila:Generici in metodi sovrascritti

public class Test { 

    public static void main(String[] args) throws Exception { 
     A a = new A(); 
     B b = new B(); 

     foo(a); 
     foo(b); 
    } 

    private static void foo(A a) { 
     System.out.println("In A"); 
    } 

    private static void foo(B b) { 
     System.out.println("In B"); 
    } 

    private static class A {} 

    private static class B extends A {} 

} 

ma questo non riesce:

public class Test { 

    public static void main(String[] args) throws Exception { 
     A<String> a = new A<>(); 
     B b = new B(); 

     foo(a); 
     foo(b); 
    } 

    private static void foo(A<String> a) { 
     System.out.println("In A"); 
    } 

    private static void foo(B b) { 
     System.out.println("In B"); 
    } 

    private static class A<T> {} 

    private static class B extends A {} 

} 

con questo errore:

Test.java:8: error: reference to foo is ambiguous, both method foo(A<String>) in Test and method foo(B) in Test match    
     foo(b);                              
     ^                               
Note: Test.java uses unchecked or unsafe operations.                    
Note: Recompile with -Xlint:unchecked for details.                     
1 error 

avrei pensato che a causa di cancellazione del tipo che questi sarebbero essenzialmente identici. Qualcuno sa cosa sta succedendo qui?

+1

Cosa significa "A a = new A <>();'? Che ne dite di 'A a = new A ();'? – Trinimon

+6

@Trinimon Questa è solo una notazione abbreviata Java7 per A a = new A (); –

risposta

2

Prima dell'alba di farmaci generici, Java aveva metodi come

public class Collections 

    public void sort(List list) {...}    [1] 

E il codice utente potrebbe avere cose come

public class MyList implements List ...    [2] 

MyList myList = ...; 
Collections.sort(myList);       [3] 

Quando farmaci generici è stato aggiunto a Java, si è deciso di convertire exis ting classi e metodi a quelli generici, senza rompere alcun codice utilizzandoli. Questo è un grande risultato in termini di difficoltà, al grande prezzo di lasciare la lingua complicata e imperfetta.

Quindi [1] è stato generato, ma [3] deve ancora essere compilato così com'è, senza dover generare [2].

L'hack è in §15.12.2.3

Ai can be converted by method invocation conversion (§5.3) to Si

fondamentalmente dicendo che se il tipo di argomento (Ai) è crudo, quindi cancellare il tipo di parametro (Si) anche ai fini della corrispondenza.

Torna al tuo esempio, vediamo perché foo(A<String>) è considerato applicabile per foo(b).

Tuttavia c'è un'altra domanda - è foo(A<String>) applicabile per [§15.12.2.2]? La risposta sembra essere "no" dalla lettera della specifica. Ma potrebbe essere un bug delle specifiche.

+0

Right, foo (A ) è espressamente applicabile per chiamare con un argomento di tipo B per compatibilità con le versioni precedenti. La parte dispari è il modo in cui questa logica interagisce con la logica specificata in 15.12.2.5 in modo che foo (B) non sia più considerato massimamente specifico per l'invocazione foo (b). –

+1

, 'B' non è un sottotipo di' A ', quindi' foo (B) 'non è più specifico di' foo (A ) ' – ZhongYu

+0

Quindi, la stranezza è che B è considerato un sottotipo di A per scopi di richiamo del metodo (per bw compat) ma non allo scopo di determinare il metodo più specifico? Penso di aver bisogno di rileggere nuovamente le specifiche, sembra un modo davvero strano per gestire la situazione. –

1
private static class B extends A {} 

sei omettendo gli argomenti di tipo per A qui. Quello che probabilmente dire è

private static class B<T> extends A<T> {} 

Inoltre, B b = new B() non ha <String> argomento, neanche; si deve fare

B<String> b = new B<String>(); 

... E, infine,

private static void foo(B b) { 
    System.out.println("In B"); 
} 

dovrebbe essere qualcosa di simile a

private static void foo(B<String> b) { 
    System.out.println("In B"); 
} 

In generale, se c'è un tipo Foo che ha un argomento generico, Foo dovrebbe fondamentalmente sempre avere un argomento tipo. In questo caso specifico, class B extends A non aveva un argomento di tipo per A, quindi B aveva bisogno di un argomento di tipo, quindi era necessario un argomento di tipo ovunque menzionato B. (L'unica importante eccezione a questa regola è instanceof espressioni, ma non hanno alcun qui.)

+0

Che dà lo stesso errore. –

+0

Vedere l'aggiornamento. –

+0

Che dà lo stesso errore :) –

3

La causa è che si stia mescolando farmaci generici e tipi di prime (B dovrebbe essere dichiarato come class B<T> extends A<T> o class B extends A<SomeType>).

La vera ragione per cui questo sta accadendo è sepolto da qualche parte in the JLS, section #15.12.2.7 e seguenti - buona fortuna a articolarlo succintamente ;-)

+0

Stessi commenti della risposta di @Louis. Questo cambiamento non è sufficiente da solo. –

+0

Penso che la sezione [# 15.12.2.5] (http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.12.2.5) potrebbe essere più applicabile qui. Il compilatore non sta cercando di inferire un tipo; sta cercando di decidere quale metodo invocare. Mi sembra che la domanda dovrebbe essere, perché il compilatore non pensa che 'foo (B)' sia più specifico di 'foo (A )', dato che sa che 'b' è di tipo' B'? – matts

+0

Sembra che sia probabile una combinazione di queste specifiche, riferimenti 15.12.2.5 ai tipi dedotti usando il processo in 15.12.2.7. Scavando tra quelli che non sono certo un fan della loro notazione - sembra che avrebbero potuto scegliere qualcosa di un po 'più espressivo. –