Auch wenn sie final
ein Feld kann außerhalb des statischen Initialisierers geändert werden und (zumindest in JVM HotSpot) wird der Bytecode einwandfrei ausgeführt.
Das Problem ist, dass der Java-Compiler dies nicht zulässt, aber das kann leicht umgangen werden mit objectweb.asm
. Hier ist eine perfekt gültige Klassendatei eine ungültige Klassendatei aus der Sicht der JVMS-Spezifikation aber es besteht die Bytecode-Verifizierung und wird dann erfolgreich unter JVM HotSpot OpenJDK12 geladen und initialisiert:
ClassWriter cw = new ClassWriter(0);
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Cl", null, "java/lang/Object", null);
{
FieldVisitor fv = cw.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, "fld", "I", null, null);
fv.visitEnd();
}
{
// public void setFinalField1() { //... }
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "setFinalField1", "()V", null, null);
mv.visitMaxs(2, 1);
mv.visitInsn(Opcodes.ICONST_5);
mv.visitFieldInsn(Opcodes.PUTSTATIC, "Cl", "fld", "I");
mv.visitInsn(Opcodes.RETURN);
mv.visitEnd();
}
{
// public void setFinalField2() { //... }
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "setFinalField2", "()V", null, null);
mv.visitMaxs(2, 1);
mv.visitInsn(Opcodes.ICONST_2);
mv.visitFieldInsn(Opcodes.PUTSTATIC, "Cl", "fld", "I");
mv.visitInsn(Opcodes.RETURN);
mv.visitEnd();
}
cw.visitEnd();
In Java sieht die Klasse grob gesagt wie folgt aus:
public class Cl{
private static final int fld;
public static void setFinalField1(){
fld = 5;
}
public static void setFinalField2(){
fld = 2;
}
}
die nicht mit javac
kann aber von der JVM geladen und ausgeführt werden.
JVM HotSpot hat eine besondere Behandlung solcher Klassen in dem Sinne, dass es verhindert, dass solche "Konstanten" an der Konstantenfaltung teilnehmen. Diese Prüfung erfolgt auf der Bytecode-Umschreibungsphase der Klasseninitialisierung :
// Check if any final field of the class given as parameter is modified
// outside of initializer methods of the class. Fields that are modified
// are marked with a flag. For marked fields, the compilers do not perform
// constant folding (as the field can be changed after initialization).
//
// The check is performed after verification and only if verification has
// succeeded. Therefore, the class is guaranteed to be well-formed.
InstanceKlass* klass = method->method_holder();
u2 bc_index = Bytes::get_Java_u2(bcp + prefix_length + 1);
constantPoolHandle cp(method->constants());
Symbol* ref_class_name = cp->klass_name_at(cp->klass_ref_index_at(bc_index));
if (klass->name() == ref_class_name) {
Symbol* field_name = cp->name_ref_at(bc_index);
Symbol* field_sig = cp->signature_ref_at(bc_index);
fieldDescriptor fd;
if (klass->find_field(field_name, field_sig, &fd) != NULL) {
if (fd.access_flags().is_final()) {
if (fd.access_flags().is_static()) {
if (!method->is_static_initializer()) {
fd.set_has_initialized_final_update(true);
}
} else {
if (!method->is_object_initializer()) {
fd.set_has_initialized_final_update(true);
}
}
}
}
}
}
Die einzige Einschränkung, die JVM HotSpot überprüft, ist, dass die final
Feld sollte nicht außerhalb der Klasse geändert werden, in der die final
Feld deklariert ist.
4 Stimmen
Das ist eine schlechte Idee. Ich würde stattdessen versuchen, den Quellcode zu bekommen und neu zu kompilieren (oder sogar zu dekompilieren/neu zu kompilieren).
0 Stimmen
System.out ist ein öffentliches, statisches, endgültiges Feld, das aber auch geändert werden kann.
21 Stimmen
@irreputable
System.out/in/err
sind so "speziell", dass sie im Java Memory Model besonders erwähnt werden müssen. Sie sind keine Beispiele, denen man folgen sollte.8 Stimmen
Gut mein Punkt ws, um einen Hack in der zwischen zu finden, um meine app arbeiten, bis die Lib verantwortlich die Änderung an der nächsten Version machen, so dass ich nicht mehr hacken müssen...
6 Stimmen
@Bill K von vor zehn Jahren: Es wäre großartig, sie neu zu kompilieren, aber sie befindet sich auf einem bereitgestellten System und ich muss sie nur patchen, bis wir die bereitgestellte Anwendung aktualisieren können!
0 Stimmen
Für Java 17 Deklarierte Felder von java.lang.reflect.Fields in jdk12 abrufen