Errore del compilatore: riferimento alla chiamata ambigua

Caso 1

static void call(Integer i) { System.out.println("hi" + i); } static void call(int i) { System.out.println("hello" + i); } public static void main(String... args) { call(10); } 

Output del caso 1: hello10

Caso 2

 static void call(Integer... i) { System.out.println("hi" + i); } static void call(int... i) { System.out.println("hello" + i); } public static void main(String... args) { call(10); } 

Mostra il reference to call ambiguous all’errore di compilazione reference to call ambiguous . Ma non ero in grado di capire. Perché ? Ma, quando ho commentato uno qualsiasi dei metodi call() del Case 2 , allora funziona perfettamente. Qualcuno può aiutarmi a capire, cosa sta succedendo qui?

    La ricerca del metodo più specifico è definita in modo molto formale in Java Language Specificaion (JLS). Ho estratto sotto gli articoli principali che si applicano cercando di rimuovere le formule formali il più ansible.

    In sintesi, gli elementi principali che si applicano alle tue domande sono:

    • JLS 15.12.2 : il tuo caso d’uso rientra nella fase 3:

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

    • Quindi JLS 15.12.2.4 determina fondamentalmente che entrambi i metodi sono applicabili, perché 10 può essere convertito sia in un Integer... o in un int... Fin qui tutto bene. E il paragrafo conclude:

    Il metodo più specifico (§15.12.2.5) è scelto tra i metodi variabili applicabili.

    • Il che ci porta a JLS 15.12.2.5 . Questo paragrafo fornisce le condizioni in base alle quali un metodo arity m(a...) è più specifico di un altro metodo arity m(b...) . Nel tuo caso d’uso con un parametro e senza generici, si riduce a:

    m(a...) è più specifico di m(b...) iif a <: b , dove <: significa is a subtype of .

    Succede che int non è un sottotipo di Integer e Integer non è un sottotipo di int .

    Per utilizzare il linguaggio JLS, entrambi i metodi di call sono quindi massimamente specifici (nessun metodo è più specifico dell'altro). In questo caso, lo stesso paragrafo conclude:

    • Se tutti i metodi massimamente specifici hanno firme override-equivalenti (§8.4.2) [...] => non è il tuo caso in quanto non sono coinvolti generici e Integer e int sono parametri diversi
    • Altrimenti, diciamo che l'invocazione del metodo è ambigua e si verifica un errore in fase di compilazione.

    NOTA

    Se hai sostituito Integer... di long... per esempio, avresti int <: long e il metodo più specifico sarebbe call(int...) *.
    Allo stesso modo, se hai sostituito int... con Number... , il metodo call(Integer...) sarebbe il più specifico.

    * C'era in realtà un bug in JDK prima di Java 7 che mostrava una chiamata ambigua in quella situazione .

    Sembra che sia correlato al bug # 6886431 , che sembra essere corretto in OpenJDK 7.

    Di seguito è la descrizione del bug,

    Descrizione del bug:

    Quando invoco un metodo con le seguenti firme sovraccariche, mi aspetto un errore di ambiguità (assumendo che gli argomenti siano compatibili con entrambi):

     int f(Object... args); int f(int... args); 

    javac considera il secondo più specifico del primo. Questo comportamento è ragionevole (lo preferisco), ma è incoerente con JLS (15.12.2).

    da JLS 15.12.2.2

    JLS 15.12.2.2 Scegli il metodo più specifico

    Se più di una dichiarazione di metodo è accessibile e applicabile a una chiamata di metodo, è necessario sceglierne una per fornire il descrittore per la spedizione del metodo di runtime. Il linguaggio di programmazione Java utilizza la regola per scegliere il metodo più specifico. L’intuizione informale è che una dichiarazione di metodo è più specifica di un’altra se qualsiasi chiamata gestita dal primo metodo può essere passata all’altra senza un errore di tipo in fase di compilazione.

    nessuno dei due metodi può essere passato all’altro (i tipi per int [] e Integer [] arent related) quindi la chiamata è ambigua

    Il compilatore non sa quale metodo dovrebbe essere chiamato. Per risolvere questo problema, devi trasmettere i parametri di input.

     public static void main(String... args) { call((int)10); call(new Integer(10)); } 

    MODIFICARE:

    È perché il compilatore tenta di convertire l’intero in int, pertanto, un cast implicito avviene prima dell’invocazione del metodo di call . Quindi il compilatore cerca quindi tutti i metodi con quel nome che può contenere. E tu ne hai 2, quindi il compilatore non sa quale di entrambi dovrebbe essere chiamato.

    Se più di un metodo può essere applicabile, rispetto alla specifica del linguaggio Java scegliendo il metodo più specifico , paragrafo 15.12.2.5 :

    Un metodo di membro variabile arity denominato m è più specifico di un altro metodo di membro variabile arity con lo stesso nome se ( <: means subtyping ):

    1. Un metodo membro ha n parametri e l'altro ha parametri k, dove n ≥ k, e:
      • I tipi dei parametri del primo metodo membro sono T1, ..., Tn-1, Tn []. ( abbiamo solo un T_n [], che è intero [], n = 1 )
      • I tipi dei parametri dell'altro metodo sono U1, ..., Uk-1, Uk []. (di nuovo solo un paramenter, che è int [], k = 1 )
      • Se il secondo metodo è generico allora lascia che R1 ... Rp (p ≥ 1) siano i suoi parametri tipo, sia Bl il limite dichiarato di Rl (1 ≤ l ≤ p), sia A1 ... Ap sia il tipo argomenti dedotti (§15.12.2.7) per questa invocazione sotto i vincoli iniziali Ti << Ui (1 ≤ i ≤ k-1) e Ti << Uk (k ≤ i ≤ n), e sia Si = Ui [R1 = A1 ,. .., Rp = Ap] (1 ≤ i ≤ k). (il metodo non è generico )
      • Altrimenti, lasciare Si = Ui (1 ≤ i ≤ k). ( S1 = int [] )
      • Per tutti i j da 1 a k-1, Tj <: Sj e, ( niente qui )
      • Per tutti j da k a n, Tj <: Sk e, ( Confronta T1 <: S1, Intero [] <: int [] )
      • Se il secondo metodo è un metodo generico come descritto sopra, allora Al <: Bl [R1 = A1, ..., Rp = Ap] (1 ≤ l ≤ p). (il metodo non è generico )

    Sebbene int primitivo sia autoboxed per wrapper Integer , int[] non è autoboxed su Integer[] , che la prima condizione non regge.

    La seconda condizione è quasi la stessa.

    Ci sono anche altre condizioni che non valgono, e quindi a causa di JLS:

    diciamo che l'invocazione del metodo è ambigua e si verifica un errore in fase di compilazione.

    Questa domanda è già stata posta più volte. La parte difficile è che f(1, 2, 3) sta chiaramente passando a int , quindi perché il compilatore non può scegliere la versione f(int...) ? La risposta deve trovarsi da qualche parte nel JLS , sul quale mi sto graffiando la testa

    In base al §15.12.2.4, entrambi i metodi sono metodi variabili applicabili , quindi il passo successivo è identificare quello più specifico.

    Unofortunatamente, il §15.12.2.5 usa il test del sottotipo T i <: S i tra i parametri formali f1 (T 1 , .. T n ) e f2 (S 1 , .. S n ) per identificare il metodo di destinazione, e poiché vi è nessuna relazione di sottotipo tra Integer e int , nessuno vince , perché né int:> IntegerInteger:> int . Alla fine del paragrafo è indicato:

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

    Un metodo m1 è strettamente più specifico di un altro metodo m2 se e solo se m1 è più specifico di m2 e m2 non è più specifico di m1.

    Si dice che un metodo sia massimamente specifico per un’invocazione di metodo se è accessibile e applicabile e non esiste un altro metodo applicabile e accessibile che sia strettamente più specifico.

    È ansible che nessun metodo sia il più specifico, perché ci sono due o più metodi che sono massimamente specifici. In questo caso:

    1. […]

    2. Altrimenti, diciamo che l’invocazione del metodo è ambigua e si verifica un errore in fase di compilazione.

    In allegato un post sul blog di Gilad Bracha (vedi l’articolo 2), a sua volta collegato nel bug report dalla risposta di @ Jayamhona.