11
import java.util.function.*; 

class Test { 
    void test(int foo, Consumer<Integer> bar) { } 
    void test(long foo, Consumer<Long> bar) { } 
    void test(float foo, Consumer<Float> bar) { } 
    void test(double foo, Consumer<Double> bar) { } 
} 

Quando compilo questo con javac -Xlint Test.java ho un paio di avvertenze:Attenzione: [sovraccarichi] Metodo M1 è potenzialmente ambigua con metodo m2

Test.java:4: warning: [overloads] test(int,Consumer<Integer>) in Test is potentially ambiguous with test(long,Consumer<Long>) in Test 
    void test(int foo, Consumer<Integer> bar) { } 
     ^
Test.java:6: warning: [overloads] test(float,Consumer<Float>) in Test is potentially ambiguous with test(double,Consumer<Double>) in Test 
    void test(float foo, Consumer<Float> bar) { } 
     ^
2 warnings 

Se cambio Consumer a Supplier gli avvertimenti scompaiono. Questo programma è privo di avvisi:

import java.util.function.*; 

class Test { 
    void test(int foo, Supplier<Integer> bar) { } 
    void test(long foo, Supplier<Long> bar) { } 
    void test(float foo, Supplier<Float> bar) { } 
    void test(double foo, Supplier<Double> bar) { } 
} 

Perché è? Cosa significa questo avvertimento? In che modo questi metodi sono ambigui? È sicuro sopprimere l'avvertimento?

+0

Cosa succede quando si tenta di chiamare le funzioni di avviso? –

+1

Sembra che l'avviso verrebbe emesso solo quando: a. è un'interfaccia funzionale (ad esempio, consumatore, fornitore), e b. qualsiasi metodo dell'interfaccia contiene parametri ... Come test ho creato le mie copie delle interfacce Consumer/Supplier e ho giocato con loro. Purtroppo non ne so abbastanza di Java funzionale per sapere perché un avviso sarebbe prodotto per questo. Correzione – Kai

+0

: il punto A non è corretto, non ha bisogno di essere un'interfaccia funzionale, solo una qualsiasi interfaccia (la classe non produce alcun avviso) con un solo metodo (non predefinito) definito. Inoltre, ho modificato i parametri di test (int, Consumer) in qualcosa di simile a impossibile essere ambiguo come test (Object, Consumer) v.s. test (Map, Consumer) e tuttavia l'avviso è ancora lanciato. In quanto tale, a meno che non si impieghi qualche programmazione davvero stravagante, non penso davvero che dovrai preoccuparti di questo. – Kai

risposta

16

Questi avvisi si verificano a causa della divertente intersezione tra la risoluzione di sovraccarico, la tipizzazione del target e l'inferenza del tipo. Il compilatore pensa un po 'a te e ti avverte perché la maggior parte dei lambda sono scritti senza tipi dichiarati esplicitamente. Ad esempio, si consideri questa chiamata:

test(1, i -> { }); 

Qual è il tipo di i? Il compilatore non può dedurlo finché non ha completato la risoluzione di sovraccarico ... ma il valore 1 corrisponde a tutti e quattro i sovraccarichi. Qualsiasi sovraccarico venga scelto influenzerebbe il tipo di target del secondo argomento, che a sua volta influenzerebbe il tipo dedotto per i. In realtà non c'è abbastanza informazioni qui per il compilatore per decidere quale metodo da chiamare, quindi questa linea sarà effettivamente causare un errore di compilazione:

error: reference to test is ambiguous 
      both method test(float,Consumer<Float>) in Test and 
      method test(double,Consumer<Double>) in Test match 

(È interessante notare che le menzioni float e double sovraccarichi, ma se si commento uno di questi fuori, si ottiene lo stesso errore rispetto alla long sovraccarico.)

si potrebbe immaginare una politica in cui il compilatore ha completato la risoluzione di sovraccarico utilizzando la regola più specifico, scegliendo così il sovraccarico con l'int arg. Avrebbe quindi un tipo di bersaglio definito da applicare al lambda. I progettisti del compilatore hanno ritenuto che fosse troppo sottile e che ci sarebbero stati casi in cui i programmatori sarebbero rimasti sorpresi di quale sovraccarico finisse per essere chiamato. Invece di compilare i programmi in un modo forse inaspettato, hanno ritenuto che fosse più sicuro rendere questo un errore e costringere il programmatore a disambiguarlo.

Il compilatore sta emettendo degli avvisi sulle dichiarazioni del metodo per indicare che il codice probabile che un programmatore scriverà per chiamare uno di questi metodi (come mostrato sopra) provocherà un errore in fase di compilazione.

per disambiguare la chiamata, si sarebbe invece dovuto scrivere

test(1, (Integer i) -> { }); 

o dichiarare qualche altro tipo esplicito per il parametro i. Un altro modo è aggiungere un cast prima del lambda:

test(1, (Consumer<Integer>)i -> { }); 

ma questo è discutibilmente peggiore. Probabilmente non vuoi che i chiamanti della tua API debbano lottare con questo genere di cose in ogni punto della chiamata.

Queste avvertenze non si verificano per il caso Supplier, perché il tipo di un fornitore può essere determinato tramite ragionamento locale, senza alcuna inferenza di tipo.

Probabilmente vorrete ripensare il modo in cui state mettendo questa API insieme.Se vuoi davvero metodi con questi tipi di argomenti, potresti fare bene a rinominare i metodi testInt, testLong, ecc. Ed evitare di sovraccaricarli completamente. Si noti che le API Java SE hanno fatto ciò in casi simili, come comparingInt, comparingLong e comparingDouble su Comparator; e anche mapToInt, mapToLong e mapToDouble su Stream.

+0

Spiegazione molto bella! Nessuna domanda lasciata dopo aver letto. – TWiStErRob

Problemi correlati