views:

138

answers:

6

Hello,

This isn't valid code:

public class MyClass
{
    private static boolean yesNo = false;

    static
    {
        if (yesNo)
        {
            System.out.println("Yes");
            return; // The return statement is the problem
        }
        System.exit(0);
    }
}

This is a stupid example, but in a static class constructor we can't return;. Why? Are there good reasons for this? Does someone know something more about this?

So the reason why I should do return is to end constructing there.

Thanks

+2  A: 

What should you return to? In a static initializer there is no caller, so a return doesn't make sense as far as I see it. Static initializers are executed when the class is loaded for the first time.

Eric Eijkelenboom
+4  A: 
  • the program flow can always be structured to go without the need for return. (In your example putting System.exit(0) in an else clause would achieve the desired result)

  • in you really need it, you can move the code in a static method and call it from the initializer:

.

static {
    staticInit();
}

private static void staticInit() {
    if (yesNo) {
        System.out.println("Yes");
        return;
    }
    System.exit(0);
}

Note that this is not a static constructor, it's a static initializer. Nothing gets constructed.

Bozho
I know you can solve it with a simple `else`. But I said it was a very stupid example. But it is true!
Martijn Courteaux
@Martijn Courteaux yes, I understood that. That's why it's in brackets, just an example of my generalization.
Bozho
+1  A: 

From JSL regarding static initializers:

"It is a compile-time error for a static initializer to be able to complete abruptly (§14.1, §15.6) with a checked exception (§11.2). It is a compile-time error if a static initializer cannot complete normally (§14.21)."

Abrupt completion (among others): "return with no value", "return with a given value", etc.

So a return statement in a static initializer is an "abrupt completion" and produces a compile-time error.

Jala
The JSL doesn't specify a reason, though, which is what this question is about.
Oak
I was just about to post the same thing, but I was trying to think: why is "abrupt completion" of a static initializer so bad that they made it a compile error?
Joe Daley
@Oak: You are right, no reason there. Specs are sometimes like axioms.
Jala
+6  A: 

I think the reason is that initializers are carried together with field initializations (and with constructors, in the case of instance initializers). In other words, the JVM only recognizes one place to initialize static fields, and thus all initializations - whether in blocks or not - must be done there.

So, for example, when you write a class:

class A {
    static int x = 3;
    static {
        y = x * x;
    }
    static int z = x * x;
}

Then it's actually as if you've written:

class A {
    static int x, y, z;
    static {
        x = 3;
        y = x * x;
        z = x * x;
    }
}

This is confirmed if you look at the disassembly:

static {};
  Code:
   0:   iconst_3
   1:   putstatic       #5; //Field x:I
   4:   getstatic       #5; //Field x:I
   7:   getstatic       #5; //Field x:I
   10:  imul
   11:  putstatic       #3; //Field y:I
   14:  getstatic       #5; //Field x:I
   17:  getstatic       #5; //Field x:I
   20:  imul
   21:  putstatic       #6; //Field z:I
   24:  return

So if you would have added a "return" somewhere in the middle of your static initializer it would also have prevented z from being calculated.

Oak
I was just about to write that when I saw this. Also note that you can have multiple static initializer blocks in a class.
Joachim Sauer
Really good answer, Oak. It also shows why an abrupt completion is considered so bad in an static initializer (as Joe's pointed out in a comment) to give a compile-time error.
Jala
A: 

I understand that the rule for static initializers is that they are only every executed one time, after the class byte-code is loaded and before executing any static method or instantiating the first object from the class. The JLS guarantees that this initialization will have been completed. To ensure that this guarantee is true, the JLS also specifies that the code cannot have been abruptly terminated (as given clearly in another answer).

Note that it is possible to load byte-code without initializing it; see Class.forName(String, boolean, ClassLoader) method. If the boolean parameter is false then this will load the class but not initialize it. The programmer can still do some reflection to discover information about that class still without it being initialized. However, once you attempt to use the class directly by calling a static method or instantiating an instance, then the JVM will proceed to initialize it first.

If any of the static initializers do abruptly terminate - which can happen with a RuntimeException, the class will be left in an invalid state. The first time, the JVM will throw an ExceptionInInitializeError (notice that is an Error which means it is considered an internal failure). From that point on it will not be possible to use the class - attempting to call a static method or instantiating an object you will instead get a NoClassDefFoundError.

The only way to recover from this situation without restarting the JVM is if you are using ClassLoaders and can replace the class loader with the failed class and rebuild the class or reinitializer in a different environment (perhaps different system properties), but the program must then be well prepared for this kind of situation.

Kevin Brock
A: 

I would reorder the statement, making it simpler/shorter. There will never be a good case where both branches of if/else need a return.

static { 
    if (!yesNo) 
       System.exit(0); // silently exiting a program is a bad idea!"
    System.out.println("Yes"); 
} 
Peter Lawrey