30

For following code:

public class StaticFinal
{
    private final static int i ;
    public StaticFinal()
    {}
}

I get compile time error:

StaticFinal.java:7: variable i might not have been initialized
        {}
         ^
1 error

Which is in accordance with JLS8.3.1.2 , which says that:

It is a compile-time error if a blank final (§4.12.4) class variable is not definitely assigned (§16.8) by a static initializer (§8.7) of the class in which it is declared.

So , the above error is completely understood.
But now consider the following :

public class StaticFinal
{
    private final static int i ;
    public StaticFinal()throws InstantiationException
    {
        throw new InstantiationException("Can't instantiate"); // Don't let the constructor to complete.
    }
}

Here, the constructor is never finished because InstantiationException is thrown in the middle of constructor. And this code compiles fine!!
Why is it? Why this code is not showing compile time error about the non initialization of final variable i ?


EDIT
I am compiling it using javac 1.6.0_25 on command prompt ( Not using any IDE )

Vishal K
  • 12,976
  • 2
  • 27
  • 38

4 Answers4

3

Interestingly enough, the code will compile whether or not the field is marked static - and in IntelliJ, it will complain (but compile) with the static field, and not say a word with the non-static field.

You're right in that JLS §8.1.3.2 has certain rules regarding [static] final fields. However, there's a few other rules around final fields that play a large role here, coming from the Java Language Specification §4.12.4 - which specify the compilation semantics of a final field.

But before we can get into that ball of wax, we need to determine what happens when we see throws - which is given to us by §14.18, emphasis mine:

A throw statement causes an exception (§11) to be thrown. The result is an immediate transfer of control (§11.3) that may exit multiple statements and multiple constructor, instance initializer, static initializer and field initializer evaluations, and method invocations until a try statement (§14.20) is found that catches the thrown value. If no such try statement is found, then execution of the thread (§17) that executed the throw is terminated (§11.3) after invocation of the uncaughtException method for the thread group to which the thread belongs.

In layman's terms - during run-time, if we encounter a throws statement, it can interrupt the execution of the constructor (formally, "completes abruptly"), causing the object to not be constructed, or constructed in an incomplete state. This could be a security hole, depending on the platform and partial completeness of the constructor.

What the JVM expects, given by §4.5, is that a field with ACC_FINAL set never has its value set after construction of the object:

Declared final; never directly assigned to after object construction (JLS §17.5).

So, we're in a bit of a pickle - we expect behavior of this during run-time, but not during compile-time. And why does IntelliJ raise a mild fuss when I have static in that field, but not when I don't?

First, back to throws - there's only a compile-time error with that statement if one of these three pieces aren't satisfied:

  • The expression being thrown is unchecked or null,
  • You try to catch the exception, and you're catching it with the right type, or
  • The expression being thrown is something that can actually be thrown, per §8.4.6 and §8.8.5.

So compiling a constructor with a throws is legitimate. It just so happens that, at run-time, it will always complete abruptly.

If a throw statement is contained in a constructor declaration, but its value is not caught by some try statement that contains it, then the class instance creation expression that invoked the constructor will complete abruptly because of the throw (§15.9.4).

Now, onto that blank final field. There's a curious piece to them - their assignment only matters after the end of the constructor, emphasis theirs.

A blank final instance variable must be definitely assigned (§16.9) at the end of every constructor (§8.8) of the class in which it is declared; otherwise a compile-time error occurs.

What if we never reach the end of the constructor?


First program: Normal instantiation of a static final field, decompiled:

// class version 51.0 (51)
// access flags 0x21
public class com/stackoverflow/sandbox/DecompileThis {

    // compiled from: DecompileThis.java

    // access flags 0x1A
    private final static I i = 10

    // access flags 0x1
    public <init>()V
            L0
    LINENUMBER 7 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
            L1
    LINENUMBER 9 L1
            RETURN // <- Pay close attention here.
    L2
    LOCALVARIABLE this Lcom/stackoverflow/sandbox/DecompileThis; L0 L2 0
    MAXSTACK = 1
    MAXLOCALS = 1
}

Observe that we actually call a RETURN instruction after successfully calling our <init>. Makes sense, and is perfectly legal.

Second program: Throws in constructor and blank static final field, decompiled:

// class version 51.0 (51)
// access flags 0x21
public class com/stackoverflow/sandbox/DecompileThis {

  // compiled from: DecompileThis.java

  // access flags 0x1A
  private final static I i

  // access flags 0x1
  public <init>()V throws java/lang/InstantiationException 
   L0
    LINENUMBER 7 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
   L1
    LINENUMBER 8 L1
    NEW java/lang/InstantiationException
    DUP
    LDC "Nothin' doin'."
    INVOKESPECIAL java/lang/InstantiationException.<init> (Ljava/lang/String;)V
    ATHROW // <-- Eeek, where'd my RETURN instruction go?!
   L2
    LOCALVARIABLE this Lcom/stackoverflow/sandbox/DecompileThis; L0 L2 0
    MAXSTACK = 3
    MAXLOCALS = 1
}

The rules of ATHROW indicate that the reference is popped, and if there's an exception handler out there, that will contain the address of the instruction on handling the exception. Otherwise, it's removed from the stack.

We never explicitly return, thus implying that we never complete construction of the object. So, the object can be considered to be in a wonky half-initialized state, all the while obeying compile-time rules - that is, all statements are reachable.

In the case of a static field, since that's not considered an instance variable, but a class variable, it does seem wrong that this sort of invocation is permissible. It may be worth filing a bug against.


Thinking back on it, it does make some sense in context, since the following declaration in Java is legal, and method bodies are congruent to constructor bodies:

public boolean trueOrDie(int val) {
    if(val > 0) {
        return true;
    } else {
        throw new IllegalStateException("Non-natural number!?");
    }
}
Makoto
  • 104,088
  • 27
  • 192
  • 230
1

As I'm understanding here we are all developers, so I believe we won't find the real response among us...this thing has something to do with compiler internals...and I think is a bug, or at least an unwanted behaviour.

Excluding Eclipse, which has some kind of incremental compiler (and therefore is able to immediately detect the problem), the command line javac performs a one-shot compilation. Now, the first snippet

public class StaticFinal {
    private final static int i ;
}

which is basically the same as having an empty constructor (as in the first example), is throwing the compile-time error, and this is fine because is respecting the specs.

In the second snippet, I think there's a bug in the compiler; it seems the compiler makes some decisions based on what the constructor is doing. This is more evident if you try to compile this one,

public class StaticFinal
{
    private final static int i ;

    public StaticFinal() 
    {
        throw new RuntimeException("Can't instantiate"); 
    }
}

This is more bizarre than your example because the unchecked exception is not declared in the method signature and will be (at least this is what I thought before reading this post) discovered only at run time.

Observing the behavior I could say (but is wrong according the specs) that.

For static final variables, the compiler tries to see if they are explicitly initialized, or initialized in a static intializer block, but, for some strange reason, it looks for something in the constructor too:

  • if they are initialized in the constructor the compiler will produce an error (you cannot assign there a value for a final static variable)
  • if the constructor is empty the compiler will produce an error (if you compile the first example, the one with the explicit zero argument constructor, the compiler breaks indicating the closing bracket of the constructor as the error line).
  • if the class cannot be instantiated because the constructor doesn't complete because an exception is thrown (this is not true for example if you write System.exit(1) instead of throwing an exception...it won't compile!), then the default value will be assigned to the static variable (!)
Curt
  • 5,518
  • 1
  • 21
  • 35
Andrea
  • 2,714
  • 3
  • 27
  • 38
0

After adding a main method to make the code print i. The code prints the value 0. This implies that the java compiler initializes the i with value 0 automatically. I wrote it in IntelliJ and had to disable the code checking to be able to build the code. Otherwise it wouldn't let me giving me the same error you were getting before throwing the exception.

JAVA code: not initialized

public class StaticFinal {
    private final static int i;
    public StaticFinal(){
        throw new InstantiationError("Can't instantiate!");
    }

    public static void main(String args[]) {
        System.out.print(i);
    }

}

Decompiled

Identical

JAVA code: Initialized

public class StaticFinal {
    private final static int i = 0;
    public StaticFinal(){
        throw new InstantiationError("Can't instantiate!");
    }

    public static void main(String args[]) {
        System.out.print(StaticFinal.i);
    }

}

Decompiled

public class StaticFinal
{

    public StaticFinal()
    {
        throw new InstantiationError("Can't instantiate!");
    }

    public static void main(String args[])
    {
        System.out.print(0);
    }

    private static final int i = 0;
}

After decompiling the code it turns out that this is not the case. As the decompiled codes and the original one are identical. The only other possibility is that the initialization is done through the Java Virtual Machine. The last changes I have made is a good enough evidence that it is the case.

Have to say good on you for spotting this.

Related questions: Here

Community
  • 1
  • 1
AmirHd
  • 10,308
  • 11
  • 41
  • 60
  • How is it going to explain : **Why java compiler is allowing this?** – Vishal K Jun 29 '13 at 08:32
  • @vishal-k It does explain why this is happening or why java compiler is allowing it. That is because it is initializing the static final variable when it is generating the bytecode. Clearly, it is a bug as IntelliJ doesn't allow it. – AmirHd Jun 29 '13 at 08:40
  • *That is because it is initializing the static final variable when it is generating the bytecode*.. By this do you want to say that final fields are never initialized? And since the static final field `i` is initialized to `0` so, this is the indication that it is a bug??? – Vishal K Jun 29 '13 at 08:42
  • In this specific case this is what happens and like I said it is a bug. When you specify a variable to be final it has to be initialized and if it doesn't it results into a compile error. The fact that this doesn't happen is a compiler bug. To make sure it is the case you can de-compile your .class file into java code. – AmirHd Jun 29 '13 at 08:46
  • You still didn't answer the first part of my question that I have asked you in comment.. And how the decompiled version of class file is going to help? – Vishal K Jun 29 '13 at 08:51
  • It gives a proof that java compiler is initializing i while it should have left it with you to do so. It also answers your question "Why java compiler is allowing this?": because it is has a bug. The bug is that is initializes a static final value on your behalf while it shouldn't have. Good on you for finding this if that is the case bro. – AmirHd Jun 29 '13 at 08:57
  • Which decompiler are you using? – Vishal K Jun 29 '13 at 09:06
  • There are different ones out there depend on your OS. What OS are you on? – AmirHd Jun 29 '13 at 09:13
  • I am on Windows XP..And I have already tried `DJ` and `cavaj` . And none of them is showing `i` being assigned the value `0`.. – Vishal K Jun 29 '13 at 09:13
  • I just used jad that is multi platform, dj-java is windows based. But straingly enough the code seem to be identical with the initial java code I have created. This could mean that the initialization happens by the virtual machine. Seriously, good on you on spotting this one. – AmirHd Jun 29 '13 at 09:16
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/32612/discussion-between-amirhd-and-vishal-k) – AmirHd Jun 29 '13 at 09:36
-2

I'd say it's simply because when you add the Throws, you're basically handling the error, so the compiler goes "oh, well, he probably knows what he's doing then". It still gives a runtime error, after all.

Marconius
  • 683
  • 1
  • 5
  • 13