6

I have this code

private static Set<String> myField;

static {
    myField = new HashSet<String>();
    myField.add("test");
}

and it works. But when I flip the order, I get an illegal forward reference error.

static {
    myField = new HashSet<String>();
    myField.add("test"); // illegal forward reference
}

private static Set<String> myField;

I'm a little bit shocked, I didn't expect something like this from Java. :)

What happens here? Why is the order of declarations important? Why does the assignment work but not the method call?

Daniel Rikowski
  • 71,375
  • 57
  • 251
  • 329

6 Answers6

10

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, and it is not confined only to static initalizers. These are bad simply because, if allowed, they'd give us unexpected results. Take a look at this bit of code:

public class ForwardRef {
    int a = b; // <--- Illegal forward reference
    int b = 10;
}

What should j be when this class is initialized? When a class is initialized, initializations are executed in order the first to the last encountered. Therefore, you'd expect the line

a = b; 

to execute prior to:

b = 10; 

In order to avoid this kind of problems, Java designers completely disallowed such uses of forward references.

EDIT

this behaviour is specified by section 8.3.2.3 of Java Language Specifications:

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.

A compile-time error occurs if any of the three requirements above are not met.

dfa
  • 114,442
  • 31
  • 189
  • 228
  • OK, I understand. But after the initial assignment myField *is* initialized. Why can't I still not call the add method? – Daniel Rikowski Jul 27 '09 at 10:34
  • If those three requirements were not there, I could create an implicit forward reference using local variables in the initializer, right? Is that the reason for these restrictions? – Daniel Rikowski Jul 27 '09 at 12:04
  • ths JLS says: "...These restrictions are designed to catch, at compile time, circular or otherwise malformed initializations...." – user85421 Jul 27 '09 at 12:42
  • 2
    @dfa: You are quoting JLS 2.0. In JLS 3.0, this section is expressed much more clearly: http://java.sun.com/docs/books/jls/third_edition/html/j3TOC.html – Stephen C Jul 27 '09 at 13:44
2

try this:

class YourClass {
    static {
        myField = new HashSet<String>();
        YourClass.myField.add("test");
    }

    private static Set<String> myField;
}

it should compile without errors according the JLS...
(don't really help, or?)

Daniel Rikowski
  • 71,375
  • 57
  • 251
  • 329
user85421
  • 28,957
  • 10
  • 64
  • 87
  • I haven't tested this, but this still violates the "left-hand rule" in the JLS. See my answer below for why. – rtperson Jul 27 '09 at 13:42
  • 1
    @DR: this is a compiler feature. Reflection can be used to circumvent many compiler checks, e.g. calling a private method from outside the class. – Robert Munteanu Jul 27 '09 at 14:30
  • Wow. +1, and a tip of the hat, to Carlos for getting this one right. – rtperson Jul 27 '09 at 14:52
  • @rtperson: it is also a *violation* of the 3rd rule: "usage is via a simple name". IMO the JLS is *difficult* to read: "The declaration of a member needs to appear textually before it is used only if ... and all of the following conditions hold...", but the JLS also gives that example "int z = UseBeforeDeclaration.x * 2; // ok - not accessed via simple name" – user85421 Jul 27 '09 at 15:24
  • 1
    @Carlos: The JLS is difficult, but I get it now. The intro to Chapter 6 distinguishes between simple names (i.e., just the name of the variable and the method -- myField.add()) and the qualified name (i.e., YourClass.myField.add()). The forward-reference error is thrown only if *all* the rules in 8.2.3.2 are broken, so changing the name from simple to qualified allows it to work. They added the simple name rule in the third edition of the JLS, so it makes sense that the mechanism here is via reflection. Very interesting. – rtperson Jul 27 '09 at 23:00
1

In Java, all initializers, static or otherwise, are evaluated in the order in which they appear in the class definition.

skaffman
  • 398,947
  • 96
  • 818
  • 769
1

See the rules for forward references in the JLS. You cannot use forward references if:

  • 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.
  • The usage is via a simple name.
  • C is the innermost class or interface enclosing the usage.

Since all of these hold for your example, the forward reference is illegal.

Andrzej Doyle
  • 102,507
  • 33
  • 189
  • 228
1

To elaborate on DFA's answer:

I think what's tripping you up is the "left hand side" rule in the second bullet point in JLS 8.2.3.2. In your initialization, myField is on the left-hand side. In your call to add, it's on the right-hand side. The code here is implicitly:

boolean result = myField.add('test')  

You're not evaluating the result, but the compiler still acts as if it's there. That's why your initialization passes while your call to add() fails.

As for why this is so, I have no idea. It may well be for the convenience of the JVM developers, for all I know.

rtperson
  • 11,632
  • 4
  • 29
  • 36
  • Well, no, for exactly the same reason. A call that returns void is still on the right-hand side, since you're not assigning into it. You've merely told the compiler that the function doesn't have any output, but that doesn't change that it is a function. – rtperson Jul 27 '09 at 14:50
0

I think the method call is problematic because the compiler cannot determine which add() method to use without a reference type for myField.

At runtime, the method used will be determined by the object type, but the compiler only knows about the reference type.

Eric Wilson
  • 57,719
  • 77
  • 200
  • 270
  • I don't think that's the problem, because with virtual methods the compiler almost never can bind the method at compile time. That's where the VMT is used: http://en.wikipedia.org/wiki/Virtual_method_table – Daniel Rikowski Jul 27 '09 at 10:49