2011-01-04 25 views
67
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(); 
    } 
} 

mia uscita prevista:Java: Chiamare un metodo eccellente, che chiama un metodo override

sottoclasse method1
method1 superclasse
method2 superclasse

uscita effettiva:

sottoclasse soddisfatte hod1
superclasse method1
sottoclasse method2

So tecnicamente ho sovrascritto un metodo pubblico, ma ho pensato che perché stavo chiamando il super, tutte le chiamate all'interno del super-sarebbe rimasto nel super, questo isn' sta succedendo. Qualche idea su come posso farlo accadere?

+2

Sospetto che potresti voler "preferire la composizione sull'ereditarietà". –

risposta

60

La parola chiave super non "bastone". Ogni chiamata al metodo viene gestita individualmente, quindi anche se si è arrivati ​​a SuperClass.method1() chiamando il numero super, ciò non influisce su nessun altro metodo che si potrebbe chiamare in futuro.

Questo significa che non v'è alcun 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 è possibile ottenere l'effetto desiderato utilizzando Reflection (vedere the documentation of java.lang.reflect.Method.invoke(Object, Object...)).

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

Quando si richiama foo(), si invoca effettivamente this.foo(). Java ti consente semplicemente di omettere lo this. Nell'esempio della domanda, il tipo di this è SubClass.

Così, quando Java esegue il codice in SuperClass.method1(), alla fine arriva this.method2();

Utilizzando super non cambia l'istanza puntato da this.Quindi la chiamata va a SubClass.method2() poiché this è di tipo SubClass.

Forse è più facile da capire quando si immagina che Java passa this come 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 si segue lo stack di chiamate, si può vedere che this non cambia mai, è sempre l'istanza creata in main().

+0

qualcuno potrebbe per piacere caricare un diagramma di questo (gioco di parole) passando attraverso lo stack? Grazie in anticipo! – laycat

+2

@laycat: non è necessario un diagramma. Basta ricordare 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 da quanto spesso hai chiamato 'super'. Quindi quando chiamate 'method2' su un'istanza di' SubClass', vedrà sempre prima quello di 'SubClass'. –

+0

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

12

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

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

8

Si sta utilizzando la parola chiave this che in realtà si riferisce all'istanza "attualmente in esecuzione dell'oggetto che si sta utilizzando", ovvero, si sta invocando this.method2(); sulla superclasse, ovvero chiamerà il metodo2 () sull'oggetto che stai utilizzando, che è la Sottoclasseria.

+8

true, e non usare 'this' non sarà di aiuto neanche. Un'invocazione non qualificata utilizza implicitamente 'this' –

+3

Perché questo viene sottoposto a upvoted? Questa non è la risposta a questa domanda. Quando scrivi 'method2()' il compilatore vedrà 'this.method2()'. Quindi, anche se rimuovi il 'questo', non funzionerà ancora. Quello che @Sean Patrick Floyd dice è corretto –

+4

@Shervin non sta dicendo nulla di sbagliato, non sta semplicemente chiarendo cosa succede se si omette 'this' –

1

Non credo che tu possa farlo direttamente. Una soluzione alternativa sarebbe avere un'implementazione interna privata di method2 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"); 
    } 

} 
2

Se non si desidera che superClass.method1 invochi subClass.method2, rendere private method2 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 ha funzionato in questo modo, il polimorfismo sarebbe impossibile (o almeno non nemmeno la metà come utile).

1

"questa" parola chiave si riferisce al riferimento di classe corrente. Ciò significa che, quando viene utilizzato all'interno del metodo, la classe 'current' è ancora SubClass e quindi la risposta è spiegata.

2

Dal momento che l'unico modo per evitare un metodo per ottenere sovrascritto è quello di utilizzare la parola chiave super-, ho pensato di risalire la method2() da SuperClass ad un altro nuovo Base classe e poi lo chiamano 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(); 
    } 
} 

uscita:

subclass method1 
superclass method1 
superclass method2 
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"); 
    } 
} 



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

OutPut:

sottoclasse method1
superclasse method1
superclasse method2

2

this si riferisce sempre al momento dell'esecuzione dell'oggetto.

Per illustrare ulteriormente il punto qui è un semplice schizzo:

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

Se si dispone di un'istanza della scatola esterna, un oggetto Subclass, ovunque vi capita di avventurarsi all'interno della scatola, anche in Superclass ' area ', è ancora l'istanza della scatola esterna.

Cosa c'è di più, in questo programma v'è solo un oggetto che viene creato fuori delle tre classi, in modo da this può sempre e solo riferimento a una cosa e che è:

enter image description here

come mostrato nella Netbeans 'Heap Walker'.

1

penso in questo modo

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

Vorrei spostare che sottoclasse un po 'a sinistra per rivelare ciò che è sotto ... (Uomo, faccio grafica amore 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 dal Java Language Specification:

La forma super.Identifier si riferisce al campo denominato Identifier dell'oggetto corrente , ma con la oggetto corrente visualizzato come istanza di la superclasse della classe corrente.

La forma T.super.Identifier si riferisce al campo denominato Identifier della dell'istanza lessicale racchiude corrispondente a T, ma con quella esempio visto come un'istanza della superclasse di T.

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

Pertanto se si utilizza super dalla superclasse si ottiene il codice dalla classe superduper [il nonno] eseguito), mentre se si utilizza this (o se è usato implicitamente) da una superclasse si continua indicando la sottoclasse (perché nessuno l'ha cambiato, e nessuno poteva farlo).

1

Per riepilogare, questo punta all'oggetto corrente e l'invocazione del metodo in java è polimorfica per sua natura. Quindi, la selezione del metodo per l'esecuzione dipende totalmente dall'oggetto puntato da questo. Pertanto, invocando il metodo method2() dalla classe parent invoca il metodo2() della classe figlio, come questo punta all'oggetto della classe figlio. La definizione di questo non cambia, indipendentemente da quale classe sia 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 di stack nel metodo sottoclasse per scoprire da dove proviene la chiamata. Ci sono probabilmente modi più intelligenti per farlo, ma funziona per me 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. Esistono naturalmente dei modi per risolvere il problema con nomi di metodi diversi o anche tipi di parametri diversi, come già menzionato nella discussione, ma nel mio caso non mi piace confondermi con nomi di metodi diversi.

+0

Questo codice è pericoloso, rischioso e costoso. La creazione di eccezioni richiede alla VM di costruire uno stacktrace completo, confrontando solo il nome e non la firma completa è soggetta a errori. Inoltre, puzza di un enorme difetto di progettazione. –

+0

Dal punto di vista delle prestazioni, il mio codice non sembra produrre più impatto di "nuova HashMap(). Size()". Tuttavia, potrei aver trascurato le preoccupazioni a cui stavate pensando e io non sono un esperto di VM. Vedo i tuoi dubbi confrontando i nomi delle classi, ma include il pacchetto che mi rende abbastanza sicuro, è la mia classe genitore. Ad ogni modo, mi piace l'idea di confrontare la firma, come faresti? In generale, se si dispone di un modo più agevole per determinare se il chiamante è la superclasse o qualcun altro di cui apprezzerei molto qui. –

+0

Se è necessario determinare se il chiamante è una super classe, penserei seriamente più a lungo se è in atto una riprogettazione. Questo è un anti-modello. –

Problemi correlati