2015-04-07 24 views
10

Il seguente codice compilato ed eseguito ok in Java 7, ma non riesce a compilare in Java 1.8.0 U25:Java 8 metodo ambiguo riferimento per classe generica

public class GenericTest { 

    public static class GenericClass<T> { 
     T value; 

     public GenericClass(T value) { 
      this.value = value; 
     } 
    } 

    public static class SecondGenericClass<T> { 
     T value; 

     public SecondGenericClass(T value) { 
      this.value = value; 
     } 
    } 


    public static<T >void verifyThat(SecondGenericClass<T> actual, GenericClass<T> matcher) { 
    } 

    public static<T >void verifyThat(T actual, GenericClass<T> matcher) { 
    } 

    @Test 
    public void testName() throws Exception { 
     verifyThat(new SecondGenericClass<>(""), new GenericClass<>("")); 
    } 

} 

Il messaggio di errore in Java 8 è simile al seguente:

Error:(33, 9) java: reference to verifyThat is ambiguous 
    both method <T>verifyThat(com.sabre.ssse.core.dsl.GenericTest.SecondGenericClass<T>,com.sabre.ssse.core.dsl.GenericTest.GenericClass<T>) in com.sabre.ssse.core.dsl.GenericTest and method <T>verifyThat(T,com.sabre.ssse.core.dsl.GenericTest.GenericClass<T>) in com.sabre.ssse.core.dsl.GenericTest match 

ho rivisto tutte le modifiche tra:
https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2
https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.12.2

Ma non sono riuscito a notare il motivo esatto per questo comportamento.

Edit:

Proprio per rispondere ad alcune osservazioni, è abbastanza chiaro che il compilatore sia in Java 7 e 8 sarà in grado di gestire tali invocazioni (con firme simile a ciò che resta dopo la compilazione tipo di tempo la cancellazione :

public static void verifyThat(SecondGenericClass actual, GenericClass matcher) { 
} 

public static void verifyThat(Object actual, GenericClass matcher) { 
} 

@Test 
public void testName() throws Exception { 
    verifyThat(new SecondGenericClass<>(""), new GenericClass<>("")); 
} 

il bytecode generato per entrambi i metodi generici, e cancellata è la stessa, e si presenta come segue:

public static verifyThat(Lcom/sabre/ssse/core/dsl/GenericTest$SecondGenericClass;Lcom/sabre/ssse/core/dsl/GenericTest$GenericClass;)V 
public static verifyThat(Ljava/lang/Object;Lcom/sabre/ssse/core/dsl/GenericTest$GenericClass;)V 

Edit2:

Compilation sotto javac 1.8.0_40 viene a mancare con lo stesso errore

+0

anche con generici reificati questo sarebbe un problema – jkschneider

+0

Possibile duplicato: http://stackoverflow.com/questions/28466925/java-type-inference-reference-is-ambiguous-in-java-8-but-not- java-7 – assylias

+0

sì, ma secondo JLS, il metodo più specifico dovrebbe essere scelto – xendoo

risposta

10

JLS, chapter §15.12.2.5 Choosing the Most Specific Method è una lettura difficile, ma contiene una sintesi interessante:

L'intuizione informale è che un metodo è più specifico di un altro eventuale invocazione gestito dal primo metodo potrebbe essere trasmessa all'altro senza un errore di tipo in fase di compilazione.

Possiamo facilmente confutare questo per il vostro caso con il seguente esempio:

GenericTest.<String>verifyThat(// invokes the first method 
    new SecondGenericClass<>(""), new GenericClass<>("")); 
GenericTest.<SecondGenericClass<String>>verifyThat(// invokes the second 
    new SecondGenericClass<>(""), new GenericClass<>(null)); 

quindi non c'è metodo più specifico qui, tuttavia, come mostra l'esempio, è possibile invocare entrambi i metodi utilizzando argomenti che rendono l'altro metodo inapplicabile.

In Java 7 è stato più semplice rendere un metodo inapplicabile a causa dei tentativi limitati (del compilatore) di trovare argomenti tipo per rendere più applicabili i metodi (ovvero inferenza di tipo limitata). L'espressione new SecondGenericClass<>("") ha il tipo SecondGenericClass<String> dedotta dal suo argomento "" e il gioco è fatto. Quindi per l'invocazione verifyThat(new SecondGenericClass<>(""), new GenericClass<>("")) gli argomenti avevano il tipo SecondGenericClass<String> e GenericClass<String> che ha reso inapplicabile il metodo <T> void verifyThat(T,GenericClass<T>).

Nota che esiste un esempio di invocazione ambigua che mostra l'ambiguità in Java 7 (e anche in Java 6): verifyThat(null, null); provocherà un errore del compilatore quando si utilizza javac.

Ma Java 8 ha Invocation Applicability Inference (lì abbiamo una differenza per JLS 7, un capitolo completamente nuovo ...) che consente al compilatore di scegliere gli argomenti di tipo che rendono applicabile un metodo candidato (che funziona tramite invocazioni annidate). È possibile trovare tali argomenti di tipo per il vostro caso particolare, si può anche trovare un argomento di tipo che si adatta sia,

GenericTest.<Object>verifyThat(new SecondGenericClass<>(""), new GenericClass<>("")); 

è senza ambiguità ambiguo (in Java 8), anche Eclipse è d'accordo su questo. Al contrario, l'invocazione

verifyThat(new SecondGenericClass<>(""), new GenericClass<String>("")); 

è sufficientemente specifica da rendere il secondo metodo inapplicabili e richiamare il primo metodo, che ci dà un suggerimento su quello che sta succedendo in Java 7, dove il tipo di new GenericClass<>("") è fissato come GenericClass<String> proprio come con new GenericClass<String>("").


La linea inferiore è, non è la scelta del metodo più specifico che passa da 7 a Java Java 8 (significativo), ma l'applicabilità a causa del tipo perfezionato inferenza. Una volta che entrambi i metodi sono applicabili, l'invocazione è ambigua in quanto nessuno dei due metodi è più specifico dell'altro.

1

Nel risolvere il metodo da utilizzare nel caso in cui più metodi sono applicabili, " ... i tipi di gli argomenti di una invocazione non possono, in generale, essere input per l'analisi. " La specifica Java 7 manca questa qualifica.

Se si sostituisce T nella seconda definizione di verifyThat per SecondGenericClass le firme corrispondono.

In altre parole, immaginare il tentativo di chiamare la seconda definizione di verifyThat come questo:

SecondGenericClass<String> t = new SecondGenericClass<String>("foo"); 
GenericTest.verifyThat(t, new GenericClass<String>("bar")); 

In fase di esecuzione, non ci sarebbe alcun modo per determinare quale versione di verifyThat per chiamare in quanto il tipo di variabile t è una sostituzione valida sia per SecondGenericClass<T> sia per T.

Si noti che se Java ha generato generici reificati (e lo farà un giorno), in questo esempio una firma del metodo non è più specifica dell'altra. colmare le lacune ...

+0

Il bytecode generato per tali funzioni è simile a quello, e secondo il jls, il compilatore dovrebbe usare un metodo più specifico se uno dei parametri è sottotipo di un altro: 'public static verifyThat (Lcom/sabre/ssse/core/dsl/GenericTest $ SecondGenericClass; Lcom/saber/ssse/core/dsl/GenericTest $ GenericClass;) V public static verifyThat (Ljava/lang/Object; Lcom/saber/ssse/core/dsl/GenericTest $ GenericClass;) V'
xendoo

+0

puoi indicarci quella parte della specifica? – jkschneider

+0

15.12.2.5. Scelta degli stati del metodo più specifico: _ "Il linguaggio di programmazione Java utilizza la regola che il metodo più specifico è scelto" _ ... _ "Un tipo S è più specifico di un tipo T per qualsiasi espressione se S < : T "_ ... – xendoo