为什么Java不允许使用super.super.method();?

426
我阅读了这个问题,并认为如果能够编写以下代码,则可以轻松解决该问题(即使不使用此方法也可以解决):
@Override
public String toString() {
    return super.super.toString();
}

我不确定它在许多情况下是否有用,但我想知道为什么它不是这样,并且其他语言中是否存在类似的东西。

你们觉得呢?

编辑: 澄清一下:是的,我知道在Java中这是不可能的,我也不是很想要它。这不是我期望它能工作,而是惊讶于得到编译器错误。我只是有这个想法,想讨论一下。


8
想要调用super.super.toString()与你选择扩展一个类并接受它的所有特性的决定相矛盾,因此不建议这样做。 - DayaMoon
22个回答

3

我猜是因为它并不经常使用。唯一能想到使用它的原因是,如果你的直接父类覆盖了某些功能,并且你正在尝试将其恢复到原始状态。

这在我的看法中违反了面向对象的原则,因为类的直接父类应该比祖父类更与你的类相关。


2
如果可能的话,我会将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();

结果为“我是超级超级”


我发现自己处于这样一种情况:我拥有SuperSuperClass和ChildClass,但没有SuperClass,因此我发现第一个解决方案很有用。 - xofon

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

     }
}

运行: A 构建成功(总时间:0秒)


1
我明白了,但是你正在创建一个新实例,所以如果对象具有任何状态,这种方法就不起作用。 - Tim Büthe

1

1
我曾经遇到过这样的情况,即在一个通用 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方法。但并不总是实际可行地期望有良好设计的架构,因此需要摆脱坚实的设计原则,并引入像这样的hack。 这只是我的个人意见...

0

这非常容易。例如:

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


0

在我看来,这是一种在Java中实现super.super.sayYourName()行为的简洁方式。

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

输出:

请求说谎:d.sayYourName(true) 返回奶奶 Fedora
请求不说谎:d.sayYourName(false) 返回小女孩 Masha


啊,所以你主张实现一个类层次结构像这样?不幸的是,如果你想从Baby(女儿的子类)访问Mama中的方法,这开始变得非常混乱... - bcr
雅科夫·费恩是正确的。换句话说,这不是一个好的例子,因为原始问题是关于在super.super中调用重写方法的。 - inor

0

我认为这是违反继承协议的问题。
通过扩展一个类,你遵守/同意它的行为和特性。
而当调用super.super.method()时,你想要打破自己的服从协议。

你不能仅仅从超类中挑选

然而,在你的代码或继承的代码中,可能会出现需要调用super.super.method()的情况 - 这通常是一个不好的设计信号!
如果supersuper super类无法重构(一些旧代码),那么选择组合而不是继承。

封装破坏是当你通过破坏封装的代码来@Override一些方法时发生的。 被设计为不可覆盖的方法被标记为final


0
在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

在 C# 中,它只适用于非虚方法,而由于在 Java 中所有方法均为虚方法,因此并没有什么不同。 - Agent_L

-1
如果您认为您需要使用超类,您可以在该类的变量中引用它。例如:
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

2
你的例子有点...嗯,不太好,因为你使用了静态方法。当使用静态方法时,根本不需要变量或super关键字。也许你在这里错过了一些基本的面向对象概念,请确保你阅读了相关资料。很抱歉,我必须给你点个踩。 - Tim Büthe
1
这个可以很容易地不使用静态方法来完成。它足够简单。我想要一个简单的例子来展示如何做到这一点。 - Ashtheking
我理解你的意思,将超级变量存储在字段中是解决这个问题的一种方法。但是,如果它是静态方法,您就不需要该变量,可以直接使用它。其次,在变量上调用静态方法是不好的实践,大多数IDE都会警告您。如果您修复答案并删除静态内容,我很乐意撤销我的投票,没有冒犯之意。 - Tim Büthe
你很接近了,但是你的代码无法编译。你试图在主方法中从静态上下文访问getNumber。你真的尝试过编译吗?(而且你的UltraFoo不应该扩展SuperFoo吗?) - Tim Büthe
我真的不想对你太过苛刻,但 new UltraFoo.getNumber() 无法编译,因为你错过了括号。然而,既然你的代码概念已经很清晰了,谢谢!我刚刚撤销了我的投票。 - Tim Büthe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接