2019/02/06
sponsored link
インスタンスの初期状態
今回はインスタンスの初期状態を自由に操ることが出来るコンストラクタというものについて解説します。その為に【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型の変数humanを宣言。
②new演算子を使ってHumanクラスをインスタンス化して、(その参照を)変数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です。これは「null」という名前を勝手につけられたわけではありません。nullというのは空っぽを意味するプログラミング用語です。生年月日は0。満腹度も0です。
なぜこうなるかと言うと、クラスをインスタンス化(new)した時に、何の値も入れられていないメンバ変数は、勝手に初期化されるようになっているからです。その勝手に初期化する際に、intやdaubleなどの数値系の型は0(及び0.0)に、boolean型はfalseに、Stringのような参照型はnullに初期化されるというルールがあるんです。
コンストラクタを呼ぶ
上記のように、オブジェクトを作った時に勝手にデフォルト値に初期化されるメンバ変数を、勝手に初期化させるのではなく、自分で値を決めて初期化する為に使うのが、コンストラクタというやつです。
このHumanクラスで言うと、名前はnobuo、生年月日は1977年1月1日、満腹度は50の状態でオブジェクトを作りたいといった時に使います。
さっそく書いていきます。
Human.java
public class Human{
String name;
int birthday;
int manpukudo;
Human(){}
void eat(){
this.manpukudo += 60;
}
}
Human( )がコンストラクタです。見ての通り、クラス名そのままです。クラス名の後ろに( )をつけただけです。( )がついているのでメソッドと見た目は変わりません。メソッドと同じような形で、そのメソッド名が大文字小文字も含めて一語一句クラス名と同じものがコンストラクタとして認識されると思っていたらいいです。
メソッドとの大きな違いは、コンストラクタは戻り値の型を指定する必要がないということです。指定せずともHumanクラスのコンストラクタは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( )は
human.eat()
で使えましたが、コンストラクタは
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( )で、そのコンストラクタが呼ばれてノブオが生まれているはずです。実行してみましょう。
ノブオが生まれました。new Human( )でコンストラクタが呼ばれている証拠です。
けど、さっきまでコンストラクタなんて書いていませんでしたよね。コンストラクタがないのにnew Human( )とすることで、(名前はnullでしたが)確かにインスタンスを作ることができました。
なぜかというと、コンストラクタが書いていないクラスには、デフォルトコンストラクタというのが初めから搭載されているんです。どこにも書いていないけど、在るんです。
デフォルトコンストラクタが呼ばれると、全てのフィールドが決められたデフォルト値の状態でそのインスタンスを生みだします。だから、さっきコンストラクタをまだ書いていない状態でnew Human( )としてインスタンスを作った時は、デフォルトコンストラクタが呼ばれて、名前null、生年月日0、満腹度0のインスタンスが生まれたんです。
コンストラクタに引数を渡す
お気づきの方は多いかもしれませんが、コンストラクタというのは決してノブオを生むために使うのではありません。
ノブオであろうと、タカシであろうと、アヤネであろうと、生み出したい名前・状態のインスタンスを生み出せるコンストラクタじゃないと意味がありません。ノブオを何人も作っても仕方ないですから。
なので、上記のノブオ生成用コンストラクタを、どんな人間でも自由に生み出せるコンストラクタに作り直します。簡単です。引数を使えばいいんです。コンストラクタもメソッドと同じく、引数を渡して処理させることができます。コンストラクタを呼ぶ時に、生み出したい人間の名前と生年月日の情報を引数として渡せばいいんです。
まずは、コンストラクタがどんな引数を受け取ることができるかを定義しましょう。メソッドと同じです。
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」は仮引数のnameを、青字の「name」はフィールドのnameを指しているということ。birthdayも同じです。
初心者は混乱しがちですが、これが理解できれば「this」の意味がより理解できるので、頭を整理してみてください。仮引数のnameとフィールドのnameを区別するために「this」を使うわけです。仮引数の変数名を全然違うものに変えてしまえば混乱しないかもしれませんが、
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); } }
これを実行すると、
ちゃんと3人ともコンストラクタに渡した通りの名前と生年月日になっていますね。このようにコンストラクタに引数を渡すことでインスタンスの初期状態を自由に操ることが出来るようになります。
ただし、メソッドと同じで、引数を受け取るようにコンストラクタを定義したら、きっちり過不足無く定義通りの型の引数をコンストラクタに渡さないとコンパイルエラーになります。
例えば、
human = new Human(19770101, "ノブオ");
こんなことしたらダメです。引数の順番が逆ですよね。
それともう一つ注意があります。
コンストラクタを自分で書くと、それまで存在していたデフォルトコンストラクタは消えてしまうんです。どういうことか?ちょっと説明。
コンストラクタを書いていない時は、デフォルトコンストラクタというのがこっそり存在していると先ほど言いました。コンストラクタを書いていないクラスをnewすると、デフォルトコンストラクタが呼ばれて全てのフィールドはデフォルト値の状態でインスタンスが作られると。
Human human = new Human();
デフォルトコンストラクタを呼ぶにはnewする時に引数を渡してはいけません。デフォルトコンストラクタは引数を受け取らないコンストラクタということです。
引数を受け取って処理するコンストラクタを自分で書いた結果、引数なしで呼べていたデフォルトコンストラクタが消えてしまうことになります。結果的に、引数を受け取れるコンストラクタを書いたクラスを、引数を渡さずにnewしてしまうと「コンストラクタに渡すべき引数が抜け落ちてますよ」ということでコンパイルエラーになってしまうんです。
困りました。名前と生年月日が分からないとオブジェクトを作れないんです。もしかしたら名前不明とか生年月日不明とかっていうオブジェクトを作るかも知れないのに。。
しかし、案ずる無かれ、実はコンストラクタのオーバーロードという手があるんです。
コンストラクタのオーバーロード
コンストラクタもメソッドと同じようにオーバーロードすることができます(参考:メソッドのオーバーロードって何?)。
さっそくやってみましょう。デフォルトコンストラクタが消えて無くなりましたので、その代りに引数を受け取らないコンストラクタを書き加えます。
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つも引数が違うだけで処理内容のよく似たコンストラクタをズラズラ書くのは不効率だし面倒です。
そんな時使えるのが、
this()
です。
this( )の使い方
これまた初心者に厳しいところです。
this( )は、「このインスタンス」を意味するthisとは全く関係ありません。this( )は「このクラスのコンストラクタ」という意味なんです。( )が付いただけで全然違うものを指します。
そして、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( )を使えばいいんです。
Human(String name, int birthday, int manpukudo){ this.name = name; this.birthday = birthday; this.manpukudo = manpukudo; }
↑3つの引数を受け取るコンストラクタ内でそれぞれのフィールドに値を代入する処理を書き、それを他のコンストラクタ内でthis( )として使い回すわけです。
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("アヤネ", 19801231); //引数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);
}
}
これを実行すると、
出来ました。3つ定義したそれぞれのコンストラクタが呼ばれているのが分かりますでしょうか?
このように複数のコンストラクタを定義しておけば、インスタンス化(new)する際に渡す引数の数や型によって自動的に適当なコンストラクタを呼ばれる状態になります。もちろん定義したものと違う引数を渡してしまうとコンパイルエラーになります。
一番多く引数を受け取るコンストラクタを定義した上で、引数の少ないコンストラクタはthis( )を使って書いていけばコンストラクタが多くなっても効率よく書けると思います。
this( )とオーバーロード
最後に、this( )の理解度チェックをいたしましょう。
↓これは先程書いた4つのコンストラクタです。
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(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についてちょっと詳しくやります。
今だけ→転職できなければ全額返金の「エンジニア転職保証コース」
絶対エンジニアになる!→テックエキスパート
フリーランスエンジニアの収入例を見てみる→レバテックフリーランス
コメント
今までなんとなくで理解してたことがすっきり整理できました!
これからの人生で活用させて頂きます!管理人さんありがとうございます!!
by ああ 2019/12/11 13:55
初学者です。大変参考になります。
下は表題「this( )とオーバーロード」の上方2つ目です。
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( )の引数に名前と生年月日と満腹度を渡した場合は、①のコンストラクタが呼ばれて、それぞれのパラメータを持つインスタンスが作られます。
同じくインスタンス化する際、名前と生年月日だけ渡した場合は②のコンストラクタが呼ばれて、…
とありますが、全て①のコンストラクタが呼ばれるのではないでしょうか?
返信お願いします。
java勉強中の者より。
by java勉強中の者 2024/06/18 20:49
java勉強中の者です。
表題「this( )とオーバーロード」の最後の方の
「答えを言うと、
②のthis( )は①のコンストラクタ、
③のthis( )は②のコンストラクタ、
④のthis( )は③のコンストラクタ、
を、それぞれ意味しています。
この微妙な違いが理解出来れば、コンス…」
④→③→②→①
③→②→①
②→①
で結局、全て①になるのではないでしょうか?
よろしくお願いします。
java勉強中の者より。
by java勉強中の者 2024/06/18 21:15