Here's a little Java trivia....
While building some GUI screens for an Eclipse RCP application, I came across what I consider to be strange behavior in the Java language. Instead of trying to explain it, take a look at the below code:
Parent.java
================================================
public abstract class Parent
{
public Parent()
{
setVarAValue();
}
public static void main( String args[] )
{
Child child = new Child();
System.out.println( "varA = [" + child.getVarAValue() + "]" );
}
protected abstract void setVarAValue();
protected abstract String getVarAValue();
}
class Child extends Parent
{
private String varA = null;
public Child()
{
super();
}
public String getVarAValue()
{
return varA;
}
protected void setVarAValue()
{
varA = "a value";
}
}
================================================
What do you think is printed out by the System.out.println statement? And don't run it to figure this out (that would be cheating!); look the code over and really try to figure it out. Step through the lines in the order they'll be executed. It's pretty simple, right? Well, if you want to see what is printed out, click on the Show Answer link below.
Don't believe me? Run it! (I ran it using the latest version of java - 1.5.0_07.) So, what's setting varA to null? The answer is the first line in the Child class: "private String varA = null;". Which to me, is very strange because varA is being used in the setVarAValue() method and then afterwards, it is being "initialized" to null. Of course, a simple fix for this problem is to remove the "= null" from the varA declaration. However, if you ask me, someone at Sun really messed this one up.
Does anybody else think this is weird behavior? Let me know what you think.
Comments
Craig, thanks for the feedback and I'm glad to see you're reading the blog! That's an excellent point you noted concerning the byte code and the initialization occurring during the constructor - which is something I didn't realize and until now, have ever ran into any problems because of it. Just to verify what you stated about the constructor is correct, I decompiled the Child class using cavaj and got the following (and as you can see, you were right on the mark):
One clarification however concerning my statements: I was not trying to say that I believed this behavior was a bug in the Java language and that it violated any Java specs. My point (to borrow Matt's phrasing) was its not intuitive behavior - which is inconsistent with the mostly intuitive Java language. I'm not saying you can't write confusing Java code because you can; however, generally speaking, Java is more intuitive than other languages (or maybe I'm just biased because it's the language I like the most?).
Mr. PMD (aka Matt Harrah), very good point on the class design and I will definitely look into re-orging the method calls - there shouldn't be functionality affected as all that's really needed is an extra method in the Parent class like initialize().
Thanks for the feedback!
Both of you are right in my opinion. Craig is right in that the JLS does state the code should behave as it does, and this fact is borne out by the actual behaviour exhibited. And Richard is right in that the behavior is weird and does not do what seems intuitive or obvious -- it violates the "principle of least surprise."
What I see as the main problem here, however, is the design and interplay of the classes and how they are initialized -- particularly the fact that the superclass' constructor calls abstract or overridable methods. Calling overridable methods during constructors is generally a dangerous proposition. PMD even has a check for this. Their explanation for this check states:
A better solution would be to avoid the confusion entirely and rethink the way your class hierarchy is set up and how the instances are created. Creational patterns like factory and builder may be better suited here.
The behavior you've demonstrated is exactly what you should expect Java to do in this instance, and it's documented in the Java Language Specification, section 12.5.
The short form is that when creating an instance of an object, the constructor will make any implicit or explicit calls to super class constructors first, then do any class-specific initialization, and then finally do any specific statements in the constructor.
In your example, "private String varA = null;" is actually interpreted by the compiler as two separate statements: (1) "private String varA;" which is scoped to the class, and (2) "varA = null;" which is scoped to *each* constructor for the class.
As a side note, all of byte code for all of the default initializations will be included in each constructor for a given class. This becomes important to understand when you start measuring code coverage of unit tests where you're testing classes with multiple constructors.
Post new comment