Problems with object creation

Every programming language has tricky details that you need to be aware of. This article will look at several such issues in Java related to the Java compiler. Test your Java skills and see if you know the answer!

The problem

A while ago a colleague of mine discovered a weird behavior when writing some integration tests. He fi rst noticed that the test worked in JDeveloper, but not in Eclipse. Then he noticed that he could make a small change to the test setup and the test would pass. Another small change and it would fail again. He got some help and managed to reduce the problem to the following:

Original code

This test fails in some environments and works in others. When the test fails it is
because result is null. How can this be? But it gets even stranger: By changing how
the value is initialized the test always pass:

Using a String constant

This test works for all environments. Why does the initialization of value affect
result? Very strange indeed!
Finally, by changing the type of the value we are back to the situation where the test
case fails in some environments, but not all.

Using Object instead of String

Challenge

Here are some challenges for you! Try to solve them before reading the solution!

  • Explain the difference between the middle test case and the other two (easy).
  • Explain why the initial test case fails in some environments and works in others (hard).
  • Find the bad design that might cause problems (and indeed does in this case!).

I can give you a hint. The differences are both related to the Java compiler. Don’t
cheat! You should at least be able to answer the irst question before reading on.

Solution

There are several things going on here. I’ll start by explaining what is going on when
we change the variable named ”value” and then I’ll look more closely at anonymous
inner classes. Finally I will describe the problem with the design.

Constant variablConstant variables

Here are the three cases again:

According to the Java Language Specifi cation (JLS) ”We call a variable, of primitive
type or type String, that is fi nal and initialized with a compile-time constant expres-
sion (§15.28) a constant variable”. Lets go through each variable:

  • ”value1” is not a constant since it does not have a compile-time constant expres-
    sion! That is, the compiler does not know what the String constructor is doing and
    therefore assumes that this needs to be evaluated at runtime.

  • ”value2” is a constant variable since it is clearly of type String and also initialized with a compile-time constant value.
  • ”value3” is not a constant since it is not a primitive type or a String. Yes, the object it refers to is a String, but the variable itself is an Object.

This means that only the middle case is a constant. When the compiler spots a
constant it automatically replaces all references with the constant value, ie in the
compiled class a constant variable is never referenced! The result is that the next
problem I will describe does not occur and the test case works.

Anonymous inner classes

As you know an inner class is able to access not only fi elds in the creating instance
but also fi nal variables in the method that creates the class. Did you ever wonder
how this magic is done? Maybe you have also noticed that it is not possible for an
anonymous class to have a constructor. When you create an anonymous inner class
the compiler actually creates the constructor for you. Let’s decompile the original
version and see what is going on.

Decompiled the original version (using JAD and javap)

Now it is easy to see that there is no magic going on! The compiler has simply added
a constructor and two instance variables. The set method is not using the fi nal vari-
able from the method, instead it is simply using its own reference to the value.
Well, this is all very nice, but why did the test case fail in some environments?
Notice how the generated constructor fi rst initializes the members and then calls super. This is normally not allowed in Java, but consider what would happen if the
compiler didn’t work this way. MyObject constructor would be called, which then
calls set and tries to use val$value which has not been initialized yet. Because of a
bug in the compiler this is exactly what happened in Java compilers before release
1.4.
OK, so if this problem is fi xed in 1.4 and later, why should you care? Take a look
at the next problem!

Bad design

The problem with the design is that the base class MyObject is calling methods in the
anonymous subclass before the subclass instance variables have been created. Unfor-
tunately this is not only a problem with anonymous classes, but a problem in general.
Take a look at the following test case and see if you know what will happen.

The test case will fail at the second assertEquals. Why? Two things are happening.
First of all ”fi nalField” is a constant variable and the compiler automatically uses 5
instead of referencing the variable, see above. Secondly, the constructor for Base is
called before the fi elds of Sub are initialized! Things are actually happening in this
order:

1. The fields in Base are initialized
2. Base constructor is run
3. The fi elds in Sub are initialized
4. Sub constructor is run

This is not a bug! This is exactly according to JLS Ӥ12.5 Creation of New Class
Instances”. The following is perhaps even more surprising:

This test case also fails because after Base and doStuff have been called, Sub is ini-
tialized and the normalField is assigned to 5. This is possible to solve by not initial-
izing normalField.

This works as expected. However, try to avoid constructions like this as it is very
easy to forget that the fi elds might not be initialized yet. There are actually more
problems with this construction as it might affect thread safety. For instance if the
subclass starts a thread in the overridden method this thread might be given access
to the uninitialized object. Brian Goetz (author of ”Java Concurrency in Practice”)
goes as far as calling this ”not properly constructed”.

Lessons learned

  • Make sure you understand constant variables and how they are used by the compiler
  • Use a recent version of Java compiler. Sun is constantly adding improvements and
    fixing bugs.

  • Avoid calling non fi nal methods from the constructor. If you have to, be aware
    that the object might not be initialized yet.

  • Do not perform unnecessary initialization of fields.

Originally published in JayView.

Leave a Reply

Close Menu