Puzzle 38: The Unwelcome Guest
The program in this puzzle models a system that attempts to read a user ID from
its environment, defaulting to a guest user if the attempt fails. The author of the
program was faced with a situation whereby the initializing expression for a static
field could throw an exception. Because Java doesn’t allow static initializers to
throw checked exceptions, the initialization must be wrapped in a try-finally
block. What does the program print?
package javapuzzlers;
public class TheUnwelcomeGuest {
public static final long GUEST_USER_ID = -1;
private static final long USER_ID;
static {
try {
USER_ID = getUserIdFromEnvironment();
} catch (IdUnavailableException e) {
USER_ID = GUEST_USER_ID;
System.out.println("Logging in as guest");
}
}
private static long getUserIdFromEnvironment()
throws IdUnavailableException {
throw new IdUnavailableException(); // Simulate an error
}
public static void main(String[] args) {
System.out.println("User ID: " + USER_ID);
}
}
class IdUnavailableException extends Exception {
IdUnavailableException() { }
}
Output:
$javac HelloWorld.java
HelloWorld.java:9: error: variable USER_ID might already have been assigned
USER_ID = GUEST_USER_ID;
^
1 error
Explanation:
What’s the problem? The USER_ID field is a blank final, which is a final field
whose declaration lacks an initializer [JLS 4.12.4]. It is clear that the exception
can be thrown in the try block only if the assignment to USER_ID fails, so it is perfectly
safe to assign to USER_ID in the catch block. Any execution of the static
initializer block will cause exactly one assignment to USER_ID, which is just what
is required for blank finals. Why doesn’t the compiler know this?
Determining whether a program can perform more than one assignment to a
blank final is a hard problem. In fact, it’s impossible. It is equivalent to the classic
halting problem, which is known to be unsolvable in general [Turing36]. To make
it possible to write a Java compiler, the language specification takes a conservative
approach to this issue. A blank final field can be assigned only at points in the
program where it is definitely unassigned. The specification goes to great
lengths to provide a precise but conservative definition for this term [JLS 16].
Because it is conservative, there are some provably safe programs that the
compiler must reject. This puzzle illustrates one such program.
Comments
Post a Comment