Pourquoi est super.super.method(); non autorisé en Java?


J'ai lu cette question et j'ai pensé que cela serait facilement résolu (pas qu'il ne soit pas résoluble sans) si on pouvait écrire:

@Override
public String toString() {
    return super.super.toString();
}

Je ne sais pas si c'est utile dans de nombreux cas, mais je me demande pourquoi ce n'est pas le cas et si quelque chose comme ça existe dans d'autres langues.

Ce que vous en pensez?

MODIFIER: Pour clarifier: oui je sais, c'est impossible en Java et ça ne me manque pas vraiment. Ce n'est rien que je m'attendais à travailler et a été surpris d'obtenir un erreur de compilateur. Je viens d'avoir l'idée et j'aime en discuter.

Author: Community, 2009-02-25

21 answers

Il viole l'encapsulation. Vous ne devriez pas pouvoir contourner le comportement de la classe parent. Il est logique de pouvoir parfois contourner le comportement de votre propre (en particulier à partir de la même méthode) mais pas celui de votre parent. Par exemple, supposons que nous ayons une "collection d'éléments" de base, une sous-classe représentant "une collection d'éléments rouges" et une sous-classe de celle représentant "une collection de gros éléments rouges". Il est logique d'avoir:

public class Items
{
    public void add(Item item) { ... }
}

public class RedItems extends Items
{
    @Override
    public void add(Item item)
    {
        if (!item.isRed())
        {
            throw new NotRedItemException();
        }
        super.add(item);
    }
}

public class BigRedItems extends RedItems
{
    @Override
    public void add(Item item)
    {
        if (!item.isBig())
        {
            throw new NotBigItemException();
        }
        super.add(item);
    }
}

C'est bien-RedItems peut soyez toujours sûr que les éléments qu'il contient sont tous rouges. Supposons maintenant que nous étions capables d'appeler super.super.ajouter ():

public class NaughtyItems extends RedItems
{
    @Override
    public void add(Item item)
    {
        // I don't care if it's red or not. Take that, RedItems!
        super.super.add(item);
    }
}

Maintenant, nous pourrions ajouter ce que nous voulons, et l'invariant dans RedItems est cassé.

Cela a-t-il un sens?

 437
Author: Jon Skeet, 2009-03-31 14:45:02

Je pense que Jon Skeet a la bonne réponse. Je voudrais juste ajouter que vous pouvez accéder à des variables ombragées à partir de superclasses de superclasses en lançant this:

interface I { int x = 0; }
class T1 implements I { int x = 1; }
class T2 extends T1 { int x = 2; }
class T3 extends T2 {
        int x = 3;
        void test() {
                System.out.println("x=\t\t"          + x);
                System.out.println("super.x=\t\t"    + super.x);
                System.out.println("((T2)this).x=\t" + ((T2)this).x);
                System.out.println("((T1)this).x=\t" + ((T1)this).x);
                System.out.println("((I)this).x=\t"  + ((I)this).x);
        }
}

class Test {
        public static void main(String[] args) {
                new T3().test();
        }
}

Qui produit la sortie:

x=              3
super.x=        2
((T2)this).x=   2
((T1)this).x=   1
((I)this).x=    0

(exemple du JLS)

Cependant, cela ne fonctionne pas pour les appels de méthode car les appels de méthode sont déterminés en fonction du type d'exécution de l'objet.

 61
Author: Michael Myers, 2018-03-26 19:49:54

Je pense que le code suivant permet d'utiliser super.super...super.méthode() dans la plupart des cas. (même si c'est uggly de le faire)

En bref

  1. créer une instance temporaire de type ancêtre
  2. copier les valeurs des champs de l'objetoriginal vers un objet temporaire
  3. invoquer la cible de la méthode sur l'objet temporaire
  4. recopier les valeurs modifiées dans l'objet d'origine

Utilisation :

public class A {
   public void doThat() { ... }
}

public class B extends A {
   public void doThat() { /* don't call super.doThat() */ }
}

public class C extends B {
   public void doThat() {
      Magic.exec(A.class, this, "doThat");
   }
}


public class Magic {
    public static <Type, ChieldType extends Type> void exec(Class<Type> oneSuperType, ChieldType instance,
            String methodOfParentToExec) {
        try {
            Type type = oneSuperType.newInstance();
            shareVars(oneSuperType, instance, type);
            oneSuperType.getMethod(methodOfParentToExec).invoke(type);
            shareVars(oneSuperType, type, instance);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    private static <Type, SourceType extends Type, TargetType extends Type> void shareVars(Class<Type> clazz,
            SourceType source, TargetType target) throws IllegalArgumentException, IllegalAccessException {
        Class<?> loop = clazz;
        do {
            for (Field f : loop.getDeclaredFields()) {
                if (!f.isAccessible()) {
                    f.setAccessible(true);
                }
                f.set(target, f.get(source));
            }
            loop = loop.getSuperclass();
        } while (loop != Object.class);
    }
}
 37
Author: Nico, 2017-07-01 23:17:58

Je n'ai pas assez de réputation pour commenter, donc je vais ajouter ceci aux autres réponses.

Jon Skeet répond parfaitement, avec un bel exemple. Matt B a un point: toutes les superclasses n'ont pas de supers. Votre code se casserait si vous appeliez un super d'un super qui n'avait pas de super.

La programmation orientée objet (qui est Java) concerne les objets, pas les fonctions. Si vous voulez une programmation orientée tâche, choisissez C++ ou autre chose. Si votre objet ne rentre pas dans sa super classe, ensuite, vous devez l'ajouter à la" classe des grands-parents", créer une nouvelle classe ou trouver un autre super dans lequel il s'intègre.

Personnellement, j'ai trouvé que cette limitation était l'une des plus grandes forces de Java. Le code est un peu rigide par rapport aux autres langages que j'ai utilisés, mais je sais toujours à quoi m'attendre. Cela aide à l'objectif "simple et familier" de Java. Dans mon esprit, appeler super.super n'est pas simple ou familier. Peut-être que les développeurs ont ressenti la même chose?

 10
Author: EllaJo, 2010-05-07 14:28:39

Il y a de bonnes raisons de le faire. Vous pouvez avoir une sous-classe qui a une méthode qui est implémentée de manière incorrecte, mais la méthode parent est implémentée correctement. Comme il appartient à une bibliothèque tierce, il se peut que vous ne puissiez pas/ne vouliez pas changer la source. Dans ce cas, vous souhaitez créer une sous-classe mais remplacer une méthode pour appeler le super.super méthode.

Comme le montrent d'autres affiches, il est possible de le faire par la réflexion, mais il devrait être possible de le faire quelque chose comme

(SuperSuperClass ce).theMethod ();

Je traite ce problème en ce moment - la solution rapide est de copier et coller la méthode superclass dans la méthode subsubclass:)

 8
Author: Larry Watanabe, 2010-05-07 14:24:56

En plus des très bons points que d'autres ont faits, je pense qu'il y a une autre raison: que faire si la superclasse n'a pas de superclasse?

Puisque chaque classe s'étend naturellement (au moins) Object, super.whatever() fera toujours référence à une méthode dans la superclasse. Mais que se passe - t-il si votre classe n'étend que Object - à quoi ferait référence super.super alors? Comment ce comportement devrait - il être géré-une erreur de compilateur, un NullPointer, etc.?

Je pense que la principale raison pour laquelle cela n'est pas autorisé est qu'il viole l'encapsulation, mais cela pourrait être une petite raison aussi.

 7
Author: matt b, 2009-02-25 15:34:04

Je pense que si vous écrasez une méthode et que vous voulez toute la version de super-classe (comme, disons pour equals), alors vous voulez pratiquement toujours appeler la version directe de superclasse en premier, laquelle appellera sa version de superclasse à son tour si elle le veut.

Je pense que cela n'a que rarement de sens (voire pas du tout. je ne peux pas penser à un cas où c'est le cas) pour appeler une version de superclasse arbitraire d'une méthode. Je ne sais pas si cela est possible en Java. Cela peut être fait en C++:

this->ReallyTheBase::foo();
 3
Author: Johannes Schaub - litb, 2009-02-25 15:14:41

À une supposition, parce qu'il n'est pas utilisé aussi souvent. La seule raison pour laquelle je pouvais voir l'utiliser est si votre parent direct a remplacé certaines fonctionnalités et que vous essayez de le restaurer à l'original.

Ce qui me semble être contraire aux principes OO, puisque le parent direct de la classe devrait être plus étroitement lié à votre classe que le grand-parent ne l'est.

 2
Author: Powerlord, 2009-02-25 15:16:12

Je mettrais le super.super corps de méthode dans une autre méthode, si possible

class SuperSuperClass {
    public String toString() {
        return DescribeMe();
    }

    protected String DescribeMe() {
        return "I am super super";
    }
}

class SuperClass extends SuperSuperClass {
    public String toString() {
        return "I am super";
    }
}

class ChildClass extends SuperClass {
    public String toString() {
        return DescribeMe();
    }
}

Ou si vous ne pouvez pas changer la classe super-super, vous pouvez essayer ceci:

class SuperSuperClass {
    public String toString() {
        return "I am super super";
    }
}

class SuperClass extends SuperSuperClass {
    public String toString() {
        return DescribeMe(super.toString());
    }

    protected String DescribeMe(string fromSuper) {
        return "I am super";
    }
}

class ChildClass extends SuperClass {
    protected String DescribeMe(string fromSuper) {
        return fromSuper;
    }
}

Dans les deux cas, le

new ChildClass().toString();

Résultats à "Je suis super super"

 2
Author: xMichal, 2013-10-29 12:32:46

Il semblerait être possible d'obtenir au moins la classe de la superclasse de la superclasse, mais pas nécessairement l'instance de celle-ci, en utilisant la réflexion; si cela peut être utile, veuillez considérer le Javadoc à http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Class.html#getSuperclass()

 1
Author: George Jempty, 2009-07-02 20:22:11
public class A {

     @Override
     public String toString() {
          return "A";
     }

}


public class B extends A {

     @Override
     public String toString() {
          return "B";
     }

}

public class C extends B {

     @Override
     public String toString() {
          return "C";
     }

}


public class D extends C {

     @Override
     public String toString() {
          String result = "";
          try {
                result = this.getClass().getSuperclass().getSuperclass().getSuperclass().newInstance().toString();
          } catch (InstantiationException ex) {
                Logger.getLogger(D.class.getName()).log(Level.SEVERE, null, ex);
          } catch (IllegalAccessException ex) {
                Logger.getLogger(D.class.getName()).log(Level.SEVERE, null, ex);
          }
          return result;
     }

}

public class Main {

     public static void main(String... args) {
          D d = new D();
          System.out.println(d);

     }
}

Exécuter: Un CONSTRUCTION RÉUSSIE (temps total: 0 secondes)

 1
Author: Boris, 2011-07-20 19:59:49

J'ai eu des situations comme celles-ci lorsque l'architecture consiste à construire des fonctionnalités communes dans une CustomBaseClass commune qui implémente au nom de plusieurs classes dérivées. Cependant, nous devons contourner la logique commune pour une méthode spécifique pour une classe dérivée spécifique. Dans de tels cas, nous devons utiliser un super.super.Implémentation de methodX.

Nous y parvenons en introduisant un membre booléen dans la CustomBaseClass, qui peut être utilisé pour différer sélectivement l'implémentation personnalisée et céder à mise en œuvre du cadre par défaut lorsque cela est souhaitable.

        ...
        FrameworkBaseClass (....) extends...
        {
           methodA(...){...}
           methodB(...){...}
        ...
           methodX(...)
        ...
           methodN(...){...}

        }
        /* CustomBaseClass overrides default framework functionality for benefit of several derived classes.*/
        CustomBaseClass(...) extends FrameworkBaseClass 
        {
        private boolean skipMethodX=false; 
        /* implement accessors isSkipMethodX() and setSkipMethodX(boolean)*/

           methodA(...){...}
           methodB(...){...}
        ...
           methodN(...){...}

           methodX(...){
                  if (isSkipMethodX()) {
                       setSKipMethodX(false);
                       super.methodX(...);
                       return;
                       }
                   ... //common method logic
            }
        }

        DerivedClass1(...) extends CustomBaseClass
        DerivedClass2(...) extends CustomBaseClass 
        ...
        DerivedClassN(...) extends CustomBaseClass...

        DerivedClassX(...) extends CustomBaseClass...
        {
           methodX(...){
                  super.setSKipMethodX(true);
                  super.methodX(...);
                       }
        }

Cependant, avec de bons principes d'architecture suivis dans framework ainsi que dans app, nous pourrions éviter de telles situations facilement, en utilisant l'approche hasA, au lieu de l'approche isA. Mais en tout temps, il n'est pas très pratique de s'attendre à une architecture bien conçue en place, d'où la nécessité de s'éloigner des principes de conception solides et d'introduire des hacks comme celui-ci. Juste mes 2 cents...

 1
Author: Ganesh Iyer, 2013-02-27 05:51:35

@Jon Skeet Belle explication. OMI si quelqu'un veut appeler super.super méthode alors il faut vouloir ignorer le comportement du parent immédiat, mais vouloir accéder au comportement du grand parent. Cela peut être réalisé par exemple De. Comme ci-dessous code

public class A {
    protected void printClass() {
        System.out.println("In A Class");
    }
}

public class B extends A {

    @Override
    protected void printClass() {
        if (!(this instanceof C)) {
            System.out.println("In B Class");
        }
        super.printClass();
    }
}

public class C extends B {
    @Override
    protected void printClass() {
        System.out.println("In C Class");
        super.printClass();
    }
}

Voici la classe du pilote,

public class Driver {
    public static void main(String[] args) {
        C c = new C();
        c.printClass();
    }
}

La sortie de ceci sera

In C Class
In A Class

Le comportement de la classe B printClass sera ignoré dans ce cas. Je ne suis pas sûr que ce soit un idéal ou une bonne pratique pour atteindre super.super, mais encore il est de travail.

 1
Author: Sanjay Jain, 2013-09-04 06:18:10

Si vous pensez que vous allez avoir besoin de la superclasse, vous pouvez la référencer dans une variable pour cette classe. Par exemple:

public class Foo
{
  public int getNumber()
  {
    return 0;
  }
}

public class SuperFoo extends Foo
{
  public static Foo superClass = new Foo();
  public int getNumber()
  {
    return 1;
  }
}

public class UltraFoo extends Foo
{
  public static void main(String[] args)
  {
    System.out.println(new UltraFoo.getNumber());
    System.out.println(new SuperFoo().getNumber());
    System.out.println(new SuperFoo().superClass.getNumber());
  }
  public int getNumber()
  {
    return 2;
  }
}

Devrait imprimer:

2
1
0
 0
Author: Ashtheking, 2011-07-20 14:26:15

IMO, c'est un moyen propre d'obtenir un comportement super.super.sayYourName() en Java.

public class GrandMa {  
    public void sayYourName(){  
        System.out.println("Grandma Fedora");  
    }  
}  

public class Mama extends GrandMa {  
    public void sayYourName(boolean lie){  
        if(lie){   
            super.sayYourName();  
        }else {  
            System.out.println("Mama Stephanida");  
        }  
    }  
}  

public class Daughter extends Mama {  
    public void sayYourName(boolean lie){  
        if(lie){   
            super.sayYourName(lie);  
        }else {  
            System.out.println("Little girl Masha");  
        }  
    }  
}  

public class TestDaughter {
    public static void main(String[] args){
        Daughter d = new Daughter();

        System.out.print("Request to lie: d.sayYourName(true) returns ");
        d.sayYourName(true);
        System.out.print("Request not to lie: d.sayYourName(false) returns ");
        d.sayYourName(false);
    }
}

Sortie:

Request to lie: d.sayYourName(true) returns Grandma Fedora
Request not to lie: d.sayYourName(false) returns Little girl Masha

 0
Author: Yakov Fain, 2011-08-13 09:48:27

Appel de super.super.method () a du sens lorsque vous ne pouvez pas changer le code de la classe de base. Cela se produit souvent lorsque vous étendez une bibliothèque existante.

Demandez-vous d'abord, pourquoi étendez-vous cette classe? Si la réponse est "parce que je ne peux pas la changer", vous pouvez créer un package et une classe exacts dans votre application, et réécrire la méthode naughty ou créer un délégué:

package com.company.application;

public class OneYouWantExtend extends OneThatContainsDesiredMethod {

    // one way is to rewrite method() to call super.method() only or 
    // to doStuff() and then call super.method()

    public void method() {
        if (isDoStuff()) {
            // do stuff
        }
        super.method();
    }

    protected abstract boolean isDoStuff();


    // second way is to define methodDelegate() that will call hidden super.method()

    public void methodDelegate() {
        super.method();
    }
    ...
}

public class OneThatContainsDesiredMethod {

    public void method() {...}
    ...
}

Par exemple, vous pouvez créer org.springframework.test.cadre.junit4.SpringJUnit4ClassRunner classe dans votre application, cette classe doit donc être chargée avant la vraie de jar. Réécrivez ensuite les méthodes ou les constructeurs.

Attention: {[11] } C'est un hack absolu, et il est fortement déconseillé d'utiliser mais ça MARCHE! L'utilisation de cette approche est dangereuse en raison de problèmes possibles avec les chargeurs de classe. Cela peut également causer des problèmes à chaque fois que vous mettrez à jour la bibliothèque contenant une classe écrasée.

 0
Author: ruruskyi, 2011-11-16 15:00:10

Je pense que c'est un problème qui rompt le contrat d'héritage.
En étendant une classe, vous obéissez / acceptez son comportement, caractéristiques
Alors que lorsque vous appelez super.super.method(), vous voulez rompre votre propre accord d'obéissance.

Vous ne pouvez tout simplement pas choisir parmi la super classe.

Cependant, il peut arriver des situations où vous ressentez le besoin d'appeler super.super.method() - généralement un mauvais signe de conception, dans votre code ou dans le code dont vous héritez !
Si le super et super super les classes ne peuvent pas être refactorisées (du code hérité), alors optez pour la composition plutôt que l'héritage.

La rupture d'encapsulation est lorsque vous @Override certaines méthodes en cassant le code encapsulé. Les méthodes conçues pour ne pas être remplacées sont marquées finale.

 0
Author: DayaMoon, 2014-04-09 08:13:39

En C#, vous pouvez appeler une méthode de n'importe quel ancêtre comme ceci:

public class A
    internal virtual void foo()
...
public class B : A
    public new void foo()
...
public class C : B
    public new void foo() {
       (this as A).foo();
    }

Vous pouvez également le faire dans Delphi:

type
   A=class
      procedure foo;
      ...
   B=class(A)
     procedure foo; override;
     ...
   C=class(B)
     procedure foo; override;
     ...
A(objC).foo();

Mais en Java, vous ne pouvez faire une telle mise au point que par un certain équipement. Une façon possible est:

class A {               
   int y=10;            

   void foo(Class X) throws Exception {  
      if(X!=A.class)
         throw new Exception("Incorrect parameter of "+this.getClass().getName()+".foo("+X.getName()+")");
      y++;
      System.out.printf("A.foo(%s): y=%d\n",X.getName(),y);
   }
   void foo() throws Exception { 
      System.out.printf("A.foo()\n");
      this.foo(this.getClass()); 
   }
}

class B extends A {     
   int y=20;            

   @Override
   void foo(Class X) throws Exception { 
      if(X==B.class) { 
         y++; 
         System.out.printf("B.foo(%s): y=%d\n",X.getName(),y);
      } else { 
         System.out.printf("B.foo(%s) calls B.super.foo(%s)\n",X.getName(),X.getName());
         super.foo(X);
      } 
   }
}

class C extends B {     
   int y=30;            

   @Override
   void foo(Class X) throws Exception { 
      if(X==C.class) { 
         y++; 
         System.out.printf("C.foo(%s): y=%d\n",X.getName(),y);
      } else { 
         System.out.printf("C.foo(%s) calls C.super.foo(%s)\n",X.getName(),X.getName());
         super.foo(X);
      } 
   }

   void DoIt() {
      try {
         System.out.printf("DoIt: foo():\n");
         foo();         
         Show();

         System.out.printf("DoIt: foo(B):\n");
         foo(B.class);  
         Show();

         System.out.printf("DoIt: foo(A):\n");
         foo(A.class);  
         Show();
      } catch(Exception e) {
         //...
      }
   }

   void Show() {
      System.out.printf("Show: A.y=%d, B.y=%d, C.y=%d\n\n", ((A)this).y, ((B)this).y, ((C)this).y);
   }
} 

ObjC.DoIt() résultat de sortie:

DoIt: foo():
A.foo()
C.foo(C): y=31
Show: A.y=10, B.y=20, C.y=31

DoIt: foo(B):
C.foo(B) calls C.super.foo(B)
B.foo(B): y=21
Show: A.y=10, B.y=21, C.y=31

DoIt: foo(A):
C.foo(A) calls C.super.foo(A)
B.foo(A) calls B.super.foo(A)
A.foo(A): y=11
Show: A.y=11, B.y=21, C.y=31
 0
Author: D.Motyl, 2017-08-07 19:56:51

Regardez ce projet Github, en particulier la variable objectHandle. Ce projet montre comment appeler réellement et avec précision la méthode des grands-parents sur un petit-enfant.

Juste au cas où le lien serait cassé, voici le code:

import lombok.val;
import org.junit.Assert;
import org.junit.Test;

import java.lang.invoke.*;

/*
Your scientists were so preoccupied with whether or not they could, they didn’t stop to think if they should.
Please don't actually do this... :P
*/
public class ImplLookupTest {
    private MethodHandles.Lookup getImplLookup() throws NoSuchFieldException, IllegalAccessException {
        val field = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP");
        field.setAccessible(true);
        return (MethodHandles.Lookup) field.get(null);
    }

    @Test
    public void test() throws Throwable {
        val lookup = getImplLookup();
        val baseHandle = lookup.findSpecial(Base.class, "toString",
            MethodType.methodType(String.class),
            Sub.class);
        val objectHandle = lookup.findSpecial(Object.class, "toString",
            MethodType.methodType(String.class),
            // Must use Base.class here for this reference to call Object's toString
            Base.class);
        val sub = new Sub();
        Assert.assertEquals("Sub", sub.toString());
        Assert.assertEquals("Base", baseHandle.invoke(sub));
        Assert.assertEquals(toString(sub), objectHandle.invoke(sub));
    }

    private static String toString(Object o) {
        return o.getClass().getName() + "@" + Integer.toHexString(o.hashCode());
    }

    public class Sub extends Base {
        @Override
        public String toString() {
            return "Sub";
        }
    }

    public class Base {
        @Override
        public String toString() {
            return "Base";
        }
    }
}

Bon codage!!!!

 0
Author: kyay, 2018-08-15 16:48:28

C'est tout simplement facile à faire. Par exemple:

C sous-classe de B et B sous-classe de A. Les trois ont la méthode methodName() par exemple.

Résumé public classe A {

public void methodName() {
    System.out.println("Class A");
}

}

La classe publique B étend un {

public void methodName() {
    super.methodName();
    System.out.println("Class B");
}

// Will call the super methodName
public void hackSuper() {
    super.methodName();
}

}

La classe publique C étend B {

public static void main(String[] args) {
    A a = new C();
    a.methodName();
}

@Override
public void methodName() {
    /*super.methodName();*/
    hackSuper();
    System.out.println("Class C");
}

}

Exécuter la sortie de classe C sera: De Classe A Classe C

Au lieu de sortie: De Classe A Classe B Classe C

 -1
Author: user2490562, 2015-02-16 12:08:56
public class SubSubClass extends SubClass {

    @Override
    public void print() {
        super.superPrint();
    }

    public static void main(String[] args) {
        new SubSubClass().print();
    }
}

class SuperClass {

    public void print() {
        System.out.println("Printed in the GrandDad");
    }
}

class SubClass extends SuperClass {

    public void superPrint() {
        super.print();
    }
}

Sortie: Imprimé dans le grand-père

 -1
Author: Andrey, 2015-04-17 05:48:36