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?
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
incall()
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:Initializing
bar
in static or instance initializer block:Moving the initialization of
foo
to an initializer block also helps.Initializing
bar
from a non-final temporary reference tofoo
:Initializing
bar
withTest.foo
(found by Tom Anderson) or withthis.foo
in non-static case:Removing
bar
and referring to the object usingfoo
insidecall()
: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 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.