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

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

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

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

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

EDIT: Прояснить: да, я знаю, что невозможно в Java, и я действительно не скучаю по нему. Это ничего, что я ожидал от работы, и был удивлен получением ошибки компилятора. У меня была идея и хотелось бы обсудить это.

Ответ 1

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

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 red or not. Take that, RedItems!
        super.super.add(item);
    }
}

Теперь мы можем добавить все, что нам нравится, и инвариант в RedItems сломан.

Это имеет смысл?

Ответ 2

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

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

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

(пример из JLS)

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

Ответ 3

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

Короче

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

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

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

Ответ 4

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

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

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

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

Ответ 5

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

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

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

Ответ 6

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

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

(SuperSuperClass this). Метод();

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

Ответ 7

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

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

this->ReallyTheBase::foo();

Ответ 8

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

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

Ответ 9

Я бы поместил тело 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();

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

Ответ 10

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

Ответ 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);

     }
}

пробег: BUILD SUCCESSFUL (общее время: 0 секунд)

Ответ 12

У меня были ситуации, подобные этим, когда архитектура должна создавать общие функции в общем 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, а не asA-подход. Но во все времена не очень практично ожидать хорошо продуманной архитектуры, и, следовательно, необходимо избегать твердых принципов проектирования и внедрять такие хаки. Только мои 2 цента...

Ответ 13

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

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, но все же она работает.

Ответ 14

IMO, это чистый способ достижения поведения 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

Ответ 15

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

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

Ответ 16

Вызов 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. Затем перепишите методы или конструкторы.

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

Ответ 17

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

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

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

Ответ 18

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

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

Ответ 19

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

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

публичный абстрактный класс A {

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

}

открытый класс B расширяет A {

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

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

}

открытый класс C расширяет 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 Выход будет: Класс А Класс C

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

Ответ 20

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

Выход: Отпечатано в GrandDad