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

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

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

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

你们怎么看?

<强>编辑: 澄清一下:是的,我知道,这在Java中是不可能的,我并不真的怀念它。这不是我期望的工作,并惊讶地得到一个编译器错误。

.我只是有个想法,想讨论一下
208970 次浏览

我认为,如果你重写一个方法,并想要它的所有超类版本(比如,比方说equals),那么你实际上总是想先调用直接超类版本,如果它愿意,直接超类版本会依次调用它的超类版本。

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

this->ReallyTheBase::foo();

它违反了封装。你不应该能够绕过父类的行为。有时能够绕过自己的类的行为(特别是在同一个方法中),但不能绕过父类的行为是有意义的。例如,假设我们有一个基类“项目的集合”,一个子类表示“红色项目的集合”,该子类的一个子类表示“大红色项目的集合”。这是有道理的:

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

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

明白吗?

我猜,因为它不常用。我能看到使用它的唯一原因是,如果您的直接父文件覆盖了某些功能,而您试图将其恢复到原始文件。

在我看来,这似乎是违反OO原则的,因为类的直接父类应该比祖父类更接近你的类。

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

因为每个类都自然地扩展(至少)Object,所以super.whatever()总是引用超类中的一个方法。但是如果你的类只扩展了Object——那么super.super指的是什么呢?该如何处理该行为-编译器错误,空指针,等等?

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

我没有足够的声誉来评论,所以我将把这个添加到其他答案中。

乔恩·斯基特用一个漂亮的例子回答得很好。Matt B有一个观点:不是所有的超类都有超类。如果你调用一个没有super的super,你的代码就会崩溃。

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

就我个人而言,我发现这种限制是Java最大的优势之一。与我使用过的其他语言相比,代码有点僵硬,但我总是知道会发生什么。这有助于实现Java“简单而熟悉”的目标。在我的脑海里,打电话给上级。超级不简单也不熟悉。也许开发者也有同感?

我认为乔恩·斯基特有正确答案。我只是想补充一点,你可以通过强制转换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)

但是,这对方法调用不起作用,因为方法调用是基于对象的运行时类型确定的。

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

我认为以下代码允许在大多数情况下使用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。超级的方法。

正如其他一些海报所示,通过反射是可以做到这一点的,但应该可以这样做

(本)SuperSuperClass .theMethod ();

我现在正在处理这个问题-快速修复是复制并粘贴超类方法到subsubclass方法:)

在我看来,这是在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);
}
}

输出:

< p > Request to lie: d.sayYourName(true) returns Grandma Fedora < br > Request not to lie: d.sayYourName(false) returns Little girl Masha < / p >

如果您认为您将需要超类,您可以在该类的变量中引用它。例如:

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


}
}
< p >运行: 一个 BUILD SUCCESSFUL(总时间:0秒)

当你不能改变基类的代码时,调用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中真正的类之前加载。然后重写方法或构造函数。

注意:这是绝对的黑客,强烈不建议使用,但它是工作!使用这种方法是危险的,因为类装入器可能存在问题。此外,这可能会导致问题,每次您将更新库包含覆盖类。

我曾经遇到过这样的情况,当体系结构是在一个公共CustomBaseClass中构建公共功能时,该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美分…

@Jon Skeet解释得好。 在我看来,如果有人想打电话给管理员。超方法则必须是想要忽略直接父元素的行为,但想要访问大父元素的行为。 这可以通过实例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

Class B printClass行为在这种情况下将被忽略。 我不确定这是一个理想的或好的做法,以达到超级。超棒,但它仍然在工作

我会把超级。如果可能的话,超级方法主体放在另一个方法中

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

结果变成“我超级超级”

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

你不能从超类中挑选

然而,当你觉得需要调用super.super.method()时,可能会发生这种情况——在你的代码或你继承的代码中,这通常是一个糟糕的设计标志!< br > 如果超级超级超级类不能被重构(一些遗留代码),则选择复合而不是继承 封装破坏是当你通过破坏封装的代码@Override一些方法。 标记了设计为不被覆盖的方法 最后的< / em >。

这很容易做到。例如:

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 < / p > 而不是输出: A类 B类 类C < / p >

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

输出:打印在祖父

在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()时,类Animal中的方法doSth()将打印this,它是一只猫。