When passing a final object (A String in the code below) it shows up as null when printed from the anonymous inner class. However, when a final value type or straight final String is passed in, its value is correctly displayed. What does final
really mean in context of the anonymous inner class and why is the object passed null?
public class WeirdInners
{
public class InnerThing
{
public InnerThing()
{
print();
}
public void print(){
}
}
public WeirdInners()
{
final String aString = "argh!".toString();
final String bString = "argh!";
System.out.println(aString);
System.out.println(bString);
InnerThing inner =new InnerThing(){
public void print()
{
System.out.println("inner"+aString); // When called from constructor, this value is null.
System.out.println("inner"+bString); // This value is correctly printed.
}
};
inner.print();
}
public static void main(String[] args)
{
WeirdInners test1 = new WeirdInners();
}
}
This is very strange behavior to me because the expectation is that the String is an object, why does calling toString()
change things?
Other info: this behavior is only observed using Java 1.4, not in Java 5. Any suggestions on a workaround? Not calling toString()
on an existing String is fair enough, but since this is only an example, it has real world implications if I perform it on a non-String object.
If you check the section on
compile-time constants
in the JLS, you'll see that calling.toString()
does make a difference. As does nonsense like prefixing withfalse?null+"":
.What is important here is the relative ordering of setting the fields that are closed over and the constructor. If you use
-target 1.4
or later (which is not the default in 1.4!) then the fields will be copied before calling the super. With the spec before 1.3 this was illegal bytecode.As is often the case in these cases,
javap -c
is useful to see what the javac compiler is doing. The spec is useful for understanding why (should you have sufficient patience).