Class and object initialization in Java

Here's everything you need to know about initializing Java classes and objects before executing them in the JVM.

Table of Contents
Show More

Classes and objects in Java must be initialized before they are used. You've previously learned that class fields are initialized to default values when classes are loaded, and that objects are initialized via constructors—but there is still more to initialization. This tutorial introduces all of Java's features for initializing classes and objects.

What you'll learn in this Java tutorial

  • How to initialize a Java class
  • How to work with class initialization blocks
  • How to initialize Java objects
download
Download the source code for example applications in this tutorial. Created by Jeff Friesen for JavaWorld.

How to initialize a Java class

Before we explore Java's support for class initialization, let's recap the steps of initializing a Java class. Consider Listing 1.

Listing 1. Initializing class fields to default values

class SomeClass {    static boolean b;    static byte by;    static char c;    static double d;    static float f;    static int i;    static long l;    static short s;    static String st; }

Listing 1 declares class SomeClass. This class declares nine fields of types boolean, byte, char, double, float, int, long, short, and String. When SomeClass is loaded, each field's bits are set to zero, which you interpret as follows:

false 0 \u0000 0.0 0.0 0 0 0 null

The previous class fields were implicitly initialized to zero. However, you can also explicitly initialize class fields by directly assigning values to them, as shown in Listing 2.

Listing 2. Initializing class fields to explicit values

class SomeClass {    static boolean b = true;    static byte by = 1;    static char c = 'A';    static double d = 2.0;    static float f = 3.0f;    static int i = 4;    static long l = 5000000000L;    static short s = 20000;    static String st = "abc"; }

Each assignment's value must be type-compatible with the class field's type. Each variable stores the value directly, with the exception of st. Variable st stores a reference to a String object that contains abc.

Referencing class fields

When initializing a class field, it's legal to initialize it to the value of a previously initialized class field. For example, Listing 3 initializes y to x's value. Both fields are initialized to 2.

Listing 3. Referencing a previously declared field

class SomeClass {    static int x = 2;    static int y = x;     public static void main(String[] args)    {       System.out.println(x);       System.out.println(y);    } }

However, the reverse is not legal: you cannot initialize a class field to the value of a subsequently declared class field. The Java compiler outputs illegal forward reference when it encounters this scenario. Consider Listing 4.

Listing 4. Attempting to reference a subsequently declared field

class SomeClass {    static int x = y;    static int y = 2;     public static void main(String[] args)    {       System.out.println(x);       System.out.println(y);    } }

The compiler will report illegal forward reference when it encounters static int x = y;. This is because source code is compiled from the top down, and the compiler hasn't yet seen y. (It would also output this message if y wasn't explicitly initialized.)

How to work with class initialization blocks

In some cases, you may want to perform complex class-based initializations. You will do this after a class has been loaded and before any objects are created from that class (assuming that the class isn't a utility class). You can use a class initialization block for this task.

A class initialization block is a block of statements preceded by the static keyword that's introduced into the class's body. When the class loads, these statements are executed. Consider Listing 5.

Listing 5. Initializing arrays of sine and cosine values

class Graphics {    static double[] sines, cosines;    static    {       sines = new double[360];       cosines = new double[360];       for (int i = 0; i < sines.length; i++)       {          sines[i] = Math.sin(Math.toRadians(i));          cosines[i] = Math.cos(Math.toRadians(i));       }    } }

Listing 5 declares a Graphics class that declares sines and cosines array variables. It also declares a class initialization block that creates 360-element arrays whose references are assigned to sines and cosines. It then uses a for statement to initialize these array elements to the appropriate sine and cosine values, by calling the Math class's sin() and cos() methods. (Math is part of Java's standard class library. I'll discuss this class and these methods in a future article.)

Combining class field initializers and class initialization blocks

You can combine multiple class field initializers and class initialization blocks in an application. Listing 6 provides an example.

Listing 6. Performing class initialization in top-down order

class MCFICIB {    static int x = 10;     static double temp = 98.6;     static    {       System.out.println("x = " + x);       temp = (temp - 32) * 5.0/9.0; // convert to Celsius       System.out.println("temp = " + temp);    }     static int y = x + 5;     static    {       System.out.println("y = " + y);    }     public static void main(String[] args)    {    } }

Listing 6 declares and initializes a pair of class fields (x and y), and declares a pair of static initializers. Compile this listing as shown:

javac MCFICIB.java

Then run the resulting application:

java MCFICIB

You should observe the following output:

x = 10 temp = 37.0 y = 15

This output reveals that class initialization is performed in top-down order.

<clinit>() methods

When compiling class initializers and class initialization blocks, the Java compiler stores the compiled bytecode (in top-down order) in a special method named <clinit>(). The angle brackets prevent a name conflict: you cannot declare a <clinit>() method in source code because the < and > characters are illegal in an identifier context.

After loading a class, the JVM calls this method before calling main() (when main() is present).

Let's take a look inside MCFICIB.class. The following partial disassembly reveals the stored information for the x, temp, and y fields:

Field #1  00000290        Access Flags                          ACC_STATIC 00000292        Name                                  x 00000294        Descriptor                            I 00000296        Attributes Count                      0  Field #2  00000298        Access Flags                          ACC_STATIC 0000029a        Name                                  temp 0000029c        Descriptor                            D 0000029e        Attributes Count                      0  Field #3  000002a0        Access Flags                          ACC_STATIC 000002a2        Name                                  y 000002a4        Descriptor                            I 000002a6        Attributes Count                      0

The Descriptor line identifies the JVM's type descriptor for the field. The type is represented by a single letter: I for int and D for double.

The following partial disassembly reveals the bytecode instruction sequence for the <clinit>() method. Each line starts with a decimal number that identifies the zero-based offset address of the subsequent instruction:

  0        bipush 10   2        putstatic MCFICIB/x I   5        ldc2_w #98.6   8        putstatic MCFICIB/temp D  11        getstatic java/lang/System/out Ljava/io/PrintStream;  14        new java/lang/StringBuilder  17        dup  18        invokespecial java/lang/StringBuilder/<init>()V  21        ldc "x = "  23        invokevirtual java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder;  26        getstatic MCFICIB/x I  29        invokevirtual java/lang/StringBuilder/append(I)Ljava/lang/StringBuilder;  32        invokevirtual java/lang/StringBuilder/toString()Ljava/lang/String;  35        invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V  38        getstatic MCFICIB/temp D  41        ldc2_w #32  44        dsub  45        ldc2_w #5  48        dmul  49        ldc2_w #9  52        ddiv  53        putstatic MCFICIB/temp D  56        getstatic java/lang/System/out Ljava/io/PrintStream;  59        new java/lang/StringBuilder  62        dup  63        invokespecial java/lang/StringBuilder/<init>()V  66        ldc "temp = "  68        invokevirtual java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder;  71        getstatic MCFICIB/temp D  74        invokevirtual java/lang/StringBuilder/append(D)Ljava/lang/StringBuilder;  77        invokevirtual java/lang/StringBuilder/toString()Ljava/lang/String;  80        invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V  83        getstatic MCFICIB/x I  86        iconst_5  87        iadd  88        putstatic MCFICIB/y I  91        getstatic java/lang/System/out Ljava/io/PrintStream;  94        new java/lang/StringBuilder  97        dup  98        invokespecial java/lang/StringBuilder/<init>()V 101        ldc "y = " 103        invokevirtual java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder; 106        getstatic MCFICIB/y I 109        invokevirtual java/lang/StringBuilder/append(I)Ljava/lang/StringBuilder; 112        invokevirtual java/lang/StringBuilder/toString()Ljava/lang/String; 115        invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V 118        return

The instruction sequence from offset 0 through offset 2 is equivalent to the following class field initializer:

static int x = 10;

The instruction sequence from offset 5 through offset 8 is equivalent to the following class field initializer:

static double temp = 98.6;

The instruction sequence from offset 11 through offset 80 is equivalent to the following class initialization block:

static {    System.out.println("x = " + x);    temp = (temp - 32) * 5.0/9.0; // convert to Celsius    System.out.println("temp = " + temp); }

The instruction sequence from offset 83 through offset 88 is equivalent to the following class field initializer:

static int y = x + 5;

The instruction sequence from offset 91 through offset 115 is equivalent to the following class initialization block:

static {    System.out.println("y = " + y); }

Finally, the return instruction at offset 118 returns execution from <clinit>() to that part of the JVM that called this method.

How to initialize Java objects

After a class has been loaded and initialized, you'll often want to create objects from the class. As you learned in my recent introduction to programming with classes and objects, you initialize an object via the code that you place in a class's constructor. Consider Listing 7.

Related:
Page 1