Associazione dinamica Java e sostituzione del metodo


89

Ieri ho avuto un colloquio telefonico tecnico di due ore (che ho superato, woohoo!), Ma ho completamente smorzato la seguente domanda riguardante l'associazione dinamica in Java. Ed è doppiamente sconcertante perché insegnavo questo concetto agli studenti universitari quando ero un assistente tecnico alcuni anni fa, quindi la prospettiva di aver dato loro informazioni sbagliate è un po 'inquietante ...

Ecco il problema che mi è stato dato:

/* What is the output of the following program? */

public class Test {

  public boolean equals( Test other ) {
    System.out.println( "Inside of Test.equals" );
    return false;
  }

  public static void main( String [] args ) {
    Object t1 = new Test();
    Object t2 = new Test();
    Test t3 = new Test();
    Object o1 = new Object();

    int count = 0;
    System.out.println( count++ );// prints 0
    t1.equals( t2 ) ;
    System.out.println( count++ );// prints 1
    t1.equals( t3 );
    System.out.println( count++ );// prints 2
    t3.equals( o1 );
    System.out.println( count++ );// prints 3
    t3.equals(t3);
    System.out.println( count++ );// prints 4
    t3.equals(t2);
  }
}

Ho affermato che l'output avrebbe dovuto essere due istruzioni print separate all'interno del equals()metodo sovrascritto : at t1.equals(t3)e t3.equals(t3). Il secondo caso è abbastanza ovvio e con il primo caso, anche se t1ha un riferimento di tipo Object, viene istanziato come tipo Test, quindi l'associazione dinamica dovrebbe chiamare la forma sovrascritta del metodo.

Apparentemente no. Il mio intervistatore mi ha incoraggiato a eseguire il programma da solo, ed ecco, c'era solo un singolo output dal metodo sovrascritto: in linea t3.equals(t3).

La mia domanda quindi è: perché? Come ho già detto, anche se t1è un riferimento di tipo Object (quindi l'associazione statica richiama il equals()metodo di Object ), l'associazione dinamica dovrebbe occuparsi di invocare la versione più specifica del metodo in base al tipo di riferimento istanziato. Cosa mi sto perdendo?


Si prega di trovare il mio post a questa risposta in cui ho fatto del mio meglio per spiegare con casi aggiuntivi. Apprezzerei molto i tuoi input :)
Devendra Lattu

Risposte:


81

Java utilizza l'associazione statica per i metodi sovraccaricati e l'associazione dinamica per quelli sovrascritti. Nel tuo esempio, il metodo equals è sovraccarico (ha un tipo di parametro diverso da Object.equals ()), quindi il metodo chiamato è associato al tipo di riferimento in fase di compilazione.

Qualche discussione qui

Il fatto che sia il metodo dell'uguaglianza non è realmente rilevante, a parte il fatto che è un errore comune sovraccaricarlo invece di sovrascriverlo, di cui sei già a conoscenza in base alla tua risposta al problema nell'intervista.

Modifica: anche una buona descrizione qui . Questo esempio mostra invece un problema simile relativo al tipo di parametro, ma causato dallo stesso problema.

Credo che se l'associazione fosse effettivamente dinamica, ogni caso in cui il chiamante e il parametro fossero un'istanza di Test comporterebbe la chiamata del metodo sovrascritto. Quindi t3.equals (o1) sarebbe l'unico caso che non verrebbe stampato.


Molte persone sottolineano che è sovraccarico e non sovrascritto, ma anche con questo ti aspetteresti che risolva correttamente quello sovraccarico. Il tuo post è in realtà l'unico finora che risponde correttamente alla domanda per quanto ne so.
Bill K

4
Il mio errore è stato completamente sfuggito al fatto che il metodo è effettivamente sovraccarico anziché sovrascritto. Ho visto "equals ()" e ho immediatamente pensato ereditato e sovrascritto. Sembra che io, ancora una volta, abbia corretto il concetto più ampio e più difficile, ma abbia sbagliato i dettagli semplici. : P
Magsol

14
un altro motivo per cui esiste l'annotazione @Override.
Matt

1
Ripeti dopo di me: "Java utilizza l'associazione statica per i metodi sovraccaricati e l'associazione dinamica per quelli sovrascritti" - +1
Mr_and_Mrs_D

1
quindi mi sono laureato senza saperlo. Grazie!
Atieh

25

Il equalsmetodo di Testnon sovrascrive il equalsmetodo di java.lang.Object. Guarda il tipo di parametro! La Testclasse si sta sovraccaricando equalscon un metodo che accetta un file Test.

Se il equalsmetodo ha lo scopo di sovrascrivere, dovrebbe usare l'annotazione @Override. Ciò causerebbe un errore di compilazione per segnalare questo errore comune.


Sì, non sono del tutto sicuro del motivo per cui mi sono perso quel dettaglio semplice ma cruciale, ma è esattamente quello il mio problema. Grazie!
Magsol

+1 per essere la vera risposta ai curiosi risultati dell'interrogante
matt b

Si prega di trovare il mio post a questa risposta in cui ho fatto del mio meglio per spiegare con casi aggiuntivi. Apprezzerei molto i tuoi input :)
Devendra Lattu

6

È interessante notare che, nel codice Groovy (che potrebbe essere compilato in un file di classe), tutte le chiamate tranne una eseguono l'istruzione print. (Quello che confronta un test con un oggetto chiaramente non chiama la funzione Test.equals (Test).) Questo perché groovy FA una digitazione completamente dinamica. Ciò è particolarmente interessante perché non ha variabili tipizzate in modo dinamico in modo esplicito. Ho letto in un paio di posti che questo è considerato dannoso, poiché i programmatori si aspettano che groovy faccia la cosa java.


1
Sfortunatamente il prezzo che Groovy paga per questo è un enorme calo delle prestazioni, poiché ogni invocazione di metodo utilizza la riflessione. Aspettarsi che una lingua funzioni esattamente come un'altra è generalmente considerato dannoso. Bisogna essere consapevoli delle differenze.
Joachim Sauer

Dovrebbe essere carino e veloce con invokedynamic in JDK7 (o anche utilizzando una tecnica di implementazione simile oggi).
Tom Hawtin - tackline

5

Java non supporta la covarianza nei parametri, solo nei tipi restituiti.

In altre parole, mentre il tipo restituito in un metodo sovrascritto può essere un sottotipo di ciò che era nell'override, ciò non è vero per i parametri.

Se il tuo parametro per uguale in Object è Object, mettere un uguale a qualsiasi altra cosa in una sottoclasse sarà un metodo sovraccarico, non sovrascritto. Quindi, l'unica situazione in cui verrà chiamato quel metodo è quando il tipo statico del parametro è Test, come nel caso di T3.

Buona fortuna per il colloquio di lavoro! Mi piacerebbe essere intervistato presso un'azienda che pone questo tipo di domande invece delle solite domande di algo / strutture dati che insegno ai miei studenti.


1
Intendi parametri controvarianti.
Tom Hawtin - tackline

In qualche modo ho completamente sorvolato sul fatto che diversi parametri del metodo creano intrinsecamente un metodo sovraccarico, non sovrascritto. Oh non preoccuparti, c'erano anche domande su algoritmi / strutture dati. : P E grazie per la fortuna, ne avrò bisogno! :)
Magsol

4

Penso che la chiave risieda nel fatto che il metodo equals () non è conforme allo standard: accetta un altro oggetto Test, non l'oggetto Object e quindi non sovrascrive il metodo equals (). Ciò significa che in realtà l'hai solo sovraccaricato per fare qualcosa di speciale quando gli viene fornito l'oggetto Test mentre gli viene assegnato l'oggetto Object chiama Object.equals (Object o). Guardando quel codice attraverso qualsiasi IDE dovresti mostrare due metodi equals () per Test.


Questo e la maggior parte delle risposte mancano di punto. Il problema non riguarda il fatto che viene utilizzato il sovraccarico anziché l'override. Questo è il motivo per cui non è il metodo di overload utilizzato per t1.equals (t3), quando t1 è dichiarato come Object ma inizializzato a Test.
Robin

4

Il metodo viene sovraccaricato anziché sovrascritto. Equals accetta sempre un oggetto come parametro.

btw, hai un oggetto su questo nell'efficace java di Bloch (che dovresti possedere).


L'efficace Java di Joshua Bloch?
DJClayworth

Efficace sì, stavo pensando a qualcos'altro mentre digitavo: D
Gilles

4

Alcune note in Dynamic Binding (DD) e Static Binding (SB) dopo aver cercato per un po ':

1.Timing eseguire : (Ref.1)

  • DB: in fase di esecuzione
  • SB: tempo del compilatore

2.Utilizzato per :

  • DB: override
  • SB: sovraccarico (statico, privato, finale) (Rif.2)

Riferimento:

  1. Eseguire il risolutore medio quale metodo preferisce utilizzare
  2. Perché non è possibile sovrascrivere il metodo con modificatore statico, privato o finale
  3. http://javarevisited.blogspot.com/2012/03/what-is-static-and-dynamic-binding-in.html

2

Se viene aggiunto un altro metodo che esegue l'override invece di sovraccaricare, spiegherà la chiamata di binding dinamico in fase di esecuzione.

/ * Qual è l'output del seguente programma? * /

public class DynamicBinding {
    public boolean equals(Test other) {
        System.out.println("Inside of Test.equals");
        return false;
    }

    @Override
    public boolean equals(Object other) {
        System.out.println("Inside @override: this is dynamic binding");
        return false;
    }

    public static void main(String[] args) {
        Object t1 = new Test();
        Object t2 = new Test();
        Test t3 = new Test();
        Object o1 = new Object();

        int count = 0;
        System.out.println(count++);// prints 0
        t1.equals(t2);
        System.out.println(count++);// prints 1
        t1.equals(t3);
        System.out.println(count++);// prints 2
        t3.equals(o1);
        System.out.println(count++);// prints 3
        t3.equals(t3);
        System.out.println(count++);// prints 4
        t3.equals(t2);
    }
}


0

La risposta alla domanda "perché?" è così che viene definito il linguaggio Java.

Per citare l' articolo di Wikipedia su covarianza e controvarianza :

La covarianza del tipo restituito è implementata nella versione del linguaggio di programmazione Java J2SE 5.0. I tipi di parametro devono essere esattamente gli stessi (invarianti) per l'override del metodo, altrimenti il ​​metodo viene invece sovraccaricato con una definizione parallela.

Le altre lingue sono diverse.


Il mio problema era più o meno equivalente a vedere 3 + 3 e scrivere 9, quindi vedere 1 + 1 e scrivere 2. Capisco come è definito il linguaggio Java; in questo caso, per qualsiasi motivo, ho completamente scambiato il metodo per qualcosa che non era, anche se ho evitato quell'errore altrove nello stesso problema.
Magsol

0

È molto chiaro che qui non esiste il concetto di override. È il sovraccarico del metodo. il Object()metodo della classe Object prende il parametro di riferimento di tipo Object e questo equal()metodo prende il parametro di riferimento di tipo Test.


-1

Cercherò di spiegarlo attraverso due esempi che sono le versioni estese di alcuni degli esempi che ho trovato online.

public class Test {

    public boolean equals(Test other) {
        System.out.println("Inside of Test.equals");
        return false;
    }

    @Override
    public boolean equals(Object other) {
        System.out.println("Inside of Test.equals ot type Object");
        return false;
    }

    public static void main(String[] args) {
        Object t1 = new Test();
        Object t2 = new Test();
        Test t3 = new Test();
        Object o1 = new Object();

        int count = 0;
        System.out.println(count++); // prints 0
        o1.equals(t2);

        System.out.println("\n" + count++); // prints 1
        o1.equals(t3);

        System.out.println("\n" + count++);// prints 2
        t1.equals(t2);

        System.out.println("\n" + count++);// prints 3
        t1.equals(t3);

        System.out.println("\n" + count++);// prints 4
        t3.equals(o1);

        System.out.println("\n" + count++);// prints 5
        t3.equals(t3);

        System.out.println("\n" + count++);// prints 6
        t3.equals(t2);
    }
}

Qui, per le righe con valori di conteggio 0, 1, 2 e 3; abbiamo riferimento di Object per o1 e t1 sul equals()metodo. Pertanto, in fase di compilazione, il equals()metodo dal file Object.class verrà limitato.

Tuttavia, anche se il riferimento di t1 è Object , ha l' inizializzazione della classe Test .
Object t1 = new Test();.
Pertanto, in fase di esecuzione chiama il public boolean equals(Object other)che è un file

metodo ignorato

. inserisci qui la descrizione dell'immagine

Ora, per contare i valori come 4 e 6, è di nuovo semplice che t3 che ha il riferimento e l' inizializzazione di Test sta chiamando il equals()metodo con parametro come riferimenti a oggetti ed è un

metodo sovraccarico

OK!

Ancora una volta, per capire meglio quale metodo chiamerà il compilatore, è sufficiente fare clic sul metodo ed Eclipse evidenzierà i metodi di tipo simile che pensa chiamerà al momento della compilazione. Se non viene chiamato in fase di compilazione, questi metodi sono un esempio di override del metodo.

inserisci qui la descrizione dell'immagine

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.