クラスの継承(まとめ)

継承とは

  • 継承するとメンバーを受け継ぐ(メンバーのアクセス修飾子に注意)
  • 継承の元となるクラス(親)は「スーパークラス」、継承によって作られた新しいクラス(子)は「サブクラス」
  • Javaの継承は「単一継承(親は1つだけ)」で、すべてのクラスの祖先となるのはObjectクラス(extendsを書かないクラスはextends Objectが省略されていると考えれば良い)。

(メンバーを受け継ぐことに加え)サブクラスでできること

  • メンバーを加えることができる(メンバーを加えない≒スーパークラスと同じ、なので加えないと意味がない)
  • 「super」でサブクラスからスーパークラスのメソッド(コンストラクタを含む)を呼び出すことができる
  • スーパークラスにあるメソッドをオーバーライド(上書き)することができる

継承の使い所

  • (なんらかの理由でクラスのメンバーの追加・修正ができない場合に)継承して機能を拡張する。
  • 共通部分をスーパークラスとしてまとめ、個別の機能をサブクラスで記述する(この用途には「抽象クラス(教科書P.114)」が適切な場合がある)
  • 同じように呼び出すことができるメソッドを持たせる(この用途には「インタフェース(教科書P.116)」が適切な場合がある)。
  • etc…

クラスの継承(2)

前回のまとめ

  • 継承するとメンバーを受け継ぐ
  • ただしメンバーのアクセス修飾子に注意
  • 継承の元となるクラスは「スーパークラス」、継承によって作られた新しいクラスは「サブクラス」
  • super: サブクラスからスーパークラスのメソッド(コンストラクタを含む)を呼び出すことができる
  • スーパークラスにあるメソッドをオーバーライド(上書き)することができる

サブクラスからスーパークラスへのキャスト

コード全体: Main1.java

サブクラスはスーパークラスのメンバーをすべて持っている。
よって、サブクラスのインスタンスはスーパークラスのインスタンスとみなしても(として扱っても)大丈夫だ。

実際に、次のようなコードが書ける。

これは、次のような状況だ。

サブクラスからスーパークラスへのキャスト

comicで使う場合はComicクラスのインスタンスとして扱うことができるが(当たり前)、comicAsBookで使う場合はBookクラスのメンバーしか使えない。つまり

は実行できるが、

はコンパイルエラーになる。

これができると何が嬉しいのだろうか?1つは「BookとComicを(Bookの情報だけになるが)同じように扱える」ということだ。前回は
BookとComicでそれぞれ配列を作ってデータを扱っていたが、まとめてBookの配列として扱うことができる。つまり、

とすることができる。ただし、bookList[1]は本来Comicだが、Book扱いになる。

Book扱いになる(つまりComicで追加されたメンバーは使えない)が、オーバーライドされたメソッドはどういう扱いになるのだろうか。
次の実行結果を確認しよう。

コメントにも書いてある通り、「どのクラスとして扱っているか」ではなく、「インスタンス化した時のクラス」の実装が使われる。

すべてのクラスはObjectクラスを継承している

コード全体: Magazine.java, Main2.java, Main3.java

雑誌を表すMagazineクラスを作ってみよう。

Objectクラス

オーバーライドする時にIDEを使うと、候補となるメソッドが表示されるが、Bookクラスで定義していないメソッドがあるのが確認できるはず。これはObjectクラスのメソッドである。
実は(Objectクラスを除く)すべてのクラスはObjectクラスを継承している。正確には「extends」で指定しない場合には、Objectクラスが自動的に継承される。
つまり「Objectを継承したBook」を継承しているのがMagazineという関係である。

すべてのクラスがObjectを継承しているのなら、Objectの配列は参照型ならすべて格納できる、ということになる。

toStringメソッド

ここでは、toStringメソッドをオーバーライドしてみよう。これにより、System.out.printlnの引数として指定した時に表示する文字列を指定することができる。

では、確認しよう。

instanceof演算子とスーパークラスからサブクラスへのキャスト

Object型のままだと、その中のインスタンスが何であってもできることが限られる。実際のインスタンスの型を判別してキャストしよう。型の判別にはinstanceof演算子(教科書P.126)を使う。instanceof演算子で、「指定した型にキャストできるか」を判断することができる。Bookクラスにキャストできる(つまりBookクラスかBookの子孫となるクラス)かどうか判別してCSVを出力してみよう。

equalsメソッド

参照型の比較には==が使えない場合が多い。比較が必要なクラスはObjectクラスのequalsメソッドをオーバーライドする、という慣例になっている(「慣例」と書いたが、多くのライブラリが前提としてるので、ほぼルールと言って良い)。
例えば、文字列の比較の例を見てみよう。

Magazineのequalsメソッドをオーバーライドしてみよう。

結果を確認しよう。

クラスの継承(1)

前回のまとめ

  • 型は大別してプリミティブ型と参照型の2つ。
  • プリミティブ型は「箱」のイメージでOK。代入は「値をコピー」
  • 参照型は「商品タグ」のイメージ。インスタンスを指し示すのがインスタンス名(変数名)。代入は「商品タグが同じところに繋がる(ポインターのコピー)」
  • プリミティブ型と参照型では、代入や比較等で(初学者にとって)直感的でない違いがあるので注意する。この違いはコードを実行して確かめること。
  • 文字列や配列は参照型。よく使われるので「特別扱い」されている(使いやすくなっている)。

クラスの継承

本を管理する

(書店を経営しているつもりになって、あるいは自分の蔵書について)本を管理するソフトを作ることを考えよう。まず、本(小説とか)を管理するクラスを作ろう。

このクラスを使った例を次に示す。

GitHubで見る:

「巻」を管理できるように拡張する

漫画などは「第1巻」、「第2巻」と、同じタイトルでシリーズになっているものが多い。この「巻」の管理はBookクラスではできないので新しくComicクラスを作ることを考えよう。ただし、1から新しく作るのではなく、Bookクラスを元に「巻」に関する情報が扱えるように拡張することを考える。

ポイント:

  • 3行目: 「extends Book」で「Bookクラスを継承する」という意味になる。
    • この場合、Bookクラスが「スーパークラス」、Comicクラスが「サブクラス」ととなる。
    • Bookクラスにあるメンバー(フィールドとメソッド)が引き継がれる(使える)。
    • アクセス修飾子(教科書P.170)によって違いがあるので注意する。
  • 4-7行目: Comicクラスだけで使えるフィールド
  • 9-14行目: Bookクラスとは違うコンストラクタにしたい(インスタンス化時に追加したフィールドに値をセットしたい)ので定義する
  • 11行目: superをメソッドのように使う(括弧をつけて引数を渡す)とスーパークラス(この場合Bookクラス)のコンストラクタが実行される。
  • 16-28行目: 巻の情報も出力したいので、getInfoCSVメソッドを上書き(オーバーライド)する。
  • 17行目: @Overrideアノテーション(教科書P.180)
  • 21,24行目: オーバーライドする前の元のメンバーを使いたい場合は、superに「.」をつけてアクセスする(thisと同じような感じ)。
  • 28-35行目: Comicクラスだけで使えるメソッド

このクラスを使った例を次に示す。

GitHubで見る:

プリミティブ型(基本型)と参照型(クラス型)

The types of the Java programming language are divided into two categories: primitive types and reference types.

Java言語の型は次の2つのカテゴリーに分けられる: プリミティブ型と参照形。

プリミティブ型(基本型)

次の8種類。整数はすべて符号付きで、C言語と違ってunsignedはない。

格納できる値 備考
boolean 真偽値(trueもしくはfalse)
char 1文字 Unicodeのコードを表す16ビットの整数。教科書P.12
byte 整数(8ビット) 教科書P.10
short 整数(16ビット) 教科書P.10
int 整数(16ビット) 教科書P.10
long 整数(32ビット) 教科書P.10
float 実数(32ビット) 教科書P.11
double 実数(64ビット) 教科書P.11

基本型の変数は、「箱」のイメージで問題ない。変数名は箱についている名前。

では、次のコードを実行してみよう(コード全体)。

(1)では、aに0、bに1を代入している。

(1) プリミティブ型の宣言と初期化

(2)で代入という操作をすると、aの値がコピーされてbに入る。

Primitive2

(3)で、aに代入するとaの値が変わる。bの値は変わらない。

Primitive3

参照型(クラス型)

参照型はクラス型ともいい、要するに、クラスによる型のこと。クラス型の変数は、クラス(やインターフェース)の実体(インスタンス)を格納する。

例えば、円(通貨じゃなくて図形の方)を表すCircleクラスを定義して、インスタンス化することを考えよう。半径を表すフィールドを持つクラスである。

この場合の変数は、プリミティブ型の時と違って、「箱」のイメージではない!「商品タグ」のイメージが適切である。特に代入演算子を使った時の処理について注意が必要だ。実際に実行して、代入がどのような操作なのか確認しよう。

(4)では、インスタンス化して、フィールドに値を代入している。変数名(インスタンス名)は商品タグのようにインスタンスに紐づいている。

(4) 参照型のインスタンス化とフィールドへの代入

(5)で、代入の操作を行っているが、これは「c2の紐付け先をc1のものと同じにする」という意味になる(C言語がわかっている人向けの説明: ポインタのコピー)。元々c2が紐づいていたインスタンスはどこからも使われていない状態になったので、ガベージコレクタ(教科書P.92)によって、そのうちメモリから消される。

Reference2

(6) c1のフィールドの値を代入によって変更すると、c2のフィールドも同じになる(というか同じものを指し示している)。

Reference3

このように、参照型では(初学者にとっては)直感と違う処理が行われるので注意する。この辺りについては「シャローコピー」や「ディープコピー」といった単語で検索してみると理解が深まるだろう。

比較

代入だけではなく、比較についても注意しなければならない。次のコードを実行して見よう(コード全体)。

参照型の比較は、「指し示しているインスタンスが同じか」という比較をしているので、フィールドの値がすべて同じであったとしても指し示しているインスタンスが違えばfalseとなる。参照型は、普通は「==」で比較してはいけない、と覚えておこう(もちろん、同じインスタンスを指し示しているか、という判別が必要なときはこれで良い)。

では、参照型で比較したい場合はどうしたらいいのだろうか?これは、地味にフィールドを比較していくしかない。Circleクラスの場合はフィールドが1つだけなので簡単だが、フィールドがたくさんある場合は大変だ。なので、比較が必要なクラスは比較するためのメソッドを作っておくのが良い。じゃあ、どうやって作るか、利用するか、ということは今後の講義で解説する。

参照型あれこれ

文字列

Javaの文字列はStringクラスのインスタンスだ。つまり参照型だ。だから、比較などでは注意しなければならない。また、文字列は非常によく使われるので、特別扱いされている。

特別扱い1: 普通のクラスはnew演算子でインスタンス化を行うが、文字列のインスタンス化はダブルクォーテーションで囲むことによってインスタンス化することができる!

ちなみに、ダブルクォーテーションを使わず、charの配列から文字列を作ると次のようになる。

特別扱い2: +演算子で結合ができる!(教科書P.13、ちなみに、「演算子のオーバーロード」という機能がある言語では、+演算子の機能を変えることができる)

Javaの文字列は様々なメソッドが用意されているので使ってみよう。

配列

配列も参照型である。

lengthという読み取り専用の特殊なフィールドのようなものがある(教科書P.23)。

例えば、for文でループ回数に使えば、文字列の長さが変わってもその部分のコードを書き換える必要はなくなる。