Javaの最終定数をオーバーライドできるのはなぜですか?
-
03-07-2019 - |
質問
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"!!!
解決
変数をシャドウイングしているという事実にもかかわらず、こちら:
Java 5-" final&quot ;;もう決まっていない
ノルウェーのMachina NetworksのNarve Saetreが昨日、私にメモを送りました。 ハンドルを変更できるのは残念だったと言って 最終的な配列。私は彼を誤解し、辛抱強く説明し始めました 配列を定数にすることができず、 配列の内容を保護します。 「いいえ」と彼は言った、「私たちは リフレクションを使用した最終的なハンドル。"
Narveのサンプルコードを試してみましたが、信じられないほど、Java 5で次のことができました。 最終的なハンドル、原始的なフィールドへのハンドルさえも修正してください!私はそれを知っていました 以前はある時点で許可されていましたが、その後は許可されませんでした。 そのため、古いバージョンのJavaでいくつかのテストを実行しました。まず、 最終フィールドを持つクラス:
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では、次を使用してプライベートフィールドにアクセスできませんでした 反射。ただし、publicを使用して別のPersonを作成できます フィールド、それに対してクラスをコンパイルし、Personを交換します クラス。実行中の場合、実行時にアクセスチェックはありませんでした コンパイルしたクラスとは異なるクラスに対して。 ただし、実行時に最終フィールドを再バインドすることはできませんでした クラスの交換またはリフレクション。
java.lang.reflect.FieldのJDK 1.1.8 JavaDocsには次のものがありました 言う:
- この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); } }
JDK 1.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&quot ;:例外スレッド" 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
これで、JDK 5.xに到達しました。 FinalFieldChangeクラスの出力は同じです JDK 1.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 Memory Modelと スレッド仕様。仕様のほとんどはハードレアです
他のヒント
非表示にしているのは、「スコープ」の機能です。小さいスコープにいるときはいつでも、好きな変数をすべて再定義でき、外側のスコープ変数は「シャドウ」になります
ところで、必要に応じて再度スコープできます:
public class A implements I {
public String KEY = "b";
public String getKey() {
String KEY = "c";
return KEY;
}
}
今、KEYは" c&quot ;;を返します
再読み込み時にオリジナルが吸い込まれたため編集。
クラスは、変数を上書きするのではなく、単に変数を非表示にしているようです:
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
}
}
この方法で定数にアクセスしないでください。代わりに静的参照を使用してください。
I.KEY //returns "a"
B.KEY //returns "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"
}
}
Jomが気づいたように。再定義されたインターフェイスメンバを使用した静的メソッドの設計は、大きな問題になる可能性があります。一般に、定数に同じ名前を使用しないようにしてください。
静的フィールドとメソッドは、それらを宣言するクラス/インターフェースにアタッチされます(ただし、実装が必要な完全に抽象クラスであるため、インターフェースは静的メソッドを宣言できません)。
したがって、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を使用することをお勧めします。