web-dev-qa-db-ja.com

Javaでプライベートメソッドをオーバーライドする

here で簡潔に説明したように、Javaでのプライベートメソッドのオーバーライドは無効です。親クラスのプライベートメソッドは「自動的に最終で、派生クラスから隠されているためです。」主にアカデミック。

方法はnotカプセル化違反で、親のprivateメソッドが「オーバーライド」されないようにします(つまり、同じ署名で、子クラス)?カプセル化の原則に従って、親のプライベートメソッドにアクセスしたり、子クラスから継承したりすることはできません。隠されています。

では、なぜ同じ名前/署名で独自のメソッドを実装することを子クラスに制限する必要があるのでしょうか?これには良い理論的基盤がありますか、またはこれは何らかの実用的な解決策ですか?他の言語(C++またはC#)にはこれに関する異なる規則がありますか?

57
Bill

オーバーライドプライベートメソッドはできませんが、派生クラスに問題なく導入できます。これはうまくコンパイルされます:

_class Base
{
   private void foo()
   {
   }
}

class Child extends Base
{
    private void foo()
    {
    }
}
_

_@Override_注釈をChild.foo()に適用しようとすると、コンパイル時エラーが発生することに注意してください。 missing an _@Override_注釈を使用している場合に警告またはエラーを表示するようにコンパイラ/ IDEを設定している限り、すべてうまくいくはずです。確かに、私はoverrideをキーワードとするC#アプローチを好みますが、Javaでそれを行うには明らかに遅すぎました。

C#のプライベートメソッドの「オーバーライド」の処理については、プライベートメソッドを最初から仮想にすることはできませんが、基本クラスのプライベートメソッドと同じ名前の新しいプライベートメソッドを確実に導入できます。

70
Jon Skeet

プライベートメソッドの上書きを許可すると、カプセル化のリークまたはセキュリティリスクが発生します。 possibleと仮定すると、次のような状況になります。

  1. プライベートメソッドboolean hasCredentials()があり、拡張クラスが次のように単純にオーバーライドできるとしましょう。

    boolean hasCredentials() { return true; }
    

    したがって、セキュリティチェックが破られます。

  2. 元のクラスがこれを防ぐ唯一の方法は、そのメソッドfinalを宣言することです。しかし、今では、これはカプセル化を通じて実装情報を漏らします。なぜなら、派生クラスは現在cannotメソッドhasCredentialsを作成しているためです。基本クラスで定義されたものと衝突します。

    それは悪いです。最初にこのメソッドがBaseに存在しないとしましょう。現在、実装者はクラスDerivedを合法的に導出し、それに期待どおりに機能するメソッドhasCredentialsを与えることができます。

    しかし、現在、元のBaseクラスのnewバージョンがリリースされています。そのパブリックインターフェイスは変更されず(また不変式も変更されません)、既存のコードを壊さないことを期待する必要があります。派生クラスのメソッドと名前が衝突するためです。

この質問は誤解に起因すると思います。

親のプライベートメソッドを "オーバーライド"できないようにする(つまり、同じ署名を使用して、子クラスで個別に実装する)ことは、カプセル化違反ではないか

括弧内のテキストは、その前のテキストのoppositeです。 Java)doesを使用すると、「同じ署名を使用して、子クラスに[プライベートメソッド]を独立して実装できます。上記で説明したように、これを許可しないとカプセル化に違反します。

しかし、「親のプライベートメソッドが「オーバーライド」されることを許可しない」ことは、何か異なるものであり、ensureカプセル化に必要です。

27
Konrad Rudolph

「他の言語(C++またはC#)にはこれに関する異なる規則がありますか?」

さて、C++にはさまざまなルールがあります。静的または動的なメンバー関数バインディングプロセスとアクセス特権の適用は直交しています。

メンバー関数にprivateアクセス特権修飾子を与えると、この関数は宣言クラスによってのみ呼び出すことができ、他のクラス(派生クラスではなく)では呼び出せません。 privateメンバー関数をvirtualとして宣言すると、純粋な仮想(virtual void foo() = 0;)であっても、アクセス特権を適用しながら、基本クラスが特殊化の恩恵を受けることができます。

virtualメンバー関数の場合、アクセス権限により、何をすべきかがわかります。

  • private virtualは、動作を特殊化することは許可されているが、メンバー関数の呼び出しは基本クラスによって確実に制御された方法で行われることを意味します
  • protected virtualは、オーバーライドするときにメンバー関数の上位クラスバージョンを呼び出す必要があることを意味します。

そのため、C++では、アクセス特権と仮想性は互いに独立しています。関数を静的にバインドするか動的にバインドするかを決定することが、関数呼び出しを解決する最後のステップです。

最後に、テンプレートメソッドのデザインパターンは、public virtualメンバー関数。

参照: 会話:実質的にあなたのもの

この記事では、private virtualメンバー関数。


ISO/IEC 14882-2003§3.4.1

名前を検索すると、名前が関数名であることが判明した場合、名前に複数の宣言を関連付けることができます。宣言は、オーバーロードされた関数のセットを形成すると言われています(13.1)。名前の検索が成功すると、オーバーロード解決(13.3)が行われます。アクセス規則(11節)は、名前の検索と関数のオーバーロードの解決(該当する場合)が成功した場合にのみ考慮されます。名前の検索、関数のオーバーロードの解決(該当する場合)、およびアクセスチェックが成功した後にのみ、名前の宣言によって導入された属性が式処理でさらに使用されます(5節)。

ISO/IEC 14882-2003§5.2.2

メンバー関数呼び出しで呼び出される関数は、通常、オブジェクト式の静的型(10節)に従って選択されますが、その関数が仮想であり、aqualified-idを使用して指定されていない場合、実際に呼び出される関数は最終的なオーバーライド(10.3)オブジェクト式の動的型で選択された関数[注:動的型は、オブジェクト式の現在の値が指すまたは参照するオブジェクトの型です。

16
Gregory Pakosz

カプセル化の原則に沿って、親のプライベートメソッドにアクセスしたり、子クラスから継承したりすることはできません。隠されています。

では、なぜ同じ名前/署名で独自のメソッドを実装することを子クラスに制限する必要があるのでしょうか?

そのような制限はありません。問題なく実行できますが、「オーバーライド」とは呼ばれません。

オーバーライドされたメソッドは動的ディスパッチの対象となります。つまり、実際に呼び出されるメソッドは、呼び出されるオブジェクトの実際のタイプに応じて実行時に選択されます。 privateメソッドでは、それは起こりません(最初のステートメントではそうではありません)。そして、それが「プライベートメソッドをオーバーライドすることはできません」というステートメントが意味するものです。

7

あなたはその投稿が言っていることを誤解していると思います。 notは、子クラスが「同じ名前/署名で独自のメソッドを実装することを制限されている」ということです。

以下に、少し編集したコードを示します。

public class PrivateOverride {
  private static Test monitor = new Test();

  private void f() {
    System.out.println("private f()");
  }

  public static void main(String[] args) {
    PrivateOverride po = new Derived();
    po.f();
    });
  }
}

class Derived extends PrivateOverride {
  public void f() {
    System.out.println("public f()");
  }
}

そして引用:

出力が「public f()」であると合理的に期待するかもしれませんが、

その引用の理由は、変数poが実際にDerivedのインスタンスを保持しているためです。ただし、メソッドはプライベートとして定義されているため、コンパイラは実際にオブジェクトのタイプではなく変数のタイプを調べます。そして、メソッド呼び出しをinvokeinstanceではなくinvokespecial(JVM仕様をチェックしていない正しいオペコードだと思う)に変換します。

3
kdgregory

それは選択と定義の問題のようです。 Javaでこれを行うことができない理由は、仕様がそう言うからです。しかし、仕様がそう言う理由はもっとありました。

C++がこれを許可するという事実は(仮想キーワードを使用して動的ディスパッチを強制する場合でも)、これを許可できなかった固有の理由がないことを示しています。

ただし、replaceメソッドは完全に合法であるようです:

class B {
    private int foo() 
    {
        return 42;
    }

    public int bar()
    {
        return foo();
    }
}

class D extends B {
    private int foo()
    {
        return 43;
    }

    public int frob()
    {
        return foo();
    }
}

(私のコンパイラで)OKをコンパイルするようですが、D.fooはB.fooに関連していません(つまり、オーバーライドしません)-bar()は常に42(B.fooを呼び出すことによって)とfrob()を常に返しますBまたはDインスタンスで呼び出されたかどうかに関係なく、(D.fooを呼び出すことにより)43を返します。

Javaがメソッドのオーバーライドを許可しない理由の1つは、Konrad Rudolphの例のようにメソッドの変更を許可しないことです。使用する必要があるため、C++はここで異なることに注意してください。動的ディスパッチを取得するための「仮想」キーワード-デフォルトではないため、hasCredentialsメソッドに依存する基本クラスのコードを変更することはできません。上記の例は、D.fooが置換しないため、これに対しても保護します。 Bからfooを呼び出します.

2
skyking

メソッドがプライベートの場合、その子には見えません。そのため、オーバーライドする意味はありません。

0
GuruKulki

用語オーバーライドを誤って使用し、説明と矛盾していることをおaびします。私の説明はシナリオを説明しています。次のコードは、Jon Skeetの例を拡張して私のシナリオを描いています。

class Base {
   public void callFoo() {
     foo();
   }
   private void foo() {
   }
}

class Child extends Base {
    private void foo() {
    }
}

使用方法は次のとおりです。

Child c = new Child();
c.callFoo();

私が経験した問題は、コードが示すように、子インスタンス変数でcallFoo()を呼び出していたにもかかわらず、親foo()メソッドが呼び出されていたことです。継承されたcallFoo()メソッドが呼び出すChild()で新しいプライベートメソッドfoo()を定義していると思っていましたが、kdgregoryが言ったことのいくつかは私のシナリオに適用されると思います-おそらく派生クラスコンストラクタの方法のためsuper()を呼び出しているか、おそらく呼び出していない。

Eclipseにはコンパイラの警告はなく、コードはコンパイルされました。結果は予想外でした。

0
Bill

前に述べたこと以外にも、プライベートメソッドのオーバーライドを許可しないという非常に意味的な理由があります...それらはプライベートです!!!

クラスを記述し、メソッドが「プライベート」であることを示す場合、外部の世界からはまったく見えないはずです。誰もアクセスしたり、オーバーライドしたり、他の何かをしたりできません。私は単にそれが排他的に私の方法であり、他の誰もそれをいじったり、それに依存したりしないことを知ることができるはずです。誰かがそれをいじることができる場合、それはプライベートとは見なされません。本当に簡単だと思います。

0
Steve