Java の変数の初期化
Java で変数を既定値で初期化
変数を次のように宣言したとします。
class MyClass1 {
int i;
boolean b;
// ...省略...
}
例えば C 言語などでは変数の既定の値が定められていないので、こうして宣言されただけではその変数に設定されている値はわかりません。 同じコードでも環境やビルド方法によって動作が変わってくるので、よくゴミが入っているというような言い方をします。
しかし、Java では変数の初期値が決められているので、上のように int 型、boolean 型の値を宣言したら、 その値はそれぞれ、0、false になります。
これが最も基本的な初期化です。
初期値はデータ型によって違います。詳しくは「Java のデータ型」 をみてください。
Java のコンストラクタでの変数の初期化
クラスを定義する際、そのクラスのオブジェクトが作られるときに呼び出される、特別な初期化メソッドを定義することができます。
これをコンストラクタといいます。
コンストラクタはクラス名と同じ名前のメソッドとして、戻り値はありません。
public class TestApp {
public static void main(String[] args) {
Animal a = new Animal(10);
a.showAge(); // Age: 10
}
}
class Animal {
int age;
public Animal(int age) {
this.age = age;
}
public void showAge() {
System.out.printf("Age: %d\n", age);
}
}
コンストラクタ内から他のコンストラクタを呼出す
同じクラス内のコンストラクタを呼出す
コンストラクタはパラメータ違いで複数定義することが可能です。 あるコンストラクタから同じクラス内のもうひとつのコンストラクタを呼出す場合は、this を用います。
次の例を見てください。
class Dog extends Animal {
String name;
public Dog(int age) {
this(age, "Pochi");
}
public Dog(int age, String name) {
super(age);
this.name = name;
}
public void showName() {
System.out.printf("Name: %s\n", this.name);
}
}
ここではコンストラクタが二つ定義されています。ひとつは 4行目 Dog(int age) で、 もうひとつは 8 行目の Dog(int age, String name) です。
前者のコンストラクタを利用した場合は、名前を既定値として "Pochi" に設定してもうひとつのコンストラクタを呼出しています。
スーパークラスのコンストラクタを呼出す
派生クラスの初期化時にスーパークラスを初期化する必要がある場合は、スーパークラスのコンストラクタを呼出す必要があります。
スーパークラスがカプセル化されていれば、派生クラスからその private フィールドへアクセスできないので、コンストラクタを経由してそれを初期化しなければならないからです。
スーパークラスのコンストラクタは、super を用いて呼出します。
具体例は次のとおりです。
ここでは簡単なクラス、Animal と Dog を定義します。コンストラクタ内の super や this に着目してください。
public class TestApp {
public static void main(String[] args) {
Animal a = new Animal(10);
a.showAge();
Dog dog = new Dog(3, "Shiba");
dog.showAge();
dog.showName();
}
}
class Animal {
int age;
public Animal(int age) {
this.age = age;
}
public void showAge() {
System.out.printf("Age: %d\n", age);
}
}
class Dog extends Animal {
String name;
public Dog(int age) {
this(age, "Pochi");
}
public Dog(int age, String name) {
super(age);
this.name = name;
}
public void showName() {
System.out.printf("Name: %s\n", this.name);
}
}
実行例は次のとおりです。
Age: 10
Age: 3
Name: Shiba
super を経由してスーパークラスの変数 age を正しく初期化できています。
Java の初期化ブロックでの変数の初期化
初期化ブロックには、次の二つがあります。
オブジェクト初期化ブロック (object initialization block)。これはオブジェクト毎に実行されます。
静的初期化ブロック (static initialization block)。これはクラスのロード時に一度だけ実行されます。このブロックはオブジェクトが作成される前に実行されるので、static 変数のみにアクセスできます。
以上、二つについて実行例をみてみましょう。
Java のオブジェクト初期化ブロック
オブジェクト初期化ブロックはクラス定義内に {} で囲んで定義します。 オブジェクトが生成される時に実行されます。
public class TestApp {
public static void main(String[] args) {
Foo foo = new Foo();
foo.printValues();
// x=2, y=3, z=6
}
}
class Foo {
int x = 2;
int y = 3;
int z;
// オブジェクト初期化ブロック
{
z = x * y;
}
public void printValues() {
System.out.printf("x=%d, y=%d, z=%d\n", x, y, z);
}
}
オブジェクト作成時に、オブジェクト初期化ブロックが実行されたために、変数 z が正しく 6 に初期化されていることがわかります。
Java の静的初期化ブロック
オブジェクト毎ではなく、クラスのロード時に一度だけ変数を初期化する場合には、次のように static 付きの初期化ブロックを利用することもできます。
import java.util.Random;
public class TestApp {
public static void main(String[] args) {
Foo f1 = new Foo();
f1.printValues();
Foo f2 = new Foo();
f2.printValues();
}
}
class Foo {
static int x;
// 静的初期化ブロック
static {
Random gen = new Random();
x = gen.nextInt(100);
}
int y;
// オブジェクト初期化ブロック
{
y = x;
x++;
}
public void printValues() {
System.out.printf("x=%d, y=%d\n", x, y);
}
}
実行例は次の通りです。(ランダムに変わります)
x=54, y=53
x=55, y=54
1行目、2行目はそれぞれ別のオブジェクトからの出力ですが、確かに変数 x の値が共通して使われていることがわかります。
このことから、静的初期化ブロックはオブジェクト毎に呼び出されているのではなく、 はじめに一度だけ呼び出されて x の値が初期化されていることがわかります。
初期化の実行順序
さて、ここまででコンストラクタやら初期化ブロックなどが、初期化のための手段として出てきました。 これらは一体どのような順序で呼ばれ、どう使ったらよいのでしょうか?
Java のクラスで定義されたフィールドの初期化は次の順番で行われます。
- クラスロード時に静的初期化ブロックが実行される
ここでは static 変数のみが初期化可能 - クラスのフィールドとして宣言した変数を既定値で初期化する
- オブジェクトの生成時に初期化ブロックが実行される
- 次にコンストラクタが実行される
各種初期化方法の使い分け
基本的にプリミティブなデータ型の変数の初期化は、曖昧さがなくなるので可能な限り宣言時に行うと良いです。
int door = 4;
オブジェクト毎のパラメータを必要とせず、なおかつ、初期化に複雑なロジックが必要になる場合は、 初期化ブロックで変数を初期化すると良いでしょう。
コンストラクタへのパラメータなど、パラメータを受け取って初めて初期化できるコードはコンストラクタに記述する必要があります。
以上、変数の初期化について説明しました。