Selezione del metodo di overload in base al tipo reale del parametro

Sto sperimentando questo codice:

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 stampi foo(12) , foo("foobar") e foo(Object o) ?

Mi aspetto che la selezione del metodo prenda in considerazione il tipo di parametro reale (non dichiarato). Mi sto perdendo qualcosa?

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

Citando la specifica del linguaggio Java :

Quando viene invocato un metodo (§15.12), il numero di argomenti effettivi (e qualsiasi argomento di tipo esplicito) e i tipi di tempo di compilazione degli argomenti vengono utilizzati, in fase di compilazione, per determinare la firma del metodo che verrà richiamato ( §15.12.2). Se il metodo che deve essere invocato è un metodo di istanza, il metodo effettivo da richiamare verrà determinato in fase di esecuzione, utilizzando la ricerca del metodo dinamico (§15.12.4).

Come accennato prima, la risoluzione del sovraccarico viene eseguita al momento della compilazione.

Java Puzzlers ha un bell’esempio per questo:

Puzzle 46: The Case of the Confusing Constructor

Questo puzzle ti presenta con due costruttori confusi. Il metodo principale richiama un costruttore, ma quale? L’output del programma dipende dalla risposta. Che cosa stampa il programma o è addirittura 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 che confonde

… Il processo di risoluzione del 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 il più specifico dei metodi o costruttori selezionati nella prima fase. Un metodo o costruttore è meno specifico di 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 Confusing (Object) accetta qualsiasi parametro passato a Confusing (double []) , quindi Confusing (Object) è meno specifico. (Ogni doppio array è un Object , ma non ogni Object è un doppio array .) Il costruttore più specifico è quindi Confusing (double []) , che spiega l’output del programma.

Questo comportamento ha senso se si passa un valore di tipo double [] ; è controintuitivo se si passa null . La chiave per comprendere questo enigma è che il test per quale metodo o costruttore è più specifico non usa i parametri attuali : i parametri che appaiono nell’invocazione. Sono 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.

Per richiamare il costruttore Confusing (Object) con un parametro null , scrivere new Confusing ((Object) null) . Ciò garantisce che solo la confusione (object) sia applicabile. Più in generale, per forzare il compilatore a selezionare un sovraccarico specifico, trasmettere i parametri effettivi ai tipi dichiarati dei parametri formali.

La capacità di inviare una chiamata a un metodo basato su tipi di argomenti è chiamata dispacciamento multiplo . In Java questo è fatto con il pattern Visitor .

Tuttavia, dal momento che hai a che fare con Integer s e String s, non puoi facilmente incorporare questo modello (non puoi modificare queste classi). Quindi, un gigantesco switch al tempo di esecuzione degli oggetti sarà la tua arma preferita.

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

Lo schema tipico per aggirare questo è controllare il tipo di object nel metodo con la firma dell’object e debind 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 ne hai molti tipi e questo è ingestibile, allora l’overloading dei metodi non è probabilmente l’approccio giusto, piuttosto il metodo pubblico dovrebbe solo prendere Object e implementare una sorta di modello di strategia per debind la gestione appropriata per tipo di object.

Ho avuto un problema simile con la chiamata al costruttore giusto di una class chiamata “Parameter” che poteva prendere diversi tipi Java di base come String, Integer, Boolean, Long, ecc. Data una serie di oggetti, voglio convertirli in una matrice dei miei oggetti Parameter chiamando il costruttore più specifico per ciascun object nell’array di input. Volevo anche definire il parametro costruttore (object o) che genererebbe un IllegalArgumentException. Ovviamente ho trovato questo metodo invocato per ogni object nel mio array.

La soluzione che ho usato era di cercare il costruttore tramite la riflessione …

 public Parameter[] convertObjectsToParameters(Object[] objArray) { Parameter[] paramArray = new Parameter[objArray.length]; int i = 0; for (Object obj : objArray) { try { Constructor 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; } 

Non sono richieste brutte istanze, istruzioni switch o pattern visitatori! 🙂

Java analizza il tipo di riferimento quando prova a determinare quale metodo chiamare. Se vuoi forzare il tuo codice, scegli il metodo “giusto”, puoi dichiarare i tuoi campi come istanze del tipo specifico:

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

Puoi anche lanciare i tuoi parametri come tipo del parametro:

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

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, allora questo è il metodo che verrà invocato. 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.