あるクラスのフィールドとメソッドを引き継いで、新しいクラスを作ることを 継承 といいます。また、「このメソッドを持つ」という約束だけを決めたものが インターフェース です。Spring入門では、Form クラスの継承、Repository のインターフェース、Serializable のように、この章の書き方が何度も出てきます。
この章で学ぶこと
-
extendsによる継承 - メソッドの上書き(オーバーライド)と
@Override - すべてのクラスの親
ObjectとtoString() -
interfaceの定義とimplements -
SerializableとserialVersionUID
10.1 この章で作るもの
この章では、ch10 パッケージに、社員のクラス Employee、それを継承した管理者のクラス Manager、インターフェース Validatable、そして Main を作ります。章の最後には、Main を実行するとコンソールに次のように表示されるようになります。
雷禅 海皇(26歳)
院照 心愛(27歳)総務部の管理者
雷禅 海皇 の年齢は正しい:true
院照 心愛 の年齢は正しい:true
| ファイル | 作業 | 内容 |
|---|---|---|
ch10/Employee.java |
新規 | 社員を表すクラス(第9章と同じ形) |
ch10/Manager.java |
新規 |
Employee を継承した、管理者を表すクラス |
ch10/Validatable.java |
新規 | 「正しい値かを調べられる」ことを表すインターフェース |
ch10/Main.java |
新規 | これらを使うクラス(main あり) |
10.2 継承
管理者は、社員の一種です。名前と年齢を持ち、さらに「管理する部署」を持ちます。Employee クラスを書き写さずに、「Employee のすべてを引き継いで、部署を足す」のが継承です。
-
srcを右クリックし、新規 → パッケージ でch10パッケージを作ります。 -
ch10パッケージを右クリックし、新規 → クラス で、名前Employee、mainの チェックを入れずに クラスを作ります。第9章と同じ内容を入力します(getter と setter は ソース → getter および setter の生成 で作れます)。ch10/Employee.javapackage ch10; /** 社員を表す */ public class Employee { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } -
ch10パッケージを右クリックし、新規 → クラス で、名前Managerと入力します。「スーパークラス」の欄がjava.lang.Objectになっているので、ch10.Employeeに書き換えて、mainの チェックを入れずに「完了」をクリックします。
図10-1 スーパークラスに ch10.Employee と入力(赤枠)public class Manager extends Employee {と書かれたクラスができます。「スーパークラス」の欄に書く代わりに、自分でextends Employeeと入力してもかまいません。 -
緑の行を入力します(getter と setter は生成で作れます)。
ch10/Manager.javapackage ch10; /** 管理者を表す。社員の情報に加えて、管理する部署を持つ */ public class Manager extends Employee { private String deptName; public String getDeptName() { return deptName; } public void setDeptName(String deptName) { this.deptName = deptName; } } -
ch10パッケージを右クリックし、新規 → クラス で、名前Main、mainに チェックを入れて クラスを作り、緑の行を入力します。ch10/Main.javapackage ch10; public class Main { public static void main(String[] args) { Employee e1 = new Employee(); e1.setName("雷禅 海皇"); e1.setAge(26); Manager m1 = new Manager(); m1.setName("院照 心愛"); m1.setAge(27); m1.setDeptName("総務部"); System.out.println(e1.getName() + "(" + e1.getAge() + "歳)"); System.out.println(m1.getName() + "(" + m1.getAge() + "歳)" + m1.getDeptName() + "の管理者"); } } -
保存して、
Main.javaを実行します。コンソール雷禅 海皇(26歳) 院照 心愛(27歳)総務部の管理者
Manager クラスには setName も getAge も書いていませんが、m1.setName(…) や m1.getAge() が使えています。extends Employee によって、Employee のフィールドとメソッドを引き継いでいるからです。
| 用語 | 意味 | 例 |
|---|---|---|
| 継承 | あるクラスのフィールドとメソッドを引き継いで、新しいクラスを作ること。class 新しいクラス extends 元のクラス と書く |
class Manager extends Employee |
| 親クラス(スーパークラス) | 引き継がれる元のクラス | Employee |
| 子クラス(サブクラス) | 引き継いで作った新しいクラス。親のすべてに、自分のフィールドやメソッドを足せる | Manager |
子クラスのオブジェクトは、親クラスの型の変数にも入れられます。Employee e = new Manager(); と書けます(管理者は社員の一種なので)。逆の Manager m = new Employee(); はエラーになります。
【知識メモ】Spring入門での継承
Spring入門では、社員登録の入力フォーム RegisterForm を継承して、社員変更の入力フォーム UpdateForm extends RegisterForm を作ります。変更のフォームは、登録のフォームの項目に社員IDを足しただけなので、継承で書くと項目を2度書かずに済みます。
【知識メモ】private なフィールドと継承
Employee の name は private なので、子クラスの Manager の中でも name と直接は書けません。getName() を通して読みます。private は「同じクラスの中だけ」であり、子クラスは別のクラスだからです。
10.3 オーバーライドと toString
すべてのクラスの親 Object
extends を書いていない Employee クラスも、実は Object というクラスを継承しています。Object は Java のすべてのクラスの親で、どのクラスも Object のメソッドを引き継いでいます。その1つが、オブジェクトを文字列にする toString() です。
-
Main.javaのmainメソッドの最後に、緑の行を書き足します。ch10/Main.java(main メソッドの最後)System.out.println(m1.getName() + "(" + m1.getAge() + "歳)" + m1.getDeptName() + "の管理者"); System.out.println(e1); } -
保存して実行します。最後に、次のような行が表示されます(
@の後ろの文字は、環境によって変わります)。コンソール(最後の行)ch10.Employee@6d06d69c
println にオブジェクトを渡すと、そのオブジェクトの toString() が呼ばれ、返ってきた文字列が表示されます。Object の toString() は「クラス名@ハッシュコード(16進形式)」という形の文字列を返すので、このような表示になりました。
オーバーライド
親クラスから引き継いだメソッドを、子クラスで書き直すことを オーバーライド といいます。Employee で toString() をオーバーライドして、名前と年齢を返すようにしてみましょう。
-
Employee.javaの最後(setAgeの後ろ)に、緑の行を書き足します。ch10/Employee.java(クラスの最後)public void setAge(int age) { this.age = age; } /** 一覧やログに出すために、名前と年齢を1行の文字列にする */ @Override public String toString() { return name + "(" + age + "歳)"; } } -
Manager.javaの最後にも、緑の行を書き足します。ch10/Manager.java(クラスの最後)public void setDeptName(String deptName) { this.deptName = deptName; } /** 社員の表示に、管理する部署を付け足す */ @Override public String toString() { return getName() + "(" + getAge() + "歳)" + deptName + "の管理者"; } } -
Main.javaの表示の3行を、緑の行のように書き換えます。ch10/Main.javapackage ch10; public class Main { public static void main(String[] args) { Employee e1 = new Employee(); e1.setName("雷禅 海皇"); e1.setAge(26); Manager m1 = new Manager(); m1.setName("院照 心愛"); m1.setAge(27); m1.setDeptName("総務部"); System.out.println(e1); System.out.println(m1); } } -
保存して実行します。
コンソール雷禅 海皇(26歳) 院照 心愛(27歳)総務部の管理者
println(e1) では Employee の toString() が、println(m1) では Manager の toString() が呼ばれています。オブジェクトの実際のクラスのメソッドが使われるので、子クラスで書き直せば、その内容に変わります。
@Override
オーバーライドするメソッドの上には @Override を付けます。@ で始まるこの印を アノテーション といいます(第13章で詳しく学びます)。@Override は、「このメソッドは親のメソッドを書き直したものです」とコンパイラーに伝える印です。
@Override を付けておくと、メソッド名を tostring と打ち間違えたときに、「型 Employee のメソッド tostring() はスーパータイプ・メソッドをオーバーライドまたは実装する必要があります」というエラーが出て、間違いに気づけます。付けなくても動きますが、必ず付ける習慣にしてください。
【便利な使い方】toString() を Eclipse に作ってもらう
メニューの ソース → toString() の生成 を選ぶと、フィールドを並べた toString() を自動で作れます。形が決まっているので、自分で書くより速くて確実です。この教材では、表示の形を決めたいので自分で書いています。
10.4 インターフェース
インターフェース は、「このメソッドを持つ」という約束だけを書いたものです。メソッドの名前・引数・戻り値の型だけを書き、中身({ } の処理)は書きません。中身は、そのインターフェースを 実装 するクラスが書きます。
-
ch10パッケージを右クリックし、新規 → インターフェース を選びます。「新規 Java インターフェース」の画面で、「名前」にValidatableと入力し、「完了」をクリックします。 -
緑の行を入力します。
ch10/Validatable.javapackage ch10; /** 値が正しいかを調べられることを表す */ public interface Validatable { /** 登録や変更の前に、値が正しい範囲かを確かめる * @return 正しければ true */ boolean isValid(); }boolean isValid();は、{}が無く、;で終わっています。「booleanを返すisValidというメソッドを持つ」という約束だけです。 -
Employee.javaのクラスの行にimplements Validatableを足し、isValidメソッドを書きます。ch10/Employee.javapackage ch10; /** 社員を表す */ public class Employee implements Validatable { private String name; private int age; /** 登録や変更の前に、年齢が 15 以上 99 以下かを確かめる * @return 範囲内なら true */ @Override public boolean isValid() { return age >= 15 && age <= 99; } public String getName() { return name; }implements Validatableと書いた時点で、isValid()を書くまでは「型 Employee は継承された抽象メソッド Validatable.isValid() を実装する必要があります」というエラーになります。約束したメソッドを必ず書かせる仕組みです。 -
Main.javaのmainメソッドの最後に、緑の行を書き足します。ch10/Main.java(main メソッドの最後)System.out.println(e1); System.out.println(m1); Validatable v1 = e1; Validatable v2 = m1; System.out.println(e1.getName() + " の年齢は正しい:" + v1.isValid()); System.out.println(m1.getName() + " の年齢は正しい:" + v2.isValid()); } -
保存して実行します。
コンソール雷禅 海皇(26歳) 院照 心愛(27歳)総務部の管理者 雷禅 海皇 の年齢は正しい:true 院照 心愛 の年齢は正しい:true
| コード | 意味 |
|---|---|
public interface Validatable { boolean isValid(); } |
インターフェースの定義。class の代わりに interface と書く。メソッドは中身を書かず ; で終える。メソッドは自動で public になる |
class Employee implements Validatable |
Employee が Validatable を 実装 する。約束したメソッドを、public を付けて中身まで書く |
Validatable v1 = e1; |
インターフェースの型の変数に、実装したクラスのオブジェクトを入れられる。v1 からは、Validatable で約束したメソッド(isValid())だけが呼べ、getName() などは呼べない |
Manager は Employee を継承しているので、isValid() も引き継いでいます。Validatable v2 = m1; と書けるのはこのためです。
インターフェースを使う理由
インターフェースの型の変数(v1)を使う側は、「isValid() が呼べる」ことだけを知っていればよく、中身がどう書かれているか、どのクラスのオブジェクトかを気にしなくて済みます。Spring入門では、EmployeeRepository というインターフェースを自分で定義し、その中身(データベースを読み書きする処理)は Spring が用意します。使う側は、employeeRepository.findByEmpId(1) のように、約束されたメソッドを呼ぶだけです。
【知識メモ】インターフェースどうしの継承
インターフェースも、extends で別のインターフェースを継承できます。Spring入門の public interface EmployeeRepository extends JpaRepository<Employee, Integer> がそれで、JpaRepository が約束しているメソッド(保存・削除など)をすべて引き継いだうえで、自分のメソッドを足しています。クラスと違い、インターフェースは複数のインターフェースを継承することもできます。
10.5 Serializable
Java には、メソッドを1つも持たないインターフェースもあります。java.io.Serializable がその1つで、「このクラスのオブジェクトは、ファイルや通信用のデータに変換してよい」という印として使われます。
-
Employee.javaのクラスの行に、Serializableを足します。Seriと入力して Ctrl + Space を押し、候補から「Serializable - java.io」を選ぶと、import java.io.Serializable;も自動で入ります。ch10/Employee.java(前半)package ch10; import java.io.Serializable; /** 社員を表す */ public class Employee implements Validatable, Serializable { private static final long serialVersionUID = 1L; private String name; private int age;複数のインターフェースを実装するときは、
,で区切って並べます。 -
Manager.javaにも、緑の行を書き足します。親クラスがSerializableなら、子クラスもSerializableになるので、同じ定数を書きます。ch10/Manager.java(前半)package ch10; /** 管理者を表す。社員の情報に加えて、管理する部署を持つ */ public class Manager extends Employee { private static final long serialVersionUID = 1L; private String deptName; -
保存して実行します。表示は変わりません。
コンソール雷禅 海皇(26歳) 院照 心愛(27歳)総務部の管理者 雷禅 海皇 の年齢は正しい:true 院照 心愛 の年齢は正しい:true
Serializable を付けると、Eclipse は「シリアライズ可能クラス Employee は long 型の static final serialVersionUID フィールドを宣言していません」という警告(黄色の印)を出します。serialVersionUID は、変換したデータがどの版のクラスのものかを表す番号で、private static final long serialVersionUID = 1L; と書いておくと警告が消えます。意味を深く考える必要はなく、「Serializable を付けたら、この1行も書く」と覚えておけば十分です。
【知識メモ】Spring入門での Serializable
Spring入門では、画面の入力内容を一時的に保存する仕組み(セッション)に Form クラスのオブジェクトを入れます。セッションに入れるクラスは Serializable を実装している必要があるので、Form クラスには implements Serializable と serialVersionUID の行を書きます。
10.6 うまくいかないときは
| 症状 | 考えられる原因と対処 |
|---|---|
| 「型 Employee は継承された抽象メソッド Validatable.isValid() を実装する必要があります」 |
implements Validatable と書いたのに、isValid() メソッドをまだ書いていないか、名前・引数・戻り値の型が違います。インターフェースの宣言と同じ形で書いてください。 |
| 「型 Employee のメソッド tostring() はスーパータイプ・メソッドをオーバーライドまたは実装する必要があります」 |
@Override を付けたメソッドの名前が、親のメソッドと違います。toString(S は大文字)に直してください。 |
| 「Validatable から継承されたメソッドの可視性を下げることはできません」 | 実装した isValid() に public が付いていません。インターフェースのメソッドを実装するときは、必ず public を付けます。 |
| 「型の不一致: Employee から Manager には変換できません」 |
Manager m = new Employee(); のように、親のオブジェクトを子の型の変数に入れています。逆は可能ですが、これはできません。 |
println(e1) で ch10.Employee@… と表示される |
toString() をオーバーライドしていないか、名前が違います。@Override を付けて確かめてください。 |
| 「Serializable を型に解決できません」 |
import java.io.Serializable; がありません。Serializable の末尾で Ctrl + Space を押して import を入れてください。 |
ほかのコンパイルエラーは、付録A「よくあるコンパイルエラー」 も参考にしてください。
10.7 まとめと確認問題
まとめ
-
継承:
class 子 extends 親で、親のフィールドとメソッドを引き継ぐ。子のオブジェクトは親の型の変数に入れられる - すべてのクラスは
Objectを継承している。println(オブジェクト)はtoString()の結果を表示する -
オーバーライド:親のメソッドを子で書き直す。必ず
@Overrideを付ける -
インターフェース:
interfaceで、メソッドの約束だけを書く。クラスはimplementsで実装し、約束したメソッドをpublicで書く。インターフェースの型の変数に、実装したクラスのオブジェクトを入れられる -
Serializableはメソッドの無いインターフェース。実装したらprivate static final long serialVersionUID = 1L;も書く。子クラスにも書く
この章で使った書き方は、付録B「文法早見表」の「メソッドとクラス」 にまとめています。
確認問題
問1 class Manager extends Employee と書いたとき、Manager のオブジェクトで Employee の getName() は使えますか。
解答を見る
使えます。継承によって、親クラスのメソッドを引き継いでいるためです。
問2 @Override を付ける利点は何ですか。
解答を見る
メソッド名や引数を打ち間違えて、親のメソッドを書き直せていないときに、コンパイルエラーで気づけることです。
問3 インターフェースのメソッドと、クラスのメソッドの書き方の違いを答えてください。
解答を見る
インターフェースのメソッドは中身({ })を書かず、boolean isValid(); のように ; で終えます。中身は、実装するクラスが書きます。
問4 Employee implements Validatable と書いたのに isValid() を書かないと、どうなりますか。
解答を見る
「型 Employee は継承された抽象メソッド Validatable.isValid() を実装する必要があります」というコンパイルエラーになります。
問5 Serializable を実装したクラスに書く定数の行を答えてください。
解答を見る
private static final long serialVersionUID = 1L;
