Javaプログラミング基礎 講義資料

コンストラクタ

コンストラクタを用いたプログラム例

Person mikio = new Person();

上のプログラムのようにオブジェクトを生成しただけでは、 その属性には何も値が入っていない状態です。 しかし、場合によっては、オブジェクトの内部の情報を初期化したい場合があります。 コンストラクタ (constructor) は、 クラスからオブジェクトを生成するときに初期化を行うものです。

コンストラクタを使ったプログラムの例を以下に示します。

Person mikio = new Person("電大 未来男", "千代田区神田錦町2-2");

コンストラクタを適切に用意すれば、 このプログラムのように、オブジェクトの生成と同時に 名前、住所情報の初期化を行うことができて便利です。 また、初期化をうっかり忘れることを防ぐことができます。

上のような初期化を行うためのクラス Person のプログラムを以下に示します。

class Person {
    String name;
    String address;

    // 名前と住所の2つの引数を受け取り初期化を行うコンストラクタ
    Person(String n, String a) {
        name = n
        address = a;
    }

    // 何も引数が与えられなかったときのコンストラクタ
    Person() {
        name = "";
        address = "";
    }


    // 通常のメソッドの宣言
    ....
    ....
    ....
}

コンストラクタはメソッドの一種のような書き方をします。 クラス名と同名のメソッドとして、結果の型を書かずに宣言します。 コンストラクタの中身にオブジェクトを生成するときの初期化処理を書きます。 return 文で値を返すことはできません。

上のプログラムでは引数の組み合わせの違うコンストラクタを2つ宣言しています。 これは、引数を2つ与えられたときの初期化の方法と、 何も引数を与えられなかったときの初期化の方法の2つを用意しています。

2つのコンストラクタを用意したため、 クラスPersonからオブジェクトを生成するには、 次の2とおりの方法で行うことができます。

Person mikio = new Person("電大 未来男", "神田錦町2-2"); // 最初のコンストラクタが使われる。
Person newComer = new Person();                          // 2番目のコンストラクタが使われる。

newComerは引数を与えずにオブジェクトを生成しているため、 クラス内部のnameaddressは空 ("") の文字列に 初期化されることになります。

コンストラクタの文法

文法をまとめておきます。 コンストラクタの宣言を含んだクラス定義の文法は次のようになります。

class クラス名 {
    属性の宣言
    ......

    クラスと同じ名前 ( 引数, ... ) {
        コンストラクタで行う処理の中身
    }

    メソッドの宣言
    ......
}

暗示的コンストラクタ

前回までのプログラムでは、コンストラクタを宣言しませんでした。 コンストラクタがなくてもプログラムはきちんと動きます。

Javaでは、コンストラクタを用意していないクラスには、 自動的にコンストラクタが作られます。 これを、暗示的コンストラクタ (implicit constructor) と呼びます。

暗示的コンストラクタの主な働きは属性の値を初期化することです。 整数や浮動小数点数の属性はすべて 0 にし、 文字列、配列やオブジェクトの属性は空 (null, ナル) の状態にします。 空の状態とは、変数が対象となるデータを指し示していない状態と考えてください。

クラス定義の中に1つでもコンストラクタの宣言を行うと、 暗示的コンストラクタは作られなくなります。

デフォルトコンストラクタ

前回までのプログラムでは、コンストラクタに引数を与えずに オブジェクトを生成してきました。 ここで使われていた引数のないコンストラクタを、 デフォルトコンストラクタ (default constructor) と呼びます。

Circle c = new Circle();

そして前回までのプログラムでは、コンストラクタを書かなかったため、 デフォルトコンストラクタが暗示的に (目には見えないけれども) 自動作成されプログラムは正しく動いていました。

しかし、今回学んだ方法でクラス定義の中に1つでもコンストラクタを書くと、 暗示的コンストラクタは自動生成されません。 以下の例のように、デフォルトコンストラクタが自動的に用意されることを期待して プログラムを書くとエラーになります。

    ....
    // エラーとなる。
    Circle c = new Circle();
    ....
    ....

class Circle {
    int x, y;

    // 引数なしのコンストラクタ (デフォルトコンストラクタ) を用意していない。
    Circle(int a, int b) {
        x = a; x = b;
    }
}

このような場合、デフォルトコンストラクタを自分で用意する必要があります。

    ....
    // エラーとならない。
    Circle c = new Circle();
    ....
    ....

class Circle {
    int x, y;

    Circle(int a, int b) {
        x = a; x = b;
    }

    // 引数なしのコンストラクタ (デフォルトコンストラクタ) を用意している。
    Circle() {
        x = 0; y = 0;
    }
}

デフォルトコンストラクタは、 オブジェクトを生成する最も一般的な方法であることから、 用意できる場合は用意するように気を配るのが良いと言えます。 逆に、初期化が必須のクラスの場合、 わざとデフォルトコンストラクタを用意しないことで、 まちがったクラスの使われ方を防ぐこともできます。 クラスを定義するときには、 自分の定義したクラスが他の場所で (あるいは、他の人によって) 再利用されるということを意識すると良いでしょう。

メソッドのオーバロード

1クラスの中には同じ名前のメソッドを重複して宣言することができます。 これをメソッドのオーバロード (overload) と呼びます。 次のプログラムは円を表すクラス定義です。 この円には x 座標と y 座標、半径、円周率という情報があり、 座標情報を登録するメソッド setCoordinate があります。

class Circle {
    int x, y;
    int radius;

    void setCoordinate() {
        x = 0;
        y = 0;
        radius = 1;
    }
    void setCoordinate(int a, int b) {
        x = a;
        y = b;
        radius = 1;
    }
    void setCoordinate(int a, int b, int r) {
        x = a;
        y = b;
        radius = r;
    }
}

public class CircleUser {
    public static void main(String[] args) {
        Circle a = new Circle();
        Circle b = new Circle();
        Circle c = new Circle();

        // 引数をすべて省略:       座標(0,0) 半径1の円とする
        a.setCoordinate();

        // 座標情報のみ与える:     座標(5,4) 半径1の円とする
        b.setCoordinate(5, 4);

        // 座標情報と半径を与える: 座標(2,3) 半径2の円とする
        c.setCoordinate(2, 3, 2);

        ....
        ....
    }
}

このクラスにはメソッド setCoordinate の宣言が 3 回行われていますね。 このメソッドは引数の数によって異なった動作をします。

setCoordinate()
最初のメソッドは、 引数をすべて省略して setCoordinate を実行したときに、 実際に実行されるメソッドの内容を宣言しています。 メソッド内では、座標 (0, 0) の半径 1 の単位円、という属性を登録しています。
setCoordinate(int a, int a)
2番目のメソッドは、 int 型整数 2 つが引数に与えられたときに 実行されるメソッドの内容を宣言しています。 引数で与えられた値の座標にある半径 1 の単位円、という属性を登録しています。
setCoordinate(int a, int b, int r)
3番目のメソッドは、 int 型整数 3 つが引数に与えられたときに 実行されるメソッドの内容を宣言しています。 1 番目と 2 番目の引数で与えられた値の座標にある、 3 番目の引数で与えられた値の半径の円、という属性を登録しています。

このように、 同じ setCoordinate という名前のメソッドでも 引数の種類や数によって、 異なった仕事をするようにできます。 メソッドのオーバロードとは、 同じメソッド名に対して複数の実装を行うことを言います。 オーバロードを行ったメソッドは、 それぞれが独立した一つのメソッドのように動作します。

この例では以下の 3 つのメソッドの宣言がありました。

引数の型、引数の数の組み合せをメソッドのシグネチャ (signature) と呼びます。 同じ名前のメソッドとして宣言されていても、 これらが 1 つでも異なれば、別々のメソッドとして扱われます。 オーバロードは、 同じメソッド名でシグネイチャが異なるメソッドについて行うことができます。

次の例ではオーバロードが可能なメソッド宣言の先頭部分を示します。

int myMethod()
int myMethod(int a)
double myMethod(int a, double x)
double myMethod(double x, int a)

特に最後の 2 つは、 いずれも int 型と double 型の引数を 1 つずつ取るメソッドですが、 引数の宣言順が異なるため、異なったシグネチャとなりオーバロードが可能です。

次の例はオーバロードの間違った例です。

double myMethod(int a, int b)
double myMethod(int x, int y)

シグネチャは引数の型と数だけで決定されます。 上の例では変数名が異なっていますが、いずれも int 型であるため、 オーバロードできません。

メソッドの仕事の一部を別のメソッドに下請けさせる

先ほどの例題、クラス Circle の3つのメソッド setCoordinate は、 中身が良く似ています。x, y, radius の 3 つの 属性に何らかの値を登録しているという意味では同じ仕事と見なせます。

class Circle {
    int x, y;
    int radius;

    void setCoordinate() {
        x = 0;
        y = 0;
        radius = 1;
    }
    void setCoordinate(int a, int b) {
        x = a;
        y = b;
        radius = 1;
    }
    void setCoordinate(int a, int b, int r) {
        x = a;
        y = b;
        radius = r;
    }
}

ここで3つめの setCoordinate に注目しましょう。 3つの引数の値を順番に、x座標, y座標, 半径に登録するという働きをしていますね。 引数の組み合わせ (シグネイチャ) が異なれば、 名前は同じでも別のメソッドと扱われるため、 3つめのメソッドを使って上の2つのメソッドを次のように書き換えることができます。

void setCoordinate() {
    setCoodinate(0, 0, 1);    // 3番目のメソッドを0,0,1を引数として実行
}
void setCoordinate(int a, int b) {
    setCoodinate(a, b, 1);    // 3番目のメソッドをa,b,1を引数として実行
}

void setCoordinate(int a, int b, int r) {
    x = a;
    y = b;
    radius = r;
}

このように、クラス内のメソッドから、 自分自身のクラスにあるメソッドを実行することもできます。 その場合、

this.setCoodinate(0, 0, 1);

のように、メソッド名の前に this というキーワードをつけることになっていますが、 特別な場合を除いてthis自体も省略することができます。

コンストラクタのオーバロードを用いた例題

前の章ではメソッドのオーバロードについて説明しました。 コンストラクタのオーバロードの典型的な例を説明します。

先ほどの例題、クラス Circle をコンストラクタを用いて書き換えてみます。

class Circle {
    int x, y;
    int radius;

    Circle() {
        x = 0;
        y = 0;
        radius = 1;
    }

    Circle(int a, int b) {
        x = a;
        y = b;
        radius = 1;
    }

    Circle(int a, int b, int r) {
        x = a;
        y = b;
        radius = r;
    }

}

先ほどメソッドsetCoordinateから、 別のsetCoordinateを実行したように、 コンストラクタの中で、別のコンストラクタを実行することができます。

x座標、y座標、半径を登録する働きをする3つ目のコンストラクタを、 他のコンストラクタからも利用するようなプログラムを考えてみます。 コンストラクタから、 自分自身のクラスの中にある別のコンストラクタを実行したい場合、 キーワード this を用います。

    Circle() {
        this(0, 0, 1);
    }

    Circle(int a, int b) {
        this(a, b, 1);
    }

    Circle(int a, int b, int r) {
        x = a;
        y = b;
        radius = r;
    }

this キーワードは、 クラス定義の中で自分自身を表すのに用います。 上の例のように this( 引数, ... ) と書くと、 自分自身のコンストラクタの中身を実行せよ、という意味になります。

キーワード this について

ここまでで何回か this というキーワードが登場しましたが、 ここで一度詳しく説明しておく必要があります。 メソッドの中で使われる属性の名前やメソッドの名前には、 「自分自身」のものであることを表すために this をつけることができます。

例えば、前の例題のクラス Circle のメソッド setCoordinate は、同じ処理を次のように書くことができます。

class Circle {
    int x, y;
    int radius;
    ......

    void setCoordinate(int a, int b, int c) {
        this.x = a;
        this.y = b;
        this.radius = c;
    }
}

x, y, radius の前にわざわざ this をつけたというわけです。 this はオブジェクト自身を意味します。 this.radius と書くと、自分自身のオブジェクトの属性 radius を明示的に指すことになりますが、 実は、this は省略しても構わない決まりになっています。

this は、メソッドの中で宣言した変数と、属性 (メソッドの外で宣言した変数) を使い分けたいときに使います。次の例を見てみましょう。

class Circle {
    int x, y;          // <------ thisをつけると明示的に、ここで
    int radius;        // <------ 宣言されている属性を指すことになる
    ......

    void setCoordinate(int x, int y, int radius) {
        this.x = x;
        this.y = y;
        this.radius = radius;
    }
}

変数 x, y, radius それぞれが、属性としてメソッドの外で宣言されており、 また、同じ名前の変数がメソッドの中で (引数として) 宣言されています。 このように、変数の名前が重複しているとき、メソッド内では 単に radius と書くと一番内側にある変数 radius を指すことになり、 this.radius と書くとメソッドの外側にある「自分自身の属性である変数 radius」を指すことになります。

thisを使う場面は、 クラスの属性として宣言された変数と、メソッド内部で宣言した変数の名前が 重複する場合です。属性を指すためには this を頭につけます。 メソッド内部で宣言した変数を指すためには、単に変数名を書きます。