I've been toying with ASM, and I believe I succeeded in adding the final modifier to an instance field of a class; however I then proceeded to instantiate said class and invoke a setter on it, which successfully changed the value of the now-final field. Am I doing something wrong with my bytecode changes, or is final enforced only by the Java compiler?
Update: (31 Jul) Here's some code for you. The main parts are
private int x
and private final int y
,In other words, we start with a class with one final (x) and one non-final (y) field. We make x final. We make setX() set y in addition to setting x. We run. Both x and y get set with no errors. The code is on github. You can clone it with:
git clone git://github.com/zzantozz/testbed.git tmp cd tmp/asm-playground
Two things of note: The reason I asked this question in the first place: both a field that I made final and a field that was already final are able to be set with what I believe to be normal bytecode instructions.
Another update: (1 Aug) Tested with both 1.6.0_26-b03 and 1.7.0-b147 with the same results. That is, the JVM happily modifies final fields at runtime.
Final(?) update: (19 Sep) I'm removing the full source from this post because it was rather lengthy, but it's still available on github (see above).
I believe I've conclusively proven the JDK7 JVM is in violation of the specification. (See the excerpt in Stephen's answer.) After using ASM to modify bytecode as described previously, I wrote it back out to a class file. Using the excellent JD-GUI, this class file decompiles to the following code:
package rds.asm; import java.io.PrintStream; public class TestPojo { private final int x; private final int y; public TestPojo(int x) { this.x = x; this.y = 1; } public int getX() { return this.x; } public void setX(int x) { System.out.println("Inside setX()"); this.x = x; this.y = x; } public String toString() { return "TestPojo{x=" + this.x + ", y=" + this.y + '}'; } public static void main(String[] args) { TestPojo pojo = new TestPojo(10); System.out.println(pojo); pojo.setX(42); System.out.println(pojo); } }
A brief glance at that should tell you that class will never compile due to reassigning a final field, and yet running that class in plain vanilla JDK 6 or 7 looks like this:
$ java rds.asm.TestPojo TestPojo{x=10, y=1} Inside setX() TestPojo{x=42, y=42}
A final variable is called a blank final variable if it is not initialized while declaration. Below are the two ways to initialize a blank final variable. A blank final variable can be initialized inside an instance-initializer block or inside the constructor.
If we initialize a variable with the final keyword, then we cannot modify its value. If we declare a method as final, then it cannot be overridden by any subclasses. And, if we declare a class as final, we restrict the other classes to inherit or extend it.
In general, final methods are faster than non-final methods because they are not required to be resolved during run-time and they are bonded at compile-time, See these Java Performance courses for more details.
4. Effectively Final. The term effectively final variable was introduced in Java 8. A variable is effectively final if it isn't explicitly declared final but its value is never changed after initialization.
Is “final” final at runtime?
Not in the sense you mean.
AFAIK, the semantics of the final
modifier are only enforced by the bytecode compiler.
There are no special bytecodes for initializing final
fields, and the bytecode verifier (apparently) doesn't check for "illegal" assignments either.
However, the JIT compiler might treat the final
modifier as a hint that things don't need to be refetched. So, if your bytecodes modify a variable marked as final
you are liable to cause unpredictable behavior. (And the same thing can happen if you use reflection to modify a final
variable. The spec clearly says so ...)
And, of course, you can modify a final
field using reflection.
UPDATE
I took a look at the Java 7 JVM spec, and it partly contradicts what I said above. Specifically, the description of the PutField opcode says:
"Linking Exceptions ... Otherwise, if the field is final, it must be declared in the current class, and the instruction must occur in an instance initialization method (
<init>
) of the current class. Otherwise, anIllegalAccessError
is thrown.".
So, while you could (in theory) assign to a final
field multiple times in the object's constructor, the bytecode verifier should prevent any attempt to load a method that contains bytecode that assign to a final
. Which ... when you think about Java security sandboxes ... is a good thing.
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