Abstract: Final fields could not be set with reflection in Java 1.1. In Java 1.2 they changed it so that we could set final fields. We again couldn't in Java 1.3 and 1.4. Since Java 5, we can again set final fields.
Welcome to the 96th edition of The Java(tm) Specialists' Newsletter. We are slowly but surely approaching the 100th newsletter edition, which I am hoping to send on the 30th of November, our 4th anniversary of The Java(tm) Specialists' Newsletter.
In my last newsletter, I invited you to say "hello" with yahoo messenger. I had some interesting conversations since then with readers from around the world. I decided to rather not publish my yahoo ID on my website, imagine if 450'000 TheServerSide readers were to suddenly decide that they all wanted to be my friend ;-) So, my yahoo ID is special knowledge, reserved for my subscribers :)
The result of the survey was interesting. Of the 80+ emails that I received, about three quarters felt that generics would make Java code more maintainable. Due to Java 5 having just been released, none of them had started using generics in production code. Most of my customers are using JDK 1.4.2, some are on 1.4.1 and others still on 1.3.1. It will be at least another six months before some of them will start moving to Java 5.
javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.
Narve Saetre from Machina Networks in Norway sent me a note
yesterday, mentioning that it was a pity that we could
change the handle to a final
array. I misunderstood him, and
started patiently explaining that we could not make an array
constant, and that there was no way of protecting the
contents of an array. "No", said he, "we can change a
final
handle using reflection."
I tried Narve's sample code, and unbelievably, Java 5 allowed
me to modify a final
handle, even a handle to a primitive
field! I knew that it used to be allowed at some point, but
that it was then disallowed, so I ran some tests with older
versions of Java. First, we need a class with
final
fields:
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; } }
In JDK 1.1.x, we were not able to access private fields using
reflection. We could, however, create another Person
with
public fields, then compile our class against that, and swap
the Person
classes. There was no access checking at
runtime if we were running against a different class to the
one that we compiled against. However, we could not rebind
final
fields at runtime using either class swapping or
reflection.
The JDK 1.1.8 JavaDocs for java.lang.reflect.Field
had the
following to say:
final
, the method throws an
IllegalAccessException.
In JDK 1.2.x, this changed a bit. We could now make
private fields accessible with the
setAccessible(true)
method. Access
of fields was now checked at runtime, so we could not use the
class swapping trick to access private fields. However,
we could now suddenly rebind final
fields! Look at this 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); } }
When I ran this in JDK 1.2.2_014, I got the following result:
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 final
field of a
primitive at declaration time, the value is inlined, if the
type is primitive or a String.
In JDK 1.3.x, Sun tightened up the access a bit, and prevented
us from modifying a final
field with
reflection. This was also the case with JDK 1.4.x. If we
tried running the FinalFieldChange class to rebind the
final
fields at runtime using
reflection, we would get:
java version "1.3.1_12": Exception thread "main" IllegalAccessException: field is final at java.lang.reflect.Field.set(Native Method) at FinalFieldChange.change(FinalFieldChange.java:8) at FinalFieldChange.main(FinalFieldChange.java:12) java version "1.4.2_05" Exception thread "main" IllegalAccessException: Field is final at java.lang.reflect.Field.set(Field.java:519) at FinalFieldChange.change(FinalFieldChange.java:8) at FinalFieldChange.main(FinalFieldChange.java:12)
Now we get to JDK 5.x. The FinalFieldChange class has the same output as 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 reflection,
I was hoping that a bug had crept into the JDK. However, we
both felt that to be unlikely, especially such a fundamental
bug. After some searching, I found the JSR-133:
Java Memory Model and Thread Specification.
Most of the specification is hard reading, and reminds me
of my university days (I used to write like that ;-)
However, JSR-133 is so important that it should be
required reading for all Java programmers. (Good
luck)
Start with chapter 9 Final Field Semantics, on page 25. Specifically, read
section 9.1.1 Post-Construction Modification of Final Fields.
It makes sense to allow updates to final
fields. For example, we could relax the requirement to have
fields non-final in JDO.
If we read section 9.1.1 carefully, we see that we should
only modify final
fields as part of our construction process.
The use case is where we deserialize an object, and then
once we have constructed the object, we initialise the final
fields, before passing it on. Once we have made the object
available to another thread, we should not change final
fields using reflection. The result would not be predictable.
It even says this: If a final field is initialized to a
compile-time constant in the field declaration, changes to
the final field may not be observed, since uses of that final
field are replaced at compile time with the compile-time
constant. This explains why our iq
field
stays the same, but country
changes.
Strangely, JDK 5 differs slightly from JDK 1.2.x, in that you
cannot modify a static final
field.
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(); } }
When we run this with JDK 1.2.x and JDK 5.x, we get the following output:
java version "1.2.2_014": stringValue = original value objValue = new Value java version "1.5.0" Exception thread "main" IllegalAccessException: Field is final at java.lang.reflect.Field.set(Field.java:656) at FinalStaticFieldChange.changeStaticField(12) at FinalStaticFieldChange.main(16)
So, JDK 5 is like JDK 1.2.x, just different?
Do you know when JDK 1.3.0 was released? I struggled to find out, so I downloaded and installed it. The readme.txt file has the date 2000/06/02 13:10. So, it is more than 4 years old (goodness me, it feels like yesterday). JDK 1.3.0 was released several months before I started writing The Java(tm) Specialists' Newsletter! I think it would be safe to say that very few Java developers can remember the details of pre-JDK1.3.0. Ahh, nostalgia isn't what it used to be! Do you remember running Java for the first time and getting this error: "Unable to initialize threads: cannot find class java/lang/Thread"?
I personally think it is risky to change something so fundamental to the language. I will read through that specification in detail to see what else has changed ...
Gotta get some beauty sleep now - tomorrow I need to work on an access control system in Java for a client.
Kind regards
Heinz
We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)
We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.