13

I'm trying to compile a Java class which javac rejects with an illegal forward reference error, where the offending reference is lexically after the referenced field. The following class is stripped down as much as possible while showing the same behavior:

java.util.concurrent.Callable and the many uses of Object are just used as placeholders to remove irrelevant pieces of code.

public class Test {
    static final Object foo = method(new java.util.concurrent.Callable<Object>() {
        @Override
        public Object call() throws Exception {
            return bar;
        }
    });

    static final Object bar = foo;

    static Object method(Object binder) {
        return null;
    }
}

When compiled using javac Test.java, javac prints the following error message:

Test.java:9: illegal forward reference
    static final Object bar = foo;
                              ^

So the compiler complains about bar's declaration referencing foo while foo should be in the scope of bar's declaration. But as soon as the reference of bar in foo's declaration is removed, e.g. by changing line 5 from return bar; to return null;, the class is accepted by the compiler.

How can this be explained? Is my understanding of forward as meaning lexically after wrong or is this some special case I'm not aware of?

Adam Zalcman
  • 26,643
  • 4
  • 71
  • 92
Feuermurmel
  • 9,490
  • 10
  • 60
  • 90
  • 1
    I can confirm that javac from Sun's JDK 1.6.0_29 rejects this as described; Eclipse Indigo compiles this without a complaint. You have found a bug in either javac or Eclipse - congratulations! – Tom Anderson Nov 22 '11 at 22:45
  • Note that you can remove the method `method`, and assign the new `Callable` directly to `foo`, and get the same error. – Tom Anderson Nov 22 '11 at 22:47
  • I think this is a bug in javac, although i'm not enough of a language lawyer to be certain. I note that you can work around it by writing `bar = Test.foo`. – Tom Anderson Nov 22 '11 at 23:02
  • gcj 4.4.3 also compiles this. – Adam Zalcman Nov 23 '11 at 00:24

2 Answers2

16

Your understanding of forward reference is correct. The reference to foo on line 9 isn't a forward reference at all since it doesn't appear textually before its declaration (see the definition of what constitutes a forward reference in section 8.3.2.3 of The Java Language Specification).

The behavior you observe is a symptom of a javac bug. See this bug report. The problem appears to be fixed in newer versions of the compiler, e.g. OpenJDK 7.

It only affects forward references used as initializers to final fields. The issue appears to affect static and non-static fields equally.

Note that the reference to bar in call() is a legal forward reference since it occurs inside a different class (see examples in section 8.3.2.3 of The Java Language Specification).

Also, note that each of the following alterations make the error disappear:

Making bar non-final:

static Object bar = foo;

Initializing bar in static or instance initializer block:

static final Object bar;

static {
  bar = foo;
}

Moving the initialization of foo to an initializer block also helps.

Initializing bar from a non-final temporary reference to foo:

static Object tmp = foo;
static final Object bar = tmp;

Initializing bar with Test.foo (found by Tom Anderson) or with this.foo in non-static case:

static final Object bar = Test.foo;

Removing bar and referring to the object using foo inside call():

static final Object foo = method(new java.util.concurrent.Callable<Object>() {
    @Override
    public Object call() throws Exception {
        return foo;
    }   
});
Adam Zalcman
  • 26,643
  • 4
  • 71
  • 92
2

The Java Language Specifications specifically mentions restrictions on object fields during the initializations phase, specifically (C is an interface or class):

A compile-time error for forward references occurs when these conditions are fulfilled:

  • 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.

The article What are the forward reference rules? contains an excellent explanation of the rules and the restrictions when it comes to initializing members and forward referencing.

Paŭlo Ebermann
  • 73,284
  • 20
  • 146
  • 210
Mechkov
  • 4,294
  • 1
  • 17
  • 25
  • The relevant passage of the JLS is [8.3.2.3 Restrictions on the use of Fields during Initialization](http://java.sun.com/docs/books/jls/third_edition/html/classes.html#287406). – Tom Anderson Nov 22 '11 at 22:55
  • 1
    I fail to see how that answers the question? For the assignment `bar = foo` these conditions are met, which means that `foo` must be declared textually before `bar` - which it is. On the other hand, for the usage `return bar`, the last condition is not met, and therefore no constraint imposed on the order of declaration. – meriton Nov 22 '11 at 23:54