あいさつの例から学ぶ interface と implements
Javaの学習用の教材ページです。
このページで分かること
人もロボットも「あいさつする」ことができます。その共通点を Greeting という型で表し、同じメソッドに渡せるようにします。
implements Greeting は「このクラスは Greeting に書かれたメソッドを使えるようにします」という宣言です。今回の例では、クラス側に public void sayHello() の処理を書きます。
v1 → v2 → v3 の順に、同じ短いプログラムを変更します。各版のコードはすべて掲載しています。青文字と説明で変更箇所を確認できます。
v1:人とロボットに、あいさつしてもらう
まずは implements を使わず、2つのクラスを個別に使います。メソッドの名前は、どちらも sayHello です。
保存するフォルダ構成
Human.javaRobot.javaMain.java
全ファイルを同じフォルダに UTF-8 で保存します。今回は package・import を書きません。
コンパイルと実行
cd /d C:\local-doc\github\pro3\10-a-00v1
javac -encoding UTF-8 *.java
java -cp . MainJDK の javac・java が使える Windows のコマンドプロンプトで実行します。保存先は C:\local-doc\github\pro3\10-a-00v1 です。フォルダがなければ作成してください。最初の cd /d コマンドで、ドライブも含めてこのフォルダに移動します。
Human.java
public class Human {
public void sayHello() {
System.out.println("こんにちは。");
}
}Robot.java
public class Robot {
public void sayHello() {
System.out.println("コンニチハ。");
}
}Main.java
public class Main {
public static void main(String[] args) {
Human human = new Human();
Robot robot = new Robot();
human.sayHello();
robot.sayHello();
}
}表示結果
こんにちは。
コンニチハ。まず、この4行を読んでみる
Human human = new Human();:人の実体(インスタンス)を作り、human という変数に入れます。左の Human は型、human は変数名です。
Robot robot = new Robot();:同じように、ロボットの実体を作ります。
human.sayHello();:人の sayHello を呼び出します。robot.sayHello();:ロボットの sayHello を呼び出します。
void は「戻り値がない」という意味です。() が空なので、呼び出すときに渡す値もありません。処理は文字を表示する1行だけです。
ここで考えること:同じ名前のメソッドを持っているだけでは、Human と Robot を「あいさつできるもの」という共通の型で扱えません。その型を次で作ります。
v2:共通の約束を interface に書く
10-a-00v1 をコピーして 10-a-00v2 を作ります。Greeting.java を追加し、Human と Robot に implements Greeting と @Override を付けます。Main.java は v1 と同じです。
保存するフォルダ構成
Greeting.javaHuman.javaRobot.javaMain.java
全ファイルを同じフォルダに UTF-8 で保存します。今回は package・import を書きません。
コンパイルと実行
cd /d C:\local-doc\github\pro3\10-a-00v2
javac -encoding UTF-8 *.java
java -cp . MainJDK の javac・java が使える Windows のコマンドプロンプトで実行します。保存先は C:\local-doc\github\pro3\10-a-00v2 です。フォルダがなければ作成してください。最初の cd /d コマンドで、ドライブも含めてこのフォルダに移動します。
Greeting.java
public interface Greeting {
void sayHello();
}Human.java
public class Human implements Greeting {
@Override
public void sayHello() {
System.out.println("こんにちは。");
}
}Robot.java
public class Robot implements Greeting {
@Override
public void sayHello() {
System.out.println("コンニチハ。");
}
}Main.java
public class Main {
public static void main(String[] args) {
Human human = new Human();
Robot robot = new Robot();
human.sayHello();
robot.sayHello();
}
}表示結果
こんにちは。
コンニチハ。interface と implements を1つずつ読む
public interface Greeting:「あいさつできるもの」を表す Greeting というインタフェースを定義します。
void sayHello();:「sayHello という、引数も戻り値もないメソッドを用意する」という約束です。ここには表示の処理を書かず、末尾を ; にします。
Human implements Greeting:Human がこの約束を実装します。Robot も同じ約束を実装します。具体的に何を表示するかは、それぞれのクラスに書きます。
@Override:約束どおりのメソッドを書いているか、コンパイラに確認してもらう目印です。例えば名前を間違えるとコンパイルエラーになります。
インタフェースのこのメソッドは暗黙に public です。Human と Robot の実装にも public が必要です。今回の通常のクラスで sayHello を削除すると、約束を満たさずコンパイルエラーになります。
表示結果は v1 と同じです。追加した宣言によって、Human と Robot の両方を Greeting 型として扱えるようになりました。次でその型を使います。
v3:同じメソッドで、人にもロボットにもあいさつしてもらう
10-a-00v2 をコピーして 10-a-00v3 を作ります。変更するのは Main.java だけです。変数の型を Greeting にし、共通の greet メソッドに渡します。
保存するフォルダ構成
Greeting.javaHuman.javaRobot.javaMain.java
全ファイルを同じフォルダに UTF-8 で保存します。今回は package・import を書きません。
コンパイルと実行
cd /d C:\local-doc\github\pro3\10-a-00v3
javac -encoding UTF-8 *.java
java -cp . MainJDK の javac・java が使える Windows のコマンドプロンプトで実行します。保存先は C:\local-doc\github\pro3\10-a-00v3 です。フォルダがなければ作成してください。最初の cd /d コマンドで、ドライブも含めてこのフォルダに移動します。
Greeting.java
public interface Greeting {
void sayHello();
}Human.java
public class Human implements Greeting {
@Override
public void sayHello() {
System.out.println("こんにちは。");
}
}Robot.java
public class Robot implements Greeting {
@Override
public void sayHello() {
System.out.println("コンニチハ。");
}
}Main.java
public class Main {
public static void main(String[] args) {
Greeting human = new Human();
Greeting robot = new Robot();
greet(human);
greet(robot);
}
public static void greet(Greeting target) {
target.sayHello();
}
}表示結果
こんにちは。
コンニチハ。左の型と、右で作る実体を分けて読む
Greeting human = new Human(); の左側は「変数を Greeting 型として使う」、右側は「Human の実体を作る」です。Human が implements Greeting を宣言しているので、この代入ができます。
Greeting robot = new Robot(); も同じです。Greeting という型にそろえても、人がロボットに変わるわけではありません。
greet(Greeting target) の target は、呼び出した側から受け取る値を入れる変数(引数)です。Greeting 型なので、Human と Robot のどちらも受け取れます。
static を付けた greet は、この例では static な main から greet(...) と直接呼べます。ここで注目するのは、引数の型が Greeting であることです。
同じ1行でも、実際の相手に応じた処理が動く
| 呼び出し | target が参照する実体 | target.sayHello() の動作 |
|---|---|---|
greet(human) | Human | こんにちは。 |
greet(robot) | Robot | コンニチハ。 |
greet の中では、相手が人かロボットかを判定する必要がありません。Greeting の約束にある sayHello を呼べます。
書き方を整理する
「型を作る」「約束を実装する」「実体を作る」を分けて考えましょう。
| 書き方 | この例での役割 |
|---|---|
interface Greeting | 共通のメソッドの形を宣言し、Greeting という型を作る。 |
implements Greeting | クラスを Greeting 型として扱えるようにし、約束のメソッドを実装する。 |
new Human() | Human の実体を作る。 |
Greeting target | あいさつできる相手を受け取る変数。 |
Human extends Ape | Human が親クラス Ape の機能を継承する。前の教材で学んだ書き方。 |
よく迷うところ
implements を書くと処理も付いてくる?
今回の Greeting にあるのはメソッドの形だけです。文字を表示する処理は Human と Robot に書きます。
名前が同じなら Greeting 型に入れられる?
この例では implements Greeting の宣言が必要です。v1 の Human を Greeting human = new Human(); と書き換えるだけでは使えません。
new Greeting() と書く?
インタフェース自体のインスタンスは作れません。この例では new Human() や new Robot() で実体を作り、Greeting 型の変数に入れます。
Greeting 型なら、Human のすべてのメソッドが使える?
Human 独自のメソッドを追加しても、Greeting 型の変数からそのまま呼べるわけではありません。この例では、Greeting に宣言した sayHello を共通に呼び出します。
ここでは抽象メソッドを宣言する基本形を学びました。インタフェースに処理を書ける default メソッドなどは、このページの範囲外です。
自分で1つ追加してみる
v3 に、あいさつする犬を追加してみましょう。
問題:Dog も同じ greet に渡す
① Dog.java を作り、Greeting を実装してください。sayHello では「ワン!」と表示します。
② Main の main メソッド内で Dog の実体を作り、Greeting 型の変数 dog に入れてください。
③ greet(dog); を追加して、もう一度コンパイル・実行してください。共通の greet メソッドの中身は、そのままで使えます。
解答を見る
Dog.java
public class Dog implements Greeting {
@Override
public void sayHello() {
System.out.println("ワン!");
}
}main メソッド内の greet(robot); の後に追加
Greeting dog = new Dog();
greet(dog);追加後の表示結果
こんにちは。
コンニチハ。
ワン!元の教材につなげる
元の教材の Lockable は、このページの Greeting にあたります。lock() と unlock() が約束のメソッドです。金庫と鍵付き宝石箱を、同じ Lockable 型として扱います。
元の Lockable[] は、共通の型を使って複数の実体を配列にまとめたものです。このページでは、まず1つずつ引数に渡す方法で共通の型を学びました。