Thursday, September 17, 2009

Watch your Java compiler

So, scenario #1 is like:


class A {
public static final String x = "foo";
public static String y = "bar";
}

class B {
public static void main(String[] args) {
System.out.println(A.x);
System.out.println(A.y);
}
}


You compile both classes and run B - you get an output as follows:
foo
bar

And then you change A thusly:


class A {
public static final String x = "baz";
public static String y = "qux";
}


and compile only A. Then run B. The output?
foo
qux

What just happened? The compiler is in-lining the static final x, but not y. Well, that comes from the Java language spec.

Scenario #2:


public abstract class A {
public A() {
System.out.println(bar().getClass());
}
public void foo() {
// do something
}
public abstract String bar();
}

public class B extends A {
final String value = getBar();
public String bar() {
return value;
}
private String getBar() {
return "barvalue";
}
public static void main(String[] args) {
System.out.println(new B().bar());
}
}

// invoke it
new B().foo();


Throws NullPointerException, but why? If you decompile B.class, you will find:


public class B extends A {
String value = null;
public B() {
super();
value = getBar();
}
public String bar() {
return value;
}
private String getBar() {
return "barvalue";
}
public static void main(String[] args) {
System.out.println(new B().bar());
}
}


So as it turns out, "final" variable is a compiler trick, as are keywords like "private" and "protected". You better be watching your Java compiler.

9 comments:

  1. Okay, wow I just tested it (same result) and that basically means that you can never replace a library jar (think bug fix) containing a constant in your app w/o recompiling all classes that might use it. That's a little scary actually.

    ReplyDelete
  2. I have never thought of this compiler implications on final variables. Very interesting post...

    Thanks,
    Senthil Kumar
    http://www.senthilb.com

    ReplyDelete
  3. Indeed it is a very interesting post, but I'm not sure that everybody understands it, even experienced Java programmers may not unterstand these cases.

    But it illustrates very well some implications of the specs.

    ReplyDelete
  4. When you say give me new B(), then JVM go to the B constructor, see that there is a parent class, go there (please, note, than value of value field is null right now!). Then from A constructor you are calling bar() method and, because it's fair, it returns the value of the value field. But it is null right now.
    It can happen because values of class fields will be setted up only when parent constructor will finish, but before its own constructor starts.
    So, just be careful =)

    ReplyDelete
  5. Jean,

    That is what I am trying to emphasize in this post - "be careful". :-) "final" is nothing but a compiler trick to push the initialization after the super() call. Similarly, "private", "protected" are also merely compiler tricks. In Groovy (and other JVM languages too, I guess) you can use any field irrespective of whether it is private or protected. When you understand how the JVM works, then these things explain themselves quite easily.

    ReplyDelete
  6. Well, the last example has nothing to do with final, from what I can see. The result ( NPE ) would have been the same had you removed the final keyword.

    Did you expect final to somehow mitigate the issue of using a data member before its initialization?

    ReplyDelete
  7. Zen,

    You are right - final has nothing to do with NPE. What I really wanted to highlight is that a final data member is not really a purely immutable reference, independent of the constructor. Rather it is a compiler trick to ensure the semantics.

    ReplyDelete
  8. I really don't understand why in the 2nd example you attribute the program's behaviour to the "final" modifier calling it a "compiler trick".

    This is merely an example of why you should NEVER invoke non-final methods from a constructor.

    --
    Salman Ahmed

    ReplyDelete
  9. Salman Ahmed,

    As I have mentioned before, I am trying to highlight that after the source is compiled as bytecode "final" is nowhere -- which confirms that "final" is merely a compiler trick and not supported natively by the JVM.

    > ...why you should NEVER invoke non-final methods from a constructor.

    This is a corollary to the impedance mismatch between Java semantics and the JVM -- you are right. However, in some cases (example: extensibility with servlets) it may not be avoidable.

    ReplyDelete

Disqus for Char Sequence