Regole di overloading Java

Recentemente mi sono imbattuto in due domande di sovraccarico che non riesco a trovare una risposta e non ho un ambiente java per eseguire qualche codice di test. Spero che qualcuno mi possa aiutare assemblando un elenco di tutte le regole seguite dai compilatori java per sovraccaricare o alternativamente indicarmi una lista che esiste già.

Innanzitutto, quando due metodi differiscono solo per un parametro varargs finale, in quali circostanze vengono richiamati e puoi chiamare il metodo varargs senza alcun argomento?

private void f(int a) { /* ... */ } private void f(int a, int... b) { /* ... */ } f(12); // calls the formsr? I would expect it to f(12, (int[])null); // calls latter, but passes null for b? // Can I force the compiler to call the second method in the same fashion // as would happen if the first method didn't exist? 

Seconda domanda, quando due metodi differiscono per tipi ereditati l’uno dall’altro che viene chiamato? Mi aspetterei che venga chiamata la versione più derivata e che il casting consenta di chiamare l’altro.

 interface A {} class B implements A {} class C implements A {} private void f(A a) {} private void f(B b) {} f(new C()); // calls the first method f(new B()); // calls the second method? f((A)(new B()); // calls the first method using a B object? 

Questi sono i due esempi, ma come lettore di codice preferirei un elenco canonico delle regole esatte ordinate utilizzate per risolvere questo problema, poiché spesso non ho il tempo di configurare un ambiente di generazione per verificare cosa sta facendo il compilatore.

Sovraccarico vs Override

La selezione dell’implementazione corretta del metodo viene eseguita in fase di esecuzione, come ben sottolineato, ora la firma del metodo da invocare viene decisa al momento della compilazione.

Sovraccarico selezione del metodo in fase di compilazione

La specifica Java Language Specification (JLS) nella sezione 15.12 Method Invocation Expressions spiega in dettaglio il processo seguito dal compilatore per scegliere il metodo giusto da richiamare.

Lì, noterai che questo è un compito in fase di compilazione . Il JLS dice nella sottosezione 15.12.2:

Questo passaggio utilizza il nome del metodo e i tipi delle espressioni di argomento per individuare metodi che sono sia accessibili che applicabili. Ci può essere più di un tale metodo, nel qual caso viene scelto quello più specifico.

In genere, i metodi vararg sono gli ultimi scelti se competono con altri metodi candidati, poiché sono considerati meno specifici di quelli che ricevono lo stesso tipo di parametro.

Per verificare la natura di compilazione di questo, è ansible eseguire il seguente test.

Dichiara una class come questa e compila.

 public class ChooseMethod { public void doSomething(Number n){ System.out.println("Number"); } } 

Dichiara una seconda class che richiama un metodo del primo e lo compila.

 public class MethodChooser { public static void main(String[] args) { ChooseMethod m = new ChooseMethod(); m.doSomething(10); } } 

Se invochi il main, l’output dice Number .

Ora aggiungi un secondo metodo più specifico sovraccarico alla class ChooseMethod e ricompilalo (ma non ricompilare l’altra class).

 public void doSomething(Integer i) { System.out.println("Integer"); } 

Se si esegue nuovamente il main, l’output è ancora Number .

Fondamentalmente, perché è stato deciso al momento della compilazione. Se ricompilate la class MethodChooser (quella con il principale) ed eseguite nuovamente il programma, l’output sarà Integer .

Pertanto, se si desidera forzare la selezione di uno dei metodi sovraccaricati, il tipo degli argomenti deve corrispondere al tipo di parametri in fase di compilazione e non solo in fase di esecuzione.

Sovrascrivere la selezione del metodo in fase di esecuzione

Ancora una volta, la firma del metodo viene decisa al momento della compilazione, ma l’effettiva implementazione viene decisa in fase di runtime.

Dichiara una class come questa e compila.

 public class ChooseMethodA { public void doSomething(Number n){ System.out.println("Number A"); } } 

Quindi dichiarare una seconda class di estensione e compilare:

 public class ChooseMethodB extends ChooseMethodA { } 

E nella class MethodChooser puoi:

 public class MethodChooser { public static void main(String[] args) { ChooseMethodA m = new ChooseMethodB(); m.doSomething(10); } } 

E se lo esegui ottieni l’output Number A , e questo è Ok, perché il metodo non è stato sovrascritto in ChooseMethodB e quindi l’implementazione che viene invocata è quella di ChooseMethodA .

Ora, aggiungi un metodo sovrascritto in MethodChooserB :

 public void doSomething(Number n){ System.out.println("Number B"); } 

E ricompilare solo questo, ed eseguire di nuovo il metodo principale.

Ora ottieni l’output Number B

Come tale, l’implementazione è stata scelta in fase di esecuzione e non è stata richiesta la ricompilazione della class MethodChooser .

Prima domanda:

La tua ipotesi è corretta. La seconda chiamata a f() chiamerà il metodo varargs. È ansible ottenere il compilatore per chiamare il secondo metodo con:

 private void f(int a) { f(a, null); } 

Seconda domanda:

Sì. Tuttavia, non è ansible estendere un’interfaccia. Se si modifica A in una class astratta, le cose verranno compilate.