2015-05-08 13 views
24

Quanto segue è codice compilato in Java 7, ma non openjdk-1.8.0.45-31.b13.fc21.Ambiguità di overload dei metodi con Java 8 primitive condizionali e non in scatola

static void f(Object o1, int i) {} 
static void f(Object o1, Object o2) {} 

static void test(boolean b) { 
    String s = "string"; 
    double d = 1.0; 
    // The supremum of types 'String' and 'double' is 'Object' 
    Object o = b ? s : d; 
    Double boxedDouble = d; 
    int i = 1; 
    f(o,     i); // fine 
    f(b ? s : boxedDouble, i); // fine 
    f(b ? s : d,   i); // ERROR! Ambiguous 
} 

Il compilatore dichiara ambigua l'ultima chiamata di metodo.

Se cambiamo il tipo del secondo parametro di f da int a Integer, quindi il codice viene compilato su entrambe le piattaforme. Perché il codice pubblicato non viene compilato in Java 8?

+2

Java 8 sul mio PC, versione 1.8.0_05, compila questo codice con successo, ma [ideone] (http://ideone.com/J0v0uE) produce l'errore del compilatore e ideone sembra eseguire Java 8u25. Potrebbe esserci una recente correzione di bug (o un bug introdotto)? – rgettman

+3

Il JLS dice che un valore dovrebbe essere attivato automaticamente o cast ma non entrambi. Se hai una versione del compilatore che consente entrambi, puoi aspettarti che una versione futura cambi. –

+1

@PeterLawrey Questo influisce su questo caso? Non vedo un'espressione cast nel codice. Non sto mai provando a fare qualcosa come trasformare un 'int' in un' Double'. –

risposta

15

Diamo prima prendere in considerazione una versione semplificata che non dispone di un condizionale ternario e non compila su Java HotSpot VM (build 1.8.0_25-b17):

public class Test { 

    void f(Object o1, int i) {} 
    void f(Object o1, Object o2) {} 

    void test() { 
     double d = 1.0; 

     int i = 1; 
     f(d, i); // ERROR! Ambiguous 
    } 
} 

L'errore del compilatore è:

Error:(12, 9) java: reference to f is ambiguous 
both method f(java.lang.Object,int) in test.Test and method f(java.lang.Object,java.lang.Object) in test.Test match 

Secondo JLS 15.12.2. Compile-Time Step 2: Determine Method Signature

procedimento è applicabile se non é applicabile da uno stretto invo cation (§15.12.2.2), invocazione libera (§15.12.2.3), o invocazione di arity variabile (§15.12.2.4).

Invocazione ha a che fare con il contesto invocazione che viene qui JLS 5.3. Invocation Contexts

spiegato Quando nessun boxe o unboxing è coinvolto in una chiamata di metodo si applica quindi rigorosa invocazione. Quando è coinvolta la boxe o l'unboxing per una chiamata di metodo, si applica la chiamata libera.

L'identificazione dei metodi applicabili è suddivisa in 3 fasi.

La prima fase (§15.12.2.2) esegue la risoluzione di sovraccarico senza consentire la conversione di boxing o unboxing o l'uso di richiamo del metodo di variabile arity. Se non viene trovato alcun metodo applicabile durante questa fase, l'elaborazione continua fino alla seconda fase.

La seconda fase (§15.12.2.3) esegue la risoluzione di sovraccarico mentre consente il boxing e l'unboxing, ma preclude ancora l'utilizzo del richiamo del metodo di variabile arity. Se non viene trovato alcun metodo applicabile durante questa fase, l'elaborazione continua fino alla terza fase.

La terza fase (§15.12.2.4) consente di combinare l'overloading con metodi di aritmetria variabile, boxing e unboxing.

Per il nostro caso non ci sono metodi applicabili con invocazione rigorosa. Entrambi i metodi sono applicabili a invocazione libera poiché il doppio valore deve essere inserito.

Secondo JLS 15.12.2.5 Choosing the Most Specific Method:

Se più di un metodo membro è accessibile e applicabile ad una chiamata di metodo , è necessario scegliere uno di fornire il descrittore per il metodo di spedizione in fase di esecuzione. La lingua Java di programmazione utilizza la regola che viene scelto il metodo più specifico.

Poi:

Un metodo applicabile M1 è più specifico di un altro metodo m2 applicabile, per un'invocazione con le espressioni degli argomenti e1, ..., ek, se uno dei seguenti sono vere :

  1. m2 è generico, e M1 è dedotto di essere più specifico di m2 per espressioni argomenti e1, ..., ek da §18.5.4.

  2. m2 non è generico, e m1 e m2 sono applicabili per stretta o allentata invocazione, e dove m1 ha tipi di parametri formali S1, ..., Sn e m2 ha formale tipi di parametri T1, ..., Tn, il tipo Si è più specifico di Ti per argomento ei per tutti i (1 ≤ i ≤ n, n = k).

  3. m2 non è generico, e m1 e m2 sono applicabili per variabile arity invocazione, e dove le variabili primi k tipi di parametri arity di m1 sono S1, ..., Sk ei tipi di parametri variabili arity primi k di m2 sono T1, ..., Tk, il tipo Si è più specifico di Ti per argomento ei per tutti i (1 ≤ i ≤ k). Inoltre, se m2 ha parametri k + 1, quindi il parametro k + 1 ° tipo di parametro arity variabile di m1 è un sottotipo del tipo di parametro arity variabile k + 1th di m2.

Le condizioni di cui sopra sono le uniche circostanze in cui un metodo può essere più specifico di un altro.

Un tipo S è più specifico di un tipo T per qualsiasi espressione se S <: T (§4.10).

può sembrare che la seconda condizione di partite di questo caso, ma in realtà lo fa non perché int non è un sottotipo di oggetto: non è vero che int <: oggetto. Tuttavia se sostituiamo int con Integer nella firma del metodo f, questa condizione dovrebbe corrispondere. Si noti che il 1 ° parametro nei metodi corrisponde a questa condizione dal Oggetto <: L'oggetto è vero.

Secondo $4.10 nessuna relazione di sottotipo/supertipo viene definita tra tipi primitivi e tipi di Classe/Interfaccia. Quindi int non è un sottotipo di Oggetto ad esempio. Pertanto int non è più specifico di Oggetto.

Poiché tra i 2 metodi non esistono metodi più specifici quindi non vi può essere strettamente più preciso e può essere più specifica metodo (JLS dà definizioni per quei termini nello stesso paragrafo JLS 15.12.2.5 Choosing the Most Specific Method) . Quindi entrambi i metodi sono specificatamente.

In questo caso il JLS dà 2 opzioni:

Se tutti i metodi specifici al massimo hanno firme di override-equivalente (§8.4.2) ...

Questo non è il nostro caso , quindi

In caso contrario, l'invocazione del metodo è ambigua e si verifica un errore in fase di compilazione.

L'errore di compilazione per il nostro caso sembra valido in base al JLS.

Cosa succede se cambiamo il tipo di parametro del metodo da int a Integer?

In questo caso, entrambi i metodi sono ancora applicabili per invocazione libera. Tuttavia, il metodo con parametro Integer è più specifico del metodo con 2 parametri Object dal numero intero <: Object. Il metodo con parametro Integer è strettamente più specifico e più specifico, quindi il compilatore lo sceglierà e non genererà un errore di compilazione.

Cosa succede se cambiamo il doppio in doppio in questa riga: double d = 1.0 ;?

In questo caso esiste esattamente 1 metodo applicabile mediante invocazione rigorosa: non è richiesto alcun boxing o unboxing per invocare questo metodo: f (Object o1, int i). Per l'altro metodo è necessario eseguire il pug di valore int in modo che sia applicabile per invocazione libera. Il compilatore può scegliere il metodo applicabile mediante invocazione rigorosa, quindi non viene generato alcun errore del compilatore.

Come Marco13 ha sottolineato nella sua testa c'è un caso simile discusso in questo post Why is this method overloading ambiguous?

Come spiegato nella risposta ci sono stati alcuni importanti variazioni relative ai meccanismi metodo di chiamata tra Java 7 e Java 8. Questo spiega il motivo per cui il codice viene compilato in Java 7, ma non in Java 8.


Ora arriva la parte divertente!

Aggiungiamo un operatore condizionale ternario:

public class Test { 

    void f(Object o1, int i) { 
     System.out.println("1"); 
    } 
    void f(Object o1, Object o2) { 
     System.out.println("2"); 
    } 

    void test(boolean b) { 
     String s = "string"; 
     double d = 1.0; 
     int i = 1; 

     f(b ? s : d, i); // ERROR! Ambiguous 
    } 

    public static void main(String[] args) { 
     new Test().test(true); 
    } 
} 

Il compilatore si lamenta metodo ambiguo invocazione. JLS 15.12.2 non impone alcuna regola speciale relativa agli operatori condizionali ternari durante l'esecuzione di richiami di metodi.

Tuttavia ci sono JLS 15.25 Conditional Operator ? : e JLS 15.25.3. Reference Conditional Expressions. Il primo categorizza le espressioni condizionali in 3 sottocategorie: espressione condizionale booleana, numerica e di riferimento. Il secondo e il terzo operando della nostra espressione condizionale hanno rispettivamente i tipi String e double. Secondo JLS la nostra espressione condizionale è un'espressione condizionale di riferimento.

Quindi, in base a JLS 15.25.3. Reference Conditional Expressions, la nostra espressione condizionale è un'espressione condizionale di riferimento poly poiché viene visualizzata in un contesto di chiamata. Il tipo della nostra espressione condizionale poli è quindi Object (il tipo di destinazione nel contesto di chiamata).Da qui potremmo continuare i passi come se il primo parametro fosse Object nel qual caso il compilatore dovrebbe scegliere il metodo con int come secondo parametro (e non lanciare l'errore del compilatore).

La parte difficile è questa nota da JLS:

sua seconda e terza espressioni operando appaiono analogamente in un contesto dello stesso tipo con tipo di destinazione T.

Da questo possiamo supporre (anche il "poly" nel nome implica questo) che nel contesto dell'invocazione del metodo i 2 operandi dovrebbero essere considerati indipendentemente. Ciò significa che quando il compilatore deve decidere se un'operazione di pugilato è richiesta per tale argomento, dovrebbe cercare in ciascuno degli operandi e vedere se può essere richiesta una boxe. Per il nostro caso specifico, String non richiede il pugilato e il doppio richiederà il pugilato. Quindi il compilatore decide che per entrambi i metodi sovraccaricati dovrebbe essere una invocazione di metodo libera. Ulteriori passi sono gli stessi come nel caso in cui invece di un'espressione condizionale ternaria usiamo un doppio valore.

Dalla spiegazione precedente sembra che lo stesso JLS sia vago e ambiguo nella parte relativa alle espressioni condizionali quando applicato a metodi sovraccaricati, quindi abbiamo dovuto formulare alcune ipotesi.

Ciò che è interessante è che il mio IDE (IntelliJ IDEA) non rileva l'ultimo caso (con l'espressione condizionale ternaria) come errore del compilatore. Tutti gli altri casi rilevati in base al compilatore java di JDK. Ciò significa che il compilatore java JDK o il parser IDE interno ha un bug.

+2

In base a questa risposta, sembra che la domanda fosse molto simile a http: // StackOverflow.it/questions/23020493/perché-è-questo-metodo-sovraccarico-ambiguo ...? – Marco13

+1

@ Marco13 Hai ragione. Ho perso una cosa importante: contesto di invocazione. Aggiornamento della mia risposta ora. – medvedev1088

+1

@ Marco13 Grazie per aver segnalato questo. Ho aggiornato la mia risposta. – medvedev1088

0

In breve:

Il compilatore non sa quale metodo per scegliere in quanto l'ordinamento tra i tipi primitivi e di riferimento non è definito nella JLS per quanto riguarda la scelta di metodo più specifico.

Quando si utilizza Integer anziché int il compilatore seleziona il metodo con Intero perché Integer è un sottotipo di Oggetto.

Quando si utilizza Double anziché double il compilatore preleva il metodo che non prevede il pugilato o l'unboxing.

Prima di Java 8 le regole erano diverse, quindi questo codice potrebbe essere compilato.

Problemi correlati