Tuesday, August 12, 2008

A not so obvious answer to: Can Java interfaces have static initializers?

Static initializers are blocks of code that are automatically executed when a class is initialized. They are particularly handy when an expression is not enough to calculate an initial value or when the evaluation of an initializing expression may throw a (checked) exception. For instance, suppose we want to initialize a (final static) field me with the IP address of the local machine. We'd like to write:
import java.net.*;
class C {
  final static InetAddress me = InetAddress.getLocalHost();
}
but we can't because, as the compiler duly reminds us, "unreported exception java.net.UnknownHostException; must be caught or declared to be thrown". Ok, that's what static initializers are for:
import java.net.*;
class C {
  static final InetAddress me;
  static {
    
InetAddress addr = null;
    try {
      
addr = InetAddress.getLocalHost();
    } catch (UnknownHostException uhe) {
      uhe.printStackTrace();
      System.exit(-1);
    
}
    
me = addr;
  }
}
Now, the raison d'ĂȘtre of this post: what if C were an interface? Could we write the following? (fields are implicitly static and final in interfaces)
interface I {
  InetAddress me;
  static {
... as before ... }
}
Well, we could write that but it won't compile ;)
Is there any workaround? Yes, there is. However, before presenting it, allow me to make another point.
Tricky question: can interfaces have static initializers? After all, interfaces cannot contain code... can they?
Since I've said the question is tricky, you've probably guessed it right... yes, interfaces can indeed have static initializers, yet you (programmer) are not allowed to write static initializers inside interfaces. Uh... does the last sentence make any sense at all? Allow me to explain; please consider:
interface Universe {
  static final Integer theAnswer = new Integer(42);
}
That's it: Universe is an interface with a (well hidden?) static initializer! The expression new Integer(42) is not a compile-time constant, so the compiler emits the code for evaluating the expression inside a static initializer. Skeptical? Well, let's see what javap -c Universe has to say about it:
interface Universe{
public static final java.lang.Integer theAnswer;

static {};
  Code:
   0:   new     #1; //class java/lang/Integer
   3:   dup
   4:   bipush  42
   6:   invokespecial   #2; //Method java/lang/Integer." ":(I)V
   9:   putstatic       #3; //Field theAnswer:Ljava/lang/Integer;
   12:  return

}
if that isn't a static initializer, I don't really know what it is ;)
I think it's rather curious that programmers are not allowed to (explicitly) write static initializers in interfaces; I don't really see what problems could arise in allowing it.
However, there is a simple workaround: put the code inside static methods of a dummy nested class (this class should be private, but we can't make it private as all interface members are implicitly public). In our running example this idea translates to:
import java.net.*;
interface I {
  InetAddress me = Dummy.getAddress();
  class Dummy {
    private static InetAddress getAddress() {
      try {
        return InetAddress.getLocalHost();
      } catch (UnknownHostException uhe) {
        uhe.printStackTrace();
        System.exit(-1);
        return null; // not really, but makes the compiler happy
      }
    }
  }
}

1 comment:

Jason Fritz said...

Awesome, thanks! It may help to point out that if you try to write a static initializer in an interface, the error you will get is:

Syntax error, insert "enum Identifier" to complete EnumHeader

(or at least that's the error I get... very misleading)