62

hereで簡潔に説明されているように、Java でのプライベート メソッドのオーバーライドは無効です。これは、親クラスのプライベート メソッドが「自動的に最終的なものであり、派生クラスから隠されている」ためです。私の質問は主に学術的なものです。

親のプライベート メソッドを「オーバーライド」できない (つまり、子クラスで同じシグネチャを使用して個別に実装する) ことがカプセル化の違反にならないのはなぜですか? 親のプライベート メソッドは、カプセル化の原則に従って、子クラスからアクセスまたは継承することはできません。隠されています。

では、子クラスが同じ名前/署名を持つ独自のメソッドを実装することを制限する必要があるのはなぜでしょうか? これには良い理論的根拠がありますか、それともこれはある種の実用的な解決策にすぎませんか? 他の言語 (C++ または C#) には、これに関する異なるルールがありますか?

4

10 に答える 10

70

You can't override a private method, but you can introduce one in a derived class without a problem. This compiles fine:

class Base
{
   private void foo()
   {
   }
}

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

Note that if you try to apply the @Override annotation to Child.foo() you'll get a compile-time error. So long as you have your compiler/IDE set to give you warnings or errors if you're missing an @Override annotation, all should be well. Admittedly I prefer the C# approach of override being a keyword, but it was obviously too late to do that in Java.

As for C#'s handling of "overriding" a private method - a private method can't be virtual in the first place, but you can certainly introduce a new private method with the same name as a private method in the base class.

于 2010-01-04T15:20:59.547 に答える
29

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

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

    boolean hasCredentials() { return true; }
    

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

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

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

    しかし今、元のクラスの新しいバージョンがリリースされています。Baseそのパブリックインターフェイスは変更されないため(また、その不変条件も変更されません)、既存のコードが破損しないことを期待する必要があります。派生クラスのメソッドと名前が衝突するため、これだけが行われます。

質問は誤解から生じていると思います:

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

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

ただし、「親のプライベートメソッドを「オーバーライド」できないようにする」ことは別のことであり、カプセル化を確実にするために必要です。

于 2010-01-04T15:26:03.337 に答える
17

「他の言語 (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) に従って選択されますが、その関数が仮想であり、修飾 ID を使用して指定されていない場合、実際に呼び出される関数は、の最終オーバーライド (10.3) になります。オブジェクト式の動的型で選択された関数 [注: 動的型は、オブジェクト式の現在の値によってポイントまたは参照されるオブジェクトの型です。

于 2010-01-04T15:49:13.073 に答える
7

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

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

そのような制限はありません。あなたは問題なくそれをすることができます、それは単に「オーバーライド」と呼ばれていません。

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

于 2010-01-04T15:34:14.757 に答える
3

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

少し編集したコードは次のとおりです。

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 仕様を確認していません) 。

于 2010-01-04T15:35:58.907 に答える
0

前に述べた以上に、プライベート メソッドのオーバーライドを許可しない非常にセマンティックな理由があります...それらはプライベートです!!!

クラスを作成し、メソッドが「プライベート」であることを示す場合、それは外部から完全に見えないようにする必要があります。誰もアクセスしたり、上書きしたり、その他のことはできません。私は、それがもっぱら私の方法であり、他の誰もそれをいじったり、依存したりしないことを知ることができなければなりません. 誰かがそれをいじることができれば、それはプライベートとは見なされません。本当に単純なことだと思います。

于 2013-11-15T15:05:22.617 に答える
0

オーバーライドという用語を誤って使用し、私の説明と矛盾していることをお詫び申し上げます。私の説明はシナリオを説明しています。次のコードは、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 にはコンパイラの警告はなく、コードはコンパイルされました。結果は予想外でした。

于 2010-01-04T22:52:58.973 に答える
0

メソッドがプライベートの場合、その子には表示されません。したがって、それをオーバーライドする意味はありません。

于 2010-01-04T15:36:18.247 に答える
-1

クラスは、それが利用可能にするメソッドとそれらの動作によって定義されます。それらが内部でどのように実装されているかではありません(たとえば、プライベートメソッドの呼び出しを介して)。

カプセル化は実装の詳細ではなく動作に関係しているため、プライベートメソッドはアイデアのカプセル化とは何の関係もありません。ある意味で、あなたの質問は意味がありません。「カプセル化の違反ではなく、コーヒーにクリームを入れるのはどうですか?」と尋ねるようなものです。

おそらく、プライベートメソッドはパブリックなものによって使用されます。あなたはそれを上書きすることができます。そうすることで、あなたは行動を変えました。

于 2010-01-04T15:32:15.717 に答える