web-dev-qa-db-ja.com

Javaの最終定数をオーバーライドできるのはなぜですか?

Javaの次のインターフェースについて考えてみます。

public interface I {
    public final String KEY = "a";
}

そして次のクラス:

public class A implements I {
    public String KEY = "b";

    public String getKey() {
        return KEY;
    }
}

クラスAがやって来て、インターフェイスIの最終定数をオーバーライドできるのはなぜですか?

自分で試してみてください:

A a = new A();
String s = a.getKey(); // returns "b"!!!
31
Yuval Adam

変数をシャドウイングしているという事実にもかかわらず、Java)の最終フィールドを変更できることを知っているのは非常に興味深いです ここ

Java 5-「final」はもうfinalではありません

ノルウェーのMachinaNetworksのNarveSaetreが昨日私にメモを送ってくれました。ハンドルを最終的な配列に変更できたのは、残念でした。私は彼を誤解し、配列を一定にすることはできず、配列の内容を保護する方法がないことを辛抱強く説明し始めました。 「いいえ」と彼は言いました。「リフレクションを使用して最終的なハンドルを変更できます。」

Narveのサンプルコードを試してみましたが、信じられないほど、Java 5を使用すると、最終的なハンドルを変更できました。プリミティブフィールドへのハンドルも変更できました。以前は許可されていたことがわかっていましたが、その後、許可されなかったため、古いバージョンのJavaでいくつかのテストを実行しました。最初に、finalフィールドを持つクラスが必要です。

public class Person {
  private final String name;
  private final int age;
  private final int iq = 110;
  private final Object country = "South Africa";

  public Person(String name, int age) {
    this.name = name;
    this.age = age;
  }

  public String toString() {
    return name + ", " + age + " of IQ=" + iq + " from " + country;
  }
}

JDK 1.1.x

JDK 1.1.xでは、リフレクションを使用してプライベートフィールドにアクセスできませんでした。ただし、パブリックフィールドを使用して別のPersonを作成し、それに対してクラスをコンパイルして、Personクラスを交換することはできます。コンパイルしたクラスとは異なるクラスに対して実行している場合、実行時にアクセスチェックは行われませんでした。ただし、クラススワッピングまたはリフレクションを使用して、実行時に最終フィールドを再バインドすることはできませんでした。

JDK 1.1.8 JavaDocs for Java.lang.reflect.Fieldには、次のように書かれています。

  • このFieldオブジェクトがJava言語アクセス制御を実施し、基になるフィールドにアクセスできない場合、メソッドはIllegalAccessExceptionをスローします。
  • 基になるフィールドがfinalの場合、メソッドはIllegalAccessExceptionをスローします。

JDK 1.2.x

JDK 1.2.xでは、これが少し変更されました。 setAccessible(true)メソッドを使用してプライベートフィールドにアクセスできるようになりました。フィールドへのアクセスが実行時にチェックされるようになったため、クラススワッピングトリックを使用してプライベートフィールドにアクセスすることはできませんでした。しかし、今では突然最終フィールドを再バインドすることができました!このコードを見てください:

import Java.lang.reflect.Field;

public class FinalFieldChange {
  private static void change(Person p, String name, Object value)
      throws NoSuchFieldException, IllegalAccessException {
    Field firstNameField = Person.class.getDeclaredField(name);
    firstNameField.setAccessible(true);
    firstNameField.set(p, value);
  }
  public static void main(String[] args) throws Exception {
    Person heinz = new Person("Heinz Kabutz", 32);
    change(heinz, "name", "Ng Keng Yap");
    change(heinz, "age", new Integer(27));
    change(heinz, "iq", new Integer(150));
    change(heinz, "country", "Malaysia");
    System.out.println(heinz);
  }
}

これをJDK1.2.2_014で実行すると、次の結果が得られました。

Ng Keng Yap, 27 of IQ=110 from Malaysia    Note, no exceptions, no complaints, and an incorrect IQ result. It seems that if we set a

タイプがプリミティブまたは文字列の場合、宣言時のプリミティブの最終フィールド。値はインライン化されます。

JDK 1.3.xおよび1.4.x

JDK 1.3.xでは、Sunはアクセスを少し厳しくし、リフレクションを使用して最終フィールドを変更できないようにしました。これは、JDK1.4.xにも当てはまりました。 FinalFieldChangeクラスを実行して、実行時にリフレクションを使用して最終フィールドを再バインドしようとすると、次のようになります。

Javaバージョン "1.3.1_12":例外スレッド "main" IllegalAccessException:フィールドはJava.lang.reflect.Field.set(ネイティブメソッド)のFinalFieldChange.change(FinalFieldChange.Java:8)のFinalFieldChange.main(FinalFieldChange。 Java:12)

Javaバージョン "1.4.2_05"例外スレッド "main" IllegalAccessException:フィールドはJava.lang.reflect.Field.set(Field.Java:519)のFinalFieldChange.change(FinalFieldChange.Java:8)のFinalFieldChange.main( FinalFieldChange.Java:12)

JDK 5.x

これで、JDK5.xにアクセスできます。 FinalFieldChangeクラスの出力は、JDK1.2.xと同じです。

Ng Keng Yap, 27 of IQ=110 from Malaysia    When Narve Saetre mailed me that he managed to change a final field in JDK 5 using

振り返ってみると、バグがJDKに忍び込んでいることを期待していました。しかし、私たち二人は、特にそのような根本的なバグはありそうもないと感じました。いくつか検索したところ、JSR-133が見つかりました:Javaメモリモデルとスレッドの仕様。仕様のほとんどは読みづらく、大学時代を思い出します(以前はそのように書いていました; -)ただし、JSR-133は非常に重要であるため、すべてのJavaプログラマーが読む必要があります。(幸運)

25ページの第9章「最終フィールドのセマンティクス」から始めます。具体的には、セクション9.1.1最終フィールドの構築後の変更をお読みください。最終フィールドの更新を許可することは理にかなっています。たとえば、JDOでフィールドを非最終にするという要件を緩和できます。

セクション9.1.1を注意深く読むと、構築プロセスの一部として最終フィールドのみを変更する必要があることがわかります。ユースケースでは、オブジェクトを逆シリアル化し、オブジェクトを作成したら、最後のフィールドを初期化してから渡します。オブジェクトを別のスレッドで使用できるようにしたら、リフレクションを使用して最終フィールドを変更しないでください。結果は予測できません。

最終フィールドがフィールド宣言でコンパイル時定数に初期化されている場合、その最終フィールドの使用はコンパイル時にコンパイル時定数に置き換えられるため、最終フィールドへの変更が観察されない場合があります。これは、私たちのiqフィールドが同じままであるが、国が変わる理由を説明しています。

不思議なことに、JDK5は静的なfinalフィールドを変更できないという点でJDK1.2.xとわずかに異なります。

import Java.lang.reflect.Field;

public class FinalStaticFieldChange {
  /** Static fields of type String or primitive would get inlined */
  private static final String stringValue = "original value";
  private static final Object objValue = stringValue;

  private static void changeStaticField(String name)
      throws NoSuchFieldException, IllegalAccessException {
    Field statFinField = FinalStaticFieldChange.class.getDeclaredField(name);
    statFinField.setAccessible(true);
    statFinField.set(null, "new Value");
  }

  public static void main(String[] args) throws Exception {
    changeStaticField("stringValue");
    changeStaticField("objValue");
    System.out.println("stringValue = " + stringValue);
    System.out.println("objValue = " + objValue);
    System.out.println();
  }
}

これをJDK1.2.xおよびJDK5.xで実行すると、次の出力が得られます。

Javaバージョン "1.2.2_014":stringValue =元の値objValue =新しい値

Javaバージョン "1.5.0"例外スレッド "main" IllegalAccessException:フィールドはJava.lang.reflect.Field.set(Field.Java:656)のFinalStaticFieldChange.changeStaticField(12)のFinalStaticFieldChange.main(16)で最終です。

つまり、JDK5はJDK1.2.xに似ていますが、違いますか?

結論

JDK 1.3.0がいつリリースされたか知っていますか?見つけるのに苦労したので、ダウンロードしてインストールしました。 readme.txtファイルの日付は2000/06/0213:10です。それで、それは4歳以上です(私、それは昨日のように感じます)。 JDK 1.3.0は、Java(tm)スペシャリストのニュースレターを書き始める数か月前にリリースされました。 Java開発者がJDK1.3.0より前の詳細を覚えていることはほとんどないと言っても差し支えないと思います。ああ、懐かしさは以前とは異なります!実行したことを覚えていますか= Java初めて、このエラーが発生しました:「スレッドを初期化できません:クラスJava/lang/Threadが見つかりません」?

18
André

あなたはそれを隠しています、それは「スコープ」の特徴です。スコープが小さい場合はいつでも、好きなすべての変数を再定義でき、外側のスコープ変数は「シャドウ」になります

ちなみに、必要に応じてもう一度スコープを設定できます。

public class A implements I {
    public String KEY = "b";

    public String getKey() {
        String KEY = "c";
        return KEY;
    }
}

これで、KEYは「c」を返します。

読み直し時にオリジナルが吸い込まれたため編集。

36
Bill K

クラスが変数を上書きするのではなく、単に非表示にしているようです。

public class A implements I {
    public String   KEY = "B";

    public static void main(String args[])
    {
        A t = new A();
        System.out.println(t.KEY);
        System.out.println(((I) t).KEY);
    }
}

これにより、「B」と「A」が出力されます。 A.KEY変数はfinalとして定義されていないため、割り当てることもできます。

 A.KEY="C" <-- this compiles.

だが -

public class C implements I{

    public static void main (String args[])
    {
        C t = new C();
        c.KEY="V"; <--- compiler error ! can't assign to final

    }
}
4
Steve B.

設計上の考慮事項として、

public interface I {
    public final String KEY = "a";
}

静的メソッドは常に親キーを返します。

public class A implements I {
    public String KEY = "b";

    public String getKey() {
        return KEY; // returns "b"
    }

    public static String getParentKey(){
        return KEY; // returns "a"
    }
}

ジョムが気づいたように。再定義されたインターフェイスメンバーを使用した静的メソッドの設計は、大きな問題になる可能性があります。一般に、定数に同じ名前を使用しないようにしてください。

この方法で定数にアクセスするのではなく、代わりに静的参照を使用してください。

I.KEY //returns "a"
B.KEY //returns "b"
2
Jorn

静的フィールドとメソッドは、それらを宣言するクラス/インターフェイスにアタッチされます(ただし、インターフェイスは、実装する必要がある完全に抽象クラスであるため、静的メソッドを宣言できません)。

したがって、public static(vartype)(varname)を持つインターフェースがある場合、そのフィールドはそのインターフェースにアタッチされます。

そのインターフェイスを実装するクラスがある場合、コンパイラトリックは(this。)varnameをInterfaceName.varnameに変換します。ただし、クラスがvarnameを再定義すると、varnameという名前の新しい定数がクラスに付加され、コンパイラーは(this。)varnameをNewClass.varnameに変換することを認識します。同じことがメソッドにも当てはまります。新しいクラスがメソッドを再定義しない場合、(this。)methodNameはSuperClass.methodNameに変換されます。それ以外の場合、(this。)methodNameはCurrentClass.methodNameに変換されます。

これが、「xフィールド/メソッドは静的な方法でアクセスする必要があります」という警告が表示される理由です。コンパイラーは、トリックを使用する場合もありますが、読みやすさの目的でより明示的であるため、ClassName.method/fieldNameを使用することをお勧めします。

1
MetroidFan2002