Java: chiamata di un super metodo che chiama un metodo sostituito


94
public class SuperClass
{
    public void method1()
    {
        System.out.println("superclass method1");
        this.method2();
    }

    public void method2()
    {
        System.out.println("superclass method2");
    }

}

public class SubClass extends SuperClass
{
    @Override
    public void method1()
    {
        System.out.println("subclass method1");
        super.method1();
    }

    @Override
    public void method2()
    {
        System.out.println("subclass method2");
    }
}



public class Demo 
{
    public static void main(String[] args) 
    {
        SubClass mSubClass = new SubClass();
        mSubClass.method1();
    }
}

il mio output previsto:

metodo della sottoclasse 1
superclasse method1
superclasse method2

uscita effettiva:

sottoclasse metodo1
superclasse metodo1
metodo1 metodo1 sottoclasse metodo2

So che tecnicamente ho ignorato un metodo pubblico, ma ho pensato che poiché stavo chiamando il super, qualsiasi chiamata all'interno del super sarebbe rimasta nel super, questo non sta accadendo. Qualche idea su come posso farlo accadere?


2
Sospetto che potresti voler "preferire la composizione all'eredità".
Tom Hawtin - tackline

Risposte:


79

La parola chiave supernon "si attacca". Ogni chiamata al metodo viene gestita individualmente, quindi anche se devi SuperClass.method1()chiamare super, ciò non influenza nessun altro metodo che potresti effettuare in futuro.

Ciò significa che c'è un modo diretto per chiamare SuperClass.method2()da SuperClass.method1()senza andare però SubClass.method2()a meno che non si sta lavorando con un caso reale di SuperClass.

Non è nemmeno possibile ottenere l'effetto desiderato utilizzando Reflection (vedere la documentazione dijava.lang.reflect.Method.invoke(Object, Object...) ).

[EDIT] Sembra esserci ancora un po 'di confusione. Fammi provare una spiegazione diversa.

Quando invocate foo(), invocate effettivamente this.foo(). Java ti consente semplicemente di omettere il file this. Nell'esempio nella domanda, il tipo di thisè SubClass.

Quindi, quando Java esegue il codice in SuperClass.method1(), alla fine arriva athis.method2();

L'utilizzo supernon cambia l'istanza puntata da this. Quindi la chiamata va a SubClass.method2()poiché thisè di tipo SubClass.

Forse è più facile capire quando immagini che Java passa thiscome primo parametro nascosto:

public class SuperClass
{
    public void method1(SuperClass this)
    {
        System.out.println("superclass method1");
        this.method2(this); // <--- this == mSubClass
    }

    public void method2(SuperClass this)
    {
        System.out.println("superclass method2");
    }

}

public class SubClass extends SuperClass
{
    @Override
    public void method1(SubClass this)
    {
        System.out.println("subclass method1");
        super.method1(this);
    }

    @Override
    public void method2(SubClass this)
    {
        System.out.println("subclass method2");
    }
}



public class Demo 
{
    public static void main(String[] args) 
    {
        SubClass mSubClass = new SubClass();
        mSubClass.method1(mSubClass);
    }
}

Se segui lo stack di chiamate, puoi vedere che thisnon cambia mai, è sempre l'istanza creata in main().


qualcuno potrebbe caricare un diagramma di questo (gioco di parole) che passa attraverso lo stack? Grazie in anticipo!
laycat

2
@laycat: non è necessario un diagramma. Ricorda solo che Java non ha "memoria" per super. Ogni volta che chiama un metodo, esaminerà il tipo di istanza e inizierà a cercare il metodo con questo tipo, indipendentemente dalla frequenza con cui viene chiamato super. Quindi, quando chiami method2su un'istanza di SubClass, vedrà sempre quella dal SubClassprimo.
Aaron Digulla

@AaronDigulla, puoi spiegare di più su "Java non ha memoria per super"?
MengT

@ Truman'sworld: come ho detto nella mia risposta: l'utilizzo supernon cambia l'istanza. Non imposta alcun campo nascosto "da ora in poi, tutte le chiamate ai metodi dovrebbero iniziare a utilizzare SuperClass". O per dirla in un altro modo: il valore di thisnon cambia.
Aaron Digulla

@AaronDigulla, quindi significa che la parola chiave super richiama effettivamente i metodi ereditati nella sottoclasse invece di andare alla super classe?
MengT

15

È possibile accedere solo ai metodi sostituiti nei metodi sovrascritti (o in altri metodi della classe sovrascrivente).

Quindi: o non sovrascrivere method2()o chiamare super.method2()all'interno della versione sovrascritta.


8

Stai usando la thisparola chiave che in realtà si riferisce all '"istanza attualmente in esecuzione dell'oggetto che stai utilizzando", cioè stai invocando this.method2();sulla tua superclasse, cioè chiamerà il metodo2 () sull'oggetto che stai' stai usando, che è la sottoclasse.


8
vero, e nemmeno il non utilizzo thisnon aiuterà. Un'invocazione non qualificata utilizza implicitamentethis
Sean Patrick Floyd

3
Perché questo viene votato positivamente? Questa non è la risposta a questa domanda. Quando scrivi method2()il compilatore vedrà this.method2(). Quindi, anche se rimuovi il this, non funzionerà ancora. Quello che sta dicendo @Sean Patrick Floyd è corretto
Shervin Asgari

4
@Shervin non sta dicendo nulla di sbagliato, semplicemente non sta chiarendo cosa succede se si lascia fuorithis
Sean Patrick Floyd

4
La risposta è giusta nel sottolineare che si thisriferisce alla "classe di istanza concreta in esecuzione" (nota in runtime) e non (come sembra credere il poster) alla "classe di unità di compilazione corrente" (dove viene utilizzata la parola chiave, nota in tempo di compilazione). Ma può anche essere fuorviante (come sottolinea Shervin): thisè anche referenziato implicitamente con la semplice chiamata al metodo; method2();è lo stesso dithis.method2();
leonbloy

7

La penso in questo modo

+----------------+
|     super      |
+----------------+ <-----------------+
| +------------+ |                   |
| |    this    | | <-+               |
| +------------+ |   |               |
| | @method1() | |   |               |
| | @method2() | |   |               |
| +------------+ |   |               |
|    method4()   |   |               |
|    method5()   |   |               |
+----------------+   |               |
    We instantiate that class, not that one!

Lasciami spostare quella sottoclasse un po 'a sinistra per rivelare cosa c'è sotto ... (Amico, adoro la grafica ASCII)

We are here
        |
       /  +----------------+
      |   |     super      |
      v   +----------------+
+------------+             |
|    this    |             |
+------------+             |
| @method1() | method1()   |
| @method2() | method2()   |
+------------+ method3()   |
          |    method4()   |
          |    method5()   |
          +----------------+

Then we call the method
over here...
      |               +----------------+
 _____/               |     super      |
/                     +----------------+
|   +------------+    |    bar()       |
|   |    this    |    |    foo()       |
|   +------------+    |    method0()   |
+-> | @method1() |--->|    method1()   | <------------------------------+
    | @method2() | ^  |    method2()   |                                |
    +------------+ |  |    method3()   |                                |
                   |  |    method4()   |                                |
                   |  |    method5()   |                                |
                   |  +----------------+                                |
                   \______________________________________              |
                                                          \             |
                                                          |             |
...which calls super, thus calling the super's method1() here, so that that
method (the overidden one) is executed instead[of the overriding one].

Keep in mind that, in the inheritance hierarchy, since the instantiated
class is the sub one, for methods called via super.something() everything
is the same except for one thing (two, actually): "this" means "the only
this we have" (a pointer to the class we have instantiated, the
subclass), even when java syntax allows us to omit "this" (most of the
time); "super", though, is polymorphism-aware and always refers to the
superclass of the class (instantiated or not) that we're actually
executing code from ("this" is about objects [and can't be used in a
static context], super is about classes).

In altre parole, citando dalle specifiche del linguaggio Java :

La forma si super.Identifierriferisce al campo denominato Identifierdell'oggetto corrente, ma con l'oggetto corrente visto come un'istanza della superclasse della classe corrente.

Il modulo si T.super.Identifierriferisce al campo denominato Identifierdell'istanza che racchiude lessicalmente corrispondente a T, ma con quell'istanza vista come un'istanza della superclasse di T.

In parole thispovere , è fondamentalmente un oggetto (* l'oggetto **; lo stesso oggetto che puoi spostare nelle variabili), l'istanza della classe istanziata, una semplice variabile nel dominio dei dati; superè come un puntatore a un blocco di codice preso in prestito che si desidera eseguire, più simile a una semplice chiamata di funzione, ed è relativo alla classe in cui viene chiamato.

Quindi se usi superdalla superclasse ottieni codice dalla classe superduper [il nonno] eseguita), mentre se usi this(o se viene usato implicitamente) da una superclasse continua a puntare alla sottoclasse (perché nessuno l'ha cambiata - e nessuno poteva).


2

Se non vuoi che superClass.method1 chiami subClass.method2, rendi privato il metodo2 in modo che non possa essere sovrascritto.

Ecco un suggerimento:

public class SuperClass {

  public void method1() {
    System.out.println("superclass method1");
    this.internalMethod2();
  }

  public void method2()  {
    // this method can be overridden.  
    // It can still be invoked by a childclass using super
    internalMethod2();
  }

  private void internalMethod2()  {
    // this one cannot.  Call this one if you want to be sure to use
    // this implementation.
    System.out.println("superclass method2");
  }

}

public class SubClass extends SuperClass {

  @Override
  public void method1() {
    System.out.println("subclass method1");
    super.method1();
  }

  @Override
  public void method2() {
    System.out.println("subclass method2");
  }
}

Se non funzionasse in questo modo, il polimorfismo sarebbe impossibile (o almeno nemmeno la metà utile).


2

Poiché l'unico modo per evitare che un metodo venga sovrascritto è usare la parola chiave super , ho pensato di spostare il metodo2 () da SuperClass a un'altra nuova classe Base e quindi chiamarlo da SuperClass :

class Base 
{
    public void method2()
    {
        System.out.println("superclass method2");
    }
}

class SuperClass extends Base
{
    public void method1()
    {
        System.out.println("superclass method1");
        super.method2();
    }
}

class SubClass extends SuperClass
{
    @Override
    public void method1()
    {
        System.out.println("subclass method1");
        super.method1();
    }

    @Override
    public void method2()
    {
        System.out.println("subclass method2");
    }
}

public class Demo 
{
    public static void main(String[] args) 
    {
        SubClass mSubClass = new SubClass();
        mSubClass.method1();
    }
}

Produzione:

subclass method1
superclass method1
superclass method2

2

this si riferisce sempre all'oggetto attualmente in esecuzione.

Per illustrare ulteriormente il punto ecco un semplice schizzo:

+----------------+
|  Subclass      |
|----------------|
|  @method1()    |
|  @method2()    |
|                |
| +------------+ |
| | Superclass | |
| |------------| |
| | method1()  | |
| | method2()  | |
| +------------+ |
+----------------+

Se hai un'istanza della scatola esterna, un Subclassoggetto, ovunque ti capiti di avventurarti all'interno della scatola, anche nell Superclass'"area", è ancora l'istanza della scatola esterna.

Inoltre, in questo programma c'è solo un oggetto che viene creato dalle tre classi, quindi thispuò riferirsi solo a una cosa ed è:

inserisci qui la descrizione dell'immagine

come mostrato in Netbeans "Heap Walker".


2
class SuperClass
{
    public void method1()
    {
        System.out.println("superclass method1");
        SuperClass se=new SuperClass();
        se.method2();
    }

    public void method2()
    {
        System.out.println("superclass method2");
    }
}


class SubClass extends SuperClass
{
    @Override
    public void method1()
    {
        System.out.println("subclass method1");
        super.method1();
    }

    @Override
    public void method2()
    {
        System.out.println("subclass method2");
    }
}

chiamando

SubClass mSubClass = new SubClass();
mSubClass.method1();

uscite

sottoclasse Method1
superclasse method1
superclasse method2


1

Non credo che tu possa farlo direttamente. Una soluzione alternativa sarebbe avere un'implementazione interna privata del metodo2 nella superclasse e chiamarla. Per esempio:

public class SuperClass
{
    public void method1()
    {
        System.out.println("superclass method1");
        this.internalMethod2();
    }

    public void method2()
    {
        this.internalMethod2(); 
    }
    private void internalMethod2()
    {
        System.out.println("superclass method2");
    }

}

1

La parola chiave "this" si riferisce al riferimento alla classe corrente. Ciò significa che, quando viene utilizzata all'interno del metodo, la classe "corrente" è ancora una sottoclasse e quindi viene spiegata la risposta.


1

Per riassumere, questo punta all'oggetto corrente e l'invocazione del metodo in java è polimorfica per natura. Quindi, la selezione del metodo per l'esecuzione dipende totalmente dall'oggetto puntato da questo. Pertanto, invocare il metodo method2 () dalla classe genitore richiama il metodo2 () della classe figlia, poiché this punta all'oggetto della classe figlia. La definizione di questo non cambia, indipendentemente dalla classe utilizzata.

PS. a differenza dei metodi, le variabili membro di classe non sono polimorfiche.


0

Durante la mia ricerca per un caso simile, ho finito per controllare la traccia dello stack nel metodo della sottoclasse per scoprire da dove proviene la chiamata. Probabilmente ci sono modi più intelligenti per farlo, ma per me funziona ed è un approccio dinamico.

public void method2(){
        Exception ex=new Exception();
        StackTraceElement[] ste=ex.getStackTrace();
        if(ste[1].getClassName().equals(this.getClass().getSuperclass().getName())){
            super.method2();
        }
        else{
            //subclass method2 code
        }
}

Penso che la domanda per avere una soluzione per il caso sia ragionevole. Ci sono ovviamente modi per risolvere il problema con diversi nomi di metodi o anche diversi tipi di parametri, come già menzionato nel thread, ma nel mio caso non mi piace confondere con diversi nomi di metodo.


Questo codice è pericoloso, rischioso e costoso. La creazione di eccezioni richiede che la VM crei uno stacktrace completo, il confronto solo sul nome e non sulla firma completa è soggetto a errori. Inoltre, puzza di un enorme difetto di progettazione.
M. le Rutte

Dal punto di vista delle prestazioni, il mio codice non sembra produrre un impatto maggiore di "new HashMap (). Size ()". Tuttavia, potrei aver trascurato le preoccupazioni a cui hai pensato e non sono un esperto di VM. Vedo il tuo dubbio confrontando i nomi delle classi, ma include il pacchetto che mi rende abbastanza sicuro, è la mia classe genitore. Comunque mi piace l'idea di confrontare invece la firma, come lo faresti? In generale, se hai un modo più agevole per determinare se il chiamante è la superclasse o qualcun altro, apprezzerei qui.
Batti Siegrist il

Se hai bisogno di determinare se il chiamante è una super classe, penserei seriamente più a lungo se è in atto una riprogettazione. Questo è un anti-pattern.
M. le Rutte

Capisco il punto ma la richiesta generale del thread è ragionevole. In alcune situazioni, può avere senso che una chiamata al metodo di una superclasse rimanga nel contesto della superclasse con qualsiasi chiamata di metodo annidata. Tuttavia, non sembra esserci alcun modo per indirizzare la chiamata al metodo di conseguenza nella Superclasse.
Batti Siegrist il

0

Più esteso l'output della domanda sollevata, questo fornirà maggiori informazioni sullo specificatore di accesso e sul comportamento di override.

            package overridefunction;
            public class SuperClass 
                {
                public void method1()
                {
                    System.out.println("superclass method1");
                    this.method2();
                    this.method3();
                    this.method4();
                    this.method5();
                }
                public void method2()
                {
                    System.out.println("superclass method2");
                }
                private void method3()
                {
                    System.out.println("superclass method3");
                }
                protected void method4()
                {
                    System.out.println("superclass method4");
                }
                void method5()
                {
                    System.out.println("superclass method5");
                }
            }

            package overridefunction;
            public class SubClass extends SuperClass
            {
                @Override
                public void method1()
                {
                    System.out.println("subclass method1");
                    super.method1();
                }
                @Override
                public void method2()
                {
                    System.out.println("subclass method2");
                }
                // @Override
                private void method3()
                {
                    System.out.println("subclass method3");
                }
                @Override
                protected void method4()
                {
                    System.out.println("subclass method4");
                }
                @Override
                void method5()
                {
                    System.out.println("subclass method5");
                }
            }

            package overridefunction;
            public class Demo 
            {
                public static void main(String[] args) 
                {
                    SubClass mSubClass = new SubClass();
                    mSubClass.method1();
                }
            }

            subclass method1
            superclass method1
            subclass method2
            superclass method3
            subclass method4
            subclass method5
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.