こんにちは。ECF Techブログ
担当 Michiharu.Tです。
Javaプログラミング入門記事の第9章をお送りいたします。
第9章のテーマは「クラス型の活用」です。
本連載の初回および章立ての一覧については下記のリンクから確認できます。
解説動画
2024/02/02 解説動画を掲載しました。
9-1 クラス同士の関係性
オブジェクト指向でプログラムの構成を考える場合、オブジェクト同士が互いにどのような関係性にあるかを考えます。
お店アプリを例にすると・・・
- 商品にクーポンを適用する。
- 利用者がポイントカードを保有する。
- 利用者が最寄り店舗を登録する。
※太字はオブジェクトです。
などの事柄をどうプログラムで構成していくか。ということを考えることになります。そして、これら複数のオブジェクトの関係性をプログラムで表現することで、よりオブジェクト指向らしいプログラムとなります。
概要だけですとイメージしづらいですので、具体的なプログラムで確認します。本章ではお店アプリを題材に「利用者がポイントカードを保有する」というケースについて考えます。
9-1-1 クラス型のフィールド
まずは3つのクラスからなるプログラムをまとめて示します。
プログラム例(PointCard.java)
public class PointCard { int id; //カード番号 int point; //残ポイント //コンストラクタ PointCard(int id, int point) { this.id = id; this.point = point; } //支払処理 void payment( int price ){ //金額の1%をポイントに加算 point += price*0.01; } }
プログラム例(User.java)
public class User { String name; //名前 PointCard card; //ポイントカード //コンストラクタ public User(String name) { this.name = name; } }
プログラム例(Main.java)
public class Main { public static void main(String[] args) { User user = new User("タロウ"); PointCard card = new PointCard(1234, 500); user.card = card; System.out.println(user.card.point); user.card.payment(1000); System.out.println(user.card.point); } }
実行結果
500 510
プログラム量が一気に増えた印象ですが、使われているのはこれまで学習した内容がほとんどです。新しい要素は次の2つです。
- Userクラスにクラス型のフィールド(
PointCard card;
)が定義されている。 - メインクラス内の
user.card.point
、user.card.payment(1000)
という記述。
それでは、各クラスの概要を説明します。
PointCardクラス
PointCardクラスは次の2つのフィールドを持っています。
- カード番号を表す
id
- 残ポイントを表す
point
コンストラクタは引数の値を各フィールドに設定する処理となっています。
また、paymentというメソッドを持っています。paymentメソッドは金額(price
)を引数にとり、金額の1%をpointフィールドに加算する処理を行います。
Userクラス
Userクラスは次の2つのフィールドを持っています。
- 名前を表す
name
- ポイントカードを表す
card
注目すべきは、ポイントカードを表すcardフィールドがPointCard型、つまりクラス型になっている点です。どのような動作になるかは、mainメソッドの処理内容を追いかけながら説明します。
コンストラクタは引数の値をnameフィールドに設定する処理となっています。
それではmainメソッドの動作を確認します。下図に基づいて説明します。
図はメインクラスの5行目までが実行されたときの処理です。
3行目:User user = new User("タロウ");
Userインスタンスを生成し、変数user
にそのアドレスを保持しています。コンストラクタを使って、nameフィールドにタロウ
を設定しています。
4行目:PointCard card = new PointCard(1234, 500);
PointCardインスタンスを生成し、変数card
にそのアドレスを保持しています。コンストラクタを使って、idフィールドに1234
、pointフィールドに500
を設定しています。
5行目:user.card = card
Userインスタンスの持つフィールドcardに変数cardの値を代入する操作です。ここでuser.card
に代入されるのは変数card
に入っているアドレスです。したがって、user.card
も変数card
と同じPointCardインスタンスを指すようになります。図のオレンジ色の矢印が該当のイメージです。
この図の状態で、7行目以降の処理が行われます。7行目のuser.card.point
は次の図のように.
を、矢印と見なすといいでしょう。
文章で表現する場合は、下のように.
を「の」と読むイメージです。
変数userの指すインスタンスの
cardフィールドが指すインスタンスの
pointフィールド
つまり、PointCardインスタンスのpointフィールドに入っている値を示しています。よって、画面には500
が表示されます。
8行目も同様です。user.card.payment(1000)
は、
変数userの指すインスタンスの
cardフィールドが指すインスタンスの
paymentメソッド
を呼び出す処理。となります。引数には1000
が渡されていますので、その1%である10
がpointフィールドに加算されることになります。
9行目で再度user.card.point
を表示すると、510
が表示されます。
9-1-2 nullの活用
UserクラスにPointCard型のフィールドを定義することで、「ユーザーがポイントカードを保有する」という関係を表現することができました。このフィールドにnullを代入することで、ユーザーがポイントカードを持っていない状態を表現することも可能です。
プログラム例として、9-1-1のUserクラスの修正版を示します。
プログラム(User.java)
public class User { String name; //名前 PointCard card; //ポイントカード public User(String name) { this.name = name; } void display() { System.out.println("名前:"+name); if (card == null) { System.out.println("カードを所持していません。"); } else { System.out.println("カード情報:"); System.out.println("ID:" + card.id + " 残ポイント:" + card.point); } } }
新たにdisplayメソッドを作成しています。11行目でcardフィールドがnullかどうかを判断する条件式を使い、表示方法を分けています。
次にメインクラスのプログラムを示します。
プログラム(Main.java)
public class Main { public static void main(String[] args) { User taro = new User("タロウ"); PointCard card = new PointCard(1234, 500); taro.card = card; User hanako = new User("ハナコ"); taro.display(); hanako.display(); } }
実行結果
名前:タロウ カード情報: ID:1234 残ポイント:500 名前:ハナコ カードを所持していません。
各処理を解説します。
- 3行目:Userインスタンスを生成し、変数taroに代入
- 4行目:PointCardインスタンスを生成し、変数cardに代入
- 5行目:taroが指すインスタンスのcardフィールドに変数cardを代入
※変数taroとcardが指すインスタンス同士が関連をもつ - 6行目:Userインスタンスを生成し、変数hanakoに代入
6行目の処理が完了した時点で、各インスタンスは次のようになっています。
変数hanakoが指すインスタンスのcardフィールドは何も指し示していないnullの状態です。
8行目で変数taroが指すインスタンスのdisplayメソッドを呼び出しています。同インスタンスはcardフィールドがPointCardインスタンスを指している状態なので、User.javaの11行目card == null
はfalse
となり、elseブロックの処理を行います。よって、カードの詳細が表示されます。
9行目の変数hanakoが指すインスタンスのdisplayメソッド呼び出しでは、同インスタンスのcardフィールドがnull
なので、card == null
がtrue
となり、「カードを所持していません。」のメッセージが表示されます。
フィールドの型 | 初期値 |
---|---|
整数型 | 0 |
浮動小数点数型 | 0.0 |
boolean型 | false |
char型 | '' |
参照型 | null |
9-2 クラス型の引数
9-1では、次のようにUserインスタンスを一旦生成し、あとからPointCardインスタンスを代入する処理を行っていました。
User taro = new User("タロウ"); PointCard card = new PointCard(1234, 500); taro.card = card;
ここでは、クラス型を引数として活用し、Userクラスのコンストラクタを改良する例をご紹介します。
プログラム例を示します。
プログラム(User.java)
public class User { String name; //名前 PointCard card; //ポイントカード public User(String name, PointCard card) { this.name = name; this.card = card; } void display() { System.out.println("名前:"+name); if (card == null) { System.out.println("カードを所持していません。"); } else { System.out.println("カード情報:"); System.out.println("ID:" + card.id + " 残ポイント:" + card.point); } } }
コンストラクタを修正し、PointCard型の引数を受け取れるようにしています。続けてメインクラスを示します。
プログラム(Main.java)
public class Main { public static void main(String[] args) { PointCard card = new PointCard(1234, 500); User taro = new User("タロウ",card); taro.display(); } }
実行結果
名前:タロウ カード情報: ID:1234 残ポイント:500
3行目でPointCardインスタンスを先に生成し、変数cardに代入。次にその変数cardをUserインスタンス生成の際に、コンストラクタに引数として渡しています。この時渡されるのは変数cardに代入されているアドレスです。したがって、コンストラクタ経由で変数userが指すインスタンスのcardフィールドにアドレスが代入されます。
これによりUserインスタンスのcardフィールドが、PointCardインスタンスを指し示すようになります。