Member initialization

Bruce Eckel's Thinking in Java Contents | Prev | Next

Java goes out of its way to guarantee that any variable is properly initialized before it is used. In the case of variables that are defined locally to a method, this guarantee comes in the form of a compile-time error. So if you say:

  void f() {
    int i;
    i++;
  }

You’ll get an error message that says that i might not have been initialized. Of course, the compiler could have given i a default value, but it’s more likely that this is a programmer error and a default value would have covered that up. Forcing the programmer to provide an initialization value is more likely to catch a bug.

//: InitialValues.java
// Shows default initial values
 
class Measurement {
  boolean t;
  char c;
  byte b;
  short s;
  int i;
  long l;
  float f;
  double d;
  void print() {
    System.out.println(
      "Data type      Inital value\n" +
      "boolean        " + t + "\n" +
      "char           " + c + "\n" +
      "byte           " + b + "\n" +
      "short          " + s + "\n" +
      "int            " + i + "\n" +
      "long           " + l + "\n" +
      "float          " + f + "\n" +
      "double         " + d);
  }
}
 
public class InitialValues {
  public static void main(String[] args) {
    Measurement d = new Measurement();
    d.print();
    /* In this case you could also say:
    new Measurement().print();
    */
  }
} ///:~ 

The output of this program is:

Data type      Inital value
boolean        false
char
byte           0
short          0
int            0
long           0
float          0.0
double         0.0

The char value is a null, which doesn’t print.

You’ll see later that when you define an object handle inside a class without initializing it to a new object, that handle is given a value of null.

You can see that even though the values are not specified, they automatically get initialized. So at least there’s no threat of working with uninitialized variables.

Specifying initialization

class Measurement {
  boolean b = true;
  char c = 'x';
  byte B = 47;
  short s = 0xff;
  int i = 999;
  long l = 1;
  float f = 3.14f;
  double d = 3.14159; <p><tt>  //. . . </tt></p>

You can also initialize non-primitive objects in this same way. If Depth is a class, you can insert a variable and initialize it like so:

class Measurement {
  Depth o = new Depth();
  boolean b = true; <p><tt>  // . . . </tt></p>

If you haven’t given o an initial value and you go ahead and try to use it anyway, you’ll get a run-time error called an exception (covered in Chapter 9).

You can even call a method to provide an initialization value:

class CInit {
  int i = f();
  //...
}

This method can have arguments, of course, but those arguments cannot be other class members that haven’t been initialized yet. Thus, you can do this:

class CInit {
  int i = f();
  int j = g(i);
  //...
}

But you cannot do this:

class CInit {
  int j = g(i);
  int i = f();
  //...
}

Constructor initialization

The constructor can be used to perform initialization, and this gives you greater flexibility in your programming since you can call methods and perform actions at run time to determine the initial values. There’s one thing to keep in mind, however: you aren’t precluding the automatic initialization, which happens before the constructor is entered. So, for example, if you say:

class Counter {

int i;

Counter() { i = 7; }

// . . .

Order of initialization

//: OrderOfInitialization.java
// Demonstrates initialization order.
 
// When the constructor is called, to create a
// Tag object, you'll see a message:
class Tag {
  Tag(int marker) {
    System.out.println("Tag(" + marker + ")");
  }
}
 
class Card {
  Tag t1 = new Tag(1); // Before constructor
  Card() {
    // Indicate we're in the constructor:
    System.out.println("Card()");
    t3 = new Tag(33); // Re-initialize t3
  }
  Tag t2 = new Tag(2); // After constructor
  void f() {
    System.out.println("f()");
  }
  Tag t3 = new Tag(3); // At end
}
 
public class OrderOfInitialization {
  public static void main(String[] args) {
    Card t = new Card();
    t.f(); // Shows that construction is done
  }
} ///:~ 

In Card, the definitions of the Tag objects are intentionally scattered about to prove that they’ll all get initialized before the constructor is entered or anything else can happen. In addition, t3 is re-initialized inside the constructor. The output is:

Tag(1)
Tag(2)
Tag(3)
Card()
Tag(33)
f()

Thus, the t3 handle gets initialized twice, once before and once during the constructor call. (The first object is dropped, so it can be garbage-collected later.) This might not seem efficient at first, but it guarantees proper initialization – what would happen if an overloaded constructor were defined that did not initialize t3 and there wasn’t a “default” initialization for t3 in its definition?

Static data initialization
//: StaticInitialization.java
// Specifying initial values in a
// class definition.
 
class Bowl {
  Bowl(int marker) {
    System.out.println("Bowl(" + marker + ")");
  }
  void f(int marker) {
    System.out.println("f(" + marker + ")");
  }
}
 
class Table {
  static Bowl b1 = new Bowl(1);
  Table() {
    System.out.println("Table()");
    b2.f(1);
  }
  void f2(int marker) {
    System.out.println("f2(" + marker + ")");
  }
  static Bowl b2 = new Bowl(2);
}
 
class Cupboard {
  Bowl b3 = new Bowl(3);
  static Bowl b4 = new Bowl(4);
  Cupboard() {
    System.out.println("Cupboard()");
    b4.f(2);
  }
  void f3(int marker) {
    System.out.println("f3(" + marker + ")");
  }
  static Bowl b5 = new Bowl(5);
}
 
public class StaticInitialization {
  public static void main(String[] args) {
    System.out.println(
      "Creating new Cupboard() in main");
    new Cupboard();
    System.out.println(
      "Creating new Cupboard() in main");
    new Cupboard();
    t2.f2(1);
    t3.f3(1);
  }
  static Table t2 = new Table();
  static Cupboard t3 = new Cupboard();
} ///:~ 

Bowl allows you to view the creation of a class, and Table and Cupboard create static members of Bowl scattered through their class definitions. Note that Cupboard creates a non- static Bowl b3 prior to the static definitions. The output shows what happens:

Bowl(1)
Bowl(2)
Table()
f(1)
Bowl(4)
Bowl(5)
Bowl(3)
Cupboard()
f(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard()
f(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard()
f(2)
f2(1)
f3(1)

The static initialization occurs only if it’s necessary. If you don’t create a Table object and you never refer to Table.b1 or Table.b2, the static Bowl b1 and b2 will never be created. However, they are created only when the first Table object is created (or the first static access occurs). After that, the static object is not re-initialized.

The order of initialization is statics first, if they haven’t already been initialized by a previous object creation, and then the non- static objects. You can see the evidence of this in the output.

  1. The first time an object of type Dog is created, or the first time a static method or static field of class Dog is accessed, the Java interpreter must locate Dog.class, which it does by searching through the classpath.
  2. As Dog.class is loaded (which creates a Class object, which you’ll learn about later), all of its static initializers are run. Thus, static initialization takes place only once, as the Class object is loaded for the first time.
  3. When you create a new Dog( ) , the construction process for a Dog object first allocates enough storage for a Dog object on the heap.
  4. This storage is wiped to zero, automatically setting all the primitives in Dog to their default values (zero for numbers and the equivalent for boolean and char).
  5. Any initializations that occur at the point of field definition are executed.
  6. Constructors are executed. As you shall see in Chapter 6, this might actually involve a fair amount of activity, especially when inheritance is involved.
Explicit static initialization

//: ExplicitStatic.java
// Explicit static initialization
// with the "static" clause.
 
class Cup {
  Cup(int marker) {
    System.out.println("Cup(" + marker + ")");
  }
  void f(int marker) {
    System.out.println("f(" + marker + ")");
  }
}
 
class Cups {
  static Cup c1;
  static Cup c2;
  static {
    c1 = new Cup(1);
    c2 = new Cup(2);
  }
  Cups() {
    System.out.println("Cups()");
  }
}
 
public class ExplicitStatic {
  public static void main(String[] args) {
    System.out.println("Inside main()");
    Cups.c1.f(99);  // (1)
  }
  static Cups x = new Cups();  // (2)
  static Cups y = new Cups();  // (2)  <p><tt>} ///:~ </tt></p>

The static initializers for Cups will be run when either the access of the static object c1 occurs on the line marked (1), or if line (1) is commented out and the lines marked (2) are uncommented. If both (1) and (2) are commented out, the static initialization for Cups never occurs.

Non-static instance initialization

//: Mugs.java
// Java 1.1 "Instance Initialization"
 
class Mug {
  Mug(int marker) {
    System.out.println("Mug(" + marker + ")");
  }
  void f(int marker) {
    System.out.println("f(" + marker + ")");
  }
}
 
public class Mugs {
  Mug c1;
  Mug c2;
  {
    c1 = new Mug(1);
    c2 = new Mug(2);
    System.out.println("c1 &amp; c2 initialized");
  }
  Mugs() {
    System.out.println("Mugs()");
  }
  public static void main(String[] args) {
    System.out.println("Inside main()");
    Mugs x = new Mugs();
  }
} ///:~ 

You can see that the instance initialization clause:

  {
    c1 = new Mug(1);
    c2 = new Mug(2);
    System.out.println("c1 &amp; c2 initialized");
  }

looks exactly like the static initialization clause except for the missing static keyword. This syntax is necessary to support the initialization of anonymous inner classes (see Chapter 7).


[21] In contrast, C++ has the constructor initializer list that causes initialization to occur before entering the constructor body, and is enforced for objects. See Thinking in C++ .



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • With JRebel, developers get to see their code changes immediately, fine-tune their code with incremental changes, debug, explore and deploy their code with ease (both locally and remotely), and ultimately spend more time coding instead of waiting for the dreaded application redeploy to finish. Every time a developer tests a code change it takes minutes to build and deploy the application. JRebel keeps the app server running at all times, so testing is instantaneous and interactive.

  • Instead of only managing projects organizations do need to manage value! "Doing the right things" and "doing things right" are the essential ingredients for successful software and systems delivery. Unfortunately, with distributed delivery spanning multiple disciplines, geographies and time zones, many organizations struggle with teams working in silos, broken lines of communication, lack of collaboration, inadequate traceability, and poor project visibility. This often results in organizations "doing the …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds