Java:オーバーライドされたメソッドを呼び出すスーパーメソッドを呼び出す


94
public class SuperClass
{
    public void method1()
    {
        System.out.println("superclass method1");
        this.method2();
    }

    public void method2()
    {
        System.out.println("superclass method2");
    }

}

public class SubClass extends SuperClass
{
    @Override
    public void method1()
    {
        System.out.println("subclass method1");
        super.method1();
    }

    @Override
    public void method2()
    {
        System.out.println("subclass method2");
    }
}



public class Demo 
{
    public static void main(String[] args) 
    {
        SubClass mSubClass = new SubClass();
        mSubClass.method1();
    }
}

私の予想される出力:

サブクラスmethod1
スーパークラスmethod1
スーパークラスmethod2

実際の出力:

サブクラスmethod1
スーパークラスmethod1
サブクラスmethod2

技術的にはパブリックメソッドをオーバーライドしたことはわかっていますが、私はスーパーを呼び出していたため、スーパー内の呼び出しはすべてスーパーにとどまるだろうと考えました。これは発生していません。どうすれば実現できるかについてのアイデアはありますか?


2
「継承よりも構成を優先したい」と思うかもしれません。
トムホーティン-タックライン

回答:


79

キーワードsuperは「固定」されません。すべてのメソッド呼び出しは、あなたがになったそうしても、個別に処理されますSuperClass.method1()呼び出すことによって、superあなたが将来的になるかもしれないことを他のメソッドの呼び出しに影響を与えないこと、。

つまり、の実際のインスタンスで作業している場合を除いて、SuperClass.method2()そこから直接呼び出す方法はありません。SuperClass.method1()SubClass.method2()SuperClass

Reflectionを使用して目的の効果を実現することもできません(のドキュメントをjava.lang.reflect.Method.invoke(Object, Object...)参照)。

[編集]まだ混乱があるようです。別の説明をしてみましょう。

を呼び出すとfoo()、実際にが呼び出されthis.foo()ます。Javaでは単にを省略できますthis。質問の例では、タイプはthisですSubClass

したがって、Javaがのコードを実行すると、SuperClass.method1()最終的に次の場所に到達します。this.method2();

を使用superしても、が指すインスタンスは変更されませんthis。だから、呼び出しがに行くSubClass.method2()ので、this型ですSubClass

Javaがthis非表示の最初のパラメーターとして渡されることを想像すると、おそらく理解が容易になるでしょう。

public class SuperClass
{
    public void method1(SuperClass this)
    {
        System.out.println("superclass method1");
        this.method2(this); // <--- this == mSubClass
    }

    public void method2(SuperClass this)
    {
        System.out.println("superclass method2");
    }

}

public class SubClass extends SuperClass
{
    @Override
    public void method1(SubClass this)
    {
        System.out.println("subclass method1");
        super.method1(this);
    }

    @Override
    public void method2(SubClass this)
    {
        System.out.println("subclass method2");
    }
}



public class Demo 
{
    public static void main(String[] args) 
    {
        SubClass mSubClass = new SubClass();
        mSubClass.method1(mSubClass);
    }
}

コールスタックをたどると、this変更されないことがわかります。これは常にで作成されたインスタンスですmain()


誰かがスタックを通過するこの(しゃれた)図をアップロードしてくれませんか?前もって感謝します!
laycat 2014

2
@laycat:図は必要ありません。Javaにはの「メモリ」がないことに注意してくださいsuper。メソッドを呼び出すたびに、インスタンスタイプが調べられ、呼び出されsuperた頻度に関係なく、このタイプのメソッドの検索が開始されます。したがってmethod2、のインスタンスを呼び出すと、SubClass常にSubClass最初からインスタンスが表示されます。
アーロンディグラ14

@AaronDigulla、「Javaにはスーパー用のメモリがない」について詳しく説明してください。
MengT 2014年

@ Truman'sworld:私の答えで言ったように:を使用superしてもインスタンスは変更されません。一部の非表示フィールドは設定されませんSuperClass。「今後、すべてのメソッド呼び出しは使用を開始する必要があります。」または別の言い方をすると、の値はthis変化しません。
アーロンディグラ2014年

@AaronDigulla、つまり、superキーワードはスーパークラスに行くのではなく、サブクラスの継承されたメソッドを実際に呼び出すことを意味しますか?
MengT 2014年

15

オーバーライドするメソッド(またはオーバーライドするクラスの他のメソッド)内のオーバーライドされたメソッドにのみアクセスできます。

したがって:オーバーライドしないか、オーバーライドされたバージョン内でmethod2()呼び出しますsuper.method2()


8

this「現在使用中のオブジェクトの現在実行中のインスタンス」を実際に参照するキーワードを使用しています。つまりthis.method2();、スーパークラスを呼び出しています。つまり、スーパークラスでmethod2()を呼び出します。再使用、これはサブクラスです。


8
true、そして使用しないthisことも助けにはなりません。修飾されていない呼び出しは暗黙的に使用しますthis
Sean Patrick Floyd

3
なぜこれが賛成されているのですか?これはこの質問に対する答えではありません。あなたが書くときmethod2()コンパイラは見るでしょうthis.method2()。したがって、thisそれを削除しても、機能しません。@Sean Patrick Floydの発言は正しい
Shervin Asgari

4
@Shervin彼は何も悪いことを言っているのではなく、あなたが除外した場合に何が起こるかを明確にしていないthis
Sean Patrick Floyd

4
答えはthis、「実行中の具体的なインスタンスクラス」(実行時に知られている)を指し、(ポスターが信じているように)「現在のコンパイル単位クラス」(キーワードが使用される場所)コンパイル時)。しかし、それは誤解を招く可能性もあります(Shervinが指摘するように)。thisプレーンなメソッド呼び出しで暗黙的に参照されます。method2();と同じthis.method2();
レオンブロイ、2011年

7

このように思います

+----------------+
|     super      |
+----------------+ <-----------------+
| +------------+ |                   |
| |    this    | | <-+               |
| +------------+ |   |               |
| | @method1() | |   |               |
| | @method2() | |   |               |
| +------------+ |   |               |
|    method4()   |   |               |
|    method5()   |   |               |
+----------------+   |               |
    We instantiate that class, not that one!

そのサブクラスを少し左に移動して、その下にあるものを明らかにしましょう...(男、私はASCIIグラフィックが大好きです)

We are here
        |
       /  +----------------+
      |   |     super      |
      v   +----------------+
+------------+             |
|    this    |             |
+------------+             |
| @method1() | method1()   |
| @method2() | method2()   |
+------------+ method3()   |
          |    method4()   |
          |    method5()   |
          +----------------+

Then we call the method
over here...
      |               +----------------+
 _____/               |     super      |
/                     +----------------+
|   +------------+    |    bar()       |
|   |    this    |    |    foo()       |
|   +------------+    |    method0()   |
+-> | @method1() |--->|    method1()   | <------------------------------+
    | @method2() | ^  |    method2()   |                                |
    +------------+ |  |    method3()   |                                |
                   |  |    method4()   |                                |
                   |  |    method5()   |                                |
                   |  +----------------+                                |
                   \______________________________________              |
                                                          \             |
                                                          |             |
...which calls super, thus calling the super's method1() here, so that that
method (the overidden one) is executed instead[of the overriding one].

Keep in mind that, in the inheritance hierarchy, since the instantiated
class is the sub one, for methods called via super.something() everything
is the same except for one thing (two, actually): "this" means "the only
this we have" (a pointer to the class we have instantiated, the
subclass), even when java syntax allows us to omit "this" (most of the
time); "super", though, is polymorphism-aware and always refers to the
superclass of the class (instantiated or not) that we're actually
executing code from ("this" is about objects [and can't be used in a
static context], super is about classes).

言い換えると、から引用Java言語仕様

フォームsuper.IdentifierIdentifier現在のオブジェクトの名前付きフィールドを参照しますが、現在のオブジェクトは現在のクラスのスーパークラスのインスタンスとして表示されます。

フォームT.super.Identifierは、Identifier対応する字句的に囲まれたインスタンスの名前付きフィールドを参照しますTが、そのインスタンスはのスーパークラスのインスタンスとして表示されますT

素人の言葉では、 this基本的にはオブジェクト(* the **オブジェクト。変数内を移動できるまったく同じオブジェクト)、インスタンス化されたクラスのインスタンス、データドメイン内のプレーン変数です。superこれは、実行したいコードの借用ブロックへのポインタのようなもので、単なる関数呼び出しのようなものであり、それが呼び出されるクラスに関連しています。

したがってsuper、スーパークラスから使用する場合、スーパーデュパークラス[祖父母]からコードを取得しますthis)、スーパークラスから使用する場合(または暗黙的に使用する場合)、サブクラスを指し続けます(誰も変更していないため、たぶん......だろう)。


2

superClass.method1でsubClass.method2を呼び出さない場合は、method2をプライベートにしてオーバーライドできないようにします。

ここに提案があります:

public class SuperClass {

  public void method1() {
    System.out.println("superclass method1");
    this.internalMethod2();
  }

  public void method2()  {
    // this method can be overridden.  
    // It can still be invoked by a childclass using super
    internalMethod2();
  }

  private void internalMethod2()  {
    // this one cannot.  Call this one if you want to be sure to use
    // this implementation.
    System.out.println("superclass method2");
  }

}

public class SubClass extends SuperClass {

  @Override
  public void method1() {
    System.out.println("subclass method1");
    super.method1();
  }

  @Override
  public void method2() {
    System.out.println("subclass method2");
  }
}

この方法でうまくいかなかった場合、ポリモーフィズムは不可能になります(または、少なくとも半分ほどは有用ではありません)。


2

メソッドがオーバーライドされるのを回避する唯一の方法はキーワードsuperを使用することなので、method2()をSuperClassから別の新しいBaseクラスに移動してから、それをSuperClassから呼び出すことを考えました:

class Base 
{
    public void method2()
    {
        System.out.println("superclass method2");
    }
}

class SuperClass extends Base
{
    public void method1()
    {
        System.out.println("superclass method1");
        super.method2();
    }
}

class SubClass extends SuperClass
{
    @Override
    public void method1()
    {
        System.out.println("subclass method1");
        super.method1();
    }

    @Override
    public void method2()
    {
        System.out.println("subclass method2");
    }
}

public class Demo 
{
    public static void main(String[] args) 
    {
        SubClass mSubClass = new SubClass();
        mSubClass.method1();
    }
}

出力:

subclass method1
superclass method1
superclass method2

2

this 常に現在実行中のオブジェクトを参照します。

ここでポイントをさらに説明するために、簡単なスケッチを示します。

+----------------+
|  Subclass      |
|----------------|
|  @method1()    |
|  @method2()    |
|                |
| +------------+ |
| | Superclass | |
| |------------| |
| | method1()  | |
| | method2()  | |
| +------------+ |
+----------------+

Subclassオブジェクトの外側のボックスのインスタンスがある場合、偶然ボックス内のSuperclass「エリア」に出くわしても、それはまだ外側のボックスのインスタンスです。

しかも、このプログラムでは三つのクラスのうちに作成される唯一のオブジェクトがあり、これthisだけで今までに一つのことを指すことができますし、それは次のとおりです。

ここに画像の説明を入力してください

Netbeansの「ヒープウォーカー」に示すとおり


2
class SuperClass
{
    public void method1()
    {
        System.out.println("superclass method1");
        SuperClass se=new SuperClass();
        se.method2();
    }

    public void method2()
    {
        System.out.println("superclass method2");
    }
}


class SubClass extends SuperClass
{
    @Override
    public void method1()
    {
        System.out.println("subclass method1");
        super.method1();
    }

    @Override
    public void method2()
    {
        System.out.println("subclass method2");
    }
}

呼び出す

SubClass mSubClass = new SubClass();
mSubClass.method1();

出力

サブクラスmethod1
スーパークラスmethod1
スーパークラスmethod2


1

直接できるとは思いません。回避策の1つは、スーパークラスにmethod2のプライベート内部実装を用意し、それを呼び出すことです。例えば:

public class SuperClass
{
    public void method1()
    {
        System.out.println("superclass method1");
        this.internalMethod2();
    }

    public void method2()
    {
        this.internalMethod2(); 
    }
    private void internalMethod2()
    {
        System.out.println("superclass method2");
    }

}

1

「this」キーワードは、現在のクラス参照を指します。つまり、メソッド内で使用した場合、「現在の」クラスは依然としてSubClassであるため、答えが説明されます。


1

要約すると、これは現在のオブジェクトを指し、Javaでのメソッド呼び出しは本質的に多態性です。したがって、実行するメソッドの選択は、これが指すオブジェクトに完全に依存します。したがって、これが子クラスのオブジェクトを指しているため、親クラスからメソッドmethod2()を呼び出すと、子クラスのmethod2()が呼び出されます。これの定義は、使用するクラスに関係なく変更されません。

PS。メソッドとは異なり、クラスのメンバー変数はポリモーフィックではありません。


0

同様のケースを調査しているときに、サブクラスメソッドのスタックトレースをチェックして、呼び出しがどこから来ているのかを調べていました。おそらくもっと賢明な方法がありますが、私にとってはうまくいき、それは動的なアプローチです。

public void method2(){
        Exception ex=new Exception();
        StackTraceElement[] ste=ex.getStackTrace();
        if(ste[1].getClassName().equals(this.getClass().getSuperclass().getName())){
            super.method2();
        }
        else{
            //subclass method2 code
        }
}

ケースの解決策を持つという質問は合理的だと思います。もちろん、スレッドですでに述べたように、さまざまなメソッド名またはさまざまなパラメータータイプの問題を解決する方法はありますが、私の場合、さまざまなメソッド名で混乱することはありません。


このコードは危険で、リスクが高く、高価です。例外を作成するには、VMが完全なスタックトレースを構築する必要があります。完全な署名ではなく名前のみで比較すると、エラーが発生しやすくなります。また、それは巨大な設計上の欠陥のようなものです。
M. le Rutte 2017年

パフォーマンスの観点から見ると、私のコードは 'new HashMap()。size()'よりも大きな影響を与えないようです。しかし、私はあなたが考えていた懸念を見逃していたかもしれません。私はVMのエキスパートではありません。クラス名を比較することであなたの疑問がわかりますが、これには私がかなり確信しているパッケージが含まれています。それは私の親クラスです。とにかく、私は代わりに署名を比較するという考えが好きです、あなたはそれをどのように行いますか?一般的に、呼び出し元がスーパークラスなのか他の誰かなのかを判断するためのよりスムーズな方法がある場合、私はここについて感謝します。
Siegristを

呼び出し元がスーパークラスであるかどうかを判断する必要がある場合、再設計が行われている場合は真剣に考えます。これはアンチパターンです。
M. le Rutte 2017年

ポイントはわかりますが、スレッドの一般的な要求は妥当です。状況によっては、スーパークラスメソッドの呼び出しが、入れ子になったメソッド呼び出しと一緒にスーパークラスのコンテキストに留まることが理にかなっています。ただし、スーパークラスではそれに応じてメソッド呼び出しを指示する方法がないようです。
Siegristを2017年

0

提起された質問の出力をさらに拡張すると、これによりアクセス指定子とオーバーライド動作に関する洞察がさらに深まります。

            package overridefunction;
            public class SuperClass 
                {
                public void method1()
                {
                    System.out.println("superclass method1");
                    this.method2();
                    this.method3();
                    this.method4();
                    this.method5();
                }
                public void method2()
                {
                    System.out.println("superclass method2");
                }
                private void method3()
                {
                    System.out.println("superclass method3");
                }
                protected void method4()
                {
                    System.out.println("superclass method4");
                }
                void method5()
                {
                    System.out.println("superclass method5");
                }
            }

            package overridefunction;
            public class SubClass extends SuperClass
            {
                @Override
                public void method1()
                {
                    System.out.println("subclass method1");
                    super.method1();
                }
                @Override
                public void method2()
                {
                    System.out.println("subclass method2");
                }
                // @Override
                private void method3()
                {
                    System.out.println("subclass method3");
                }
                @Override
                protected void method4()
                {
                    System.out.println("subclass method4");
                }
                @Override
                void method5()
                {
                    System.out.println("subclass method5");
                }
            }

            package overridefunction;
            public class Demo 
            {
                public static void main(String[] args) 
                {
                    SubClass mSubClass = new SubClass();
                    mSubClass.method1();
                }
            }

            subclass method1
            superclass method1
            subclass method2
            superclass method3
            subclass method4
            subclass method5
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.