Again and again I find myself in situations where I want to do this:
class Outer
{
private static final Runnable Inner = new Runnable()
{
private static final String CONSTANT1 = "foo";
private static final int CONSTANT2 = CONSTANT1.length(); //'static' gives error
private static final int CONSTANT3 = f(); //'static' gives error
private static int f() //'static' gives error
{
return CONSTANT1.length();
}
@Override public void run() {}
};
private static final String CONSTANT1 = "foo";
private static final int CONSTANT2 = CONSTANT1.length(); //this is fine!
private static final int CONSTANT3 = f(); //this is fine!
private static int f() //this is fine!
{
return CONSTANT1.length();
}
}
In the editor of the IDE (IntelliJ IDEA) the error reads "Inner classes cannot have static declarations", which is misleading, because if that was the case, then CONSTANT1
would also have an error, but it does not.
Outside of the editor, the compiler reports the error as follows: java: Illegal static declaration in inner class <anonymous bla bla bla> modifier 'static' is only allowed in constant variable declarations
.
So, does anyone know the technical reasons behind this limitation imposed by the java compiler? Why is modifier 'static' only allowed in constant variable declarations in anonymous class instances? What is the difficulty with allowing 'static' in these cases?
Best Answer
Java tries to behave sensibly, but since it is a quite complex language, it does occasionally fail. You have found an interesting edge case where a guard against invalid behaviour creates a false negative.
Usually,
static
fields can be initialized with arbitrary expressions, as long as they do not reference instance variables.Inner classes belong to an instance of the outer class, not to the outer class itself. While the inner classes are logically distinct, they share the same physical class. This means that
static
fields are shared amongst all inner classes. This breaks the logical separation, unless thestatic
fields are constants (see JLS §8.1.3) and cannot be mutated, that is: they cannot be reassigned, and cannot change their value. I believe this restriction is sensible, given the implementation of inner classes.Reassignment can be prevented with
final
, but Java has no mechanism to track mutating and non-mutating operations. C++ has such a mechanism and can mark methods asconst
and evenconstexpr
for compile-time computable operations. All Java has is a concept of constant expressions (JLS §15.28): basically just literals and arithmetic.So an inner class can contain the constant
static final String CONSTANT1 = "foo"
. However,CONSTANT1.length()
is not a constant expression because you are invoking a method. In practice this should not be a problem because theString
class is immutable, but the JLS does not special-case theString
class. Likewise,f()
is not a constant expression.Now the really sad thing is that your
new Runnable() { ... }
inner class is created in a static context, so the logically-separate inner class problem does not exist. It would be reasonably possible for anonymous classes in a static context to be declaredstatic
, asstatic
inner classes do not have any of the restrictions of inner classes. However, the JLS requires that anonymous classes are never static (JLS §15.9.5).So a complex system such as Java has slight inconsistencies, which is annoying but not surprising. This inconsistency could be resolved by either tracking constant operations for all methods (which would be a huge change to the language), or by removing non-static inner classes, which IMHO would have been sensible because it is trivial to implement them yourself. However, that is not the Java we have.