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 valuen" +
      "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

Within
a class, the order of initialization is determined by the order that the
variables are defined within the class. Even if the variable definitions are
scattered throughout in between method definitions, the variables are
initialized before any methods can be called – even the constructor. For
example:

//: 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

If
you want to place initialization at the point of definition, it looks the same
as for non-statics. But since there’s only a single piece of storage for a
static,
regardless of how many objects are created the question of when that storage
gets initialized arises. An example makes this question clear:

//: 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

class
Spoon {


static int i;


static {


i = 47;


}


// . . .

So
it looks like a method, but it’s just the
static
keyword followed by a method body. This code, like the other
static
initialization, is executed only once, the first time you make an object of
that class
or
you access a
static
member of that class (even if you never make an object of that class). For
example:

//: 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++
.

More by Author

Must Read