17

次のコードを検討してください。

enum E {
    A { public int get() { return i; } },
    B { public int get() { return this.i; } },
    C { public int get() { return super.i; } },
    D { public int get() { return D.i; } };

    private int i = 0;
    E() { this.i = 1; }
    public abstract int get();
}

最初の 2 つの enum 定数宣言 (A & B) でコンパイル エラーが発生しますが、最後の 2 つは正常にコンパイルされます (C & D)。エラーは次のとおりです。

行 A のエラー 1: 非静的変数 i は静的コンテキストから参照できません
行 B のエラー 2: i has private access in E

インスタンスメソッドであるため、インスタンス変数に思い通りにgetアクセスできない理由がわかりません。i

注:privateの宣言からキーワードを削除iすると、コードもコンパイル可能になりますが、これもわかりません。

Oracle JDK 7u9 を使用します。

編集

コメントで指摘されているように、これは列挙型に固有のものではなく、以下のコードは同じ動作を生成します。

class E {
    static E a = new E() { public int get() { return i; } };
    static E b = new E() { public int get() { return this.i; } };
    static E c = new E() { public int get() { return super.i; } };
    static E d = new E() { public int get() { return d.i; } };

    private int i = 0;
}
4

4 に答える 4

6

観察される動作は、Java 言語仕様によって義務付けられています。具体的には、囲んでいる型のフィールドへの暗黙的なアクセスと、プライベート メンバーは継承されないという規則です。

非修飾フィールド アクセス

A { public int get() { return i; } }

仕様では次のことが義務付けられています。

列挙型定数のオプションのクラス本体は、直接囲んでいる列挙型を拡張する匿名クラス宣言 (§15.9.5) を暗黙的に定義します。クラス本体は、無名クラスの通常の規則によって管理されます。特に、コンストラクターを含めることはできません。

これにより、式iがいくぶんあいまいになります。つまり、囲んでいるインスタンスのフィールドを参照しているのか、それとも内部インスタンスを参照しているのかということです。残念ながら、内部インスタンスはフィールドを継承しません:

private と宣言されたクラスのメンバーは、そのクラスのサブクラスに継承されません。

したがって、コンパイラは、囲んでいるインスタンスのフィールドにアクセスするつもりであると結論付けますが、静的ブロック内にあるため、囲んでいるインスタンスないため、エラーが発生します。

フィールドへのアクセスthis

B { public int get() { return this.i; } },

仕様では次のことが義務付けられています。

一次式として使用される場合、キーワード this は、インスタンス メソッドが呼び出されたオブジェクト (§15.12) への参照、または構築中のオブジェクトへの参照である値を示します。

したがって、外側のクラスではなく、内側のクラスのフィールドが必要であることは明らかです。

コンパイラがフィールド アクセス式を拒否する理由this.i 次のとおりです。

private と宣言されたクラスのメンバーは、そのクラスのサブクラスに継承されません。

つまり、private フィールドは、そのサブタイプではなく、フィールドを宣言するタイプの参照を介してのみアクセスできます。それはそう、

B { public int get() { return ((E)this).i; } },

うまくコンパイルされます。

スーパー経由でアクセス

このように、super、メソッドが呼び出されたオブジェクト (または構築中のオブジェクト) を参照します。したがって、内部インスタンスを意味することは明らかです。

さらに、 super は 型Eであるため、宣言が表示されます。

他のフィールドからのアクセス

D { public int get() { return D.i; } };

ここでは、 で宣言されDた static フィールドへの非修飾アクセスです。これは静的フィールドであるため、どのインスタンスを使用するかという問題は議論の余地がなく、アクセスは有効です。DE

ただし、列挙オブジェクトが完全に構築された後にのみフィールドが割り当てられるため、非常に脆弱です。構築中に誰かが get() を呼び出すと、 aNullPointerExceptionがスローされます。

おすすめ

これまで見てきたように、他のタイプのプライベート フィールドへのアクセスには、やや複雑な制限が適用されます。必要になることはめったにないため、開発者はこれらの微妙な点に気付いていない可能性があります。

フィールドを作成するprotectedとアクセス制御が弱くなりますが (つまり、パッケージ内の他のクラスがフィールドにアクセスできるようになります)、これらの問題は回避されます。

于 2013-01-03T17:39:05.043 に答える
0

privateメソッドは、同じクラス ファイル内にあるネストされたクラスでアクセスできます。

このため、最初の例は、A が E の匿名サブクラスであっても機能します。2 番目の例がコンパイルされない理由は興味深いですが、誤解を招くエラー メッセージであると思われます。

A { public int get() { return super.i; } };

コンパイルするが

A { public int get() { return i; } };

与える

error: non-static variable i cannot be referenced from a static context

これが静的なコンテキストである場合、これは明らかに正しくsuper.iありません。

マルコが指摘するように

A { public int get() { return this.i; } };

エラーメッセージを生成します

error: i has private access in E

これはより適切かもしれません。つまり、このフィールドには明示的にアクセスできますが、暗黙的にアクセスすることはできません。

于 2013-01-03T12:22:45.347 に答える