573 Stimmen

Privates statisches Endfeld mit Java-Reflexion ändern

Ich habe eine Klasse mit einer private static final Feld, das ich leider während der Laufzeit ändern muss.

Bei der Verwendung von reflection erhalte ich diesen Fehler: java.lang.IllegalAccessException: Can not set static final boolean field

Gibt es eine Möglichkeit, den Wert zu ändern?

Field hack = WarpTransform2D.class.getDeclaredField("USE_HACK");
hack.setAccessible(true);
hack.set(null, true);

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.

1021voto

polygenelubricants Punkte 362173

Unter der Annahme, dass keine SecurityManager Sie daran hindert, können Sie mit setAccessible um sich fortzubewegen private und das Zurücksetzen des Modifikators, um die final und ändern Sie tatsächlich eine private static final Feld.

Hier ist ein Beispiel:

import java.lang.reflect.*;

public class EverythingIsTrue {
   static void setFinalStatic(Field field, Object newValue) throws Exception {
      field.setAccessible(true);

      Field modifiersField = Field.class.getDeclaredField("modifiers");
      modifiersField.setAccessible(true);
      modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

      field.set(null, newValue);
   }
   public static void main(String args[]) throws Exception {      
      setFinalStatic(Boolean.class.getField("FALSE"), true);

      System.out.format("Everything is %s", false); // "Everything is true"
   }
}

Unter der Annahme, dass keine SecurityException ausgelöst wird, gibt der obige Code Folgendes aus "Everything is true" .

Was hier tatsächlich geschieht, ist Folgendes:

  • Das Primitive boolean Werte true y false sur main auf den Referenztyp autoboxed sind Boolean "Konstanten" Boolean.TRUE y Boolean.FALSE
  • Die Reflexion dient der Veränderung der public static final Boolean.FALSE um auf den Boolean genannt von Boolean.TRUE
  • Infolgedessen wird jedes Mal, wenn ein false wird autoboxiert an Boolean.FALSE bezieht sie sich auf dieselbe Boolean als die, auf die sich die Boolean.TRUE
  • Alles, was war "false" jetzt ist "true"

Verwandte Fragen


Vorbehalte

Wenn Sie so etwas tun, sollten Sie äußerst vorsichtig sein. Es kann sein, dass es nicht funktioniert, weil ein SecurityManager vorhanden sein, aber auch wenn dies nicht der Fall ist, kann es je nach Nutzungsmuster funktionieren oder auch nicht.

JLS 17.5.3 Nachträgliche Änderung der endgültigen Felder

In einigen Fällen, etwa bei der Deserialisierung, muss das System die final Felder eines Objekts nach der Konstruktion. final Felder können über Reflexion und andere implementierungsabhängige Mittel geändert werden. Das einzige Muster, bei dem dies eine vernünftige Semantik hat, ist eines, bei dem ein Objekt konstruiert wird und dann die final Felder des Objekts aktualisiert werden. Das Objekt sollte weder für andere Threads sichtbar gemacht werden, noch sollte die final Felder gelesen werden, bis alle Aktualisierungen der final Felder des Objekts vollständig sind. Das Einfrieren eines final treten sowohl am Ende des Konstruktors auf, in dem die final gesetzt wird, und unmittelbar nach jeder Änderung eines final Feld durch Reflexion oder einen anderen speziellen Mechanismus.

Selbst dann gibt es eine Reihe von Komplikationen. Wenn ein final Feld in der Felddeklaration mit einer Kompilierzeitkonstante initialisiert wird, werden Änderungen an der final Feldes darf nicht beachtet werden, da die Verwendung dieses final werden zur Kompilierzeit durch die Kompilierzeitkonstante ersetzt.

Ein weiteres Problem ist, dass die Spezifikation eine aggressive Optimierung der final Felder. Innerhalb eines Threads ist es zulässig, die Reihenfolge der Lesevorgänge eines final mit den Änderungen eines endgültigen Feldes, die nicht im Konstruktor vorgenommen werden.

Siehe auch

  • JLS 15.28 Konstante Ausdrücke
    • Es ist unwahrscheinlich, dass diese Technik mit einer primitiven private static final boolean weil er als Kompilierzeitkonstante inlinefähig ist und der "neue" Wert daher möglicherweise nicht beobachtbar ist

Anhang: Über die bitweise Manipulation

Im Wesentlichen,

field.getModifiers() & ~Modifier.FINAL

schaltet das Bit aus, das der Modifier.FINAL de field.getModifiers() . & ist das bitweise-und, und ~ ist das Bitwise-Komplement.

Siehe auch


Konstante Ausdrücke merken

Kannst du das Problem immer noch nicht lösen, bist du in eine Depression verfallen, wie ich es getan habe? Sieht Ihr Code so aus?

public class A {
    private final String myVar = "Some Value";
}

Beim Lesen der Kommentare zu dieser Antwort, insbesondere des Kommentars von @Pshemo, wurde ich daran erinnert, dass Konstante Ausdrücke unterschiedlich gehandhabt werden, so dass es unmöglich um sie zu ändern. Daher müssen Sie Ihren Code so ändern, dass er wie folgt aussieht:

public class A {
    private final String myVar;

    private A() {
        myVar = "Some Value";
    }
}

wenn Sie nicht der Eigentümer der Klasse sind... Ich fühle mit Ihnen!

Weitere Einzelheiten über die Gründe für dieses Verhalten dies lesen ?

6 Stimmen

Als ich das gesehen habe: System.out.format("Everything is %s", false); // "Everything is true" Ich dachte, du würdest uns verarschen, dann habe ich den Rest deines Codes gelesen, aber es wirft eine Frage der Lesbarkeit auf, einige Dinge, die du einfach als wahr (oder in diesem Fall, falsch) akzeptierst.

53 Stimmen

@thecoop, @HalfBrian: Es besteht kein Zweifel, dass dies AUSSERGEWÖHNLICH BÖSE aber dieses Beispiel wurde absichtlich gewählt. Meine Antwort zeigt nur, wie dies unter bestimmten Umständen möglich ist. Das ekelhafteste Beispiel, das mir einfällt, wurde absichtlich gewählt, in der Hoffnung, dass sich die Leute vielleicht sofort vor dieser Technik ekeln, anstatt sich in sie zu verlieben.

74 Stimmen

Yo, Kumpel. Ich habe gehört, du magst Reflexion, also habe ich auf dem Feld reflektiert, damit du reflektieren kannst, während du reflektierst.

68voto

erickson Punkte 256579

Wenn der Wert, der einem static final boolean Feld zur Kompilierzeit bekannt ist, ist es ein konstant. Felder der primitiven oder String Typ können Kompilierzeitkonstanten sein. Eine Konstante wird in jedem Code, der auf das Feld verweist, eingefügt. Da das Feld zur Laufzeit nicht wirklich gelesen wird, hat eine Änderung keine Auswirkungen.

El Java-Sprachbeschreibung sagt dies:

Wenn ein Feld eine konstante Variable ist ist (§4.12.4), dann wird das Löschen des Schlüsselworts final oder die Änderung seines Wertes nicht die Kompatibilität mit bereits existierenden Binärdateien, indem diese nicht ausgeführt werden, sondern sie werden keinen neuen Wert sehen für die Verwendung des Feldes, es sei denn, sie neu kompiliert werden. Dies gilt selbst dann, wenn die Verwendung selbst kein Kompilierzeitausdruck ist konstanter Ausdruck ist (§15.28)

Hier ist ein Beispiel:

class Flag {
  static final boolean FLAG = true;
}

class Checker {
  public static void main(String... argv) {
    System.out.println(Flag.FLAG);
  }
}

Wenn Sie dekompilieren Checker sehen Sie, dass anstelle des Verweises auf Flag.FLAG schiebt der Code einfach den Wert 1 ein ( true ) auf den Stapel (Anweisung #3).

0:   getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
3:   iconst_1
4:   invokevirtual   #3; //Method java/io/PrintStream.println:(Z)V
7:   return

0 Stimmen

Das war mein erster Gedanke, aber dann erinnerte ich mich daran, dass Java zur Laufzeit kompiliert, wenn Sie das Bit zurücksetzen würden, würde es einfach mit ihm als Variable statt einer Konstanten neu kompiliert.

4 Stimmen

@Bill K - Nein, das bezieht sich nicht auf die JIT-Kompilierung. Die abhängigen Klassendateien enthalten tatsächlich die eingefügten Werte und keinen Verweis auf die unabhängige Klasse. Es ist ein ziemlich einfaches Experiment zu testen; ich werde ein Beispiel hinzufügen.

1 Stimmen

Wie passt dies mit @polygenelubricants 's Antwort, wo er neu definiert Boolean.false? - aber Sie haben Recht, ich habe dieses Verhalten gesehen, wenn Dinge nicht korrekt neu kompilieren.

20voto

Eine kleine Kuriosität aus der Java Language Specification, Kapitel 17, Abschnitt 17.5.4 "Write-protected Fields":

Normalerweise darf ein Feld, das endgültig und statisch ist, nicht geändert werden. System.in, System.out und System.err sind jedoch statische endgültige Felder die aus Legacy-Gründen von den Methoden System.in, System.out und System.err geändert werden dürfen. System.setIn, System.setOut und System.setErr. Wir bezeichnen diese Felder als schreibgeschützt, um sie von gewöhnlichen endgültigen Feldern zu unterscheiden.

Quelle: http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.4

10voto

iirekm Punkte 7639

Ich habe es auch integriert mit Bibliothek von Joor

Verwenden Sie einfach

      Reflect.on(yourObject).set("finalFieldName", finalFieldValue);

Außerdem habe ich ein Problem mit override was bei den bisherigen Lösungen nicht der Fall zu sein scheint. Verwenden Sie dies jedoch sehr vorsichtig und nur, wenn es keine andere gute Lösung gibt.

2 Stimmen

Wenn ich dies versuche (JDK12), erhalte ich eine Ausnahme: "Kann das Feld final ___ nicht setzen".

3 Stimmen

@AaronIba Das ist in Java 12+ nicht mehr erlaubt.

8voto

nndru Punkte 1977

Zusammen mit der bestplatzierten Antwort können Sie einen etwas einfacheren Ansatz verwenden. Apache Commons FieldUtils Klasse verfügt bereits über eine bestimmte Methode, die diese Aufgabe übernehmen kann. Schauen Sie sich bitte an FieldUtils.removeFinalModifier Methode. Sie sollten die Instanz des Zielfeldes und das Kennzeichen für die Erzwingung der Zugänglichkeit angeben (wenn Sie mit nicht öffentlichen Feldern arbeiten). Weitere Informationen finden Sie unter aquí .

0 Stimmen

Dies ist eine viel einfachere Lösung als die derzeit akzeptierte Antwort

5 Stimmen

Ist es das? Das Kopieren einer Methode klingt nach einer einfacheren Lösung als das Importieren einer ganzen Bibliothek (die dasselbe tut wie die Methode, die Sie kopieren würden).

4 Stimmen

Funktioniert nicht in Java 12+: java.lang.UnsupportedOperationException: In java 12+ final cannot be removed.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X