Here's everything you need to know about initializing Java classes and objects before executing them in the JVM.
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
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.
() 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.
Listing 7. Using the constructor to initialize an object
class City
{
private String name;
int population;
City(String name, int population)
{
this.name = name;
this.population = population;
}
@Override
public String toString()
{
return name + ": " + population;
}
public static void main(String[] args)
{
City newYork = new City("New York", 8491079);
System.out.println(newYork); // Output: New York: 8491079
}
}
Listing 7 declares a City
class with name
and population
fields. When a City
object is created, the City(String name, int population)
constructor is called to initialize these fields to the called constructor’s arguments. (I’ve also overridden Object
‘s public String toString()
method to conveniently return the city name and population value as a string. System.out.println()
ultimately calls this method to return the object’s string representation, which it outputs.)
Before the constructor is called, what values do name
and population
contain? You can find out by inserting System.out.println(this.name); System.out.println(this.population);
at the start of the constructor. After compiling the source code (javac City.java
) and running the application (java City
), you would observe null
for name
and 0
for population
. The new
operator zeroes an object’s object (instance) fields before executing a constructor.
As with class fields, you can explicitly initialize object fields. For example, you could specify String name = "New York";
or int population = 8491079;
. However, there’s usually nothing to gain by doing this, because these fields will be initialized in the constructor. The only benefit that I can think of is to assign a default value to an object field; this value is used when you call a constructor that doesn’t initialize the field:
int numDoors = 4; // default value assigned to numDoors
Car(String make, String model, int year)
{
this(make, model, year, numDoors);
}
Car(String make, String model, int year, int numDoors)
{
this.make = make;
this.model = model;
this.year = year;
this.numDoors = numDoors;
}
Object initialization mirrors class initialization
Apart from constructors, object initialization mirrors class initialization. Instead of class field initializers, you have object field initializers. Furthermore, instead of class initialization blocks, you have object initialization blocks. You can also reference previously declared and initialized object fields but you cannot reference subsequently declared and initialized object fields. All of these concepts are demonstrated in Listing 8.
Listing 8. How object initialization mirrors class initialization
class Mirror
{
int x = 2;
int y = x;
{
System.out.println("x = " + x);
System.out.println("y = " + y);
}
public static void main(String[] args)
{
Mirror mirror = new Mirror();
}
}
Listing 8 reveals that an object initialization block is a block of statements introduced into the class body. Unlike a class initialization block, an object initialization block isn’t prefixed by anything. Because you can initialize an object in a constructor, about the only good use for an object initialization block is in the context of an anonymous class, which doesn’t have a constructor and which I’ll discuss in a future article.
Compile Listing 8 (javac Mirror.java
) and run the resulting application (java Mirror
). You’ll discover the following output:
x = 2
y = 2
Combining constructors and object field initializers and object initialization blocks
You can combine multiple constructors, object field initializers, and object initialization blocks in an application. Listing 9 provides an example.
Listing 9. A strange way to do object initialization
class MCOFIOIB
{
MCOFIOIB()
{
System.out.println("MCOFIOIB() called");
}
int x = 5;
{
x += 6;
}
int i;
MCOFIOIB(int i)
{
this.i = i;
System.out.println("MCOFIOIB(i) called: i = " + i);
}
{
System.out.println("i = " + i);
System.out.println("x = " + x);
}
public static void main(String[] args)
{
new MCOFIOIB();
System.out.println();
new MCOFIOIB(6);
}
}
Listing 9 declares a pair of constructors (MCOFIOIB()
and MCOFIOIB(int i)
), a pair of object fields (x
and i
), and a pair of object initialization blocks. Compile this listing as follows:
javac MCOFIOIB.java
Then run the resulting application:
java MCOFIOIB
You should observe the following output:
i = 0
x = 11
MCOFIOIB() called
i = 0
x = 11
MCOFIOIB(i) called: i = 6
This output is from the creation of two MCOFIOIB
objects. The first part originates from new MCOFIOIB();
and the second part originates from new MCOFIOIB(6);
. Each part reveals that object field initializers and object initialization blocks execute before a constructor executes. Furthermore, it reveals that object field initializers and object initialization blocks execute in top-down order. (x
had to be initialized to 11
before x = 11
could be output.)
() methods
If you were to examine the bytecode that the compiler generates for MCOFIOIB.class
, you would observe the presence of <init>()
methods instead of constructors. The JVM invokes these methods instead of constructors.
You would also observe the following partial disassembly of the x
and i
fields:
Field #1
0000026d Access Flags
0000026f Name x
00000271 Descriptor I
00000273 Attributes Count 0
Field #2
00000275 Access Flags
00000277 Name i
00000279 Descriptor I
0000027b Attributes Count 0
Next, you would observe the following information and bytecode sequence for the MCOFIOIB()
constructor:
0 aload_0
1 invokespecial java/lang/Object/()V
4 aload_0
5 iconst_5
6 putfield MCOFIOIB/x I
9 aload_0
10 dup
11 getfield MCOFIOIB/x I
14 bipush 6
16 iadd
17 putfield MCOFIOIB/x I
20 getstatic java/lang/System/out Ljava/io/PrintStream;
23 new java/lang/StringBuilder
26 dup
27 invokespecial java/lang/StringBuilder/()V
30 ldc "i = "
32 invokevirtual java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder;
35 aload_0
36 getfield MCOFIOIB/i I
39 invokevirtual java/lang/StringBuilder/append(I)Ljava/lang/StringBuilder;
42 invokevirtual java/lang/StringBuilder/toString()Ljava/lang/String;
45 invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
48 getstatic java/lang/System/out Ljava/io/PrintStream;
51 new java/lang/StringBuilder
54 dup
55 invokespecial java/lang/StringBuilder/()V
58 ldc "x = "
60 invokevirtual java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder;
63 aload_0
64 getfield MCOFIOIB/x I
67 invokevirtual java/lang/StringBuilder/append(I)Ljava/lang/StringBuilder;
70 invokevirtual java/lang/StringBuilder/toString()Ljava/lang/String;
73 invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
76 getstatic java/lang/System/out Ljava/io/PrintStream;
79 ldc "MCOFIOIB() called"
81 invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
84 return
The instruction sequence from offset 0 through offset 1 is equivalent to invoking the Object
superclass’s no-argument constructor:
new Object();
The instruction sequence from offset 4 through offset 17 is equivalent to the following class initialization block:
int x = 5;
{
x += 6;
}
The instruction sequence from offset 20 through offset 73 executes the second object initialization block:
{
System.out.println("i = " + i);
System.out.println("x = " + x);
}
The instruction sequence from offset 76 through offset 84 executes the MCOFIOIB()
constructor code and returns execution to the constructor’s caller.
Again, don’t worry about what the bytecode means. The important thing to remember is the initialization order. When you call MCOFIOIB()
, the following tasks are performed:
- The superclass’s noargument constructor is invoked first.
- Object field initializers and object initialization blocks are then executed in top-down order.
- The constructor’s code is executed last.
For brevity, I won’t present the bytecode sequence for the MCOFIOIB(int i)
constructor; it’s very similar to MCOFIOIB()
. The only difference is that the final code to be executed is MCOFIOIB(int i)
‘s code and not MCOFIOIB()
‘s code.
Conclusion
In this Java tutorial, you’ve learned how to use class field initializers and class initialization blocks to initialize classes, and how to use constructors, object field initializers, and object initialization blocks to initialize objects. While relatively simple, class and object initialization is vital: the JVM must initialize classes and objects before they are used.
Now that you know how initialization works, you’ve largely completed your exploration of Java’s class and object language features. The next tutorial in this series dips into interfaces, a slightly more advanced topic for beginning Java developers.