スッキリわかるJava入門 第10章 カプセル化③
第10章 カプセル化③
getter/setterの存在価値
- Heroクラスのnameフィールドのカプセル化前後。
// カプセル化を行う前 public class Hero { String name; }
// カプセル化を行った後 public class Hero { private String name; public String getName(){ return this.name; } public void setName(String name) { this.name = name; } }
メリット1:Read Only、Write Onlyのフィールドを実現できる
- setName()メソッドを削除すれば、外部から読めるが書き換えられない(Read Only)フィールドにできる。よく多用されるテクニック。
- getName()メソッドを削除すれば、外部から自由に書き換えできるが、読めない(Write Only)フィールドにできる。あまり使われない。
メリット2:フィールドの名前など、クラスの内部設計を自由に変更できる
- たとえば、何らかの理由でnameというフィールド名をnewNameに変更したい場合。
- getter/setterを準備せず、他のクラスからnameフィールドを直接読み書きしていた場合、他のクラスすべての開発者にアクセスするフィールド名を変更してもらう必要がある。
- nameフィールドを隠し、外部からはgetter/setter経由で読み書きさせるなら、フィールド名の変更は自由。getter/setterの中身でフィールド名を使っている箇所だけ修正すれば良い。
メリット3:フィールドへのアクセスを検査できる
- クラス外部からnameフィールドの値を書き換えたい場合、setterを使う必要がある=setterを実行せずに、nameフィールドを書き換える事は不可能。
- これを利用し、setterで「設定されようとしている値が妥当かを検査する」こともJavaプログラミングの定石。
- 以下のsetName()メソッドは、nameフィールドへの値を変更しようとするたびに検査を行う。
public class Hero { private String name; public String getName() { return this.name; } public void setName(String name) { // 代入前に、代入しようとしている値が妥当かを検査する if (name == null) { // nullが設定されそうになった! throw new IllegalArgumentException("名前にnullが設定されそうになりました。処理を中断します。"); } if (name.length() <= 1) { // 文字列長が異常(短すぎる) throw new IllegalArgumentException( "1文字以下の名前が設定されそうになりました。処理を中断します。"); } if (name.length() >= 8) { // 文字列長が異常(長すぎる) throw new IllegalArgumentException( "8文字以上の名前が設定されそうになりました。処理を中断します。"); } this.name = name; // 検査完了!代入しても大丈夫。 } }
クラスに対するアクセス制御
2つのアクセス制御レベル
名前 | Javaでの記述 | 許可する範囲 | 制限 |
---|---|---|---|
package private | (何も書かない) | 自分と同じパッケージに属するクラス | 厳しい |
public | public | すべてのクラス | 緩い |
- クラス宣言の前にはpublicをつけると丸暗記していたが、何も書かない場合、そのクラスは同一パッケージに属するクラスからのアクセスのみ許可される。
- 他のパッケージに属するクラスからのアクセスが禁止される。イメージとしては「他のパッケージに属するクラスから、そのクラスの存在自体が見えなくなる」と捉えた方がよい。
非publicクラスとソースファイル
- 別パッケージのクラスから見えない代わりに、publicクラスでは許可されない次の2つが許可されている。
- ①クラスの名前はソースファイル名と異なってもよい。
- ②1つのソースファイルに複数宣言してもよい。
カプセル化を支えている考え方
メソッドでフィールドを保護する
- 「外部から直接触られないよう、メソッドという殻(カプセル)によってフィールドが保護されている」ように見える事から、カプセル化という名前が付いている。
- なぜカプセル化ではメソッドではなくフィールドを保護しようとするのか?それは、メソッドよりフィールドの方が異常な状態(不具合)になりやすいから。
- メソッドの処理内容は、プログラミング段階で決定し、一度コンパイルされればプログラム実行中に変化することはない。
- フィールドの中身は、プログラムが動作する間に逐次、変化していく。動作中に異常な値になる危険性もある。結果的に不具合の多くは「フィールドに予期しない値が入る」という形で発現する。
- 「プログラムの不具合を減らすためには、メソッドよりもフィールドを保護する事が重要」。
スッキリわかるJava入門 第10章 カプセル化②
第10章 カプセル化②
publicやpackage privateを利用する
- 勇者は戦うのが仕事なので、いろいろなクラスからattack()メソッドが呼び出される可能性がある。
- どのようなクラスからでも呼び出せるようにpublic指定を付けておく。
- sleep()メソッドにはpublicを付けない。この場合package privateを指定したと見なされ、同じパッケージに属するクラスからの呼び出しのみ可能となる。
public class Hero { // 省略 void sleep() { // 省略 } public void attack(Matango m){ // 省略 }
メンバに関するアクセス修飾の定石
- フィールドはすべてprivate
- メソッドはすべてpublic
- クラスはすべてpublic(メンバではないが、クラスの定石)
- 基本はこのパターンに沿ってアクセス修飾を行い、その後クラス内部だけで利用するメソッドのみをprivateに指定し直すような「微調整」をすればよい。
getterとsetter メソッドを経由したフィールド操作
- 基本的にフィールドは、メソッド経由でアクセスする
- 他のクラスの開発者がバグを含んだコードを書いても、勇者のHPをマイナス100にする事は不可能。
- HPに異常な値が設定される不具合に直面しても、そのときはメソッド(attack(),sleep())どちらかのバグという事が想定できる。
単純にフィールド値を取り出すだけのメソッド
- 王様クラスが勇者クラスの名前を知るには?
- HeroクラスにgetNameメソッドを追加して、王様が勇者の名前を知ることができるようにする。
public class Hero { private String name; public String getName(){ return this.name; } }
- Kingクラスでは、nameフィールドにアクセスしている部分を、getName()を呼び出すように修正する。
public class King { void talk(Hero h) { System.out.println("王様:ようこそ我が国へ、勇者" + h.getName() + "よ。"); } }
getterメソッドの書き方
getterメソッドの書き方の定石
// 定石 public 値を取り出すフィールドの型 getフィールド名(){ return this.フィールド名; } // サンプル public String getName(){ return this.name; } }
メソッド名の慣習的なルール
- メソッド名の最初の3文字を「get」にし、それに続けて「フィールド名の先頭を大文字にしたもの」にする。
- フィールド名がnameならgetName()となる。
- 例外として戻り値がboolean型の場合のみisXxxx()というメソッド名にすることがある。
- このメソッド名の付け方はJava開発者の間で常識になっている風習みたいなもの。
スッキリわかるJava入門 第10章 カプセル化①
第10章 カプセル化①
カプセル化とは?
アクセス制御されない怖さ
不具合
- Heroクラスを使ったゲームのテスト中の不具合
- ①一度もモンスターと戦っていないのに勇者のHPがマイナス100になっている。
- 別開発者の「宿屋クラス」の不具合で「h.hp = -100;」とコーディングされていた。(コンパイルエラーにならない)
- ②冒険中にお城で会話をすると、なぜか勇者が理由もなく急死してゲームオーバーになる。
- 別開発者の「王様クラス」の不具合で「bye()」ではなく「die()」メソッドが呼ばれていた。
必要なアクセス制御
- ①Heroクラス以外からはhpフィールドに値を設定できない
- ②die()メソッドを呼べるのはHeroクラスだけ
4つのアクセス制御レベル
- Javaでは、それぞれのメンバ(フィールド及びメソッド)に対してアクセス制御の設定を行うことができる。
- ですが、それぞれのメンバに「Aクラスからの利用は許す」のように細かく指定すると、とても手間がかかってしまう。
- ざっくりと4段階からアクセス制御の方法を選ぶようになっている。
privateを利用する
必要なアクセス制御①の対応として、HPはprivateにする。
public class Hero { private int hp; String name; Sword sword; static int money; Hero(){ this("ダミー"); } Hero(String name){ this.hp = 100; this.name = name; } void sleep() { this.hp = 100; System.out.println(this.name + "は、眠って回復した!"); } }
- hpフィールドにprivateを指定したため、宿屋クラスのcheckIn()メソッドでは「hpフィールドにアクセスできない」コンパイルエラーが発生する。
- 勇者のhpフィールドが、いっさい変更できなくなる訳ではない。
- atack()やsleep()のように、同じheroクラスのメソッドからはhpフィールドにアクセスできる。
- 宿屋クラスのcheckIn()メソッドからは、勇者のsleep()メソッドを呼ぶように修正すればよい。
- privateであっても、this.~での読み書きは可能。
必要なアクセス制御②の対応として、die()はprivateにする。
- die()メソッドも、王様などほかのクラスからみだりに呼び出される事がないようにprivateにする。
- 外部のクラスからは呼び出せなくなるが、同じクラス内にあるattack()メソッドからの呼び出しは問題ない。
public class Hero { private int hp; String name; Sword sword; static int money; Hero(){ this("ダミー"); } Hero(String name){ this.hp = 100; this.name = name; } void bye(){ System.out.println("勇者は別れを告げた"); } private void die() { System.out.println(this.name + "は死んでしまった!"); System.out.println("GAME OVERです。"); } }
スッキリわかるJava入門 第9章 さまざまなクラス機構(練習問題)
第9章 さまざまなクラス機構(練習問題)
練習9
public class Cleric { // コンストラクタ public Cleric(String name, int hp, int mp) { this.name = name; this.hp = hp; this.mp = mp; } public Cleric(String name, int hp) { this(name, hp, MAX_MP); } public Cleric(String name) { this(name, MAX_HP, MAX_MP); } // 名前 String name; // HP int hp = 50; // 最大HP static final int MAX_HP = 50; // staticを追加 // MP int mp = 10; // 最大MP static final int MAX_MP = 10; // staticを追加 // 魔法_セルフエイド public void selfAid() { System.out.println(name + "は魔法「セルフエイド」を唱えた!"); // MPを5消費 this.mp -= 5; // HPを最大HPまで回復 this.hp = this.MAX_HP; System.out.println("最大HPまで回復した."); } // 祈る public int pray(int sec) { System.out.println(name + "は、" + sec + "秒祈った!"); // ランダムで0~2ポイント補正 int correction = new java.util.Random().nextInt(3); System.out.println("** 補正ポイント:" + correction); // MP回復量(仮) int tempAidPoint = sec + correction; // 最大MPより回復しないよう制御 //(MP回復量(仮)またはMP減少値の最小値を採用) int aidPoint = Math.min(tempAidPoint, this.MAX_MP - this.mp); System.out.println("** 回復前のMP:" + this.mp); // MP回復 this.mp += aidPoint; System.out.println("MPが" + aidPoint + "ポイント回復した."); System.out.println("** 回復後のMP:" + this.mp); return aidPoint; } }
public class Main { public static void main (String[] args) { // コンストラクタ1 Cleric clericShuto = new Cleric("シュウトウ"); System.out.println("名前:" + clericShuto.name); System.out.println("HP:" + clericShuto.hp); System.out.println("MP:" + clericShuto.mp); // コンストラクタ2 Cleric clericKondoh = new Cleric("コンドウ", 30); System.out.println("名前:" + clericKondoh.name); System.out.println("HP:" + clericKondoh.hp); System.out.println("MP:" + clericKondoh.mp); // コンストラクタ3 Cleric clericGita = new Cleric("ギータ", 40, 9); System.out.println("名前:" + clericGita.name); System.out.println("HP:" + clericGita.hp); System.out.println("MP:" + clericGita.mp); // コンストラクタ3 Cleric clericKuri = new Cleric("クリハラ", 100, 100); System.out.println("名前:" + clericKuri.name); System.out.println("HP:" + clericKuri.hp); System.out.println("MP:" + clericKuri.mp); } }
実行結果
C:\work\ex91>java Main 名前:シュウトウ HP:50 MP:10 名前:コンドウ HP:30 MP:10 名前:ギータ HP:40 MP:9 名前:クリハラ HP:100 MP:100
改善①:コンストラクタの重複コードが最小限となるように修正
修正前
// コンストラクタ public Cleric(String name) { this.name = name; } public Cleric(String name, int hp) { this(name); // コンストラクタ1を呼出し this.hp = hp; } public Cleric(String name, int hp, int mp) { this(name, hp); // コンストラクタ2を呼出し this.mp = mp; }
修正後
// コンストラクタ public Cleric(String name, int hp, int mp) { this.name = name; this.hp = hp; this.mp = mp; } public Cleric(String name, int hp) { this(name, hp, this.MAX_MP); } public Cleric(String name) { this(name, this.MAX_HP, this.MAX_MP); }
改善②:コンパイルエラーを修正(修正後の「 this.MAX_HP」「this.MAX_MP」)
- コンストラクタはインスタンス生成前なので、thisを用いて「自分自身のインスタンスのメンバ」は利用できない。thisは「自分自身のインスタンス」を意味している。
- 静的フィールドへのアクセスに変更すればOK(this.→Cleric.)。静的フィールドであれば、インスタンスを1つも生み出さなくても利用可能になる。(金型の上に作られる箱)
コンパイルエラー
C:\work\ex91>javac Main.java Cleric.java Cleric.java:10: エラー: スーパータイプのコンストラクタの呼出し前はthisを参照できません this(name, hp, this.MAX_MP); ^ Cleric.java:13: エラー: スーパータイプのコンストラクタの呼出し前はthisを参照できません this(name, this.MAX_HP, this.MAX_MP); ^ Cleric.java:13: エラー: スーパータイプのコンストラクタの呼出し前はthisを参照できません this(name, this.MAX_HP, this.MAX_MP); ^ エラー3個
修正後
// コンストラクタ public Cleric(String name, int hp, int mp) { this.name = name; this.hp = hp; this.mp = mp; } public Cleric(String name, int hp) { this(name, hp, Cleric.MAX_MP); } public Cleric(String name) { this(name, Cleric.MAX_HP, Cleric.MAX_MP); }
改善③:引数で指定したHP/MPが最大値を超えないように制御
修正後
// コンストラクタ public Cleric(String name, int hp, int mp) { this.name = name; this.hp = Math.min(hp, Cleric.MAX_HP); this.mp = Math.min(mp, Cleric.MAX_MP); } public Cleric(String name, int hp) { this(name, hp, Cleric.MAX_MP); } public Cleric(String name) { this(name, Cleric.MAX_HP, Cleric.MAX_MP); }
スッキリわかるJava入門 第9章 さまざまなクラス機構③
第9章 さまざまなクラス機構③
静的メンバ クラス上に準備されるフィールド
- インスタンスの独立性…newによって生成される個々のインスタンスは基本的に独立した存在。
- たとえば勇者3名がパーティを組んで冒険するRPGを作る場合、次のようなクラスを書くと、各フィールド(name,hp,money)を各インスタンスが別々に持つことになる。
public class Hero { String name; int hp; int money; }
- しかし、プログラムを開発していると「各インスタンスで共有したい情報」が出てくることがある。
- たとえばRPGはパーティで行動しているので「パーティで1つの財布」を設定したいなど。
- 同じクラスから生成されたインスタンスでフィールドを共有したい場合、フィールド宣言の先頭にstaticキーワードを追加する。
public class Hero { String name; int hp; static int money; }
- staticキーワードが指定してあるフィールドは特に静的フィールド(static field)といわれ、下記の3つの特殊な効果をもたらす。
①フィールド変数の実体がクラスに準備される
静的フィールドへのアクセス方法
- クラス名.フィールド名
public class Main { public static void main(String[] args) { Hero h1 = new Hero(); Hero h2 = new Hero(); System.out.println(h1.hp); System.out.println(Hero.money); } }
②全インスタンスに、箱の分身が準備される
- 共通財産である金額が格納される変数(Hero.money)は、あくまで金型に作られる。
- しかし、同時にh1やh2といった各インスタンスにもmoneyという名前で「箱の分身」が準備され、金型の箱の別名として機能するようになる。
- つまり、「h1.money」や「h2.money」という分身の箱に値を代入すれば、本物の箱Hero.moneyにその値が代入される。(インスタンス(h1,h2)経由で、静的フィールド(Hero.money)へのアクセスが可能となる)
- 実質的に「h1.money」「h2.money」「Hero.money」はどれも同一の箱を指すことになる。
- このようすは「h1とh2がmoneyフィールドを共有している」と考えることもできるので「静的フィールドを用いれば、インスタンス間でフィールドを共有できる」と解説されることもある。
静的フィールドへの別名によるアクセス
- 「インスタンスが含まれる変数名.静的フィールド名」と書いても良いが、「クラス名.静的フィールド名」と同じ意味。
public class Main { public static void main(String[] args) { Hero h1 = new Hero(); Hero h2 = new Hero(); Hero.money = 100; System.out.println(Hero.money); -- 100と表示 System.out.println(h1.money); -- 100と表示 h1.money = 300; System.out.println(h2.money); -- 300と表示 } }
③インスタンスを1つも生み出さなくても箱が利用可能になる
- 「Hero.money」は金型の上に作られる箱。よって、1つの実態もまだ生み出されていない状況にあっても利用することができる。
public class Main { public static void main(String[] args) { Hero.money = 100; System.out.println(Hero.money); -- 100と表示 } }
- 静的フィールドはクラス(金型)にフィールド(箱)が所属するという特徴からクラス変数と言われることもある。
public static final コンビネーション
- 多くの場合、staticはfinalやpublicと一緒に利用され、「変化しない定数を各インスタンスで共有するため」に利用される。
静的メソッド
- Heroクラスに「勇者たちの所持金をランダムに設定する」setRandomMoney()メソッドを追加する場合。
public class Hero { String name; int hp; static int money; static void setRandomMoney() { Hero.money = (int)(Math.random() * 1000); } }
- staticキーワードがついているメソッドは、静的メソッド(static method)またはクラスメソッド(class method)と言う。
- 静的フィールドとあわせて静的メンバ(static member)と総称される。
- 静的メソッドを定義すると、静的フィールドと同様に以下の3つの効果が現れる。
①メソッド自体がクラスに属するようになる
- 静的メソッドは、その実体が各インスタンスではなくクラスに属し、「クラス名.メソッド名();」で呼び出せる。
③インスタンスを1つも生み出さなくても箱が利用可能になる
- 静的メソッドは、1つもインスタンスを生み出していない状況でも呼び出せる。
public class Main { public static void main(String[] args) { Hero.setRandomMoney(); System.out.println(Hero.money); -- ランダムな金額が表示 Hero h1 = new Hero(); System.out.println(h1.money); -- 同じ額を表示 } }
静的メソッドの制約
- 静的メソッドの中に記述するコードでは、staticが付いていないフィールドやメソッドは利用できない。
public class Hero { String name; int hp; static int money; static void setRandomMoney() { Hero.money = (int)(Math.random() * 1000); System.out.println(this.name + "たちの所持金を初期化しました"); -- this.nameでコンパイルエラー } }
- 静的メソッドsetRandomMoney()は、まだ1つも勇者インスタンスが存在しない状況でも呼び出されることがあるメソッド。
- しかし「this.name」は、thisを用いて「自分自身のインスタンスのメンバ」を利用しようとしている。
- もし仮想世界に1つも勇者インスタンスがない状況でsetRandomMoney()が動いたら、「this.name」は処理できない。
スッキリわかるJava入門 第9章 さまざまなクラス機構②
第9章 さまざまなクラス機構②
コンストラクタ 生まれたてのインスタンスの状態
- クラスを用いたプログラミングを始めると、ある「めんどうくささ」を感じる。
public class Main { public static void main(String[] args) { Hero h1 = new Hero(); h1.name = "ミナト"; h1.hp = 100; Hero h2 = new Hero(); h2.name = "アサカ"; h2.hp = 100; Wizard w = new Wizard(); w.name = "スガワラ"; w.hp = 50; w.heal(h1); // ミナトを回復させる(HP: 100 -> 110) w.heal(h2); // アサカを回復させる(HP: 100 -> 110) w.heal(h2); // アサカを回復させる(HP: 110 -> 120) } }
フィールドの初期値
int型,short型,long型 等の数値の型 | 0 |
---|---|
char型(文字) | \u0000 |
boolean型 | false |
int[]などの配列型 | null |
String型などのクラス型 | null |
フィールド初期値を自動設定する
- 実際の開発現場において、大規模な開発を行う場合、1人ですべてを開発することは、まずあり得ない。
- クラスを作るにあたっては、自分以外の開発者がHeroクラスを利用することも考えておかなければならない。
- その開発者が正しくHPに100を代入してくれるとは限らない。
- 「勇者のHPが100であること」は、勇者自身に関する事で、Heroクラスの開発者が一番よく知っていること。(Heroクラス側で責任を持つこと)
- javaでは「インスタンスが生まれた直後に自動実行される処理」をプログラミングできるようになっている。
public class Hero { String name; int hp; Sword sword; public void attack() { System.out.println(this.name + "は攻撃した!"); System.out.println("敵に5ポイントのダメージをあたえた!"); } public Hero() { this.hp = 100; // hpフィールドを100で初期化 } }
- 9行目のHero()メソッドは、このクラスがnewされると、newと同時にメソッド内部で定義しておいた処理が自動的に行われる。
- このようなメソッドをコンストラクタ(constructor)と呼ぶ。
- newされると自動的に実行されてHPに100が代入される。そのためmainメソッド側でHPに初期値を代入する必要はない。
コンストラクタの定義条件
- 「自動実行されるメソッドの条件」を満たすように定義する。
コンストラクタとみなされる条件
- ①メソッド名がクラス名と完全に等しい
- ②メソッド宣言に戻り値が記述されていない(voidもダメ)
コンストラクタに情報を渡す
- HPフィールドは「100」という固定の値で初期化すればよいため、単純なコンストラクタで済んだ。
- 勇者の名前は生み出すインスタンスによって異なる。
- コンストラクタが「毎回異なる追加情報」を引数で受け取れるように宣言する。
public class Hero { String name; int hp; Sword sword; public void attack() { System.out.println(this.name + "は攻撃した!"); System.out.println("敵に5ポイントのダメージをあたえた!"); } public Hero(String name) { this.hp = 100; this.name = name; // 引数の値でnameフィールドを初期化 } }
- このようなHeroクラスを利用する場合は、コンストラクタが実行される際に渡して欲しい引数をnewする際に指定する。
public class Main { public static void main(String[] args) { Hero h = new Hero("ミナト"); System.out.println(h.hp); System.out.println(h.name); } }
2つ以上の同名コンストラクタを定義する
- 現在のHeroクラスには「文字列引数を1つ受け取るコンストラクタ」が定義されている。
- このコンストラクタを作ったことによって、インスタンスを生成するときには、名前を指定する必要が生じた。(new Hero();を実行するとエラーとなる)
- この問題は「引数を受け取らないコンストラクタ」も同時に定義することで解決できる。
public class Hero { String name; int hp; Sword sword; public void attack() { System.out.println(this.name + "は攻撃した!"); System.out.println("敵に5ポイントのダメージをあたえた!"); } public Hero(String name) { this.hp = 100; this.name = name; // 引数の値でnameフィールドを初期化 } public Hero() { this.hp = 100; this.name = "ダミー"; } }
public class Main { public static void main(String[] args) { Hero h1 = new Hero("ミナト"); System.out.println(h1.name); Hero h2 = new Hero(); System.out.println(h2.name); } }
暗黙のコンストラクタ
他のコンストラクタを呼び出す
- 2つ以上のコンストラクタを定義していると、そのうち「重複する処理」が気になる。
- そこで思いつくのが、コンストラクタ②の中で、コンストラクタ①を呼び出す方法。
- Javaではコンストラクタを直接呼び出すことができないため、以下のコードはコンパイルエラーとなる。
public class Hero { String name; int hp; Sword sword; public void attack() { System.out.println(this.name + "は攻撃した!"); System.out.println("敵に5ポイントのダメージをあたえた!"); } public Hero(String name) { // コンストラクタ① this.hp = 100; this.name = name; } public Hero() { // コンストラクタ② this.Hero("ダミー"); } }
別コンストラクタの呼び出しに関するルール
- 「コンストラクタの先頭で、別のコンストラクタを専用の文法を用いて呼び出す場合に限って」特別に許される。
- 「this.クラス名(引数);」と記述する事はできない。
- その代わりに「this(引数);」と記述する。
public class Hero { String name; int hp; Sword sword; public void attack() { System.out.println(this.name + "は攻撃した!"); System.out.println("敵に5ポイントのダメージをあたえた!"); } public Hero(String name) { // コンストラクタ① this.hp = 100; this.name = name; } public Hero() { // コンストラクタ② this("ダミー"); } }
スッキリわかるJava入門 第9章 さまざまなクラス機構①
第9章 さまざまなクラス機構①
仮想世界の真の姿
クラス型変数とその中身
public class Main{ public static void main(String[] args) { Hero h; h = new Hero(); h.hp = 100; } }
Step1:Hero型変数の確保
Step2:インスタンスの生成
- 4行目「h = new Hero();」は代入文。代入の場合は左辺より先に右辺が評価される。
- 「new Hero()」が実行されると、JVMはヒープ領域から必要な量のメモリを確保する。
- 仮に3299番地から24バイト分(3299~3945番地)が確保されたとする。
Step3:参照の代入
- 右辺の実行が終了した後、4行目は「h = 右辺の実行結果」という状態になる。
- 右辺の実行結果とは、newを実行することによって生成されたインスタンスのために確保されたメモリの先頭番地。
- 変数hには3299という数字が代入される。変数hに入っているアドレス情報を参照と言う。(配列型と同じ)
- クラス型と配列型は総称して「参照型」と呼ばれる。
同一インスタンスを指す変数
public class Main { public static void main(String[] args) { Hero h1; h1 = new Hero(); h1.hp = 100; Hero h2; h2 = h1; h2.hp = 200; System.out.println(h1.hp); } }
クラス型をフィールドに用いる
// まず、Swordクラスを定義しておく public class Sword { String name; int damage; }
// 次にHeroクラスを定義する public class Hero { String name; int hp; Sword sword; public void attack() { System.out.println(this.name + "は攻撃した!"); System.out.println("敵に5ポイントのダメージをあたえた!"); } }
- Heroクラスの5行目「Sword sword」。このようにフィールドにクラス型を使っても問題ない。
- あるクラスが別のクラスをフィールドとして利用している関係を「has-aの関係」と言う。
- Hero has-a Sword(勇者は剣を持っている)
public class Main { public static void main(String[] args) { Sword s = new Sword(); s.name = "炎の剣"; s.damage = 10; Hero h = new Hero(); h.name = "ミナト"; h.hp = 100; h.sword = s; System.out.println("現在の武器は" + h.sword.name); } }
- 3行目「Sword s = new Sword()」でswordフィールドに生成済みの剣インスタンスの番地を、9行目「h.sword = s」で代入。
- 10行目の「h.sword.name」は、勇者「の」剣「の」名前を出力する。
クラス型をメソッド引数や戻り値に用いる
- クラス型はフィールドの型として用いることができるだけではなく、メソッドの引数や戻り値の型として利用することもできる。