第17講・STEP 03

コンストラクタって何? this( )の意味

インスタンスを作るときに、名前や生年月日も一緒に設定できたら便利ですよね。今回はHumanクラスを使って、初期状態を決めるコンストラクタと、初期化処理をまとめるthis( )の使い方を見ていきましょう。

インスタンスの初期状態

 今回はインスタンスの初期状態を自由に操ることが出来るコンストラクタというものについて解説します。その為に【Java】オブジェクトって何?から連載でいじっているHumanクラスを使います。現時点で、Humanクラスはこんな状態です。

Human.java

public class Human{
    String name;
    int birthday;
    int manpukudo;

    void eat(){
        this.manpukudo += 60;
    }
}

 ご覧のようにこのHumanクラスには4つのメンバが存在します。
3つの変数 name,birthday,manpukudo と
1つのメソッド eat( ) です。

 このHumanクラスをインスタンス化します。Humanクラスと同じパッケージ内に作ったHumanTestクラスでやります。

HumanTest.java

public class HumanTest{
    public static void main(String[] args){

        Human human;             //①
        human = new Human();     //②

    }
}

 これで、Humanオブジェクトが作られました。

 この状態でそれぞれのパラメータの値が一体どうなっているのか?出力してみましょう。

HumanTest.java

public class HumanTest{
    public static void main(String[] args){
        Human human;
        human = new Human();

        System.out.println("名前:" + human.name);
        System.out.println("生年月日:" + human.birthday);
        System.out.println("満腹度:" + human.manpukudo);
    }
}

 ↑こんな感じで、作ったオブジェクトのメンバ変数に何が格納されているか全部出力しちゃいます。

実行結果
名前:null
生年月日:0
満腹度:0

 名前はnullです。これは「null」という名前が付いたわけではありません。nullは、どのオブジェクトも参照していないことを表します。空文字列の""とも違います。生年月日と満腹度は0です。

 なぜこうなるかと言うと、クラスをインスタンス化(new)した時に、何の値も入れられていないメンバ変数は、勝手に初期化されるようになっているからです。intやdoubleなどの数値系の型は0(及び0.0)に、boolean型はfalseに、Stringのような参照型はnullに初期化されるというルールがあるんです。

勝手に呼ばれるコンストラクタ

 というように、インスタンスフィールド(メンバ変数)はデフォルト値でインスタンス化されるわけですが、コンストラクタという仕組みを使って、固有の初期値を持ったインスタンスを作ってみたいと思います。名前はノブオ、生年月日は1977年1月1日、満腹度は50の状態のインスタンスを作ります。

Human.java

public class Human{
    String name;
    int birthday;
    int manpukudo;

    Human(){}

    void eat(){
        this.manpukudo += 60;
    }
}

 Human( )がコンストラクタです。名前は、大文字小文字も含めてクラス名と同じにします。後ろに( )と処理を書く{ }があるのでメソッドに似ていますが、コンストラクタはメソッドとは別のものです。

 コンストラクタには戻り値の型を書きません。voidも付けません。コンストラクタの役割はインスタンスの初期化で、コンストラクタ自身がオブジェクトを返すわけではありません。初期化されたインスタンスへの参照は、new Human()という式の結果になります。

 コンストラクタはクラスの{ }の中に、メソッドとは並べて書きます。メンバ変数とメソッドの間にまとめると見やすいと思います。メソッドの中に書くわけではありません。

 上記の状態ではまだコンストラクタの中身は定義していません。メソッドと同じようにそのコンストラクタが呼ばれたらどういう処理をすればいいのかを後ろの{ }の中に書いていきましょう。

Human.java

public class Human{
    String name;
    int birthday;
    int manpukudo;

    Human(){
        this.name = "ノブオ";
        this.birthday = 19770101;
        this.manpukudo = 50;
    }

    void eat(){
        this.manpukudo += 60;
    }
}

 オブジェクトって何? 〔メンバメソッドについて〕 〔thisについて〕で説明しましたが「this」は「このインスタンス」という意味でした。

 thisを使ってこのインスタンスのフィールドにアクセスしています(「フィールド」というのは「メンバ変数」と同義です。同じものでもいろんな言い方があるのでややこしいですね・・)。

 これでこのコンストラクタを使えば、名前がノブオ、生年月日が1977年1月1日、満腹度50のオブジェクトを一発で作れます。が、コンストラクタを使うというのはどういうことでしょう?

 メンバメソッドのeat( )は

HumanTest.java

human.eat()

で使えましたが、コンストラクタは

HumanTest.java

human.Human()

とはやりません。じゃあ、どうやって使うのか?こうでしょ。↓

HumanTest.java

public class HumanTest{
    public static void main(String[] args){
        Human human;
        human = new Human();
    }
}

 ん?コンストラクタをどこで使ったの?と思いますよね。実は「new Human()」でコンストラクタが呼ばれているんです。

 逆にいえば、「new Human()」はそのクラスのコンストラクタを呼ぶ為の記述なんです。その証拠に、どんなオブジェクトが出来ているかさっきと同じように出力させてみましょう。

HumanTest.java

public class HumanTest{
    public static void main(String[] args){
        Human human;
        human = new Human();

        System.out.println("名前:" + human.name);
        System.out.println("生年月日:" + human.birthday);
        System.out.println("満腹度:" + human.manpukudo);
    }
}

 ↑このHumanTestクラスはさっきのデフォルト値が出力された時と一切変えていません。ただ、Humanクラスにノブオを生みだすコンストラクタを加えただけです。new Human( )で、そのコンストラクタが呼ばれてノブオが生まれているはずです。実行してみましょう。

実行結果
名前:ノブオ
生年月日:19770101
満腹度:50

 ノブオが生まれました。new Human( )でコンストラクタが呼ばれている証拠です。

 けど、さっきまでコンストラクタなんて書いていませんでしたよね。コンストラクタがないのにnew Human( )とすることで、(名前はnullでしたが)確かにインスタンスを作ることができました。

 なぜかというと、コンストラクタが書いていないクラスには、デフォルトコンストラクタというのが初めから搭載されているんです。どこにも書いていないけど、インスタンスフィールドをデフォルト値で初期化してインスタンスを作るコンストラクタがひっそり存在していたのです。

コンストラクタに引数を渡す

 お気づきの方は多いかもしれませんが、コンストラクタというのは決してノブオを生むために使うのではありません。

 ノブオであろうと、タカシであろうと、アヤネであろうと、生み出したい名前・状態のインスタンスを生み出せるコンストラクタじゃないと意味がありません。ノブオを何人も作っても仕方ないですから。

 なので、上記のノブオ生成用コンストラクタを、どんな人間でも自由に生み出せるコンストラクタに作り直します。簡単です。引数を使えばいいんです。コンストラクタもメソッドと同じく、引数を渡して処理させることができます。コンストラクタを呼ぶ時に、生み出したい人間の名前と生年月日の情報を引数として渡せばいいんです。

 まずは、コンストラクタがどんな引数を受け取ることができるかを定義しましょう。メソッドと同じです。

Human.java

public class Human{
    String name;
    int birthday;
    int manpukudo;

    Human(String name, int birthday){

    }

    void eat(){
        this.manpukudo += 60;
    }
}

 このようにHumanクラスのコンストラクタの定義部分の( )の中に、受け取るべき変数を宣言します。仮引数というやつです。メソッドと同じですね(参考:メソッドの引数について)。
 複数の場合はこのように「,」で区切ればいくつでも引数を受け取れるようにできます。生み出したい人間の名前と生年月日の値を受け取れるように、第一引数に「String」第二引数に「int」を受け取るようにします。仮引数の名前はそれぞれnameとbirthdayにします。

 では、その受け取った値をどうするか?を定義します。

Human.java

public class Human{
    String name;
    int birthday;
    int manpukudo;

    Human(String name, int birthday){
        this.name = name;
        this.birthday = birthday;
        this.manpukudo = 50;
    }

    void eat(){
        this.manpukudo += 60;
    }
}

 ここでは、右辺のnameは仮引数、左辺のthis.nameはこのインスタンスのフィールドです。this.name = name;で、受け取った名前をフィールドに代入しています。birthdayも同じです。

 初心者は混乱しがちですが、これが理解できれば「this」の意味がより理解できるので、頭を整理してみてください。仮引数のnameとフィールドのnameを区別するために「this」を使うわけです。仮引数の変数名を全然違うものに変えてしまえば混乱しないかもしれませんが、

Human.java

Human(String a, int b)

仮引数の変数名をこんな意味のないものにしてしまうと、可読性が落ちますよね?aって何だったっけ?ってことになり兼ねません。もちろんプログラム的には問題はないのですが。

 これで、このコンストラクタは、渡された第一引数(String型)をフィールドnameに代入して、第二引数(int型)をフィールドbirthdayに代入してインスタンスを作るようになりました。フィールドmanpukudoは誰であろうと50が初期値になるようにしておきました。

 しかし、コンストラクタに引数を渡すには、どうするのか?

 簡単です。コンストラクタを呼ぶnew Human( )の( )の中に引数を記述することで、コンストラクタに引数を渡すことができるんです。

 では、コンストラクタに引数を渡して、いろんなインスタンスを生みだしましょう。

HumanTest.java

public class HumanTest{
    public static void main(String[] args){
        Human human = new Human("ノブオ", 19770101);
        Human human2 = new Human("タカシ", 19801231);
        Human human3 = new Human("アヤネ", 20110101);
    }
}

 これで3人分のオブジェクトを作ることができました。本当にノブオとタカシとアヤネになっているか、また出力してみましょう。ちょっと長いですが、一人ずつ名前と生年月日と満腹度を出力します。

HumanTest.java

public class HumanTest{
    public static void main(String[] args){
        Human human = new Human("ノブオ", 19770101);
        Human human2 = new Human("タカシ", 19801231);
        Human human3 = new Human("アヤネ", 20110101);

        System.out.println("名前:" + human.name);
        System.out.println("生年月日:" + human.birthday);
        System.out.println("満腹度:" + human.manpukudo);
        System.out.println("");

        System.out.println("名前:" + human2.name);
        System.out.println("生年月日:" + human2.birthday);
        System.out.println("満腹度:" + human2.manpukudo);
        System.out.println("");

        System.out.println("名前:" + human3.name);
        System.out.println("生年月日:" + human3.birthday);
        System.out.println("満腹度:" + human3.manpukudo);
    }
}

 これを実行すると、

実行結果
名前:ノブオ
生年月日:19770101
満腹度:50

名前:タカシ
生年月日:19801231
満腹度:50

名前:アヤネ
生年月日:20110101
満腹度:50

 ちゃんと3人ともコンストラクタに渡した通りの名前と生年月日になっていますね。このようにコンストラクタに引数を渡すことでインスタンスの初期状態を自由に操ることが出来るようになります。

 ただし、メソッドと同じで、引数を受け取るようにコンストラクタを定義したら、きっちり過不足無く定義通りの型の引数をコンストラクタに渡さないとコンパイルエラーになります。

 例えば、

HumanTest.java

human = new Human(19770101, "ノブオ");

 こんなことしたらダメです。引数の順番が逆ですよね。

 それともう一つ注意があります。
 コンストラクタを自分で書くと、それまでひっそり存在していたデフォルトコンストラクタは消えてしまうんです。デフォルトコンストラクタは引数を受け取らないコンストラクタなので、以下のように何も渡しませんでした。

 コンストラクタを一つも宣言していないクラスには、引数なしのデフォルトコンストラクタが自動的に用意されます。一つでも自分で宣言すると、自動的には用意されなくなります。

HumanTest.java

Human human = new Human();

 String型とint型の引数を受け取るコンストラクタを書いたことで、new Human()としてしまうとコンパイルエラーになります。引数なしで呼べるコンストラクタがないからです。

 ちょっと困りましたね。便利なコンストラクタを書いたばっかりに、オブジェクトを作る際には必ず名前と生年月日を渡さなければならなくなりました。もしかしたら名前不明とか生年月日不明とかっていうオブジェクトを作るかも知れないのに。。

 しかし、案ずる無かれ、実はコンストラクタのオーバーロードという方法で柔軟なインスタンス化を実現できます。

コンストラクタのオーバーロード

 コンストラクタもメソッドと同じようにオーバーロードすることができます(参考:メソッドのオーバーロードって何?)。

 さっそくやってみましょう。デフォルトコンストラクタが消えて無くなりましたので、その代りに引数を受け取らないコンストラクタを書き加えます。

Human.java

public class Human{
    String name;
    int birthday;
    int manpukudo;

    Human(String name, int birthday){   //1つ目のコンストラクタ
        this.name = name;
        this.birthday = birthday;
        this.manpukudo = 50;
    }
    Human(){                            //2つ目のコンストラクタ
        this.name = "不明";
        this.birthday = 0;
        this.manpukudo = 50;
    }

    void eat(){
        this.manpukudo += 60;
    }
}

 これで、Humanクラスをnewする時(コンストラクタを呼ぶ時)、String型とint型の引数を渡せば、1つ目のコンストラクタが呼ばれて、引数を渡さずにnewすると2つ目のコンストラクタが呼ばれるようになりました。

 もし名無しで生年月日不明の人間オブジェクトを作りたくなったら、今まで通り、引数に何も入れずにnewすればいいことになります。その場合、名前は「不明」、生年月日は「0」、満腹度は「50」の状態でインスタンスが作られるようにしました。

 こうして2つのコンストラクタができたわけですが、やっている処理はよく似ていると言えば似ていますよね。ただ単にフィールドに与えられた値を代入しているだけですから。
 まだ2つだけなら大したことないですが、もし3つも4つも引数が違うだけで処理内容のよく似たコンストラクタをズラズラ書くのは不効率だし面倒です。

 そんな時使えるのが、

Human.java

this()

です。

this( )の使い方

 今度はthis(...)です。これはコンストラクタの中から、同じクラスの別のコンストラクタを呼ぶための書き方です。this.nameのようにフィールドを指定する書き方とは役割が違います。別のインスタンスを作るのではなく、同じインスタンスの初期化を別のコンストラクタに任せます。

 そして、this( )はコンストラクタの中で使います。

 頭、混乱しますよね。分かります。初心者にとって非常に難しいところです。ギターで言うFコードです。笑

 けど大丈夫です。とりあえずやってみましょう。

Human.java

public class Human{
    String name;
    int birthday;
    int manpukudo;

    Human(String name, int birthday){
        this.name = name;
        this.birthday = birthday;
        this.manpukudo = 50;
    }
    Human(){
        this("不明", 0);
    }

    void eat(){
        this.manpukudo += 60;
    }
}

 こういうことです。

 落ち着いて考えて下さい。このthis( )は1つ目のコンストラクタのことです。2つ目のコンストラクタの中で1つ目のコンストラクタを呼んでいるんです。

 this(“不明”, 0)というふうに、2つの引数(Stringとint)を渡しているので、これは1つ目のコンストラクタで定義した仮引数と合致するため、結果的に、このthis( )は1つ目のコンストラクタを指していると解釈されるわけです。

 メソッドのオーバーロードと全く同じ仕組みです。

 この状態で、引数無しでインスタンス化(new)すると、1つめのコンストラクタに(“不明”, 0)という引数を渡すことになり、名前が不明、生年月日が0、満腹度が50のインスタンスが誕生することになります。

 このように、コンストラクタの中でthis( )を使うことで、そのクラスで定義されている他のコンストラクタを呼ぶことができるわけです。

 では、理解を深める為にもう一つコンストラクタを作ります。今度は満腹度も操作できるコンストラクタを作りましょう。

Human.java

public class Human{
    String name;
    int birthday;
    int manpukudo;

    Human(String name, int birthday){
        this.name = name;
        this.birthday = birthday;
        this.manpukudo = 50;
    }
    Human(){
        this("不明", 0);
    }
    Human(String name, int birthday, int manpukudo){
        this(name, birthday, manpukudo);   //①
    }

    void eat(){
        this.manpukudo += 60;
    }
}

 インスタンス化(new)する際に引数を3つ渡されたら、満腹度もセットできるようにするんだ!ってことでthis( )を使ってこんなことしました。

 ①ではthis( )に対して3つの引数を渡しています。

 3つの引数を受け取るコンストラクタって、どれでしょう?自分自身ですよね。つまりこのthis( )はこの3つめのコンストラクタを指していることになります。

 ん? 3つ目のコンストラクタを呼ぶと、3つ目のコンストラクタが呼ばれる・・?

 アウトです。

 コンストラクタがthis(...)で自分自身を呼ぶ書き方は、コンパイルエラーになります。別のコンストラクタを経由して自分に戻る場合も同じです。実行時に呼び出し続けるのではなく、コンパイルの段階で禁止されています。

 う~ん、ってことは受け取れる引数を増やしたい時はthis( )は使えないのか・・?

 そんなことはありません。賢明な読者様ならお気づきですよね?

 最も引数が多いコンストラクタの定義部分に具体的な処理内容を書いて、より引数の少ないコンストラクタ内でthis( )を使えばいいんです。

Human.java

Human(String name, int birthday, int manpukudo){
    this.name = name;
    this.birthday = birthday;
    this.manpukudo = manpukudo;
}

 ↑3つの引数を受け取るコンストラクタ内でそれぞれのフィールドに値を代入する処理を書き、それを他のコンストラクタ内でthis( )として使い回すわけです。

Human.java

Human(String name, int birthday, int manpukudo){ //①
    this.name = name;
    this.birthday = birthday;
    this.manpukudo = manpukudo;
}
Human(String name, int birthday){ //②
    this(name, birthday, 50);
}
Human(String name){ //③
    this(name, 0, 50);
}
Human(){ //④
    this("不明", 0, 50);
}

 インスタンス化する際、つまりnew Human( )の引数に名前と生年月日と満腹度を渡した場合は、①のコンストラクタが呼ばれて、それぞれのパラメータを持つインスタンスが作られます。

 同じくインスタンス化する際、名前と生年月日だけ渡した場合は②のコンストラクタが呼ばれて、自動的に満腹度は50のインスタンスが作られます。

 同じくインスタンス化する際、引数に名前だけ渡した場合は③のコンストラクタが呼ばれて、自動的に生年月日は0、満腹度は50のインスタンスが作られます。

 引数に何も渡さずにインスタンス化した場合は④のコンストラクタが呼ばれて、自動的に名前は不明、生年月日は0、満腹度は50のインスタンスが作られます。

 コンストラクタを書く順番はプログラムには影響しませんが、一番引数の多いコンストラクタを一番上に書いて、それ以降のコンストラクタ内でthis( )を使うようにすれば、可読性も高まると思います。

 では、実際にこれらのコンストラクタを使ってオブジェクトを作ってみたいと思います。そしてまた出来たインスタンスのそれぞれのフィールドを出力します。

HumanTest.java

public class HumanTest{
    public static void main(String[] args){
        Human human = new Human("ノブオ", 19770101, 100); //引数3つ
        Human human2 = new Human("アヤネ", 20110101);     //引数2つ
        Human human3 = new Human();                      //引数なし

        System.out.println("名前:" + human.name);
        System.out.println("生年月日:" + human.birthday);
        System.out.println("満腹度:" + human.manpukudo);
        System.out.println("");

        System.out.println("名前:" + human2.name);
        System.out.println("生年月日:" + human2.birthday);
        System.out.println("満腹度:" + human2.manpukudo);
        System.out.println("");

        System.out.println("名前:" + human3.name);
        System.out.println("生年月日:" + human3.birthday);
        System.out.println("満腹度:" + human3.manpukudo);
    }
}

 これを実行すると、

実行結果
名前:ノブオ
生年月日:19770101
満腹度:100

名前:アヤネ
生年月日:20110101
満腹度:50

名前:不明
生年月日:0
満腹度:50

出来ました。4つ定義したうち、今回使った3つのコンストラクタが呼ばれているのが分かりますでしょうか?

 このように複数のコンストラクタを定義しておけば、インスタンス化(new)する際に渡す引数の数や型によって引数に合うコンストラクタが選ばれる状態になります。もちろん定義したものと違う引数を渡してしまうとコンパイルエラーになります。

 一番多く引数を受け取るコンストラクタを定義した上で、引数の少ないコンストラクタはthis( )を使って書いていけばコンストラクタが多くなっても効率よく書けると思います。

this( )とオーバーロード

最後に、this( )の理解度チェックをいたしましょう。

 ↓これは先程書いた4つのコンストラクタです。

Human.java

Human(String name, int birthday, int manpukudo){ //①
    this.name = name;
    this.birthday = birthday;
    this.manpukudo = manpukudo;
}
Human(String name, int birthday){ //②
    this(name, birthday, 50);
}
Human(String name){ //③
    this(name, 0, 50);
}
Human(){ //④
    this("不明", 0, 50);
}

 これを実行結果を変えずに、以下のように書き換えることができます。

Human.java

Human(String name, int birthday, int manpukudo){ //①
    this.name = name;
    this.birthday = birthday;
    this.manpukudo = manpukudo;
}
Human(String name, int birthday){ //②
    this(name, birthday, 50);
}
Human(String name){ //③
    this(name, 0);
}
Human(){ //④
    this("不明");
}

 違いは、this( )に渡している引数の数です。

 上のコードでは、それぞれのthis( )が指しているのは全て①のコンストラクタです。しかし下のコードでは、違います。

 下のコードのそれぞれのthis( )がどのコンストラクタのことなのか?考えてみて下さい。

答えを言うと、
②のthis( )は①のコンストラクタ、
③のthis( )は②のコンストラクタ、
④のthis( )は③のコンストラクタ、
を、それぞれ意味しています。

 この微妙な違いが理解出来れば、コンストラクタ及びthis( )の使い方に関して理解できていると言えると思います。

 さあ、異常に長かったですが、コンストラクタについてはこれで終わりです。

 どんなクラスでもいいので自分で作ってインスタンス化してみたら理解が深まると思うので、挑戦してみてください。お手本を写すなんてことしても全く勉強になりません。自分で考えて書くのが大事です。こんなクラスを作ってみよう!と自分で設計してみるのが何より勉強になると思います。

 次回はstaticについてちょっと詳しくやります。

目次へ

質問・コメント

読み込み中

この記事についての質問や、学習して気づいたことをお寄せください。

コメントを書く

投稿フォームを準備しています。

コメントと一緒に公開される名前です。
3,000文字以内。パスワードなどの個人情報は書き込まないでください。

コメントは送信後すぐに公開されます。

この記事のコメント

旧サイトに寄せられたコメントも当時の投稿内容のまま掲載しています。現在の記事では変更している内容や、旧環境についてのやり取りも含まれます。

コメントを読み込んでいます。