This was originally a blog post on radio written by Corey. Since radio was decommissioned, it is being cross posted here.
This post is actually made by request. Someone came to me and asked me to cover forward referencing and the rules that go along with it. As a side note, I don't mind doing posts "on request." Feel free to drop me a line (you can reach me through JavaRanch - I'd suggest posting in the SCJP forum) and I'll see what I can do. I can't promise I'll cover all topics, but I'll do what I can. Anyway, that aside, here's my attempt at covering forward references.
First of all, let's discuss what a "forward reference" is and why it is bad. A forward reference is a reference to a variable that has not yet been initialized. These are bad simply because, if allowed, they'd give us unexpected results. Take a look at this bit of code:
What should j be when this class is initialized? When a class is initialized, initializations are executed in order from top to bottom. Therefore, you'd expect the line "j = i" to execute prior to "i = 5". So, what does that put into j? 5? Or something else?
Rather than try to determine what should happen in such a case, the designers of Java decided to disallow such a situation. If you try to compile this code, you'll get an error about an "illegal forward reference." Basically, the compiler is telling you that you can't use the value of i before that variable has been initialized.
So when do you have to worry about forward references? Well, the JLS points out 3 criteria that must be met before we have to worry about a forward reference. They are as follows (as listed in the JLS, §184.108.40.206 Restrictions on the use of Fields during Initialization):
The declaration of a member needs to appear before it is used only if the member is an instance (respectively static) field of a class or interface C and all of the following conditions hold:
The usage occurs in an instance (respectively static) variable initializer of C or in an instance (respectively static) initializer of C.
The usage is not on the left hand side of an assignment.
C is the innermost class or interface enclosing the usage.
If we try to apply those rules to the example above, this is what we find. First of all, we must see if the usage appears within an instance (or static) variable initializer. In my example, it clearly does. The reference to the variable i appears in the instance variable initlialzier for j. Okay, item two. Is the usage on the left hand side of an assignment? No - it's on the right hand side, so we pass test 2. Finally, is C, which would be ForwardRef, the innermost class enclosing this reference? Yes, it is. Therefore, we pass all three tests and we have to worry about forward references.
Let's look at a few cases where we wouldn't have to worry about forward references.
This code compiles and runs fine. Why? Well, we don't pass the first test. The usage of the reference is not in an instance initializer or instance member initializer. In this case, it's in the constructor. At this point, I think it's important to remember how objects are instantiated - more precisely, the order in which things are executed when we instantiate an object.
When we create an instance of an object, we first initialize the parent of the object we are instantiating. Once that is complete, we then execute any instance initializers and/or instance member initializers in order from top to bottom. When that is done, we execute the contents of the constructor. (You can find complete details about this process in the JLS, §12.5 Creation of New Class Instances.)
As you can see from that series of events, the instance member initializer "int i = 5;" is executed prior to the body of the constructor. Therefore, the variable i is safely initialized before we try to use its value. That's why we don't need to worry about forward references in such a case. Let's look at another case in which we need not worry about forward references.
Notice that I've used an instance initializer in which I reference the instance variable, i. Also, recall that instance initializers and instance member initializers are exuected in order from top to bottom. Therefore, the line "i = 10;" is going to be executed prior to "int i = 5;". Yet this does not produce an error. Why not?
Well, in this case, we're not trying to read from i, we're assigning a value to it. Therefore, there's no "dangerous" operation occurring here and the compiler allows it. Okay, one more case in which we don't have to worry about forward references.
In this case, our "forward" reference is embedded within the inner class, InnerForwardRef. This is perfectly legal because, by the time we can instantiate our inner class, we must, necessarily, have created an instance of its enclosing class. Therefore, a ForwardRef object would have been full initialized (including the instance member i) prior to the initialization of j within InnerForwardRef. If that doesn't make sense, just think about how we'd instantiate InnerForwardRef: "new ForwardRef().new InnerForwardRef();". We first create the instance of ForwardRef and then create the instance of InnerForwardRef.
Ok, so now that we've seen all the reasons behind the three criteria listed earlier, let's throw a wrench into things. Keep in mind that your compiler isn't a mind reader and it can do only so much. Take a look at this code:
What do you get from compiling and executing that code? A compiler error? 5? Nope, you get neither. This code compiles just fine and produces the output 0.
Yup - it spits out the number 0. Why? Well, this is a case in which we've "fooled" the compiler into performing a forward reference. The compiler will look for some simple cases of forward references, but it's not going to go digging through your methods to see if you're performing an illegal reference. That's up to you to watch out for.
So why zero? Well, all primitive data types have a default value of 0. When we start the initialization procedure for an object, we must allocate enough memory on the heap to hold that object. That memory includes place-holders for all of the instance members of that object. When those place-holders are created, they are originally filled with zeroes (or null for reference variables). That's why you get a 0. Based on this, you might see that it's rather dangerous to initialize member variables from method return values. Generally, it's not a big deal, but it's a case where the compiler gives you a little slack in the rope. Just be careful not to hang yourself. ;-)
Those are the basics when it comes to forward references. I'd recommend checking out the JLS, §220.127.116.11 Restrictions on the use of Fields during Initialization for more details. There's even a very elaborate example at the end of that section that covers lots of cases and shows which types of references are legal and which are not.
Until next time,
Re: Forward Referencing
Thanks a lot . THe article was very helpful.
Hi, Thanks for clarifying many doubts on forward referencing. But this is very confusing, for every situation there is exception. If method is returning static value as you described above it will return "0". We can assign values without declaring, but when we try to assign the same value to some other variable it will throw error. Is it not confusing?
I have a doubt. Consider the following example where inner class is declared as static and the variable we are trying to forward reference is also declared static. Here too, there is no compilation error, but this inner class can be instantiated without creating an object of the outer class.
Consider the following example where inner class is declared as static...but this inner class can be instantiated without creating an object of the outer class.
And "inner class" that is declared as static is not truly an inner class. Rather, it is a "nested" class. Look here for more details about inner classes vs. nested classes.
I had already gone thru this article and thats why this doubt surfaced. Consider the following code.
Reply | Permalink Re: Forward Referencing
As I pointed out above, the "inner" class that you have defined here isn't really an inner class, at all - it's a nested class. However, for your question, that doesn't really matter.
When your class, outer, is loaded, all static initializers are run sequentially, from top to bottom. Note that, when outer is loaded, inner is not - it's not loaded until it's needed and it's not needed until outer.main is executed, and that doesn't happen until outer is fully initialized.
Here's a slight modification to your application so that you can more easily see the execution path:
The output from that looks like this:
So, as you can see from that output, the outer class is fully initialized before the line "in = out" is ever executed. That's why you don't have any issues with forward referencing. I hope that helps.
That was a very good example. I am clear now. Thanks...