Why Isn't a Qualified Static Final Variable Allowed in a Static Initialization Block

Why isn't a qualified static final variable allowed in a static initialization block?

The JLS holds the answer (note the bold statement):

Similarly, every blank final variable must be assigned at most once; it must be definitely unassigned when an assignment to it occurs. Such an assignment is defined to occur if and only if either the simple name of the variable (or, for a field, its simple name qualified by this) occurs on the left hand side of an assignment operator. [§16]

This means that the 'simple name' must be used when assigning static final variables - i.e. the var name without any qualifiers.

Static final field initialization from static initializer

In order to initialise a final variable in a initialization block, the simple name of the variable should be use. i.e. the variable name alone with out any qualifiers.

It is stated on the java language specification as below

"Similarly, every blank final variable must be assigned at most once; it must be definitely unassigned when an assignment to it occurs. Such an assignment is defined to occur if and only if either the simple name of the variable, or its simple name qualified by this, occurs on the left hand side of an assignment operator. A Java compiler must carry out a specific conservative flow analysis to make sure that, for every assignment to a blank final variable, the variable is definitely unassigned before the assignment; otherwise a compile-time error must occur."

Initializing static final variables in java

Specified by the rules for Definite Assignment:

Let C be a class, and let V be a blank static final member field of C,
declared in C. Then:

  • V is definitely unassigned (and moreover is not definitely assigned) before the leftmost enum constant, static initializer
    (§8.7), or static variable initializer of C.

  • V is [un]assigned before an enum constant, static initializer, or static variable initializer of C other than the leftmost iff V is
    [un]assigned after the preceding enum constant, static initializer, or
    static variable initializer of C.

In layman's terms:

  • Using a static initializer to initialize a static final field by referencing its simple name is OK since that field is definitely assigned after the initializer. Effectively, there's context into the class in which the static initializer is declared in, and you are not performing any illegal assignment by referring to the field by its simple name; instead, you're fulfilling the requirement that the field must be definitely assigned.

  • Using a static initializer to initialize a static final field by referencing its qualified name is illegal, since the class must be initialized when referring to a static property of it (in your case, Test.A must be initialized prior, and A is assigned the default value of null upon completion of initialization).

Why Java wouldn't allow initialisation of static final variable (e.g. static final int d) in constructor?

A static variable is shared by all instances of the class, so each time to create an instance of your class, the same variable will be assigned again. Since it is final, it can only be assigned once. Therefore it is not allowed.

static final variables should be guaranteed to be assigned just once. Therefore they can be assigned either in the same expression in which they are declared, or in a static initializer block, which is only executed once.

Final static variable initialized in static block using method that throws exception

Basically, you should use a local variable:

BufferedImage localImage = null;
try
{
localImage = ImageIo.read(...);
}
catch (IOException e)
{
// No need to set a value, given that it's already going to be null.
// You probably want to log though.
}
connectionImage = localImage;

Now you know for sure that your connectionImage variable will be assigned exactly once.

I would then use a conditional operator for width, which makes it clearer that there's exactly one assignment:

width = connectionImage != null ? connectionImage.getWidth() : 0;

Static final variable along with static initializer block

My guess is that your field is of either a primitive type or String, and is initialized with a compile-time constant expression.

For static final fields initialized with a constant expression (and only such fields) - any code which refers to the field will have the constant value baked into it, rather than going via the static field which would cause class initialization. The "constant expression" part is important though. We can see this with a small test app:

class Fields {

public static final String CONSTANT = "Constant";
public static final String NON_CONSTANT = new String("Non-constant");

static {
System.out.println("Initializing");
}
}

public class Test {
public static void main(String arg[]) {
System.out.println(Fields.CONSTANT);
System.out.println(Fields.NON_CONSTANT);
}
}

The output is:

Constant
Initializing
Non-constant

Accessing the constant field does not require initialization, but accessing the non-constant one does. Using a non-final field would have the same effect: it would no longer count as a constant, basically.

The information about "this is a constant" gets baked into the class declaring a field. For example, using javap -c Fields we see the two fields:

public static final java.lang.String CONSTANT;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: String Constant

public static final java.lang.String NON_CONSTANT;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL

Note the ConstantValue part of the CONSTANT field metadata, which is missing from the NON_CONSTANT field metadata.

See section 15.28 of the JLS for more on what constitutes a constant expression.

Section 12.4.1 of the JLS specifies when a class is initialized:

A class or interface type T will be initialized immediately before the first occurrence of any one of the following:

  • T is a class and an instance of T is created.

  • T is a class and a static method declared by T is invoked.

  • A static field declared by T is assigned.

  • A static field declared by T is used and the field is not a constant variable (§4.12.4).

  • T is a top level class (§7.6), and an assert statement (§14.10) lexically nested within T (§8.1.3) is executed.

(Emphasis mine.)

Why static initializer allow re-initialization of static variable in Java?

As long as you manipulate the static variable within the class it is initialized you won't get an error from the compiler. If you had declared it as final the behavior you mentioned would have occurred. Keep in mind that static means that the variable is shared across all instances of the object, so it only gets initialized and memory allocated once.

why static block cannot access the static variable defined after it

After some further reading, I think Pavel is not quite accurate in this point as @Holger pointed out in the comment.

we have initialized i variable twice, which is not possible, since the field is static.

As the 12.4.2. Detailed Initialization Procedure points out

For each class or interface C, there is a unique initialization lock LC. The mapping from C to LC is left to the discretion of the Java Virtual Machine implementation.

I suppose the initialization twice is okay to the class initializer itself as long as it's just once to the invoking clients.

But the demos Pavel provided still stands its position so I basically just reuse it here but with different explanation.

static {
i = 2;
j = i + 5;
// no one knows whether "i" here initialized properly here
}
static int i = 10;
static int j;

But when you use MyClass.i directly in j = MyClass.i + 5, the compiler would know it would then be okay as 8.3.3. Forward References During Field Initialization detailed with four conditions.

Specifically, it is a compile-time error if all of the following are
true:

  1. The declaration of a class variable in a class or interface C appears
    textually after a use of the class variable;

  2. The use is a simple name in either a class variable initializer of C
    or a static initializer of C;

  3. The use is not on the left hand side of an assignment;

  4. C is the innermost class or interface enclosing the use.

And there is a detailed discussion in this answer already.

To wind it up, I think this would be for predictable behavior to add these restrictions. Once again, the other official purpose pointed out in 8.3.3. Forward References During Field Initialization.

These restrictions are designed to catch, at compile time, circular or otherwise malformed initializations.



Related Topics



Leave a reply



Submit