Почему super.super.method (); не разрешено в Java?

Я прочитал этот вопрос и подумал, что его легко решить (не то чтобы без него невозможно), если бы можно было написать:

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

Я не уверен, что это полезно во многих случаях, но мне интересно, почему это не так и существует ли что-то подобное в других языках.

Что вы ребята думаете?

РЕДАКТИРОВАТЬ: Чтобы уточнить: да, я знаю, это невозможно в Java, и я действительно не скучаю по этому. Я не ожидал, что это сработает, и был удивлен, получив ошибку компилятора. У меня просто возникла идея, и я хотел бы ее обсудить.

Ответов (22)

Решение

Это нарушает инкапсуляцию. У вас не должно быть возможности обойти поведение родительского класса. Иногда имеет смысл иметь возможность обойти поведение вашего собственного класса (особенно из того же метода), но не поведение вашего родителя. Например, предположим, что у нас есть базовая «коллекция элементов», подкласс, представляющий «коллекцию красных элементов», и подкласс того, что представляет «коллекцию больших красных элементов». Имеет смысл иметь:

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

Это нормально - RedItems всегда может быть уверен, что все элементы, которые он содержит, красные. Теперь предположим , что мы были в состоянии назвать 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);
    }
}

Теперь мы можем добавить все, что захотим, и инвариант in RedItems будет нарушен.

Имеет ли это смысл?

Вызов super.super.method () имеет смысл, когда вы не можете изменить код базового класса. Это часто случается, когда вы расширяете существующую библиотеку.

Сначала спросите себя, почему вы расширяете этот класс? Если ответ - «потому что я не могу его изменить», вы можете создать точный пакет и класс в своем приложении и переписать непослушный метод или создать делегат:

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() {...}
    ...
}

Например, вы можете создать класс org.springframework.test.context.junit4.SpringJUnit4ClassRunner в своем приложении, чтобы этот класс загружался раньше реального из jar. Затем перепишите методы или конструкторы.

Внимание: это абсолютный хакер, и его настоятельно НЕ рекомендуется использовать, но он РАБОТАЕТ! Использование этого подхода опасно из-за возможных проблем с загрузчиками классов. Также это может вызывать проблемы каждый раз, когда вы обновляете библиотеку, содержащую перезаписанный класс.

Казалось бы, можно, по крайней мере, получить класс суперкласса суперкласса, хотя и не обязательно его экземпляр, с помощью отражения; если это может быть полезно, рассмотрите Javadoc по адресу http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Class.html#getSuperclass ()

@Jon Skeet Хорошее объяснение. ИМО, если кто-то хочет вызвать метод super.super, тогда нужно игнорировать поведение непосредственного родителя, но нужно получить доступ к поведению главного родителя. Этого можно добиться с помощью экземпляра Of. Как показано ниже, код

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

Вот класс драйвера,

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

Результатом этого будет

In C Class
In A Class

В этом случае поведение printClass класса B будет проигнорировано. Я не уверен, что это идеальная или хорошая практика для достижения super.super, но все же она работает.

Я бы поместил тело метода super.super в другой метод, если это возможно

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

Или, если вы не можете изменить суперкласс, вы можете попробовать следующее:

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

В обоих случаях

new ChildClass().toString();

результаты на "Я супер"

Я считаю, что это проблема, которая нарушает договор о наследстве.
Расширяя класс, вы подчиняетесь / соглашаетесь с его поведением, особенностями.
Во время звонка super.super.method() вы хотите нарушить собственное соглашение о повиновении.

Вы просто не можете выбрать вишню из суперкласса .

Однако могут возникнуть ситуации, когда вы почувствуете необходимость позвонить super.super.method() - обычно это признак плохого дизайна в вашем коде или в коде, который вы наследуете!
Если суперклассы и суперклассы не могут быть реорганизованы (некоторый унаследованный код), выберите композицию вместо наследования. Нарушение

инкапсуляции - это когда вы @Override некоторые методы, нарушая инкапсулированный код. Методы, которые нельзя переопределить, помечаются как окончательные .

У меня были такие ситуации, когда архитектура должна была создать общие функции в общем CustomBaseClass, который реализуется от имени нескольких производных классов. Однако нам нужно обойти общую логику для конкретного метода для определенного производного класса. В таких случаях мы должны использовать реализацию super.super.methodX.

Мы достигаем этого, вводя логический член в CustomBaseClass, который можно использовать для выборочной отсрочки пользовательской реализации и перехода к реализации фреймворка по умолчанию там, где это необходимо.

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

Однако при соблюдении хороших архитектурных принципов как в рамках, так и в приложении, мы могли бы легко избежать таких ситуаций, используя подход hasA вместо подхода isA. Но всегда не очень практично ожидать наличия хорошо спроектированной архитектуры и, следовательно, необходимости отходить от твердых принципов проектирования и внедрять подобные хаки. Только мои 2 цента ...

Это просто сделать. Например:

Подкласс C от B и подкласс B от A. Например, оба из трех имеют метод methodName ().

public abstract class A {

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

}

public class B extends A {

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

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

}

public class C extends 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");
    }

}

Выполнить класс C Выходные данные будут: Класс A Класс C

Вместо вывода: Класс A Класс B Класс C

В C# вы можете вызвать метод любого предка следующим образом:

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

Также вы можете сделать это в Delphi:

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

Но в Java такую ​​фокусировку можно сделать только с помощью каких-то приспособлений. Один из возможных способов:

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 ():

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

Посмотрите на этот проект Github, особенно на переменную objectHandle. Этот проект показывает, как на самом деле и точно вызвать метод дедушки и бабушки для внуков.

На случай, если ссылка сломается, вот код:

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

Удачного кодирования !!!!

Ключевое слово super - это просто способ вызвать метод в суперклассе. В руководстве по Java: https://docs.oracle.com/javase/tutorial/java/IandI/super.html

Если ваш метод переопределяет один из методов своего суперкласса, вы можете вызвать переопределенный метод с помощью ключевого слова super.

Не верьте, что это ссылка на суперобъект !!! Нет, это просто ключевое слово для вызова методов суперкласса.

Вот пример:

class Animal {
    public void doSth() {
        System.out.println(this);   // It's a Cat! Not an animal!
        System.out.println("Animal do sth.");
    }
}

class Cat extends Animal {
    public void doSth() {
        System.out.println(this);
        System.out.println("Cat do sth.");
        super.doSth();
    }
}

Когда вы вызываете cat.doSth(), метод doSth() в классе Animal печатает, this и это кошка.

Предположительно, потому что он используется не так часто. Единственная причина, по которой я мог его использовать, - это то, что ваш прямой родитель переопределил некоторые функции, и вы пытаетесь восстановить их до исходного состояния.

Мне кажется, что это противоречит принципам объектно-ориентированного программирования, поскольку прямой родитель класса должен быть более тесно связан с вашим классом, чем дедушка и бабушка.

Я думаю, что если вы перезаписываете метод и хотите использовать всю equals его версию суперкласса (например, например ), то вы практически всегда хотите сначала вызвать прямую версию суперкласса, которая, в свою очередь, вызовет свою версию суперкласса, если она хочет .

Я думаю, что в редких случаях имеет смысл (если вообще есть. Я не могу вспомнить случай, когда это имеет место) вызывать какую-то произвольную версию метода суперкласса. Я не знаю, возможно ли это вообще на Java. Это можно сделать на C++:

this->ReallyTheBase::foo();

В дополнение к очень хорошим замечаниям, высказанным другими, я думаю, есть еще одна причина: что, если суперкласс не имеет суперкласса?

Поскольку каждый класс естественным образом расширяется (по крайней мере) Object, super.whatever() всегда будет ссылаться на метод в суперклассе. Но что, если ваш класс только расширяется Object - на что тогда super.super ссылаться? Как следует обрабатывать такое поведение - ошибку компилятора, NullPointer и т. Д.?

Я думаю, что основная причина, по которой это запрещено, заключается в том, что это нарушает инкапсуляцию, но это тоже может быть небольшой причиной.

Думаю, у Джона Скита есть правильный ответ. Я просто хотел бы добавить, что вы можете получить доступ к теневым переменным из суперклассов суперклассов путем приведения 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();
        }
}

который производит вывод:

х = 3
super.x = 2
((T2) это) .x = 2
((T1) это) .x = 1
((I) это) .x = 0

(пример из JLS )

Однако это не работает для вызовов методов, поскольку вызовы методов определяются в зависимости от типа среды выполнения объекта.

У меня недостаточно репутации, чтобы комментировать, поэтому я добавлю это к другим ответам.

Джон Скит прекрасно отвечает с красивым примером. Мэтт Б. прав: не все суперклассы имеют суперклассы. Ваш код сломается, если вы назовете супер или супер, у которого нет супер.

Объектно-ориентированное программирование (которым является Java) - это объекты, а не функции. Если вы хотите ориентированное на задачи программирование, выберите C++ или что-то еще. Если ваш объект не вписывается в этот суперкласс, вам нужно добавить его в «класс дедушки и бабушки», создать новый класс или найти другой суперкласс, в который он вписывается.

Лично я считаю это ограничение одной из самых сильных сторон Java. Код несколько жесткий по сравнению с другими языками, которые я использовал, но я всегда знаю, чего ожидать. Это помогает достичь «простой и знакомой» цели Java. На мой взгляд, позвонить в super.super непросто и непросто. Может быть, разработчики чувствовали то же самое?

Я думаю, что следующий код позволяет использовать super.super ... super.method () в большинстве случаев. (даже если это некрасиво)

Суммируя

  1. создать временный экземпляр типа предка
  2. копировать значения полей из исходного объекта во временный
  3. вызвать целевой метод для временного объекта
  4. копировать измененные значения обратно в исходный объект

Использование :

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

Для этого есть несколько веских причин. У вас может быть подкласс, метод которого реализован неправильно, но родительский метод реализован правильно. Поскольку он принадлежит сторонней библиотеке, возможно, вы не сможете / не захотите изменить источник. В этом случае вы хотите создать подкласс, но переопределить один метод для вызова метода super.super.

Как показано на некоторых других плакатах, это можно сделать через отражение, но должно быть возможно сделать что-то вроде

(SuperSuperClass это) .theMethod ();

Я сейчас занимаюсь этой проблемой - быстрое решение - скопировать и вставить метод суперкласса в метод подкласса :)

ИМО, это чистый способ добиться super.super.sayYourName() поведения на 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);
    }
}

Выход:

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

Если вы думаете, что вам понадобится суперкласс, вы можете указать его в переменной для этого класса. Например:

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

Следует распечатать:

2
1
0
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);

     }
}

запустить: СТРОИТЬ УСПЕШНО (общее время: 0 секунд)

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

Продукт: Отпечатано в Дедушке.