こんにちは。ECF Techブログ
担当 Michiharu.Tです。
Javaプログラミング入門記事の第8章をお送りいたします。
第8章のテーマは「コンストラクタ」です。
本連載の初回および章立ての一覧については下記のリンクから確認できます。
解説動画
2024/01/27 解説動画を掲載しました。
8-1 コンストラクタの基本
8-1-1 コンストラクタの定義
コンストラクタ(constructor)は主にフィールドの初期化に用いられる特殊な処理ブロックで、クラス内に定義可能な要素のひとつです。まずは定義方法をご紹介します。
コンストラクタの定義
クラス名(引数...){ //フィールドの初期化などの処理 }
定義方法はメソッドと似ていますが、次の点が異なります。
- メソッド名にあたる部分はクラス名と同じにしなければならない。
- 戻り値の概念がない。
商品を表すItemクラスを例にした場合、次のようなコンストラクタ定義が考えられます。
プログラム例(Item.java)
public class Item { //フィールド String name; //名前 int price; //価格 //コンストラクタの定義 Item(String data1, int data2){ //2つのフィールドに引数の値を代入 name = data1; price = data2; } void display() { System.out.println(name + ":" + price); } }
7~11行目がコンストラクタを定義した部分です。このコンストラクタはString型とint型の値を引数として受け取り、それぞれをフィールドに代入する処理となっています。
8-1-2 コンストラクタの利用
それでは次に、8-1-1で定義したコンストラクタを持つItemクラスを利用するプログラムを記述します。まず始めにコンストラクタを利用する(呼び出す)ための文法を示します。
コンストラクタの呼び出し
new クラス名(引数...)
文法は上記のとおり、インスタンスを生成する際の文法と同じです。つまり、コンストラクタの呼び出しはインスタンス生成のタイミングで行われます。
プログラム例を示します。Itemクラスは8-1-1で定義されたものを使用します。
プログラム例(Main.java)
public class Main { public static void main(String[] args) { //インスタンスの生成(コンストラクタ呼びだし) Item mikan = new Item("みかん",200); //displayメソッドの実行 mikan.display(); } }
実行結果
みかん:200
解説
4行目のインスタンス生成の文が実行された際の動作イメージを図に示します。
図の右側はItemクラスで、コンストラクタが定義されています。図の左のメインクラスでは、最初の処理でItemクラスのインスタンスを生成する文が実行されます。このタイミングでコンストラクタが呼び出されます。
呼び出し時の引数は"みかん"
と200
になっており、この2つの値がコンストラクタに渡されて、コンストラクタ内の処理が動作します。コンストラクタ内の処理は、引数で渡された値をフィールドに代入する処理となっています。
これにより、生成されたばかりのItemインスタンスのフィールドに値が代入されることになります。
生成されたインスタンスは、メインクラス側で変数mikanに代入されていますので、mikanの名前でインスタンスを操作できるようになります。
次にメインクラスの6行目でmikan.display()
が実行され、実行結果のように表示されます。
8-2 デフォルトコンストラクタ
8-2-1 存在しないコンストラクタ
少し振り返りになりますが、7章で最初に登場したItemクラスとメインクラスは次のようなものでした。
この時、Itemクラスにはコンストラクタがありませんでした。ですが、メインクラスではインスタンス生成、つまり、コンストラクタの呼び出しが行われています。これはどういうことなのでしょうか。
その答えはデフォルトコンストラクタの存在にあります。
8-2-2 デフォルトコンストラクタ
デフォルトコンストラクタは、プログラムにより明示的にコンストラクタが定義されない場合に限り自動で追加される、引数無し・処理無しのコンストラクタです。
つまり、7章で定義していたItemクラスにはデフォルトコンストラクタが作成され、メインクラスがインスタンス生成をする際はデフォルトコンストラクタを呼び出していたというわけです(下図)。
8-2-3 デフォルトコンストラクタの注意点
コンストラクタ作成の手間を省略できるデフォルトコンストラクタですが、ここで注意点についても説明したいと思います。プログラム例を示します。
プログラム例(Item.java)
public class Item { //フィールド String name; //名前 int price; //価格 //コンストラクタの定義 Item(String data1, int data2){ //2つのフィールドに引数の値を代入 name = data1; price = data2; } void display() { System.out.println(name + ":" + price); } }
プログラム例(Main.java)
public class Main { public static void main(String[] args) { Item mikan = new Item(); } }
このプログラムは、Main.javaをコンパイルする際にエラーとなってしまいます。
Main.javaの3行目では、インスタンス生成時に引数のないコンストラクタを呼び出す記述となっています。ですが、Itemクラスには引数つきのコンストラクタしかありません。また、プログラムで明示的にコンストラクタを定義しているため、デフォルトコンストラクタが作られることもありません。したがって、Itemクラスには引数なしのコンストラクタが存在しないことになるため、エラーとなってしまいます。
8-3 this表記
this表記についてご紹介します。this表記は自身のインスタンスを表す特殊な表記です。使い方はいくつかありますが、ここではコンストラクタでよく用いられる自身のフィールドを表す表記をご紹介します。
8-3-1 良い識別子とは
変数名やメソッド名、引数名などのいわゆる識別子は、データや処理の内容が推測できる名称とすることが、良い方法の1つとされています。この観点から、Itemクラスのコンストラクタに用いられていた引数名data1
、data2
は、データの内容が推測できる良い識別子とは言えません。
Itemクラス(コンストラクタ部)
Item(String data1, int data2){ //2つのフィールドに引数の値を代入 name = data1; price = data2; }
だからといって、次の様に修正することもできません。
Itemクラス(コンストラクタ部)
Item(String name, int price){ //2つのフィールドに引数の値を代入 name = name; price = price; }
なぜならこの場合、代入処理に用いられているnameやpriceはすべて引数名を指していると判断されてしまうため、フィールドへの値の代入とはならないからです(下図)。
this表記を使うと、これらの問題を改善することができます。
8-3-2 this表記の使い方
this表記を使ったフィールドの表し方は次のとおりです。
thisを使ったフィールドの指定
this.フィールド名
Itemクラスのコンストラクタを、上の表記を使って書き換えた例を示します。
プログラム例(Item.java)
public class Item { //フィールド String name; //名前 int price; //価格 //コンストラクタの定義 Item(String name, int price){ //2つのフィールドに引数の値を代入 this.name = name; this.price = price; } void display() { System.out.println(name + ":" + price); } }
コンストラクタの記述を修正しています。フィールドへの代入はthis.price = price;
のようになっています。この代入文においてthis.price
はフィールドprice
を表し、price
は引数のprice
を表すため、正しい代入ができます(下図)。また、引数名をフィールド名と同じとすることで、データ内容の推測がしやすくなります。
8-4 コンストラクタのオーバーロード
コンストラクタもメソッドの様にオーバーロードすることができます。ここでは、商品を表すItemクラスに新たに種別を表すフィールドが追加される例を題材とし、オーバーロードについて見ていきます。
プログラム例(Item.java)
public class Item{ String name; //名前 int price; //価格 String category;//種別 //コンストラクタ1 Item(String name, int price){ this.name = name; this.price = price; this.category = "未定"; } //コンストラクタ2 Item(String name, int price, String category){ this.name = name; this.price = price; this.category = category; } void display(){ System.out.println( name + ":" + price ); System.out.println("("+ category +")"); } }
7,14行目にそれぞれコンストラクタが定義されています。コンストラクタがオーバーロード可能な条件もメソッドと同様です。引数部分を型だけ並べたときに区別ができるかどうか。 です。上の例ではコンストラクタ1が(String, int)
、コンストラクタ2が(String, int, String)
となり区別ができるのでオーバーロード定義が可能です。
また、displayメソッドには新しいフィールドcategoryを表示する処理(20行目)が追加されています。
次にメインクラスのプログラムを見ながら、動作の様子を確認します。
プログラム例(Main.java)
public class Main{ public static void main(String[] args){ Item mikan = new Item("みかん",200,"果物"); Item ticket = new Item("ギフト券",1000); mikan.display(); ticket.display(); } }
実行結果
みかん:200 (果物) ギフト券:1000 (未定)
解説
- メインプログラムでは3,4行目でItemインスタンスの生成を行っています。
- 3行目は引数3つのコンストラクタを呼び出しているため、Itemクラスのコンストラクタ2が実行されます。
- 4行目は引数2つのコンストラクタを呼び出しているため、Itemクラスのコンストラクタ1が実行されます。
8-4-1 thisによるコンストラクタ呼び出し
8-4で示したプログラムには、7,8行目と13,14行目に次のような全く同じ処理が存在します。
this.name = name; this.price = price;
これをthisを使ったコンストラクタ呼び出しを使って改良したいと思います。文法は次のようになります。
this(引数)
この記述を使うと、自クラス内の別のコンストラクタを呼び出すことができます。Itemクラスを改良したプログラム例を示します。
プログラム例(Item.java)
public class Item{ String name; //名前 int price; //価格 String category;//種別 //コンストラクタ1 Item(String name, int price){ this(name,price,"未定"); } //コンストラクタ2 Item(String name, int price, String category){ this.name = name; this.price = price; this.category = category; } void display(){ System.out.println( name + ":" + price ); System.out.println("("+ category +")"); } }
7行目の記述を
this.name = name; this.price = price;
から
this(name,price,"未定");
に変更しています。修正したコンストラクタが、メインクラスから呼び出される様子を下図に示します。
- メインクラスの4行目で引数2つのコンストラクタが呼び出され、インスタンス生成が行われます。
- 引数2つのコンストラクタでは、即座に自クラスの引数3つのコンストラクタが呼び出されます。
- 引数3つのコンストラクタが、各フィールドに値を設定します。
なお、thisによるコンストラクタ呼び出しは別のコンストラクタの先頭にしか記述することができません。たとえば上記Itemクラスのコンストラクタ1を次のように書いてしまうとエラーになります。
//コンストラクタ1 Item(String name, int price){ System.out.println("インスタンス初期化!"); this(name,price,"未定"); }