Java Language Design – Why ‘static’ Modifier Only Allowed in Constant Variable Declarations?

javalanguage-design

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 the static 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 as const and even constexpr 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 the String class is immutable, but the JLS does not special-case the String 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 declared static, as static 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.