前回のまとめ
- 継承するとメンバーを受け継ぐ
- ただしメンバーのアクセス修飾子に注意
- 継承の元となるクラスは「スーパークラス」、継承によって作られた新しいクラスは「サブクラス」
- super: サブクラスからスーパークラスのメソッド(コンストラクタを含む)を呼び出すことができる
- スーパークラスにあるメソッドをオーバーライド(上書き)することができる
サブクラスからスーパークラスへのキャスト
コード全体: Main1.java
サブクラスはスーパークラスのメンバーをすべて持っている。
よって、サブクラスのインスタンスはスーパークラスのインスタンスとみなしても(として扱っても)大丈夫だ。
実際に、次のようなコードが書ける。
10 11 12 13 14 |
Book book = new Book("ブック", "作者A", 1000); Comic comic = new Comic("コミック", "作者B", 2000, 1, false); // スーパークラスのインスタンスとして名前をつけることができる。 Book comicAsBook = (Book)comic; |
これは、次のような状況だ。
comicで使う場合はComicクラスのインスタンスとして扱うことができるが(当たり前)、comicAsBookで使う場合はBookクラスのメンバーしか使えない。つまり
1 |
System.out.println(comic.getTitleWithVolume()); |
は実行できるが、
1 |
System.out.println(comicAsBook.getTitleWithVolume()); |
はコンパイルエラーになる。
これができると何が嬉しいのだろうか?1つは「BookとComicを(Bookの情報だけになるが)同じように扱える」ということだ。前回は
BookとComicでそれぞれ配列を作ってデータを扱っていたが、まとめてBookの配列として扱うことができる。つまり、
21 22 23 24 |
// スーパークラスの配列にサブクラスのインスタンスを入れることができる。 Book[] bookList = new Book[2]; bookList[0] = book; bookList[1] = comic; |
とすることができる。ただし、bookList[1]は本来Comicだが、Book扱いになる。
Book扱いになる(つまりComicで追加されたメンバーは使えない)が、オーバーライドされたメソッドはどういう扱いになるのだろうか。
次の実行結果を確認しよう。
35 36 37 38 39 40 41 42 |
// オーバーライドしたメソッドはどちらが使われるか。 System.out.println(comic.getTaxIncludedPrice()); System.out.println(comicAsBook.getTaxIncludedPrice()); // どちらの型で呼ばれているかに関係なく、インスタンスの型のメソッドが使われる // 配列の場合も同様 for (int i=0; i<bookList.length; i++) { System.out.println(bookList[i].getInfoAsCsv()); } |
コメントにも書いてある通り、「どのクラスとして扱っているか」ではなく、「インスタンス化した時のクラス」の実装が使われる。
すべてのクラスはObjectクラスを継承している
コード全体: Magazine.java, Main2.java, Main3.java
雑誌を表すMagazineクラスを作ってみよう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class Magazine extends Book { public int year; public int month; public Magazine(String title, String author, int price, int year, int month) { super(title, author, price); this.year = year; this.month = month; } @Override public String getInfoAsCsv() { return super.getInfoAsCsv() + "," + year + "年" + month + "月"; } } |
Objectクラス
オーバーライドする時にIDEを使うと、候補となるメソッドが表示されるが、Bookクラスで定義していないメソッドがあるのが確認できるはず。これはObjectクラスのメソッドである。
実は(Objectクラスを除く)すべてのクラスはObjectクラスを継承している。正確には「extends」で指定しない場合には、Objectクラスが自動的に継承される。
つまり「Objectを継承したBook」を継承しているのがMagazineという関係である。
すべてのクラスがObjectを継承しているのなら、Objectの配列は参照型ならすべて格納できる、ということになる。
8 9 10 11 12 |
Object[] objectList = new Object[4]; objectList[0] = "文字列"; objectList[1] = new Book("ブック", "著者1", 1000); objectList[2] = new Comic("コミック", "著者2", 2000, 2, false); objectList[3] = new Magazine("雑誌", "編集部", 500, 2018, 1); |
toStringメソッド
ここでは、toStringメソッドをオーバーライドしてみよう。これにより、System.out.printlnの引数として指定した時に表示する文字列を指定することができる。
20 21 22 23 |
@Override public String toString() { return title + " " + year + "年" + month + "号"; } |
では、確認しよう。
14 15 16 17 18 19 |
// System.out.printlnでそのまま表示 System.out.println("そのまま表示"); for (int i = 0; i<objectList.length; i++) { System.out.println(objectList[i]); } System.out.println(); |
instanceof演算子とスーパークラスからサブクラスへのキャスト
Object型のままだと、その中のインスタンスが何であってもできることが限られる。実際のインスタンスの型を判別してキャストしよう。型の判別にはinstanceof演算子(教科書P.126)を使う。instanceof演算子で、「指定した型にキャストできるか」を判断することができる。Bookクラスにキャストできる(つまりBookクラスかBookの子孫となるクラス)かどうか判別してCSVを出力してみよう。
21 22 23 24 25 26 27 28 29 |
// Bookとして扱えるものだけCSVを出力する System.out.println("Bookとして扱えるものだけCSVを出力する"); for (int i = 0; i<objectList.length; i++) { if (objectList[i] instanceof Book) { Book b = (Book)objectList[i]; System.out.println(b.getInfoAsCsv()); } } System.out.println(); |
equalsメソッド
参照型の比較には==が使えない場合が多い。比較が必要なクラスはObjectクラスのequalsメソッドをオーバーライドする、という慣例になっている(「慣例」と書いたが、多くのライブラリが前提としてるので、ほぼルールと言って良い)。
例えば、文字列の比較の例を見てみよう。
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
String s1 = "あいうえお".substring(1, 4); // いうえ String s2 = "あいうえお".substring(1, 4); // いうえ // 文字列の==による比較 if (s1 == s2) { System.out.println("s1とs2は同じ"); } else { System.out.println("s1とs2は違う"); } // 文字列のequalsメソッドによる比較 if (s1.equals(s2)) { System.out.println("s1とs2は同じ"); } else { System.out.println("s1とs2は違う"); } |
Magazineのequalsメソッドをオーバーライドしてみよう。
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
// 注: このequalsメソッドはサンプルのため不十分 // IDEで生成するかLombokを利用する方が良い @Override public boolean equals(Object object) { if (!(object instanceof Magazine)) { return false; } Magazine another = (Magazine)object; if (!title.equals(another.title)) { return false; } if (!author.equals(another.author)) { return false; } if (price != another.price) { return false; } if (year != another.year) { return false; } if (month != another.month) { return false; } return true; } |
結果を確認しよう。
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
// equalsメソッドをオーバライドしていないBookの比較 Book b1 = new Book("ブック", "著者", 1000); Book b2 = new Book("ブック", "著者", 1000); if (b1.equals(b2)) { System.out.println("b1とb2は同じ"); } else { System.out.println("b1とb2は違う"); } // equalsメソッドをオーバライドしているMagazineの比較 Magazine m1 = new Magazine("雑誌", "編集部", 500, 2018, 1); Magazine m2 = new Magazine("雑誌", "編集部", 500, 2018, 1); if (m1.equals(m2)) { System.out.println("m1とm2は同じ"); } else { System.out.println("m1とm2は違う"); } |