javaコンパイラの奇妙さ:フィールドは同じクラスで宣言されているが、“見えない”

StackOverflow https://stackoverflow.com/questions/1604147

質問

eclipseコンパイラは、フィールドsが表示されないことを示す次のコードのコンパイルを拒否します。 (IBMのAspect Jコンパイラーも拒否し、「解決できなかった」と述べています)なぜですか?

public class Test {

    String s;

    void foo(Object o) {
        String os = getClass().cast(o).s;
    }
}

Java言語仕様の状態:

  

そうでなければ、デフォルトがあると言います   アクセス。次の場合にのみ許可されます。   アクセスは内部から発生します   型が宣言されているパッケージ。

私が理解する方法では、フィールドは同じコンパイル単位で、したがって同じパッケージ内で宣言され、アクセスされるため、アクセスできるはずです。

さらに奇妙なことに、からダウンキャストを追加しますか? Test Test に拡張すると、フィールドが表示されます。つまり、次のコードがコンパイルされます。

public class Test {

    String s;

    void foo(Object o) {
        Test t = getClass().cast(o);
        String os = t.s;
    }
}

コンパイラのバグに遭遇したか、Java仕様を誤解しましたか?

編集: 今、別のコンピューターにいます。ここでは、javacはコードを受け入れますが、Eclipseはまだ受け入れません。このマシンのバージョン:

  

Eclipseプラットフォーム

     

バージョン:3.4.2ビルドID:   M20090211-1700

JDK 1.6.0

編集2 実際、javacはコードを受け入れます。 IBMのAscpect Jコンパイラを使用するantビルドを実行してテストしました...

役に立ちましたか?

解決

これを試してください:

void foo(Object o) {
    Test foo = getClass().cast(o);
    String so = foo.s;
}

[明確にするために編集]:

getClass()。cast(o)は、タイプ ' capture#1-of? Test ではなく、Test 'を拡張します。そのため、この問題はジェネリックおよびコンパイラーによる処理方法に関連しています。ジェネリックの仕様の詳細はわかりませんが、一部のコンパイラ(ここのコメントごと)がコードを受け入れる場合、これは仕様のループホールであるか、これらのコンパイラの一部が仕様に完全に準拠していないことを示します。 / p>

[最後の考え]: ここでは、Eclipseコンパイラーが実際に(慎重に)正しいと考えています。オブジェクト o は、実際にはTestの拡張(および別のパッケージで定義)である可能性があり、コンパイラは実際にそうであるかどうかを知る方法がありません。そのため、別のパッケージで定義されている拡張機能のインスタンスの最悪のケースとして処理しています。クラス Test final 修飾子を追加すると、フィールド s へのアクセスが許可された場合、それは非常に正しいはずですが、そうではありません。

他のヒント

さて、見てみましょう。コンパイラは、パッケージ内のエンティティによって foo()が呼び出されることを適切に保証できないため、 s が表示されることを保証できないと思います。たとえば、追加

protected void bar() {
    foo();
}

そしてサブクラス Banana 別のパッケージ

public void quux() { bar(); }

おっと! getClass() Banana を生成しますが、これは s を見ることができません。

編集:ある意味で、other.package.Bananaはフィールド s 持っていません。バナナが同じパッケージに含まれていた場合、 独自の s プロパティを保持でき、 Test を参照する必要があります。 s super 経由。

あなたの言っていることを再現できません。これらはどちらも、警告、エラー、またはjavacを直接使用することなく正常にコンパイルされます。

WinXP、javac 1.6.0_16


いいえeclipse(v3.4.1、ビルドID:M20080911-1700)で試しましたが、最初の場合は次のように表示されます:

The field Test.s is not visible

少なくともコンパイラー準拠レベル1.6および1.5の場合。 面白いことに、クイック修正オプションを見ると、 Change to 's' 解像度がリストされています。もちろん、これは問題を解決しません。したがって、Eclipseコンパイラーとクイックフィックス「ジェネレーター」これについても異なる見解があるようです;-)


最初に取得したEclipseのコンパイラコンプライアンスレベル1.4(予想どおり)の場合

s cannot be resolved or is not a field

2番目の場合は

Type mismatch: cannot convert from Object to Test

コマンドラインで -source 1.4 target -1.4 を直接指定すると、 javac が最初のコードを示します

cannot find symbol

2番目の場合は

incompatible types

実際には、Genericsが必要とする場合を除き、ほとんどすべての場合において、Javaキャスト演算子を使用する方が安全です。 こちらで説明しました。 Javaキャスト演算子は詳細を確認しますが、ここでは適切なツールです。

cast メソッドを演算子で置き換えると、Eclipseで問題なくコンパイルされます。

public class Test {

    String s;

    void foo(Object o) {
        String os = ((Test) o).s;
    }
}

alphazero は正しいと思いますこちら、Eclipseは用心深くなっています。

非常に奇妙です。 (私にとって)未知の理由で、eclipseコンパイラーは明示的なキャストを必要とします:

void foo(Object o) {
    String os = ((Test)getClass().cast(o)).s;
}

コードはSunのJDKでキャストせずに完全にコンパイルされます(GNU / Linuxでバージョン1.6.0_16を実行しています)。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top