こんにちは。ECF Techブログ
担当 Michiharu.Tです。
Javaプログラミング入門記事の第7章をお送りいたします。
第7章のテーマは「オブジェクト」です。
第7章は2回のパートに分けて掲載します。今回はPart2です。
本連載の初回および章立ての一覧については下記のリンクから確認できます。
解説動画
2024/01/23 解説動画を掲載しました。
7-5 クラス型変数
クラス名を型とする変数(本講座ではクラス型変数と呼称します)について、その詳細を説明します。
7-5-1 クラス型変数の本質
クラス型変数はすべて参照型変数となります。前回の7-4-2のプログラムを再掲し、イメージを示します。
プログラム例(Item.java)
public class Item { //フィールド String name; //名前 int price; //価格 //メソッド void display() { System.out.println(name + ":" + price); } }
プログラム例(Main.java)
public class Main { public static void main(String[] args) { //インスタンスの生成 Item mikan = new Item(); //フィールドへの代入 mikan.name = "みかん"; mikan.price = 200; //メソッドの利用 mikan.display(); } }
図を踏まえて、改めてMain.javaのプログラムについて説明します。
4行目のnew Item()
により、メモリ領域内にインスタンス用の領域が確保されます。左辺は変数への代入になっていますが、ここで代入されるのはインスタンスそのものではなく、メモリ内に確保された領域のアドレスです。
上図のように確保された領域のアドレスを1500番と仮定すると、変数mikanには1500が代入されることになります。これにより、変数mikanを使ってインスタンスに対する操作・指示ができるようになるというしくみです。mainメソッドでは以降、変数mikanを使ってフィールドに値を設定したり、メソッドを呼び出したりしています。
7-5-2 クラス型変数の動作確認
それでは、クラス型変数が参照型変数であることを確認してみましょう。プログラム例を示します。Itemクラスは7-5-1で再掲しているものを使用します。
プログラム例(Main.java)
public class Main { public static void main(String[] args) { Item item1 = new Item(); Item item2 = item1; item1.name = "みかん"; item1.price = 200; item2.display(); } }
実行結果
みかん:200
解説
- 3行目:Itemインスタンスを生成し、変数
item1
に代入しています。 - 4行目:Item型の変数
item2
を宣言し、変数item1
を代入しています。この時代入されるのは、変数item1
が保持しているアドレスです。よって、下図のオレンジ色の矢印のように変数item1
と同じインスタンスを示します。 - 5,6行目:変数
item1
を使って、フィールドに値を代入しています。 - 7行目:変数
item2
を使ってdisplayメソッドを呼び出しています。変数item2
が指しているのは、変数item1
が指すのと同じインスタンスなので、5,6行目でフィールドに設定した値が反映されています。
7-6 メソッドのオーバーロード
1つのクラス内に同じ名前のメソッドを複数定義することを、メソッドのオーバーロード(overload) と呼びます。類似する機能を持つメソッドを作成する際に便利です。
ここでは、Itemクラスに次のような2つのメソッドを追加する例を使って説明します。
プログラム例(Item.java)
public class Item { //フィールド String name; //名前 int price; //価格 //メソッド void display() { System.out.println(name + ":" + price); } void displayWithMessage(String message){ System.out.println( message ); System.out.println(name + ":" + price); } }
上のItemクラスでは、これまで登場したdisplayメソッドに加え、商品情報と共にメッセージを表示するdisplayWithMessageメソッドを追加しています。
これら2つのメソッドは「商品情報を表示する」という点で類似したメソッドと言えます。このような、動作が類似するメソッドはオーバーロードのしくみを利用して同じメソッド名にすることで、効率的にプログラムを記述できることがあります。
7-6-1 オーバーロードを使ったプログラム
それでは、オーバーロードのしくみを活かして作成したItemクラスとメインクラスのプログラムを見てみましょう。
プログラム例(Item.java)
public class Item { //フィールド String name; //名前 int price; //価格 //メソッド void display() { System.out.println(name + ":" + price); } //displayメソッドを追加。 void display(String message) { System.out.println(message); System.out.println(name + ":" + price); } }
プログラム例(Main.java)
public class Main { public static void main(String[] args) { Item mikan = new Item(); mikan.name = "みかん"; mikan.price = 200; //2つのメソッドを呼び出し mikan.display(); mikan.display("本日のお買い得!"); } }
実行結果
みかん:200 本日のお買い得! みかん:200
解説
Item.javaにおいて、7行目,12行目のそれぞれで、同じdisplayという名前のメソッドが定義されています。
Main.javaでは7,8行目において、それぞれのdisplayメソッドを呼び出しています(下図)。
最初の呼び出しは引数を指定せず呼び出しているため、Item.javaの7行目に定義されたdisplayメソッドが呼び出され、
みかん:200
が表示されます。次の呼び出しは引数に文字列を指定して呼び出しているため、12行目に定義されたdisplayメソッドが呼び出され、
本日のお買い得! みかん:200
が表示されます。
7-6-2 オーバーロード可能な条件
メソッドのオーバーロードは、メソッド名を同じにすればよい。というわけではありません。メソッドのオーバーロードが可能になるには条件があります。それは、引数を型だけ並べたときに区別がつけられることです。下図で説明します。
図の左はオーバーロードが可能な例です。2つのメソッドmethodAが定義されています。上のmethodAは引数の型だけを並べると(int, int)
となります。それに対し下のmethodAは引数の型だけを並べると(int)
です。双方が区別可能なため、オーバーロードが可能です。
一方、図の右はオーバーロードができない例です。左のプログラム同様2つのメソッドmethodAが定義されています。こちらはいずれのメソッドも引数の型だけ並べると (int, int)
となり、区別がつけられません。この場合はオーバーロードができません。
なお、図にもあるとおり、戻り値の型の違いはオーバーロードの可否とは無関係です。
7-6-3 自身のメソッドを呼び出す
ここでは、自クラスのメソッドを呼び出すことで、7-6-1で示したItemクラスに少し改良を加えたいと思います。プログラム例を示します。
プログラム例(Item.java)
public class Item { //フィールド String name; //名前 int price; //価格 //メソッド void display() { System.out.println(name + ":" + price); } //displayメソッドを追加。 void display(String message) { System.out.println(message); display(); } }
修正箇所は14行目です。修正前はSystem.out.println(name + ":" + price);
となっていました。つまり、7行目のdisplayメソッドと全く同じ処理をしていました。全く同じ処理なら7行目のメソッドに処理を任せてしまおう。という発想からこのような修正となっています。
同一クラス内のメソッドを呼び出す際の書き方は下のとおりです。
メソッド名(引数...)
インスタンス内でのメソッド呼び出しのイメージは次のようになります。
上図のように、インスタンス自身の引数のないdisplayメソッドが呼び出されます。フィールドも自身のフィールドを参照することになるため、7-6-1に示すMain.javaを再度実行しても、同じ実行結果が得られます。
7-7 プログラムの保守性
7-6-3で示したプログラムの修正は、プログラムの保守性を高めるための修正という事ができます。保守性とは、簡単に言えばプログラムの維持のしやすさ、修正のしやすさのことです。
7-6-3で修正したItem.javaは、元々次のようなプログラムになっていました。
プログラム例(Item.java 7-6-1再掲)
public class Item { //フィールド String name; //名前 int price; //価格 //メソッド void display() { System.out.println(name + ":" + price); } void display(String message){ System.out.println( message ); System.out.println(name + ":" + price); } }
このプログラムでは8行目と13行目に全く同じ処理が存在します。同じ処理が複数存在するプログラムは一般的に保守性が高いとは言えません。なぜなら、修正が発生した際に複数箇所の修正が発生するためです。
もし、上記プログラムの
System.out.println(name + ":" + price);
の部分を、
System.out.println("商品名:" + name); System.out.println("価格:" + price);
の様に変更したい。となった場合、修正箇所が2箇所となってしまいます。一方、7-6-3で示したItemクラスの場合は、修正箇所が1箇所(8行目のみ)だけで済みます。
保守性は、オブジェクト指向プログラミングを学ぶ上での重要な観点の1つです。