Warum kann endgültige Konstanten in Java außer Kraft gesetzt werden?
-
03-07-2019 - |
Frage
Betrachten Sie die folgende Schnittstelle in Java:
public interface I {
public final String KEY = "a";
}
Und die folgende Klasse:
public class A implements I {
public String KEY = "b";
public String getKey() {
return KEY;
}
}
Warum ist es möglich, für die Klasse A zu kommen und Schnittstelle ich die endgültige Konstante außer Kraft setzen?
Versuchen Sie selbst:
A a = new A();
String s = a.getKey(); // returns "b"!!!
Lösung
Trotz der Tatsache, dass Sie die Variable Shadowing es ganz interessant zu wissen, dass Sie letzte Felder in Java ändern können, wie Sie hier :
Java 5 - "final" ist nicht endgültig mehr
Narve Saetre von Machina Networks in Norwegen schickte mir eine Notiz gestern, zu erwähnen, dass es schade war, dass wir den Griff zu einem ändern könnten final Array. Ich verstand ihn falsch und begann geduldig zu erklären dass wir nicht einen Array konstant machen, und dass es keine Möglichkeit, den Inhalt eines Feldes schützt. „Nein“, sagte er, „wir ändern können letzter Griff Reflexion verwendet wird. "
Ich habe versucht Narve den Beispielcode und unglaublich, Java 5 erlaubt mir ändern, um eine endgültige Griff, auch mit einem Griff zu einem primitiven Feld! Ich wusste, dass es verwendete, an einem gewissen Punkt zu dürfen, aber das war es dann nicht zulässig, so lief ich ein paar Tests mit älteren Versionen von Java. Erstens brauchen wir eine Klasse mit dem letzten Feldern:
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
In JDK 1.1.x waren wir private Felder für den Zugriff auf nicht in der Lage verwenden Reflexion. Wir konnten jedoch erstellen Sie eine andere Person mit öffentlichen Felder, dann ist unsere Klasse gegen das kompilieren, und tauschen Sie die Person Klassen. Es gab keinen Zugang zur Laufzeit zu überprüfen, ob wir liefen gegen eine andere Klasse der, die wir gegen zusammengestellt. Allerdings können wir nicht endgültig Felder zur Laufzeit erneut binden entweder Klasse-Swapping oder Reflexion.
Das JDK 1.1.8 JavaDocs für java.lang.reflect.Field hatte folgende zu sagen:
- Wenn dieses Feld Objekt Java erzwingt Sprache Zugriffskontrolle und das darunter liegende Feld ist nicht zugänglich, wobei das Verfahren wirft eine Illegal.
- Wenn das zugrunde liegende Feld endgültig ist, wirft die Methode eine Illegal.
JDK 1.2.x
In JDK 1.2.x, änderte sich dies ein wenig. Wir können nun private Felder machen zugänglich mit dem setAccessible (true) Methode. Der Zugang von Feldern war jetzt zur Laufzeit überprüft, so konnten wir nicht die Klasse Swapping Trick private Felder zuzugreifen. Allerdings konnten wir jetzt plötzlich rebind endgültig Felder! Schauen Sie sich diesen Code:
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); } }
Als ich lief die in JDK 1.2.2_014, ich hatte folgendes Ergebnis:
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
letzte Feld eines primitiven bei der Deklaration wird der Wert inlined, wenn der Typ ist primitiv oder ein String.
JDK 1.3.x und 1.4.x
In JDK 1.3.x verschärfte Sun den Zugang ein wenig, und hinderte uns ein Endfeld mit Reflexion zu verändern. Dies war auch der Fall bei JDK 1.4.x. Wenn wir die FinalFieldChange Klasse versuchen, laufen erneut zu binden die letzten Felder zur Laufzeit mithilfe von Reflektion, würden wir bekommen:
java version "1.3.1_12": Ausnahme thread "main" Illegal: Feld ist endgültig bei java.lang.reflect.Field.set (native Methode) bei FinalFieldChange.change (FinalFieldChange.java:8) bei FinalFieldChange.main (FinalFieldChange.java:12)
java version "1.4.2_05" Exception thread "main" Illegal: Feld ist endgültig bei java.lang.reflect.Field.set (Field.java:519) bei FinalFieldChange.change (FinalFieldChange.java:8) bei FinalFieldChange.main (FinalFieldChange.java:12)
JDK 5.x
Nun kommen wir zum JDK 5.x. Die FinalFieldChange Klasse hat die gleiche Leistung wie in 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
Reflexion, ich hatte gehofft, dass ein Fehler in das JDK gekrochen war. Jedoch, wir beide das Gefühl, dass es unwahrscheinlich, vor allem eine solche grundlegenden Fehler. Nach einiger Suche fand ich die JSR-133: Java-Speichermodell und Gewinde Spezifikation. Der größte Teil der Spezifikation ist hart Lesen underinnert mich an meine Studienzeit (habe ich so zu schreiben ;-) Allerdings JSR-133 ist so wichtig, dass sie das Lesen erforderlich sein sollten für alle Java-Programmierer. (Viel Glück)
Beginnen Sie mit Kapitel 9 Schlussfeld Semantics, auf Seite 25. Genauer gesagt, Abschnitt 9.1.1 Post-Bau Modifikation von Final Feldern lesen. Es Sinn macht Aktuelles zu endgültigen Feldern zu ermöglichen. Zum Beispiel könnten wir entspannen die Anforderung haben Felder Nicht-Finale in JDO.
Wenn wir Abschnitt 9.1.1 sorgfältig zu lesen, sehen wir, dass wir nur ändern sollten letzte Felder im Rahmen unserer Bauprozess. Der Anwendungsfall ist wo wir deserialisieren ein Objekt, und dann, sobald wir aufgebaut haben, die Objekt, initialisieren wir die letzten Felder, bevor es vorbei an. Sobald wir das Objekt zur Verfügung zu einem anderen Thread gemacht haben, sollten wir nicht ändern letzte Felder mit Reflexion. Das Ergebnis wäre nicht vorhersehbar ist.
Es gibt sogar sagt: Wenn ein abschließendes Feld zu einem Kompilierung-initialisiert in der Felddeklaration konstant, Änderungen an dem letzten Feld kann nicht beobachtet werden, da Anwendungen von diesem letzten Feld bei der Kompilierung ersetzt werden Zeit mit der Compile-Zeit konstant. Dies erklärt, warum unser iq Feld bleibt gleich, aber Land ändert.
Seltsam, 5 JDK unterscheidet sich geringfügig von JDK 1.2.x, dass Sie nicht können Ändern ein statisches letztes Feld.
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(); } }
Wenn wir dies mit JDK 1.2.x und 5.x JDK ausführen, erhalten wir die folgende Ausgang:
java version "1.2.2_014": String = ursprünglicher Wert objValue = new Wert
java version "1.5.0" Exception thread "main" Illegal: Feld ist endgültig bei java.lang.reflect.Field.set (Field.java:656) bei FinalStaticFieldChange.changeStaticField (12) an FinalStaticFieldChange.main (16)
So, JDK 5 ist wie JDK 1.2.x, nur anders?
Fazit
Sie wissen, wenn JDK 1.3.0 veröffentlicht wurde? Ich kämpfte, um herauszufinden, so dass ich heruntergeladen und installiert. Die readme.txt-Datei hat das Datum 2000.06.02 13.10. So ist es mehr als 4 Jahre alt (Güte mir, es fühlt sich an wie gestern). JDK 1.3.0 wurde mehrere Monate, bevor ich entlassen begann Newsletter The Java (tm) Spezialisten schreiben! Ich denke, es würde sicher sein, dass nur sehr wenige Java-Entwickler zu sagen, können die Details erinnern von Pre-JDK1.3.0. Ahh, Nostalgie ist nicht, was es früher! Machst du erinnert Java zum ersten Mal ausgeführt wird und immer diese Fehlermeldung: "Kann nicht Threads initialisieren: nicht Klasse java / lang / Thema finden"?
Andere Tipps
Sie verstecken es, es ist ein Feature von „Scope“. Jedes Mal, wenn Sie in einem kleineren Umfang sind, können Sie alle Variablen definieren Sie mögen und die äußeren Umfang Variablen werden „Beschattet“
Durch die Art und Weise, können Sie es Umfang wieder, wenn Sie mögen:
public class A implements I {
public String KEY = "b";
public String getKey() {
String KEY = "c";
return KEY;
}
}
Jetzt wird Return-Taste "c";
Edited, weil die ursprüngliche sog beim erneuten Lesen.
Es sieht aus wie Ihre Klasse ist einfach die Variable versteckt, nicht überschrieben wird es:
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);
}
}
Dies wird gedruckt "B" und "A", wie Sie gefunden. Sie können sogar zuweisen, da die A.KEY Variable nicht als endgültig definiert ist.
A.KEY="C" <-- this compiles.
Aber -
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
}
}
Sie sollten keinen Zugriff auf Ihre Konstante auf diese Weise kann die statische Referenz statt:
I.KEY //returns "a"
B.KEY //returns "b"
Als Design-Betrachtung,
public interface I {
public final String KEY = "a";
}
Die statischen Methoden liefern immer den übergeordneten Schlüssel.
public class A implements I {
public String KEY = "b";
public String getKey() {
return KEY; // returns "b"
}
public static String getParentKey(){
return KEY; // returns "a"
}
}
Genau wie Jom hat bemerkt. Die Gestaltung von statischen Methoden neu definierten Schnittstelle Mitglieder verwenden könnte ein schweres Problem. In der Regel versucht Verwendung die gleichen Namen für die Konstante zu vermeiden.
Statische Felder und Methoden sind an die Klasse / Schnittstelle sie zu deklarieren (obwohl Schnittstellen nicht statische Methoden erklären können, wie sie ganz abstrakte Klassen sind, die umgesetzt werden müssen).
Wenn Sie also eine Schnittstelle mit einem öffentlichen statischen haben (Vartype) (varname), das Feld an diese Schnittstelle angeschlossen ist.
Wenn Sie eine Klasse, die eine Schnittstelle implementieren, die Compiler-Trick-Transformationen (dies.) Varname in InterfaceName.varname. Aber, wenn Sie Ihre Klasse varname definiert, wird eine neue Konstante mit dem Namen varname Ihre Klasse angebracht ist, und der Compiler weiß jetzt übersetzen (diese). Varname in NewClass.varname. Gleiches gilt für Methoden:., Wenn die neue Klasse nicht das Verfahren neu definieren, (. Diese) methoden in SuperClass.methodName übersetzt wird, andernfalls (. Diese) methoden in CurrentClass.methodName übersetzt
Aus diesem Grund werden Sie die Warnung begegnen „x Feld / Methode in einer statischen Art und Weise zugegriffen werden sollte“. Der Compiler sagen Ihnen, dass, obwohl es den Trick verwenden kann, würde es vorziehen, dass Sie ClassName.method / Feldnamen verwendet, da es noch deutlicher aus Gründen der Lesbarkeit ist.