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

Tim*_*the 347 java superclass

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

@Override
public String toString() {
    return super.super.toString();
}
Run Code Online (Sandbox Code Playgroud)

我不确定它在许多情况下是否有用,但我想知道为什么它不是,如果这样的东西存在于其他语言中.

你们有什么感想?

编辑: 澄清:是的,我知道,这在Java中是不可能的,我不会真的错过它.这不是我期望的工作,并且惊讶于编译错误.我刚才有了这个想法并想讨论它.

Jon*_*eet 466

它违反了封装.您不应该绕过父类的行为.有时候能够绕过你自己班级的行为(特别是在同一个方法中)而不是你父母的行为是有意义的.例如,假设我们有一个基础"项目集合",一个表示"红色项目集合"的子类,以及表示"大红色项目集合"的子类.有:有意义:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

没关系 - 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以添加任何我们喜欢的内容,并且不变量RedItems就会被破坏.

那有意义吗?

  • 很好的例子.但我认为当基类接受项目时设计很糟糕,但派生类拒绝它们,因为派生类不能用作基类的替代品(违反替换原则).是正确的思考,还是这样的层次结构清洁? (37认同)
  • @ JohannesSchaub-litb我认为它违反了Liskov替换原则,因为如果一个代码编写Item的契约并使用RedItems的实例,那么将会出现意外的NotRedItemException.我总是被告知子类应该采用超级输入集并返回输出的子集.换句话说,子类永远不应该拒绝对超类有效的输入或者产生超类不能产生的输出,这包括抛出超类不抛出的错误. (12认同)
  • 你的意思是构成而不是继承吗?这个例子不是避免继承的理由 - 尽管还有很多其他的. (4认同)
  • @Tim:这只是一个容易理解的违反封装的例子.它可能是设置属性而不是.此外,并非所有方面都在类型级别可见.泛型不是一切的答案. (3认同)
  • 关于这个评论:"现在我们可以添加任何我们喜欢的东西,并且RedItems中的不变量被破坏了."如果你在添加覆盖代码之前没有调用`super.add(item);`,那么这将是真的.但是我理解你的整体观点并且弄清楚为什么从`add(item)`调用`super.super.add(item)`是错误的. (3认同)
  • @piechuckerr:有时书籍,有时是博客文章,有时只是体验...... (3认同)
  • 是的,我认为我也同意琼.超类可以很好地说,子类中的行为可以更严格 - 然后我认为层次结构很好. (2认同)
  • 我知道这样做不是一个好的设计,但我认为这不足以阻止这种语言.我因此而遇到很大麻烦,因为我需要修复RedItems.add中的一个错误并且没有它的来源.但是如果不在NaughtyItems.add中使用Items.add,就无法修复bug. (2认同)
  • 想知道为什么C++允许这个...我猜不同的范例?http://stackoverflow.com/questions/357307/c-how-to-call-a-parent-class-function-from-derived-class-function (2认同)
  • @cosjav:“RedItems 不包含任何非 RedItems”的不变量不会被破坏。“add 方法应该添加一个项目会被破坏”的合同,但这是一个略有不同的合同。这可以通过 RedItems 声明最终方法来解决,以防止将来覆盖,但我们仍然需要防止 super.super.add 在 *other* 方法中被调用。 (2认同)
  • @DayaMoon:继承并不*总是*公开父级实现的细节。它可能会这样做,但它没有*必须*。这对我来说听起来有点夸大其词。 (2认同)
  • @DayaMoon:我*会*说继承增加了违反封装的*风险*,这就是为什么我谨慎地应用它,并赞同“为继承而设计或禁止它”的想法(理解为设计而设计)继承需要很多努力)。 (2认同)
  • @DayaMoon:关键是`super.m()` 可能正在执行一些应该*总是* 发生的动作——如果你可以跳过它,你就破坏了封装。老实说,我不确定这次讨论是否会富有成效 - 或者 SO 评论是一个合适的地方:( (2认同)

Mic*_*ers 68

我认为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();
        }
}
Run Code Online (Sandbox Code Playgroud)

产生输出:

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

(来自JLS的例子)

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

  • 如果变量的名称与超类中的变量名称相同,并且由于某种原因您不能(或不允许)更改它,您仍然可以使用相同的名称访问超类的变量.你问,有什么用?嗯......我从来没用过它. (6认同)

小智 39

我认为以下代码允许在大多数情况下使用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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 多么可怕的事情!我给你+1只是为了弄清楚如何做到这一点:) (23认同)
  • 通过反射你可以做任何事情,是的:)你甚至可以使字符串变得可变. (10认同)
  • 这是一个很好的技巧,但即使这并不总是等同于调用不可用的,但需要的)super.super,这是因为super.super调用将带有C(C + B + A)的上下文,而你的答案创建一个实例没有B和C的上下文的A.因此,如果每个doThat调用getContext(),并且每个类中的getContext实现不同,则此答案将不起作用.在你的回答中它将使用A的getContext(),而调用不可用的super.super会导致使用C的getContext. (6认同)

小智 11

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

Jon Skeet以一个美丽的榜样出色地回答.Matt B有一个观点:并非所有超类都有超级.如果你打电话给一个没有超级超级的super,你的代码就会破坏.

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

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

  • 你说"并非所有的超级班都都超级".好吧,除了java.lang.Object之外什么都可以给你"null".所以我想说,几乎所有人都有超级. (3认同)
  • 如果super.super.super ... super太多,可以通过使super.super.super ... super成为编译时错误来轻松解决。考虑到Java仅具有公共继承,因此如果有人更改了继承层次结构,则需要更改接口。因此,我不会担心super ^ n会令人恐惧。 (2认同)

Lar*_*abe 7

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

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

(SuperSuperClass this).theMethod();

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

  • @Larry,这正是我所处的情况,也正是我使用的修复方法。好决定! (2认同)

mat*_*t b 6

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

由于每个类自然地扩展(至少)Object,所以super.whatever()总是引用超类中的方法.但是,如果你的课程只延伸了Object- 那会super.super是什么意思呢?应该如何处理这种行为 - 编译错误,NullPointer等?

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

  • 显然,这将是一个编译器错误 - 它是编译器确实具有的信息. (4认同)

Joh*_*itb 5

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

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

this->ReallyTheBase::foo();
Run Code Online (Sandbox Code Playgroud)

  • 如果有人编写了一个子类,其中一种方法的实现不正确,但超类方法完成了 90% 的工作,这是有道理的。然后你想做一个子类,并重写调用超类超类方法的方法,并添加你自己的10%。 (7认同)