2009-10-15 6 views
91

sto sperimentando con questo codice:selezione metodo di overload in base al tipo reale del parametro

interface Callee { 
    public void foo(Object o); 
    public void foo(String s); 
    public void foo(Integer i); 
} 

class CalleeImpl implements Callee 
    public void foo(Object o) { 
     logger.debug("foo(Object o)"); 
    } 

    public void foo(String s) { 
     logger.debug("foo(\"" + s + "\")"); 
    } 

    public void foo(Integer i) { 
     logger.debug("foo(" + i + ")"); 
    } 
} 

Callee callee = new CalleeImpl(); 

Object i = new Integer(12); 
Object s = "foobar"; 
Object o = new Object(); 

callee.foo(i); 
callee.foo(s); 
callee.foo(o); 

Questo stampa foo(Object o) tre volte. Mi aspetto che la selezione del metodo prenda in considerazione il tipo di parametro reale (non dichiarato). Mi sto perdendo qualcosa? C'è un modo per modificare questo codice in modo che possa stampare foo(12), foo("foobar") e foo(Object o)?

risposta

72

mi aspetto che la selezione del metodo di prendere in considerazione l' reale (non la dichiarata ) tipo di parametro. Mi manca qualcosa?

Sì. La tua aspettativa è sbagliata. In Java, la spedizione del metodo dinamico avviene solo per l'oggetto su cui viene richiamato il metodo, non per i tipi di parametri dei metodi sovraccaricati.

Citando il Java Language Specification:

Quando un metodo viene richiamato (§15.12), il numero di argomenti reali (ed eventuali argomenti di tipo esplicite) e le tipi di compilazione degli argomenti vengono utilizzati, al momento della compilazione, per determinare la firma del metodo che verrà richiamato (§15.12.2). Se il metodo da richiamare è un metodo di istanza , il metodo effettivo su da richiamare verrà determinato al momento dell'esecuzione , utilizzando la ricerca del metodo dinamico (§15.12.4).

+3

Puoi spiegare le specifiche che hai citato per favore. Le due frasi sembrano contraddirsi l'una con l'altra. L'esempio sopra utilizza i metodi di istanza, tuttavia il metodo invocato non è chiaramente determinato in fase di esecuzione. –

+10

@Alex Worden: il tipo di tempo di compilazione ** dei parametri del metodo ** viene utilizzato per determinare la firma del metodo da chiamare, in questo caso 'foo (Object)'. In fase di esecuzione, la classe dell'oggetto ** il metodo viene richiamato ** determina quale implementazione di quel metodo viene chiamata, tenendo conto che potrebbe trattarsi di un'istanza di una sottoclasse del tipo dichiarato che sovrascrive il metodo. –

12

Possibilità di inviare una chiamata a un metodo basato su tipi di argomenti è chiamato multiple dispatch. In Java questo è fatto con Visitor pattern.

Tuttavia, dal momento che si tratta di Integer se String s, non è possibile incorporare facilmente questo modello (non è possibile modificare queste classi). Quindi, un gigante switch su runtime dell'oggetto sarà la tua arma preferita.

11

In Java il metodo da chiamare (come nel caso in cui la firma del metodo da utilizzare) viene determinato al momento della compilazione, quindi va con il tipo di tempo di compilazione.

Il modello tipico per aggirare questo è controllare il tipo di oggetto nel metodo con la firma dell'oggetto e delegare al metodo con un cast.

public void foo(Object o) { 
     if (o instanceof String) foo((String) o); 
     if (o instanceof Integer) foo((Integer) o); 
     logger.debug("foo(Object o)"); 
    } 

Se si dispone di molti tipi e questo è ingestibile, quindi overloading dei metodi probabilmente non è l'approccio giusto, piuttosto il metodo pubblico dovrebbe solo prendere oggetti e mettere in atto una sorta di modello di strategia di delegare la gestione appropriata per ogni tipo di oggetto .

2

Java analizza il tipo di riferimento quando tenta di determinare quale metodo chiamare.Se si vuole forzare il codice si sceglie il metodo 'giusta', è possibile dichiarare i vostri campi come istanze del tipo specifico:

Integeri = new Integer(12); 
String s = "foobar"; 
Object o = new Object(); 

Si potrebbe anche lanciare i vostri params come il tipo di param:

callee.foo(i); 
callee.foo((String)s); 
callee.foo(((Integer)o); 
71

Come accennato prima la risoluzione di sovraccarico viene eseguita in fase di compilazione.

Java Puzzlers ha un bel esempio per questo:

Puzzle 46: Il caso del costruttore Confondere

Questo puzzle si presenta con due costruttori di confusione. Il metodo principale richiama un costruttore, ma quale? L'output del programma dipende dalla risposta. Che cosa stampa il programma o è legale ?

public class Confusing { 

    private Confusing(Object o) { 
     System.out.println("Object"); 
    } 

    private Confusing(double[] dArray) { 
     System.out.println("double array"); 
    } 

    public static void main(String[] args) { 
     new Confusing(null); 
    } 
} 

Soluzione 46: Caso del costruttore Confondere

... processo di risoluzione di sovraccarico di Java opera in due fasi. La prima fase seleziona tutti i metodi o costruttori che sono accessibili e applicabili. La seconda fase seleziona lo più specifico dei metodi o dei costruttori selezionati nella prima fase. Un metodo o costruttore è meno specifico di rispetto a un altro se può accettare qualsiasi parametro passato all'altro [JLS 15.12.2.5].

Nel nostro programma, entrambi i costruttori sono accessibili e applicabili. Il costruttore Confondere (Object) accetta qualsiasi parametro passato Confondere (double []), così Confondere (Object) è meno specifico. (Ogni doppio array è un oggetto , ma non ogni oggetto è un doppio array.) Il costruttore più specifica è quindi Confondere (double []), il che spiega l'output del programma.

Questo comportamento ha senso se si passa un valore di tipo double []; è controintuitivo se si passa nullo. La chiave per comprendere questo enigma è che il test per quale metodo o costruttore è più specifico non utilizza i parametri effettivi: i parametri che appaiono nel richiamo. Vengono utilizzati solo per determinare quali sovraccarichi sono applicabili. Una volta che il compilatore determina quali sovraccarichi sono applicabili e accessibili, seleziona il sovraccarico più specifico, utilizzando solo i parametri formali: i parametri che appaiono nella dichiarazione.

di avviare la Confondere (Object) costruttore con un parametro nullo, scrivere nuovo Confondere ((Object) null). Ciò garantisce che solo Confusione (oggetto) sia applicabile.Più in genere, per forzare il compilatore a selezionare un sovraccarico specifico, trasmettere i parametri effettivi ai tipi dichiarati dei parametri formali.

+4

Spero che non sia troppo tardi per dire "una delle migliori spiegazioni su SOF". Grazie :) – TheLostMind

+3

Non è mai troppo tardi per le buone parole :) –

+4

Credo che se abbiamo aggiunto anche il costruttore 'private Confusing (int [] iArray)' non sarebbe riuscito a compilare, non sarebbe? Perché ora ci sono due costruttori con la stessa specificità. – Risser

4

Avevo un problema simile con la chiamata del costruttore giusto di una classe chiamata "Parametro" che poteva prendere diversi tipi di base Java come String, Integer, Boolean, Long, ecc. Data una serie di Oggetti, voglio convertire in una matrice dei miei oggetti Parameter chiamando il costruttore più specifico per ciascun oggetto nell'array di input. Volevo anche definire il parametro costruttore (oggetto o) che genererebbe un IllegalArgumentException. Ovviamente ho trovato questo metodo invocato per ogni oggetto nel mio array.

La soluzione che ho usato è stato quello di cercare il costruttore attraverso la riflessione ...

public Parameter[] convertObjectsToParameters(Object[] objArray) { 
    Parameter[] paramArray = new Parameter[objArray.length]; 
    int i = 0; 
    for (Object obj : objArray) { 
     try { 
      Constructor<Parameter> cons = Parameter.class.getConstructor(obj.getClass()); 
      paramArray[i++] = cons.newInstance(obj); 
     } catch (Exception e) { 
      throw new IllegalArgumentException("This method can't handle objects of type: " + obj.getClass(), e); 
     } 
    } 
    return paramArray; 
} 

No brutto instanceof, istruzioni switch, o un motivo visitatore richiesto! :)

0

Se esiste una corrispondenza esatta tra il numero e i tipi di argomenti specificati nella chiamata al metodo e la firma del metodo di un metodo sovraccarico, questo è il metodo che verrà richiamato. Stai usando riferimenti a oggetti, quindi java decide al momento della compilazione che per il parametro Object esiste un metodo che accetta direttamente Object. Quindi ha chiamato quel metodo 3 volte.

Problemi correlati