Nov 2, 2010

Instance initializers in Java

The body of a class declares members (fields and methods and nested classes and interfaces), instance and static initializers, and constructors. At the beginning of an object's life, the Java virtual machine (JVM) allocates enough memory on the heap to accommodate the object's instance variables. When that memory is first allocated, however, the data it contains is unpredictable. If the memory were used as is, the behavior of the object would also be unpredictable. To guard against such a scenario, Java makes certain that memory is initialized, at least to predictable default values, before it is used by any code.

Initialization is important because, historically, uninitialized data has been a common source of bugs. Bugs caused by uninitialized data occur regularly in C, for example, because it doesn't have built-in mechanisms to enforce proper initialization of data. C programmers must always remember to initialize data after they allocate it and before they use it. The Java language, by contrast, has built-in mechanisms that help you ensure proper initialization of the memory occupied by a newly-created object. With proper use of these mechanisms, you can prevent an object of your design from ever being created with an invalid initial state.

The Java language has three mechanisms dedicated to ensuring proper initialization of objects: instance initializers (also called instance initialization blocks), instance variable initializers, and constructors. (Instance initializers and instance variable initializers collectively are called "initializers.") All three mechanisms result in Java code that is executed automatically when an object is created. When you allocate memory for a new object with the new operator or the newInstance() method of class Class, the Java virtual machine will insure that initialization code is run before you can use the newly-allocated memory. If you design your classes such that initializers and constructors always produce a valid state for newly-created objects, there will be no way for anyone to create and use an object that isn't properly initialized.

And, when the object is created in Java, there is an order to initialize the object.
1. Set fields to default initial values (0, false, null)
2. Call the constructor for the object (but don't execute the body of the constructor yet)
3. Invoke the constructor of the superclass
4. Initialize fields using initializers and initialization blocks
5. Execute the body of the constructor

Let's have some samples.
public class InstanceInitializers1 {
 int i = 10;
 {
  System.out.println(i);
  i = 8;
 }

 public static void main(String[] args) {
  new InstanceInitializers1();
 }
}
As you all expected, the output is 10.

Java 1.1 introduced the instance initializer, which is also called the instance initialization block. Instance initializers are a useful alternative to instance variable initializers whenever:
(1) initializer code must catch exceptions, or
(2) perform fancy calculations that can't be expressed with an instance variable initializer.
You could, of course, always write such code in constructors. But in a class that had multiple constructors, you would have to repeat the code in each constructor. With an instance initializer, you can just write the code once, and it will be executed no matter what constructor is used to create the object. Instance initializers are also useful in anonymous inner classes, which can't declare any constructors at all.

The code inside an instance initializer may not return. Except in the case of anonymous inner classes, an instance initializer may throw checked exceptions only if the checked exceptions are explicitly declared in the throws clause of every constructor in the class. Instance initializers in anonymous inner classes, on the other hand, can throw any exception. When you write an initializer (either an instance variable initializer or instance initializer), you must be sure not to refer to any instance variables declared textually after the variable being initialized. In other words, you can't make a forward reference from an initializer. If you disobey this rule, the compiler will give you an error message and refuse to generate a class file. When an object is created, initializers are executed in textual order -- their order of appearance in the source code. This rule helps prevent initializers from using instance variables that have yet to be properly initialized.

Even though, the instance variable are get initialized with the default values(as indicated above) before the initialization block runs, we can't make forward reference. If you disobey this rule, the compiler will give you an error message and refuse to generate a class file. For example,
public class InstanceInitializers2 {
 {
  System.out.println(i);
  i = 8;
 }
 int i = 10;

 public static void main(String[] args) {
  new InstanceInitializers1();
 }
}
The above code won't compile because of forward referencing. But, with the below code snaps, you can see the forward referencing and initializing with the default values.
public class Initializers1 {
 int j = getI();
 int i = 10;

 public static void main(String[] args) {
  System.out.println(new Initializers1().j);
 }
 public int getI() {
  return i;
 }
}
What do you expecting as output? The value 0 will be printed. Because, when the getI() method is invoked, the value of i is 0(the default value).

The instance initializer block can throw exceptions, but, every constructor of the class should declare to throw that exception or one of its super exception. Let's have a example.
import java.io.*;

public class InitializerException {
 {
  if(true) {
   System.out.println("A");
   throw new FileNotFoundException();//A checked exception
  }
 }

 public InitializerException() throws FileNotFoundException {
 }

 public static void main(String[] args) throws Exception {
  new InitializerException();
 }
}
This is from the JLS,

It is a compile-time error if an instance initializer cannot complete normally. It is compile-time error if an instance initializer of a named class can throw a checked exception unless that exception or one of its supertypes is explicitly declared in the throws clause of each constructor of its class and the class has at least one explicitly declared constructor. An instance initializer in an anonymous class can throw any exceptions.
And, we need a if(true) { code snaps because, it's said that, "A break, continue, return, or throw statement cannot complete normally" so we used it escape it.

No comments: