web-dev-qa-db-ja.com

なぜsuper.super.method(); Javaで許可されていませんか?

この質問 を読んで、次のように書くことができれば簡単に解決できると考えました(それがなければ解決できないわけではありません)。

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

多くの場合それが有用かどうかはわかりませんが、whyがそうではなく、このようなものが他の言語に存在するのだろうかと思います。

皆さんはどう思いますか?

EDIT:明確にするために:はい、Javaでは不可能であり、実際に見逃すことはありません。これは私が仕事をすることを期待したものではなく、コンパイラエラーを取得して驚いた。私はちょうど考えを持っていて、それを議論したいです。

337
Tim Büthe

カプセル化に違反します。親クラスの動作をバイパスすることはできません。 ownクラスの動作を(特に同じメソッド内から)バイパスすることはできますが、親の動作はバイパスできないことがあります。たとえば、「アイテムのコレクション」というベース、「赤いアイテムのコレクション」を表すサブクラス、「大きな赤いアイテムのコレクション」を表すサブクラスがあるとします。持っていることは理にかなっています:

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は、それに含まれるアイテムがすべて赤であることを常に確信できます。 were 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の不変式は壊れています。

それは理にかなっていますか?

458
Jon Skeet

ジョン・スキートには正しい答えがあると思います。 thisをキャストすることにより、スーパークラスのスーパークラスからシャドウ化された変数にアクセスできるcanを追加したいだけです。

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 の例)

ただし、メソッド呼び出しはオブジェクトの実行時タイプに基づいて決定されるため、これはメソッド呼び出しでは機能しません。

67
Michael Myers

ほとんどの場合、次のコードでsuper.super ... super.method()を使用できます。 (たとえそうするのが苦手でも)

要するに

  1. 祖先タイプの一時インスタンスを作成します
  2. フィールドの値をoriginal objectから一時的なものにコピーします
  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);
    }
}
39
Nico

コメントするのに十分な評判がないので、これを他の回答に追加します。

ジョン・スキートは、美しい例を挙げて素晴らしく答えています。マットBにはポイントがあります。すべてのスーパークラスにスーパーがあるわけではありません。スーパーを持たないスーパーのスーパーを呼び出すと、コードが破損します。

オブジェクト指向プログラミング(Javaとは)は、関数ではなくオブジェクトに関するものです。タスク指向プログラミングが必要な場合は、C++などを選択してください。オブジェクトがスーパークラスに収まらない場合は、「祖父母クラス」にオブジェクトを追加するか、新しいクラスを作成するか、オブジェクトが収まる別のスーパーを見つける必要があります。

個人的には、この制限がJavaの最大の強みの1つであることがわかりました。コードは私が使用した他の言語に比べてやや硬いですが、何を期待するかは常に知っています。これは、Javaの「シンプルで馴染みのある」目標に役立ちます。私の考えでは、super.superの呼び出しは単純でもおなじみでもありません。おそらく開発者は同じように感じましたか?

11
EllaJo

これにはいくつかの正当な理由があります。誤って実装されたメソッドを持つサブクラスがありますが、親メソッドは正しく実装されています。これはサードパーティのライブラリに属しているため、ソースを変更できないか、変更したくない場合があります。この場合、サブクラスを作成しますが、1つのメソッドをオーバーライドしてsuper.superメソッドを呼び出します。

他のポスターで示されているように、リフレクションを介してこれを行うことは可能ですが、次のようなことができるはずです

(SuperSuperClass this).theMethod();

私は今この問題を扱っています-クイックフィックスはスーパークラスメソッドをコピーしてサブサブクラスメソッドに貼り付けることです:)

7
Larry Watanabe

他の人が作った非常に良い点に加えて、別の理由があると思います:スーパークラスにスーパークラスがない場合はどうでしょうか?

すべてのクラスが(少なくとも)Objectを自然に拡張するため、super.whatever()は常にスーパークラスのメソッドを参照します。しかし、クラスがObjectのみを拡張する場合-super.superは何を参照しますか?コンパイラエラー、NullPointerなど、その動作はどのように処理する必要がありますか?

これが許可されない主な理由は、カプセル化に違反しているためだと思いますが、これも小さな理由かもしれません。

6
matt b

メソッドを上書きし、そのすべてのスーパークラスバージョン(equalsなど)を使用する場合、実質的に常に最初に直接スーパークラスバージョンを呼び出したいと思います。望む。

メソッドの任意のスーパークラスのバージョンを呼び出すことはめったに意味をなさないと思います(もしそうなら。 Javaでそれが可能かどうかはわかりません。 C++で実行できます。

this->ReallyTheBase::foo();

推測では、それほど頻繁には使用されないためです。私がそれを使用するのを見ることができる唯一の理由は、あなたの直接の親がいくつかの機能を無効にし、元に戻すことを試みている場合です。

クラスの直接の親は祖父母よりもあなたのクラスにより密接に関係しているはずなので、OO原則に反しているように思えます。

3
Powerlord

可能であれば、別のメソッドに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();

「私はスーパースーパーです」の結果

2
xMichal

this 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";
        }
    }
}

ハッピーコーディング!!!!

2
kyay

リフレクションを使用して、必ずしもスーパークラスのスーパークラスのクラスを取得することは可能ですが、必ずしもそのインスタンスではありません。これが役立つ可能性がある場合は、 http://Java.Sun.com/j2se/1.5.0/docs/api/Java/lang/Class.html#getSuperclass() のJavadocを検討してください。

1
George Jempty

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クラスを作成して、このクラスを実際のクラスの前にロードできるようにすることができます。瓶。次に、メソッドまたはコンストラクタを書き換えます。

注意:これは絶対的なハックであり、使用することは強く推奨されていませんが、機能しています!クラスローダーに問題がある可能性があるため、このアプローチの使用は危険です。また、これは、上書きされたクラスを含むライブラリを更新するたびに問題を引き起こす可能性があります。

1
ruruskyi

@Jon Skeet素敵な説明。 IMOでsuper.superメソッドを呼び出したい場合は、直接の親の動作を無視する必要がありますが、祖父母の動作にアクセスする必要があります。これは、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を達成するための理想的または良いプラクティスであるかどうかはわかりませんが、それでも機能しています。

1
Sanjay Jain
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秒)

1
Boris

アーキテクチャが、いくつかの派生クラスに代わって実装する共通の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(...);
                       }
        }

ただし、アプリケーションだけでなくフレームワークでも優れたアーキテクチャ原則に従っているため、isAアプローチの代わりにhasAアプローチを使用することで、このような状況を簡単に回避できます。しかし、適切に設計されたアーキテクチャを適切に実装することを常に期待することはあまり実用的ではないため、堅実な設計原則から逃れ、このようなハッキングを導入する必要があります。ちょうど私の2セント...

1
Ganesh Iyer

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では、いくつかのギアによってのみこのようなフォーカスを行うことができます。可能な方法の1つは次のとおりです。

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
0
D.Motyl

スーパークラスが必要になると思われる場合は、そのクラスの変数でそれを参照できます。例えば:

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

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

出力:

Request to lie: d.sayYourName(true) returns Grandma Fedora
Request not to lie: d.sayYourName(false) returns Little girl Masha

0
Yakov Fain

これは相続協定を破る問題だと思います。
クラスを拡張することにより、その動作、機能に従います/同意します
super.super.method()を呼び出すとき、あなたはあなた自身の服従協定を破りたいです。

スーパークラスからチェリーピックすることはできません

ただし、super.super.method()を呼び出す必要があると感じた場合に、状況が発生することがあります。通常、コード内または継承するコード内の設計記号が不適切です。
superおよびsuper superクラスをリファクタリングできない場合(一部のレガシーコード)、継承よりも合成を選択します。

カプセル化の破壊とは、カプセル化されたコードを破壊することによって@ Overrideメソッドを実行した場合です。オーバーライドされないように設計されたメソッドには、finalとマークされています。

0
DayaMoon