I was modifying some legacy code (defined as code with no automated tests) and found something unexpected when I tried to use it. The original code was rather complicated (possibly because there were no tests), but this simplified version has the same odd behavior:
public class Test {
static {
initName();
}
static String name = "Sue";
static Test instance = new Test();
public static String getName() {
return name;
}
private static void initName() {
name = "Bond, James Bond";
}
public static void main(String[] args) {
System.out.println("My name is " +
Test.getName() + ".");
}
}
So we have a class with one static variable, three static methods and one static initializer. Other than the static initializer, it looks like a pretty simple class that's easy to understand doesn’t it?
In case you’re not familiar with static initializers, let me paraphrase a bit of Sun’s Java Tutorial: "A static initialization block is a normal block of code enclosed in braces, { }, and preceded by the static keyword. They can appear anywhere in the class body, and the Java runtime guarantees they are called in the order in which they appear in the source code."
That sounds kind of cool, but what’s the point? Why would you use one? Well, a static initializer executes when the class is loaded into memory and it is normally used to do some kind of one-time processing for the class. In this case, the initializer calls the initName() method when the Test class gets loaded into memory and before it’s main method executes.
So, getting back to the puzzle, the question is this: What does the above code do when you run the class?
Here are the possible answers:
A) It prints “My name is Sue.”
B) It prints “My name is Bond, James Bond.”
C) Nothing, because it doesn’t compile.
D) None of the above.
I recommend taking some time to figure out your own answer before reading any further. You’ll get more out of this is you do. Don’t worry, I’ll don’t mind the wait.
Ready to look at the answers? Good. First things first. Answer C, "Nothing, because it doesn't compile" is not correct. I included it just so I can tell you that I don’t like that kind of answer on a puzzle, or Java certification exam for that matter. Any IDE worth using will tell you if the code will compile or not. While it may have been a useful skill years a go, when our only tools were stone knives and bearskins, we’ve had better tools for so long now that it seems unreasonable to ask a question where “it won’t compile” is the right answer. OK, back to the puzzle.
What about answer B? Well, if you’ve attended one of Josh Bloch’s Java Puzzlers talks (or read my earlier Mini Puzzler), you know the whole point of this is to show you code that looks simple but behaves strangely. So you probably didn’t pick answer B - even though it looks like that’s exactly what should happen. And you would be right in doing so. The code doesn’t print “My name is Bond, James Bond.”
What about the ever popular “None of the above.” Sorry, not this time. Answer D is not correct either.
By the process of elimination, the correct answer must be A. It prints “My name is Sue.” and, as it turns out, that is just what does happen. Don't trust me on this, run it yourself - then come back for the explanation.
When I ran the original test code, I didn’t believe the results. It didn’t make any sense so I ran it in the debugger. If you do the same, you can see that the initName() method does get called before the main() method, and that when initName() finishes, the value of “name” is “Bond, James Bond” - just as we expected.
So what’s going on? How does “name” get set to “Sue”? Well, to mis-quote the spirit of Obi-Wan, “Use the source, Luke.”
In looking at the source, you can see that right after the static initializer, we’re declaring and initializing the “name” variable. Which seems odd because the initName() method already set “name” to a value - so it must already exist, right?
Sort of. The compiler sets aside space for the variable and includes code to initialize it but, because “name” is a static variable, its initialization happens when the class is loaded into memory instead of when the class constructor is called. It also appears that, like a static initializer, static variable initialization also happens “in the order in which it appears in the source code.” So after the static initializer is called, the code that initializes “name” to “Sue” executes and overwrites the value we expected to see. Believe it, or not.
So, how do we fix it? Simple. Swap the two lines so the variable is declared and initialized before the static initializer block gets called. Actually, there’s an even better solution for this kind of situation. Since the static initializer is only determining the value for one variable, have the line that declares the variable call a private static method that returns the variable’s initial value. Doing so would’ve prevented this problem in the first place, and ensures that nobody can accidently re-order the lines and start wondering why they’re seeing a line from a Johnny Cash song instead of something Sean Connery ought to be saying.
If you’ve got any feedback on this please post a comment.
Thanks for reading,
Burk
But if you run it in the GroovyConsole it outputs... My name is Bond, James Bond. I wonder how many people have ported their Java code to Groovy and now it's acting strangely.
ReplyDeleteAndy,
ReplyDeleteVery interesting. I hadn't thought to try it in Groovy but now you've got me wondering...
Thanks for the comment,
Burk
Im glad to see your making progress learning java. Post an article when you have 10 more years experience.
ReplyDeleteAnonymous:
ReplyDeleteI'M glad to see YOU'RE making progress learning English. Post a comment when you have 10 more YEARS' experience.
So can we conclude that member variable initializations behave as if they were static (or instance) block initializers, running in the same sequence as the explicit initialization blocks? That would explain the behavior you demonstrated.
ReplyDeletedovwas,
ReplyDeleteThanks for the question, it got me to do the legwork to find out what's supposed to be going on so I could give you an authoritative answer.
Chapter 12 of the Java Language Specification (3rd edition), covers "Execution" and section 4 looks at the initialization process. The 5th paragraph of section 12.4.1 says, "The static initializers and class variable initializers are executed in textual order, and may not refer to class variables declared in the class whose declarations appear textually after the use, even though these class variables are in scope (§8.3.2.3)."
So, the answer to your question is "Yes". According to the Java Language Spec, static initializers and static variable initializers both occur in the order specified by the source code.
Now the question is what's up with Groovy? Does it really behave differently, and if so, why?