Could someone explain why the following code fails? I have the following five classes:
public class TestReplaceLogger {
public static void main(String[] arv) throws Exception {
ClassWithFinalFields classWithFinalFields = new ClassWithFinalFields();
Field field = ClassWithFinalFields.class.getDeclaredField("LOG");
// Comment this line and uncomment out the next line causes program work
Logger oldLogger = (Logger)field.get(null);
//Logger oldLogger = classWithFinalFields.LOG;
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, new MockLogger());
classWithFinalFields.log();
}
}
public class ClassWithFinalFields {
public static final Logger LOG = new RealLogger();
public void log() {
LOG.log("hello");
}
}
public interface Logger {
public void log(String msg);
}
public class RealLogger implements Logger{
public void log(String msg) {
System.out.println("RealLogger: " + msg);
}
}
public class MockLogger implements Logger {
public void log(String msg) {
System.out.println("MockLogger: " + msg);
}
}
What the code is trying to do is to use reflection to replace the LOG variable in the ClassWithFinalFields class. As it stands, the class throws an IllegalAccessException
when it tries to set the field at the end of TestReplaceLogger
.
However, if I replace
Logger oldLogger = (Logger)field.get(null);
with
Logger oldLogger = classWithFinalFields.LOG;
then the code runs without problems and prints log "MockLogger: hello" as expected.
So the question is, why does reading the final field through reflection stop the program working? It looks like the final modifier can no longer be removed so you get an IllegalAccessException but I have no idea why. I can speculate that may be it is something to do with the compiler optimisation or classloader ordering but, despite having had a look at the byte code, I've no real idea what is going on.
If people are wondering why I would want to do this at all, it started off as looking for a way to mock out some awkward logging during unit tests whilst we were upgrading some software. Now I'm just curious as to what on earth is going on under the covers.
If anyone want to see it, the stack trace is
Exception in thread "main" java.lang.IllegalAccessException: Can not set static final org.matthelliwell.reflection.Logger field org.matthelliwell.reflection.ClassWithFinalFields.LOG to org.matthelliwell.reflection.MockLogger
at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:73)
at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:77)
at sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl.set(UnsafeQualifiedStaticObjectFieldAccessorImpl.java:77)
at java.lang.reflect.Field.set(Field.java:741)
at org.matthelliwell.reflection.TestReplaceLogger.main(TestReplaceLogger.java:23)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
It's very bad because it ties your UI to your method names, which should be completely unrelated. Making an seemingly innocent change later on can have unexpected disastrous consequences. Using reflection is not a bad practice. Doing this sort of thing is a bad practice.
field. set(null, p_fieldValue); This will let you set the static field.
FALSE. Reflection is used to change the public static final Boolean.
Reflection of Java MethodsMethod[] methods = obj. getDeclaredMethod(); Here, the getDeclaredMethod() returns all the methods present inside the class.
You are accessing the field object bypassing its public api. Of course, anything may happen if you do that. In particular, different implementations of the Java API may behave differently.
In the Oracle JDK, Field assumes the modifiers to be final, and therefore caches the fieldAccessor (see Field.getFieldAccessor()
). You have changed the modifiers, but not invalidated that cache, causing the old field accessor to be used, which still believes the field to be final.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With