|
|
|
Sponsored Link •
|
|
Advertisement
|
Initializers
Besides providing constructors, Java offers one other way for you to
assign an initial value to instance variables: initializers.
As mentioned previously, the two kinds of initializers in Java are
instance variable initializers and instance initializers.
Instance variable initializers
In a constructor, you have the freedom to write as much code as needed
to calculate an initial value. In an instance variable initializer, you
have only an equals sign and one expression. For example, if you wanted
to always start coffee cups out with 355 milliliters of fresh brewed
coffee in them, you could initialize innerCoffee with a
constructor:
// In source packet in file init/ex9/CoffeeCup.java
class CoffeeCup {
private int innerCoffee;
public CoffeeCup() {
innerCoffee = 355;
}
// ...
}
Alternatively, you could initialize innerCoffee with an
instance variable initializer:
// In source packet in file init/ex10/CoffeeCup.java
class CoffeeCup {
private int innerCoffee = 355; // "= 355" is an initializer
// no constructor here
// ...
}
The right-hand side of the equals sign in an initializer can be any expression that evaluates to the type of the instance variable.
Instance initializers
Java 1.1 introduced the instance initializer, which is also
called the instance initialization block. Here is the same
CoffeeCup class with its innerCoffee variable initialized
by an instance initializer:
// In source packet in file init/ex19/CoffeeCup.java
class CoffeeCup {
private int innerCoffee;
// The following block is an instance initializer
{
innerCoffee = 355;
}
// no constructor here
// ...
}
This manner of initializing innerCoffee yields the same
result as the previous two examples: innerCoffee is
initialized to 355.
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.
Initializers can't make forward references
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.
For example, here is a virtual cafe class that has four chairs for every table:
// In source packet in file init/ex11/VirtualCafe.java
class VirtualCafe {
private int tablesCount = 20;
private int chairsCount = 4 * tablesCount;
//...
}
These initializers work fine. The chairsCount initializer,
= 4 * tablesCount, refers to an instance variable declared
textually before it, so the compiler is happy. Because initializers
are executed in textual order, tablesCount is already
initialized to 20 by the time chairsCount's initializer
multiplies it by four. Thus, chairsCount is initialized to
80.
If you were able to use instance variables declared textually later, you could end up with unexpected behavior:
// In source packet in file init/ex12/VirtualCafe.java
// THIS WON'T COMPILE, BUT AS A THOUGHT EXPERIMENT,
// IMAGINE IT WERE POSSIBLE
class VirtualCafe {
private int chairsCount = 4 * tablesCount;
private int tablesCount = 20;
//...
}
If the above declaration were possible, chairsCount's
initializer would use tablesCount before
tablesCount were assigned a value of 20. At that point,
the tablesCount variable would have its default initial
value of zero. Hence, this code would initialize
chairsCount to four times zero. If you do the math, you
will discover that, in this case, chairsCount does not
initialize to 80.
Getting around the forward reference rule
Although this kind of forward referencing is disallowed by the compiler
in an attempt to help programmers avoid just the above kind of mistake,
you can't let down your guard completely. There is still a way you
could inadvertently (or purposefully) circumvent the compiler's
preventative restrictions:
// In source packet in file init/ex13/VirtualCafe.java
class VirtualCafe {
private int chairsCount = initChairsCount();
private int tablesCount = 20;
private int initChairsCount() {
return tablesCount * 4;
}
//...
}
The above code compiles fine, and has the same result as the previous
thought experiment. Here chairsCount's initializer
sneakily invokes a method that uses tablesCount before its
initializer has been executed. When initChairsCount()
calculates tablesCount * 4, tablesCount is
still at its default initial value of zero. As a result,
initChairsCount() returns zero, and chairsCount
is initialized to zero.
|
Sponsored Links
|