Java: Appel d'une super méthode qui appelle une méthode surchargée


97
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();
    }
}

ma sortie attendue:

sous-classe méthode1
superclasse méthode1
superclasse méthode2

sortie réelle:

sous-classe méthode1
superclasse méthode1
sous-classe méthode2

Je sais techniquement que j'ai remplacé une méthode publique, mais j'ai pensé que parce que j'appelais le super, tous les appels dans le super resteraient dans le super, cela ne se produit pas. Des idées sur la façon dont je peux y arriver?


2
Je soupçonne que vous voudrez peut-être "préférer la composition à l'héritage".
Tom Hawtin - tackline

Réponses:


80

Le mot super- clé ne "colle" pas. Chaque appel de méthode est géré individuellement, donc même si vous y parvenez SuperClass.method1()en appelant super, cela n'influence aucun autre appel de méthode que vous pourriez faire à l'avenir.

Cela signifie qu'il n'y a pas de moyen direct d'appeler SuperClass.method2()depuis SuperClass.method1()sans y aller, SubClass.method2()sauf si vous travaillez avec une instance réelle de SuperClass.

Vous ne pouvez même pas obtenir l'effet souhaité en utilisant Reflection (voir la documentation dejava.lang.reflect.Method.invoke(Object, Object...) ).

[EDIT] Il semble encore y avoir une certaine confusion. Laissez-moi essayer une explication différente.

Lorsque vous invoquez foo(), vous invoquez en fait this.foo(). Java vous permet simplement d'omettre le fichier this. Dans l'exemple de la question, le type de thisest SubClass.

Ainsi, lorsque Java exécute le code SuperClass.method1(), il arrive finalement àthis.method2();

L'utilisation superne change pas l'instance pointée par this. Donc l'appel va à SubClass.method2()puisque thisest de type SubClass.

Peut-être que c'est plus facile à comprendre quand on imagine que Java passe thiscomme un premier paramètre caché:

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);
    }
}

Si vous suivez la pile d'appels, vous pouvez voir que cela thisne change jamais, c'est toujours l'instance créée dans main().


quelqu'un pourrait-il s'il vous plaît télécharger un diagramme de ce (jeu de mots) en passant par la pile? Merci d'avance!
laycat

2
@laycat: Il n'y a pas besoin de diagramme. N'oubliez pas que Java n'a pas de "mémoire" pour super. Chaque fois qu'il appelle une méthode, il examine le type d'instance et commence à rechercher la méthode avec ce type, quelle que soit la fréquence de l'appel super. Ainsi, lorsque vous appelez method2une instance de SubClass, elle verra toujours celle du SubClasspremier.
Aaron Digulla

@AaronDigulla, Pouvez-vous expliquer plus sur le "Java n'a pas de mémoire pour super"?
MengT

@ Truman'sworld: comme je l'ai dit dans ma réponse: l'utilisation superne change pas l'instance. Il ne définit pas de champ caché "à partir de maintenant, tous les appels de méthode devraient commencer à utiliser SuperClass". Ou pour le dire autrement: la valeur de thisne change pas.
Aaron Digulla

@AaronDigulla, cela signifie-t-il que les super mots clés invoquent en fait les méthodes héritées de la sous-classe au lieu d'aller dans la super classe?
MengT

15

Vous ne pouvez accéder aux méthodes remplacées que dans les méthodes de substitution (ou dans d'autres méthodes de la classe de substitution).

Donc: ne remplacez pas method2()ou appelez super.method2()dans la version remplacée.


8

Vous utilisez le thismot - clé qui fait référence à "l'instance en cours d'exécution de l'objet que vous utilisez", c'est-à-dire que vous invoquez this.method2();sur votre superclasse, c'est-à-dire qu'il appellera la méthode2 () sur l'objet que vous ' re using, qui est la sous-classe.


8
vrai, et ne pas utiliser thisn'aidera pas non plus. Une invocation non qualifiée utilise implicitementthis
Sean Patrick Floyd

3
Pourquoi cela a-t-il été voté? Ce n'est pas la réponse à cette question. Lorsque vous écrivez, method2()le compilateur verra this.method2(). Donc, même si vous supprimez le, thiscela ne fonctionnera toujours pas. Ce que @Sean Patrick Floyd dit est correct
Shervin Asgari

4
@Shervin il ne dit rien de mal, il ne dit tout simplement pas ce qui se passe si vous oubliezthis
Sean Patrick Floyd

4
La réponse est juste en soulignant que se thisréfère à la "classe d'instance en cours d'exécution concrète" (connue à l'exécution) et non (comme l'affiche semble le croire) à la "classe d'unité de compilation actuelle" (où le mot-clé est utilisé, connu dans temps de compilation). Mais cela peut aussi être trompeur (comme le souligne Shervin): thisest également référencé implicitement avec l'appel de méthode simple; method2();est le même quethis.method2();
leonbloy

7

J'y pense de cette façon

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

Permettez-moi de déplacer cette sous-classe un peu vers la gauche pour révéler ce qui se trouve en dessous ... (Mec, j'adore les graphiques 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).

En d'autres termes, citant la spécification du langage Java :

Le formulaire super.Identifierfait référence au champ nommé Identifierde l'objet courant, mais avec l'objet courant vu comme une instance de la superclasse de la classe courante.

Le formulaire T.super.Identifierfait référence au champ nommé Identifierde l'instance lexicalement englobante correspondant à T, mais avec cette instance considérée comme une instance de la superclasse de T.

En termes simples, thisest essentiellement un objet (* l'objet **; le même objet que vous pouvez déplacer dans les variables), l'instance de la classe instanciée, une simple variable dans le domaine des données; superest comme un pointeur vers un bloc de code emprunté que vous souhaitez exécuter, plus comme un simple appel de fonction, et il est relatif à la classe où il est appelé.

Par conséquent, si vous utilisez à superpartir de la superclasse, vous obtenez le code de la superduper classe [le grand-parent] exécuté), tandis que si vous utilisez this(ou si elle est utilisée implicitement) d'une superclasse, il continue de pointer vers la sous-classe (car personne ne l'a changé - et personne pourrait).


2

Si vous ne voulez pas que superClass.method1 appelle subClass.method2, rendez la méthode2 privée afin qu'elle ne puisse pas être remplacée.

Voici une suggestion:

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");
  }
}

Si cela ne fonctionnait pas de cette façon, le polymorphisme serait impossible (ou du moins même pas à moitié aussi utile).


2

Puisque le seul moyen d'éviter qu'une méthode soit remplacée est d'utiliser le mot - clé super , j'ai pensé à déplacer la méthode2 () de SuperClass vers une autre nouvelle classe de base , puis à l'appeler depuis 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();
    }
}

Production:

subclass method1
superclass method1
superclass method2

2

this fait toujours référence à l'objet en cours d'exécution.

Pour illustrer davantage ce point, voici une simple esquisse:

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

Si vous avez une instance de la boîte extérieure, un Subclassobjet, partout où vous vous aventurez à l'intérieur de la boîte, même dans la Superclass«zone», c'est toujours l'instance de la boîte extérieure.

De plus, dans ce programme, il n'y a qu'un seul objet qui est créé à partir des trois classes, donc thisne peut jamais faire référence à une chose et c'est:

entrez la description de l'image ici

comme indiqué dans le «Heap Walker» de Netbeans .


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");
    }
}

appel

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

les sorties

sous-classe méthode1
superclasse méthode1
superclasse méthode2


1

Je ne pense pas que vous puissiez le faire directement. Une solution de contournement serait d'avoir une implémentation interne privée de method2 dans la superclasse et de l'appeler. Par exemple:

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

«this» mot-clé fait référence à la référence de classe actuelle. Cela signifie que lorsqu'elle est utilisée dans la méthode, la classe 'courante' est toujours SubClass et donc, la réponse est expliquée.


1

Pour résumer, cela pointe vers l'objet actuel et l'invocation de la méthode en java est polymorphe par nature. Ainsi, la sélection de la méthode pour l'exécution dépend totalement de l'objet pointé par ceci. Par conséquent, l'appel de la méthode method2 () à partir de la classe parent invoque method2 () de la classe enfant, car this pointe vers l'objet de la classe enfant. La définition de ceci ne change pas, quelle que soit la classe utilisée.

PS. contrairement aux méthodes, les variables membres de classe ne sont pas polymorphes.


0

Au cours de mes recherches pour un cas similaire, j'ai fini par vérifier la trace de la pile dans la méthode de sous-classe pour savoir d'où vient l'appel. Il existe probablement des moyens plus intelligents de le faire, mais cela fonctionne pour moi et c'est une approche dynamique.

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
        }
}

Je pense que la question d'avoir une solution pour le cas est raisonnable. Il existe bien sûr des moyens de résoudre le problème avec différents noms de méthode ou même différents types de paramètres, comme déjà mentionné dans le fil de discussion, mais dans mon cas, je n'aime pas confondre avec différents noms de méthode.


Ce code est dangereux, risqué et coûteux. La création d'exceptions nécessite que la machine virtuelle construise une trace de pile complète, la comparaison uniquement sur le nom et non sur la signature complète est sujette aux erreurs. En outre, cela sent un énorme défaut de conception.
M. le Rutte

Du point de vue des performances, mon code ne semble pas produire plus d'impact que «new HashMap (). Size ()». Cependant, j'ai peut-être négligé les préoccupations auxquelles vous avez pensé et je ne suis pas un expert en VM. Je vois votre doute en comparant les noms de classe, mais il inclut le package ce qui me rend assez sûr, c'est ma classe parente. Quoi qu'il en soit, j'aime l'idée de comparer la signature à la place, comment feriez-vous cela? En général, si vous avez un moyen plus simple de déterminer si l'appelant est la superclasse ou toute autre personne que j'apprécierais ici.
Battre Siegrist

Si vous avez besoin de déterminer si l'appelant est une super classe, je réfléchirais sérieusement plus longtemps si une refonte est en place. C'est un anti-modèle.
M. le Rutte

Je vois le point mais la demande générale du fil est raisonnable. Dans certaines situations, il peut être logique qu'un appel de méthode de superclasse reste dans le contexte de superclasse avec n'importe quel appel de méthode imbriquée. Cependant, il ne semble y avoir aucun moyen de diriger l'appel de méthode en conséquence dans la Superclasse.
Battre Siegrist

0

Plus étendu la sortie de la question soulevée, cela donnera plus d'informations sur le spécificateur d'accès et le comportement de remplacement.

            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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.