403

我读了这个问题,并认为如果有人可以写,这很容易解决(并不是说没有它就无法解决):

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

我不确定它在很多情况下是否有用,但我想知道为什么它没有用,以及其他语言中是否存在类似的东西。

你们有什么感想?

编辑: 澄清一下:是的,我知道,这在 Java 中是不可能的,我并没有真正想念它。这不是我期望的工作,并且很惊讶得到编译器错误。我只是有这个想法,并喜欢讨论它。

4

22 回答 22

525

它违反了封装。您不应该能够绕过父类的行为。有时能够绕过您自己的类的行为(尤其是在同一方法中)而不是您父母的行为是有意义的。例如,假设我们有一个基础“项目集合”,一个表示“红色项目集合”的子类和一个表示“大红色项目集合”的子类。拥有以下内容是有意义的:

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

现在我们可以添加任何我们喜欢的东西,并且不变量 inRedItems被破坏了。

那有意义吗?

于 2009-02-25T15:15:40.750 回答
86

我认为 Jon Skeet 有正确的答案。我只想补充一点,您可以通过强制转换从超类的超类中访问阴影变量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
超级.x = 2
((T2)this).x=2
((T1)这个).x= 1
((I)this).x=0

(来自JLS的示例)

但是,这不适用于方法调用,因为方法调用是根据对象的运行时类型确定的。

于 2009-02-25T15:50:32.290 回答
46

我认为以下代码允许在大多数情况下使用 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);
    }
}
于 2010-04-22T15:54:45.193 回答
12

我没有足够的声誉发表评论,所以我会将其添加到其他答案中。

Jon Skeet 的回答非常出色,举了一个漂亮的例子。Matt B 有一点:不是所有的超类都有超类。如果您调用没有超级的超级的超级,您的代码将会中断。

面向对象的编程(Java 就是)都是关于对象的,而不是函数。如果您想要面向任务的编程,请选择 C++ 或其他东西。如果您的对象不适合它的超类,那么您需要将它添加到“祖父类”,创建一个新类,或者找到另一个适合它的超类。

就个人而言,我发现这种限制是 Java 的最大优势之一。与我使用的其他语言相比,代码有些僵化,但我总是知道会发生什么。这有助于实现 Java 的“简单而熟悉”的目标。在我看来,调用 super.super 并不简单也不熟悉。也许开发人员也有同感?

于 2009-02-25T15:50:03.930 回答
7

这样做有一些很好的理由。您可能有一个子类,它的方法实现不正确,但父方法实现正确。因为它属于第三方库,您可能无法/不愿意更改源。在这种情况下,您要创建一个子类,但要重写一个方法来调用 super.super 方法。

正如其他一些海报所示,可以通过反射来做到这一点,但应该可以做类似的事情

(SuperSuperClass this).theMethod();

我现在正在处理这个问题 - 快速解决方法是将超类方法复制并粘贴到子类方法中:)

于 2010-05-07T14:24:56.613 回答
6

除了其他人提出的非常好的观点之外,我认为还有另一个原因:如果超类没有超类怎么办?

由于每个类都自然地扩展(至少)Objectsuper.whatever()因此将始终引用超类中的方法。但是,如果您的课程只扩展了怎么办Object——那会super.super指什么?应该如何处理这种行为 - 编译器错误、NullPointer 等?

我认为不允许这样做的主要原因是它违反了封装,但这也可能是一个小原因。

于 2009-02-25T15:34:04.293 回答
4

我认为,如果您覆盖一个方法并想要它的所有超类版本(例如 for equals),那么您实际上总是希望首先调用直接超类版本,如果需要,它会依次调用它的超类版本.

我认为调用某个方法的任意超类版本几乎没有意义(如果有的话。我想不出它确实如此)。我不知道这在Java中是否可行。它可以在 C++ 中完成:

this->ReallyTheBase::foo();
于 2009-02-25T15:14:41.067 回答
3

猜测一下,因为它不经常使用。我可以看到使用它的唯一原因是如果您的直接父母已经覆盖了某些功能并且您正在尝试将其恢复为原始功能。

在我看来,这违反了面向对象的原则,因为班级的直接父母应该比祖父母与您的班级更密切相关。

于 2009-02-25T15:16:12.903 回答
3

当您无法更改基类的代码时,调用 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 之前加载。然后重写方法或构造函数。

注意:这是绝对的 hack,强烈不建议使用,但它可以工作!由于类加载器可能存在问题,因此使用这种方法很危险。每次更新包含被覆盖类的库时,这也可能会导致问题。

于 2011-11-16T11:00:30.757 回答
3

@Jon Skeet 很好的解释。IMO,如果有人想调用 super.super 方法,那么必须要忽略直接父母的行为,但要访问祖父母的行为。这可以通过instance 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

在这种情况下,B 类 printClass 行为将被忽略。我不确定这是实现 super.super 的理想做法还是好的做法,但它仍然有效。

于 2013-09-04T06:18:10.383 回答
3

看看这个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";
        }
    }
}

快乐编码!!!!

于 2018-08-15T16:48:28.210 回答
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();

结果“我超级超级”

于 2013-10-29T10:28:18.377 回答
1

似乎至少可以使用反射获得超类的超类的类,尽管不一定是它的实例;如果这可能有用,请考虑http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Class.html#getSuperclass()上的 Javadoc

于 2009-07-02T20:22:11.997 回答
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 BUILD SUCCESSFUL(总时间:0 秒)

于 2011-07-20T11:48:36.283 回答
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 方法轻松避免这种情况。但在任何时候,期望设计良好的架构都不是很实际,因此需要摆脱坚实的设计原则并引入这样的技巧。只是我的2美分...

于 2013-02-26T19:26:09.373 回答
0

super.super.sayYourName()IMO,这是一种在 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

于 2011-03-27T18:35:12.873 回答
0

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

你只是不能从超级类中挑选

但是,当您觉得需要调用时,可能会发生这种情况super.super.method()——通常是在您的代码或您继承的代码中的糟糕设计标志!
如果supersuper super类无法重构(一些遗留代码),那么选择组合而不是继承。

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

于 2014-04-09T07:55:15.097 回答
0

这很容易做到。例如:

B的C子类和A的B子类。例如,这三个都有方法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 类

而不是输出:Class A Class B Class C

于 2015-02-16T12:08:56.273 回答
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
于 2017-08-07T19:56:51.843 回答
-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
于 2011-06-04T19:03:35.913 回答
-1
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 中打印

于 2015-04-17T05:34:34.383 回答
-1

关键字 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()Animalthis

于 2018-10-23T03:04:19.013 回答