web-dev-qa-db-fra.com

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

Je lis cette question et pensais que cela serait facilement résolu (non pas que ce ne soit pas résoluable sans) si on pouvait écrire:

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

Je ne suis pas sûr que ce soit utile dans de nombreux cas, mais je me demande pourquoi ce n'est pas le cas et si quelque chose comme cela existe dans d'autres langues.

Qu'en pensez-vous?

EDIT: Pour clarifier: oui, je le sais, c’est impossible en Java et je ne le manque pas vraiment. Ce n'est rien que je m'attendais à travailler et a été surpris d'obtenir une erreur de compilation. J'ai juste eu l'idée et aime en discuter.

341
Tim Büthe

Cela 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 classe propre (en particulier au sein de la même méthode) mais pas celui de vos parents. Par exemple, supposons que nous ayons une base "collection d’articles", une sous-classe représentant "une collection d’articles rouges" et une sous-classe de celle-ci représentant "une collection de gros objets 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 toujours être assuré que les éléments qu'il contient sont tous rouges. Supposons maintenant que nous étions capables d'appeler super.super.add ():

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

Nous pouvons maintenant ajouter ce que nous voulons et l'invariant dans RedItems est cassé.

Cela a-t-il du sens?

462
Jon Skeet

Je pense que Jon Skeet a la bonne réponse. Je voudrais juste ajouter que vous pouvez accéder aux variables ombrées des superclasses de superclasses en convertissant 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 tiré de JLS )

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

68
Michael Myers

Je pense que le code suivant permet d'utiliser super ... super.method () dans la plupart des cas. (même si c'est moche de faire ça)

En bref

  1. créer une instance temporaire de type ancêtre
  2. copier les valeurs des champs de original objet dans un temporaire
  3. invoquer la méthode cible sur un objet temporaire
  4. copier 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);
    }
}
39
Nico

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

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

La programmation orientée objet (ce que Java est) concerne uniquement 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, vous devez l'ajouter à la "classe de grands-parents", créer une nouvelle classe ou trouver un autre super dans lequel il s'inscrit.

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 langues que j'ai utilisées, mais je sais toujours à quoi m'attendre. Cela contribue à l'objectif "simple et familier" de Java. Dans mon esprit, appeler super 2 n'est pas simple ni familier. Peut-être les développeurs ont-ils ressenti la même chose?

11
EllaJo

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

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

(SuperSuperClass this) .theMethod ();

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

7
Larry Watanabe

En plus des très bons arguments que d'autres ont présentés, je pense qu'il y a une autre raison: que se passe-t-il si la super-classe n'a pas de super-classe?

Comme chaque classe s'étend naturellement (au moins) Object, super.whatever() fera toujours référence à une méthode de la superclasse. Mais que se passe-t-il si votre classe ne se prolonge que Object - à quoi ferait-on _super.super_? Comment gérer ce comportement - une erreur du compilateur, un NullPointer, etc.?

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

6
matt b

Je pense que si vous écrasez une méthode et souhaitez utiliser la version super-classe de celle-ci (comme par exemple pour equals), vous voudrez presque toujours appeler en premier la version directe de la superclasse, que l'on appellera sa version superclass. à son tour si ça veut.

Je pense qu'il est rarement logique (voire pas du tout. Je ne peux pas penser à un cas où cela se produit) d'appeler la version d'une méthode d'une superclasse arbitraire. Je ne sais pas si c'est possible du tout en Java. Cela peut être fait en C++:

this->ReallyTheBase::foo();
4

À deviner, parce que ce n'est pas utilisé aussi souvent. La seule raison pour laquelle je vois l’utiliser, c’est si votre parent direct a annulé certaines fonctionnalités et que vous essayez de les restaurer à l’original.

Ce qui me semble aller à l’encontre des OO principes, car le parent direct de la classe devrait être plus étroitement lié à votre classe que ne l’est le grand-parent.

3
Powerlord

Je mettrais le corps de la méthode super-super 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 pour "Je suis super super"

2
xMichal

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

Juste au cas où le lien serait brisé, 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";
        }
    }
}

Bonne codage !!!!

2
kyay

Il semblerait possible au moins d’obtenir 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 consulter la Javadoc à l'adresse http://Java.Sun.com/j2se/1.5.0/docs/api/Java/lang/Class.html#getSuperclass ( )

1
George Jempty

L'appel de super.super.method () est logique 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 prolongez-vous cette classe? Si la réponse est "parce que je ne peux pas le changer", vous pouvez créer un package et une classe exacts dans votre application, et réécrire la méthode coquine 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 une classe org.springframework.test.context.junit4.SpringJUnit4ClassRunner dans votre application. Cette classe doit donc être chargée avant la vraie pot. Puis réécrivez les méthodes ou les constructeurs.

Attention: Ceci est un hack absolu, et il est fortement recommandé de ne pas l'utiliser, mais ça marche! L'utilisation de cette approche est dangereuse en raison de problèmes possibles avec les chargeurs de classes. Cela peut également causer des problèmes chaque fois que vous mettez à jour une bibliothèque contenant une classe écrasée.

1
ruruskyi

@ Jon Skeet Belle explication. IMO si quelqu'un veut appeler la méthode super-super alors il faut vouloir ignorer le comportement du parent immédiat, mais vouloir accéder au comportement du grand parent. Ceci 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 de conducteur,

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

La sortie de ce sera

In C Class
In A Class

Le comportement printClass de classe B sera ignoré dans ce cas. Je ne suis pas sûr que ce soit un idéal ou une bonne pratique pour atteindre des super résultats, mais cela fonctionne toujours.

1
Sanjay Jain
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);

     }
}

run: A BUILD SUCCESSFUL (temps total: 0 secondes)

1
Boris

J'ai eu des situations comme celles-ci lorsque l'architecture consiste à créer des fonctionnalités communes dans un CustomBaseClass commun 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 une implémentation super.smet.

Nous y parvenons en introduisant un membre booléen dans CustomBaseClass, qui peut être utilisé pour différer de manière sélective l’implémentation personnalisée et céder le passage à l’implémentation de la structure par défaut, le cas échéant.

        ...
        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 le cadre ainsi que dans l'application, nous pourrions facilement éviter de telles situations, en utilisant une approche hasA au lieu d'une approche isA. Mais à tout moment, il n’est pas très pratique d’attendre une architecture bien conçue, d'où la nécessité de s’éloigner de principes de conception solides et d’introduire de tels piratages. Juste mes 2 cents ...

1
Ganesh Iyer

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

Aussi, vous pouvez 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 cette mise au point qu'avec un peu de vitesse. 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);
   }
} 

résultat du résultat objC.DoIt ():

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
D.Motyl

Si vous pensez avoir besoin de la super-classe, 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
Ashtheking

IMO, c’est un moyen simple d’obtenir le 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
Yakov Fain

Je pense que c'est un problème qui rompt l'accord de succession.
En prolongeant une classe, vous obéissez/acceptez son comportement,
Lorsque vous appelez super.super.method(), vous souhaitez rompre votre propre contrat d'obéissance.

Vous ne pouvez tout simplement pas choisir votre classe dans la super classe.

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

La rupture d’encapsulation se produit 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 final.

0
DayaMoon