2015-09-09 24 views
62

CodicePerché il compilatore preferisce un sovraccarico int a un sovraccarico del char di vararg per un char?

public class TestOverload { 

    public TestOverload(int i){System.out.println("Int");} 
    public TestOverload(char... c){System.out.println("char");} 

    public static void main(String[] args) { 
     new TestOverload('a'); 
     new TestOverload(65); 
    } 
} 

uscita

Int 
Int 

E 'un comportamento previsto? Se è così, allora perché? Mi aspetto: char, int

Nota: io sto usando Java 8

+0

soluzione - aggiungere un '(char) metodo' – ZhongYu

+46

[La prima regola di programmazione: è sempre colpa tua] (https://blog.codinghorror.com/the-first-rule-of-programming-its-always-your-fault/) – gronostaj

+5

Scelta di design interessante che hanno fatto lì. In C# credo che l'equivoco di "..." sia param. C# avrebbe scelto il carattere, un comportamento che ritengo più sensato, ma naturalmente è una lingua diversa. –

risposta

79

Metodi con varargs (...) hanno la priorità più bassa quando il compilatore determina quale metodo per scegliere sovraccarico. Pertanto TestOverload(int i) viene scelto su TestOverload(char... c) quando si chiama TestOverload con un singolo parametro char'a', poiché un char può essere promosso automaticamente a int.

JLS 15.12.2:

  1. La prima fase (§15.12.2.2) esegue la risoluzione di sovraccarico senza permettere boxe o conversione unboxing, o l'uso di variabile arità invocazione di metodi. Se non viene trovato alcun metodo applicabile durante questa fase , l'elaborazione continua fino alla seconda fase. Ciò garantisce che tutte le chiamate valide nel linguaggio di programmazione Java prima di Java SE 5.0 non sono considerate ambigue come il risultato dell'introduzione di metodi di variabile arit, boxing implicito e/o unboxing. Tuttavia, la dichiarazione di un metodo di variabile aritmetica (§8.4.1) può modificare il metodo scelto per una determinata espressione del metodo di metodo , poiché un metodo di aritma variabile viene considerato come un metodo fisso nella prima fase. Ad esempio, dichiarando m (Object ...) in una classe che dichiara già m (Object) causa m (Object) a più lungo per alcune espressioni di chiamata (come m (null)), come m (Object []) è più specifico.

  2. La seconda fase (§15.12.2.3) esegue la risoluzione di sovraccarico consentendo boxe e unboxing, ma preclude comunque l'utilizzo del metodo variabile arietà invocazione. Se non viene trovato alcun metodo applicabile durante questa fase , l'elaborazione continua fino alla terza fase. Ciò garantisce che un metodo non venga mai scelto tramite il richiamo del metodo di variabile aritmetica se è applicabile tramite il metodo di richiamo del metodo arity fisso.

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

EDIT:

E si desidera forzare il compilatore a chiamare il costruttore TestOverload(char... c), è possibile passare al costruttore chiamare un char[]:

new TestOverload (new char[] {'a'}); 
+0

Oh, anche se chiamerà il metodo corretto o darà un errore di ambiguità. C'è un modo per chiamare il metodo corretto senza cambiare la firma del metodo? –

+7

@AmitGupta È possibile passare un array di caratteri con un singolo carattere: 'nuovo TestOverload (nuovo char [] {'a'});' – Eran

+3

'nuovo TestOverload (nuovo char [] {'a'})' –

36

Sì, si prevede comportamento. La precedenza per il metodo di chiamata va in questo modo:

  1. Widending
  2. Boxing
  3. varargs

Di seguito è estratto da Java docs relative al medesimo: -

Il processo di determinazione l'applicabilità inizia determinando i metodi potenzialmente applicabili (§15.12.2.1).

Il resto del processo è suddiviso in tre fasi, per garantire la compatibilità con le versioni del linguaggio di programmazione Java precedenti a Java SE 5.0. Le fasi sono:

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.

Ciò garantisce che tutte le chiamate valide nel linguaggio di programmazione Java prima di Java SE 5.0 non siano considerate ambigue in seguito all'introduzione di metodi di arbitraggio variabile, boxing implicito e/o unboxing. Tuttavia, la dichiarazione di un metodo arity variabile (§8.4.1) può cambiare il metodo scelto per una determinata espressione di richiamo del metodo di metodo, poiché un metodo di arity variabile viene trattato come un metodo di arity fisso nella prima fase. Ad esempio, dichiarando m (Object ...) in una classe che dichiara già m (Object) causa m (Object) a non essere più scelto per alcune espressioni di invocazione (come m (null)), come m (Object []) è più specifico.

La seconda fase (§15.12.2.3) esegue la risoluzione di sovraccarico consentendo al tempo stesso boxing e unboxing, ma preclude comunque 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.

Ciò garantisce che un metodo non venga mai scelto tramite il richiamo del metodo arity variabile se è applicabile tramite invocazione del metodo arity fisso.

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

+0

Scusa, ho iniziato a modificare quella domanda ma poi l'ho realizzata dietro le mie capacità :-). Quindi downvoted e salvato ciò che mai ho risolto. Inizialmente ho pensato solo a domande del corpo. Ma non posso formattare alcuna domanda. – Panther

13

Solid consigliare dai Joshua Bloch (Effective Java, 2nd Ed): "scegliere solo come argomenti per un metodo di overload quelli che hanno tipi differenti -radically-"

Un oggetto con un tipo radicalmente diverso è uno che non può essere ragionevolmente gettato in un altro dei tipi di argomento.Seguendo questa regola puoi potenzialmente risparmiare ore di debugging di un errore misterioso che può accadere quando il compilatore sceglie in fase di compilazione il metodo di overload che non ti aspetti.

le linee di codice violano questa regola e aprire la porta per i bug:

public TestOverload(int i){System.out.println("Int");} 
public TestOverload(char... c){System.out.println("char");} 

Un char è interconvertibili con una int e quindi l'unico modo è possibile prevedere cosa accadrà con le invocazioni è di andare a la specifica del linguaggio Java e leggi le regole un po 'arcane su come vengono risolti i sovraccarichi.

Fortunatamente, questa situazione non dovrebbe richiedere la ricerca JLS. Se hai argomenti che non sono radicalmente diversi tra loro, probabilmente l'opzione migliore è non sovraccaricare. Dare ai metodi nomi diversi in modo che non ci sia alcuna possibilità di errore o confusione da parte di chiunque possa aver bisogno di mantenere il codice.

Il tempo è denaro.

+1

Penso che l'overloading con tipi simili sia accettabile se il comportamento dei sovraccarichi è molto simile. per esempio. 'int max (int x, int y)' e 'long max (long x, long y)' stanno perfettamente bene. – CodesInChaos

+0

Solo il problema è quando i costruttori non sono sovraccaricati di metodi. Non puoi scegliere un nome diverso. Come nel mio caso. Tuttavia ho modificato il mio codice in modo che utilizzi questo polimorfismo runtime per determinare quale metodo dovrebbe essere chiamato. :) –

+0

Per amplificare un punto chiave, quale tipo di metodo potrebbe * utilmente * prendere un char o un int e fare qualcosa di distintivo in ogni caso? Non riesco davvero a pensarne uno. Sono anche d'accordo che "l'opzione migliore non è sovraccaricare". Bella risposta a una disfunzione linguistica. – msw

0

Presi il codice this link e modificate alcune parti di esso:

public static void main(String[] args) { 
    Byte i = 5; 
    byte k = 5; 
    aMethod(i, k); 
} 

//method 1 
static void aMethod(byte i, Byte k) { 
    System.out.println("Inside 1"); 
} 

//method 2 
static void aMethod(byte i, int k) { 
    System.out.println("Inside 2"); 
} 

//method 3 
static void aMethod(Byte i, Byte k) { 
    System.out.println("Inside 3 "); 
} 

//method 4 
static void aMethod(Byte i, Byte ... k) { 
    System.out.println("Inside 4 "); 
} 

Il compilatore dà errore (Il metodo è ambiguo per il tipo sovraccarico) per metodi 1, 2 e 3, ma non 4 (perché ?)

La risposta sta nel meccanismo utilizzato da java per associare le chiamate di metodo alle firme dei metodi. Il meccanismo avviene in tre fasi, in ciascuna fase se esso trova metodo di corrispondenza all'arresto:

+ prima fase: uso ampliamento a trovare Metodo corrispondenza (nessun metodo corrispondente trovate)

+ fase due: (anche) utilizzare boxing/unboxing per trovare corrispondenza metodo (metodo di 1,2 e 3 partite)

+ fase tre: (anche) utilizzare args var (! metodo 4 partite)