为什么是super.super.method(); 不允许在Java中?
我读了这个问题,并认为如果可以这样写的话, 这个问题很容易就可以解决了(不是不能解决的)
@Override public String toString() { return super.super.toString(); }
我不确定它在很多情况下是否有用,但我想知道为什么不这样做,如果在其他语言中存在这样的情况。
你们有什么感想?
编辑:澄清:是的,我知道,这在Java中是不可能的,我真的不想错过它。 这是我所期望的工作,并感到惊讶得到一个编译器错误。 我只是有想法,想讨论一下。
它违反封装。 你不应该能够绕过父类的行为。 有时候能够绕过自己class级的行为(特别是在同一方法中)而不是父母的行为是有道理的。 例如,假设我们有一个基本的“项目集合”,一个表示“红色项目集合”的子类和一个表示“大红色项目集合”的子类。 这是有道理的有:
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
的不variables被破坏。
那有意义吗?
我认为Jon Skeet有正确的答案。 我只想补充一点,你可以通过强制types来访问超类的超类的影子variables:
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的例子)
但是,这不适用于方法调用,因为方法调用是根据对象的运行时types确定的。
我认为下面的代码允许在大多数情况下使用super.super … super.method()。 (即使这样做很不好)
简而言之
- 创build祖先types的临时实例
- 将原始对象的字段值复制到临时对象
- 调用临时对象的目标方法
- 将修改后的值复制回原始对象
用法:
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); } }
我没有足够的评价,所以我会将其添加到其他答案。
Jon Skeet用一个漂亮的例子来回答非常好。 马特B有一个观点:并不是所有的超类都有超类。 你的代码会打破,如果你叫超级超级没有超级。
面向对象的编程(Java就是)是关于对象的,而不是函数。 如果你想要面向任务的编程,selectC ++或其他的东西。 如果你的对象不适合它的超级类,那么你需要把它添加到“祖父类”,创build一个新的类,或find另一个它适合的超级。
就我个人而言,我发现这个限制是Java最大的优势之一。 与我使用过的其他语言相比,代码有些僵化,但是我总是知道期待什么。 这有助于Java的“简单而熟悉的”目标。 在我看来,调用super.super并不简单,也不熟悉。 也许开发者也是一样的?
除了其他人提出的很好的观点外,我认为还有另外一个原因:如果超级class没有超级class呢?
由于每个类自然延伸(至less) Object
, super.whatever()
将总是引用超类中的一个方法。 但是,如果你的类只是扩展Object
– 那么super.super
指的是什么呢? 应该如何处理这种行为 – 编译器错误,空指针等?
我认为这不被允许的主要原因是它违反封装,但这也可能是一个小原因。
这样做有一些很好的理由。 你可能有一个子类有一个方法实现不正确,但父方法是正确实现的。 由于它属于第三方图书馆,您可能无法/不愿意更改来源。 在这种情况下,你想创build一个子类,但是重载一个方法来调用super.super方法。
如其他一些海报所示,通过反思可以做到这一点,但应该有可能做类似的事情
(SuperSuperClass this).theMethod();
我现在正在处理这个问题 – 快速修复是将超类方法复制并粘贴到subsubclass方法:)
我认为如果你覆盖了一个方法,并想要它的所有超级版本(比如说equals
),那么你实际上总是想要首先调用直接的超类版本,如果它会依次调用它的超类版本想。
我认为它只是很less有意义(如果有的话,我不能想到它的情况)来调用一些任意超类的方法版本。 我不知道Java中是否有可能。 它可以在C ++中完成:
this->ReallyTheBase::foo();
在猜测,因为它不经常使用。 我可以看到使用它的唯一原因是,如果您的直接父母已经覆盖了一些function,并且您正在尝试将其恢复到原始状态。
在我看来,反对面向对象的原则,因为class级的直接父母应该和你的class级的关系比祖父母更密切。
如果可能的话,我会把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();
结果为“我超级超级”
至less可以使用reflection来获取超类的超类的类,尽pipe不一定是它的实例; 如果这可能有用,请考虑位于http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Class.html#getSuperclass()的Javadoc。;
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秒)
当架构要在一个通用的CustomBaseClass中build立通用的function时,我曾经遇到类似的情况,这个通用的CustomBaseClass是代表几个派生类实现的。 但是,我们需要绕开具体派生类的具体方法的共同逻辑。 在这种情况下,我们必须使用super.super.methodX实现。
我们通过在CustomBaseClass中引入一个布尔成员来实现这一点,可以使用这个布尔成员select性地推迟自定义实现,并在需要的地方屈服于默认的框架实现。
... 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方法轻易地避免这种情况。 但是,在任何时候,期望精心devise的架构都是不现实的,因此需要摆脱坚实的devise原则,并引入这样的黑客。 只是我2分钱…
@Jon Skeet很好的解释。 国际海事组织如果有人想要调用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(); } }
这里是司机class,
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的理想还是好的做法,但它仍然在工作。
如果你认为你将需要超类,你可以引用它在这个类的variables。 例如:
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
国际海事组织,这是一个干净的方式来实现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
调用super.super.method()当你不能改变基类的代码时是有意义的。 当您扩展现有的库时,通常会发生这种情况。
先问自己,你为什么要延续这个课程? 如果答案是“因为我不能改变它”,那么你可以在你的应用程序中创build确切的包和类,并重写调皮的方法或创build委托:
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() {...} ... }
例如,你可以在你的应用程序中创buildorg.springframework.test.context.junit4.SpringJUnit4ClassRunner类,所以这个类应该在jar之前加载。 然后重写方法或构造函数。
注意:这是绝对的黑客攻击,极不推荐使用,但它的工作! 使用这种方法是危险的,因为类加载器可能存在问题。 此外,这可能会导致您每次更新包含被覆盖的类的库。
我认为这是违反inheritance协议的问题。
通过扩展一个类你服从/同意它的行为,function
在调用super.super.method()
,你想打破你自己的服从协议。
你不能从超级类select樱桃 。
然而,当你感觉需要调用super.super.method()
– 通常是一个糟糕的devise符号,在你的代码或你inheritance的代码中,可能会发生这种情况。
如果超级和超级超级类不能被重构(一些遗留代码),然后selectinheritance的组合。
当你打破封装的代码覆盖一些方法时,封装破坏是。 devise不被覆盖的方法标记为最终的 。
在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: Ay=%d, By=%d, Cy=%d\n\n", ((A)this).y, ((B)this).y, ((C)this).y); } }
objC.DoIt()结果输出:
DoIt: foo(): A.foo() C.foo(C): y=31 Show: Ay=10, By=20, Cy=31 DoIt: foo(B): C.foo(B) calls C.super.foo(B) B.foo(B): y=21 Show: Ay=10, By=21, Cy=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: Ay=11, By=21, Cy=31
这很容易做到。 例如:
B的B子类和B子类的B子类。三者都有方法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类输出将是:Class A Class C
而不是输出:Class A Class B Class C
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上打印