4
votes

Before you say that this question has already been answered tons of times, here is my code snippet:

final int x;
try {
    x = blah(); 
} catch (MyPanicException e) {
    abandonEverythingAndDie();
}
System.out.println("x is " + x);

If invoking abandonEverythingAndDie() has the effect of ending the execution of the whole program (say because it invokes System.exit(int) ), then x is always initialized whenever it is used.

Is there a way in the current Java language to make the compiler happy about variable initialization, by informing it that abandonEverythingAndDie() is a method which never returns control to the caller?

I do not want to

  • remove the final keyword
  • initialize x while declaration,
  • nor to put the println in the scope of the try...catch block.
5
just initialize the Variable. final int x=0; try { x = blah(); } catch (MyPanicException e) { abandonEverythingAndDie(); } System.out.println("x is " + x); - Anders Anderson
@AndersAnderson x is final, that's not possible. - Florent Bayle
@AndersAnderson have you tried initializing final variable twice? Once at point of declaration then inside try? - Kuba
Jepp, haven't thought about it being final... Then there is just no option. - Anders Anderson
@MadProgrammer No, you will have a "The final local variable x may already have been assigned". - Florent Bayle

5 Answers

5
votes

Not without cheating a little by providing a little bit of extra information to the compiler:

final int x;
try {
    x = blah();
} catch (MyPanicException e) {
    abandonEverythingAndDie();
    throw new AssertionError("impossible to reach this place"); // or return;
}
System.out.println("x is " + x);

You can also make abandonEverythingAndDie() return something (only syntactically, it will of course never return), and call return abandonEverythingAndDie():

final int x;
try {
    x = blah();
} catch (MyPanicException e) {
    return abandonEverythingAndDie();
}
System.out.println("x is " + x);

and the method:

private static <T> T abandonEverythingAndDie() {
    System.exit(1);
    throw new AssertionError("impossible to reach this place");
}

or even

throw abandonEverythingAndDie();

with

private static AssertionError abandonEverythingAndDie() {
    System.exit(1);
    throw new AssertionError("impossible to reach this place");
}
4
votes

No, the compiler only checks that your code is legal. In this context, abandonEverythingAndDie is treated as a blackbox and the compiler considers that not all branches cover the initialization of the variable. Even the below code is not accepted by the compiler:

final int x;
try {
    x = blah(); 
} catch (MyPanicException e) {
    abandonEverythingAndDie();
    System.exit(-1); // the System.exit() itself is treated as a blackbox :)
}
System.out.println("x is " + x);

In other words, the compiler does not "think" about the possible dynamic execution in order to compile the program.

2
votes

Surely the simplest, and least hacky approach is just to use a temporary variable...

final int x;

int temp;   
try {
    temp = blah(); 
} catch (MyPanicException e) {
    abandonEverythingAndDie();
}
x = temp;
System.out.println("x is " + x);

Simple.

1
votes

I am not sure if that is an option but you can add

throw new RuntimeException()

or

return;

in your catch

final int x;
try {
    x = blah(); 
} catch (MyPanicException e) {
    abandonEverythingAndDie();
    throw new RuntimeException(); // here
}
System.out.println("x is " + x);

Even if this added throw will not be executed because of abandonEverythingAndDie, compiler will know that flow of control from this catch block can't go back to System.out.println("x is " + x); so it will not require initialization of x.

1
votes

Logically, why would you want to do something with x if blah() had failed? x would be uninitialized and this can be dangerous, and so Java prevents this. It's helping you in ways older languages like c would not. So move the println inside would be the obvious solution.

try {
    final int x = blah(); 
    System.out.println("x is " + x);
} catch (MyPanicException e) {
    abandonEverythingAndDie();
}

I think SRP could apply here, depending on your actual code, exception catching is arguably a responsibility, so I might split this one method into two. One that doesn't care about handling, and one that only handles.

public void doBlah throws MyPanicException {
    final int x = blah(); 
    System.out.println("x is " + x);
}

public void tryBlahOrDie {
    try{
      doBlah();
    } catch (MyPanicException e) {
        abandonEverythingAndDie();
    }
}