2013-07-07 4 views
8

私は、アクセス指定子がデフォルトでpublicであるメソッドを持つクラスを持っています。今、このクラスをサブクラスで拡張したいと思います。このメソッドをオーバーライドして、アクセス指定子を「private」にしたいとします。このコードをコンパイルすると、私はコンパイルエラーを取得しています:サブクラスで弱い特権を割り当てることができない理由

"attempting to assign weaker access privileges".

誰かがサブクラスに弱い権限を割り当てると間違っているものを私に説明していただけますか?ここで

は、コンパイル・エラーの原因となったコードです:

class Superclass 
{ 
    void foo() 
    { 
     System.out.println("Superclass.foo"); 
    } 
} 

class Subclass extends Superclass 
{ 
    private void foo() 
    { 
     System.out.println("Subclass.foo"); 
    } 
} 
+3

興味深い質問!この非常事態*はC++で許可されています。 – quamrana

+1

"私は、デフォルトでアクセス指定子がpublicであるメソッドを持つクラスを持っています"。実際、デフォルトのアクセス指定子は公開されていません。あなたが特定のアクセス指定子を挙げていないのであれば、それは「デフォルト」です:)。 – pikrut

+0

@quamranaそして、スーパークラス型の変数を使ってオブジェクトにアクセスすると、C++ではどうなりますか?コンパイラは確かにそれを通過させる...それは実行時にエラーになりますか? –

答えて

25

短い答えは、それはタイプの代替を壊してしまうため、それが許可されていないということです。 Liskov Substititution Principle (LSP)も参照してください。

Java(および他のプログラミング言語)の多態性は、サブクラスのインスタンスを扱うことができるかどうかを、のようにスーパークラスのインスタンスとして扱うことに依存しています。しかし、この方法は、サブクラスに制限されている場合、あなたはコンパイラが例えばアクセスルールは、メソッドが呼び出されることを可能にするかどうか...

を把握することはできませんことを見つけ、あなたの例のコードは合法だったと仮定することができます:

// Assume this code is in some other class ... 

SuperClass s1 = new SuperClass(); 

s1.foo();       // OK! 

SuperClass s2 = new Subclass(); 

s2.foo();       // What happens now? 

SuperClass s3 = OtherClass.someMethod(); 

s3.foo();       // What happens now? 

あなたはどうかs2.foo()上の意思決定をベースにした場合、あなたはSubclassの抽象化境界の外側からprivateメソッドの呼び出しを許可し、s2の宣言された型では許可されています。

s2が参照するオブジェクトの実際のタイプに基づいて判断すると、アクセスチェックを静的に行うことはできません。 s3の場合は、これがさらに明確になります。コンパイラは、someMethodによって返されるオブジェクトの実際の型が何であるかを知る方法を全く持っていません。

実行時例外が発生する可能性のあるアクセスチェックは、Javaアプリケーションのバグの大きな原因になります。ここで議論されている言語制限は、この厄介な問題を回避します。

+1

+1正しいのですが、あなたの答えを理解するためには、質問によって暗示されるよりも多くの知識が必要です。 ;) –

+1

@PeterLawrey - 申し訳ありません...私は死んだ魚に対処する手助けをしなければなりませんでした。文字通り。 –

+0

問題はありません。何らかの理由で+1しました。 ;)私は時差ぼけを扱っています、その2:55はここにいます。眠るために戻る必要があります。 –

7

すでにスーパークラスでより多くのアクセスを許可しているため、アクセスを制限することはできません。例えば

SuperClass sc = new SubClass(); 
sc.foo(); // is package local, not private. 

scのアクセスは、参照scないコンパイラは、オブジェクトが実行時にあるどのようなタイプのすべてのケースで知ることが不可能であるため、それが参照するものの種類によって決定されます。これが安全な前提であるためには、サブクラスは親によって与えられたコントラクトを尊重しなければならないか、またはそれは有効なサブクラスではない。これは、メソッドが実装されているが、サブクラスがアクセスできない(またはアクセスできない)というサブクラスと異なることはありません。

親を介してサブクラスメソッドにしかアクセスできない直接。この問題は、親がメソッドを追加するタイミングを知らず、メソッドを作成するときにprivateをプライベートにして別の方法でアクセスできないようにするために行います。

BTWリフレクションによってプライベートメソッドにアクセスすることはできますが、これはJVMのすべての種類の問題を引き起こすという副作用があります。例えばそれはそれが正常に呼び出すことができる方法がないと判断するかもしれませんが、プライベートメソッドを保持する必要があります。

要するに、あなたが言うことを意味し、分割された性格を持たないコードが必要です。それはパッケージローカルかどちらかの間に何かの種類ではなく、実際にどちらかではないプライベートです。これは他の方法ではこのような問題ではありません。すなわち、サブクラスが公開されている場合。より多くのメソッドを実装できるように、サブクラスを親より多くの場所で使用できることを意味します。

+3

はい、それはなぜ(彼が求めているのですか)なのですか? – Bohemian

0

このような構造を使用することによる明らかな問題(Peter Lawrey氏の答えは、LSP)は、その背後にある理論についても読むことができます。つまり、メインタイプをサブクラス。それはスーパークラスの契約を破り、すなわちサブクラスオブジェクトは、IS-スーパークラスオブジェクト同様の置換原則を無効にしているため、スーパークラスのメソッドのアクセス修飾子を収縮

+0

+1 LSPが理由です。良いリンク。 – Bohemian

1

が無効オーバーライドです。これが許可された場合は、あなたのサブクラスが、その方法公共を持っていないため

public void doSomething(SuperClass sc) { 
    sc.publicMethodInSuperClass(); 
} 

doSomething(new SubClass()); // would break 

、上記のクライアントコードが壊れます。

参考:これが許された場合
Liskov substitution principle

5

、あなたがアクセスすべきではないメソッドを呼び出すことができ、それを通してバックドアが存在することになります。 method()はクラスのスーパーでpublicあるので

が、これは

class Super { 
    public void method() { 
     System.out.println("Super"); 
    } 
} 


class Sub extends Super { 
    // This is not allowed, but suppose it was allowed 
    protected void method() { 
     System.out.println("Sub"); 
    } 
} 

// In another class, in another package: 
Super obj = new Sub(); 
obj.method(); 

obj.method

が可能である許可されていることを言うことができます。 objがSubのインスタンスを実際に参照しているため、そのクラスでメソッドが保護されているため、許可しないでください。

外部からアクセスできないようなクラスSubのメソッドへの呼び出しを制限するには、この制限が適用されます。

0

私は短い答えは、コンパイラの作家がこのように動作するようにルールを設定していると思います。 LSPは現在の問題とは何の関係もありません。

私は、この制限があると考えることができる唯一の理由は、サブクラスがクライアントプログラマとしてインターフェイスから派生した場合、インターフェイスのすべてのアクセス可能なメソッドを派生クラスへの参照から呼び出すことができるクラス。

OPが示したコードを記述できるとします。派生クラスへの参照がある場合は、派生クラスのpublicメンバーを呼び出すことができるはずです(この場合は何もありません)。ただし、参照を基本クラスへの参照を取るメソッドに渡します。このメソッドは、公開メソッドまたはパッケージメソッドを呼び出すことを期待します(foo)。これは他の投稿者が探しているLSPです!

C++例:動的メソッドディスパッチで

class Superclass{ 
public: 
virtual void foo(){ cout << "Superclass.foo" << endl; } 
}; 

class Subclass: public Superclass{ 
virtual void foo(){ cout << "Subclass.foo" << endl; } 
}; 

int main(){ 
Superclass s1; 
s1.foo() // Prints Superclass.foo 

Subclass s2; 
// s2.foo(); // Error, would not compile 

Superclass& s1a=s2; // Reference to Superclass 'pointing' to Subclass 

s1a.foo(); // Compiles OK, Prints Subclass.foo() 
} 
+0

* "この制限があると考えることができる唯一の理由は、サブクラスがインターフェイスから派生したときに、クライアントプログラマとして、インターフェイスのすべてのアクセス可能なメソッドを派生クラスへの参照から呼び出すことができると期待している"* - それは基本的にLSPです。なぜなら、クライアントプログラマがそれを行うことができなければ、そのサブクラスを親クラスに置き換えることはできないからです! –

+0

いいえ。 LSPは、基本クラスについてしか知りませんが、基底クラスが特定の方法で動作することを期待するクライアントの視点を指します。あなたの言語が許可しているので、派生クラスをクライアントに提供すると、派生クラスはクライアント関数が引き続き動作するように同じように動作するはずです。 – quamrana

+0

私はあなたがLSPを狭く解釈していると思います。 L型の定義は以下の通りである:* "サブタイプ要件:タイプTのオブジェクトxについて証明可能なプロパティφ(x)とする。次に、タイプSのオブジェクトyについてφ(y)は真でなければならない。ここでSはTのサブタイプ"*。この場合、φはアクセス可能なメソッド 'foo'を持っています。これは、クライアントが依存している 'SuperClass'のプロパティで、' SubClass'のプロパティである必要があります。そうでなければ、後者のインスタンスは前者のインスタンスに代わるものではありません。 https://en.wikipedia.org/wiki/Liskov_substitution_principleを参照してください。 –

0

、オーバーライドされたメソッドの呼び出しは、実行時に解決されなく時間をコンパイルされています。それは呼び出し時に参照されているオブジェクトに基づいています...

Superclass ref=new Subclass(); 
ref.foo() 

さて、実行時に、Javaは声明ref.foo()渡って来るとき、それはのfoo()を呼び出す必要があります。

は今より弱いアクセス権限が許可され、私たちはあなたのコードにいくつかの他のクラスを次のような文を作成して仮定しますSubclass ... foo()サブクラスのメソッドはあなたのコードでプライベートと宣言されています。プライベートクラスは独自のクラスの外で呼び出すことができます。したがって、競合が発生し、ランタイム例外が発生します。

関連する問題