ポリモーフィズムとは
- 同じ名前のメソッドで異なる挙動を実現
- Triangle,RectangleクラスでShapeクラスを継承し、同名のgetAreaメソッドを定義
- 同じShape型の変数でもオブジェクトの型が違うので、オブジェクト型に応じて基底/派生クラスのメソッドが呼ばれる
- →異なる機能を同じ名前で呼ぶことができる!
- メリット:
- 保守性に優れる
- 機能の差し替えはインスタンスの差し替えのみ
- 理解しやすい
- 保守性に優れる
Shape.java
public class Shape {
protected double width;
protected double height;
public Shape(double width, double height) {
this.width = width;
this.height = height;
}
//図形の面積を取得(派生クラスでオーバーライドする)
public double getArea() {
return 0d;
}
//public abstract double getArea();
}
Triangle.java
public class Triangle extends Shape {
public Triangle(double width, double height) {
super(width, height);
}
//三角形の面積取得
@Override
public double getArea() {
return this.width * this.height / 2;
}
}
Rectangle.java
public class Rectangle extends Shape {
public Rectangle(double width, double height) {
super(width, height);
}
//四角形の面積を取得
@Override
public double getArea() {
return this.width * this.height;
}
}
public class PolymorphismBasic {
public static void main(String[] args) {
//Triangle型のオブジェクトを代入(アップキャスト)
Shape tri = new Triangle(10, 50);
//Rectangle型のオブジェクトを代入(アップキャスト)
Shape rec = new Rectangle(10, 50);
System.out.println(tri.getArea()); //250.0
System.out.println(rec.getArea()); //500.0
}
}
抽象メソッド
- 上の例では基底クラスShapeがgetAreaメソッドをオーバーライド期待しているが強制ではない
- 抽象メソッド:それ自体は中身(機能)を持たない空メソッド
-
抽象クラス:抽象メソッドを含んだクラス
- 抽象クラスを継承したクラスは全抽象メソッドをオーバーライドする義務を持つ
- 抽象メソッドによって特定のメソッドが派生クラスでオーバーライドされることを保証できる
使い方
-
抽象メソッド定義には
abstract
修飾子をつける- 基底クラスでブロック中身を持つことはできず、
;
で宣言終了するのみ
- 基底クラスでブロック中身を持つことはできず、
- 抽象メソッドを含んだクラスにはclassブロックに
abstract
修飾子をつける
Shape.java
public abstract class Shape {
protected double width;
protected double height;
public Shape(double width, double height) {
this.width = width;
this.height = height;
}
//基底クラスで中身を持つことはできない!
public abstract double getArea();
}
インターフェース
- Javaでは多重継承を認めないので、一度に継承できるクラスは常に1つのみ
- 不要なメソッドもとりあえずオーバーライドしないといけない
- →インターフェースを使う
- 配下メソッドが全部抽象メソッドであるクラス
- 多重継承が可能
- それぞれのメソッドは意味的な塊で異なるインターフェースに振り分ける
- 派生クラス側で必要なメソッドのみ選択できる
インターフェースの定義
-
interface
命令- public:全クラスからアクセス可能
- abstract:抽象クラス
- strictfp:浮動小数点を環境依存しない方法で演算
- 配下に抽象メソッドを含むのは明らかなのでabstruatはつけなくても同じ(一般的に省略)
- インターフェース名はPascal記法
public interface Shape {
double getArea();
}
定義可能メンバー
- 抽象メソッド
- defaultメソッド
- クラスメソッド
- 定数フィールド
- 入れ子staticクラス/インターフェース
インターフェース実装
- 定義済みインターフェースを継承してクラスを定義すること
- インターフェース実装したクラスを実装クラスという
implements インターフェース名
- 複数インターフェース実装する時は
implements Shape, Hoge, Bar
- 継承と組み合わせて
extends MyParent implements Shape
//Shapeインターフェースの実装クラス
public class Rectangle implements Shape {
private double width;
private double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public double getArea() {
return this.width * this.height;
}
}
インターフェースメンバー
- 抽象メソッド、定数フィールド、defaultメソッド、staticメソッド(Java8以降)
定数フィールド
- インターフェースはインスタンス化できないのでインターフェースフィールドは定義不可
- フィールドは無条件に
public static final
- 冗長なので以下のように書く
interface MyApp{
String TITLE = "不死鳥の騎士団";
double NUMBER = 5;
}
staticフィールド(Java8以降)
- 既存のインターフェースに関連して何らかの機能をまとめておきたい時
defaultメソッド(Java8以降)
- 実装クラス側で明示的に実装されない場合既定で採用される実装
public interface Loggable {
default void log(String msg) {
System.out.println("Log: " + msg);
}
}
//logメソッドをオーバーライドしない
public class LoggableImpl implements Loggable {
}
InterfaceDefault.java
public class InterfaceDefault {
public static void main(String[] args) {
var l = new LoggableImpl();
//Loggableクラスのdefaultメソッドが呼ばれる
l.log("Harry Potter"); //Log:Harry Potter
}
}
- superで明示的に実装クラスからdefaultメソッドを呼ぶこともできる
//logメソッドをオーバーライドしない
public class LoggableImpl implements Loggable {
@Override
public void log(String msg) {
Loggable.super.log(msg);
System.out.println("LogImpl: " + msg);
}
}
InterfaceDefault.java
public class InterfaceDefault {
public static void main(String[] args) {
var l = new LoggableImpl();
l.log("Harry Potter");
//Log:Harry Potter
//LogImpl:Harry Potter
}
}
多重継承の注意
抽象メソッドが重複
- メソッド名と引数型が同じで戻り値のみ異なるときコンパイルエラー
- 実装側の戻り値は最下位の派生型である必要
public interface IHoge {
void foo();
//CharSequence foo(); //OK
}
public interface IHoge2 {
String foo();
}
public class HogeImpl implements IHoge, IHoge2 {
@Override
public String foo() { } //戻り値の型がIHoge.foo()と互換性なし
}
定数フィールドが重複
- データ型が異なる場合も、型/値が等しい場合も、参照時エラー
public interface Hoge {
int DATA = 0;
}
public interface Hoge2 {
String DATA = "This is an apple.";
}
public class HogeImpl implements Hoge, Hoge2 {
public void foo() {
System.out.println(DATA); //エラー
}
}
defaultメソッドが重複
- 実装を持つのでメソッド名、引数、戻り値全部合致した場合もエラー
-
super
で実装クラスで明示的にdefaltメソッドを参照することはOK - 階層関係に差がある場合、同名のdefaultメソッドを使用してもOK
- より近い実装関係(直接実装関係)にあるクラスが優先
public interface DHoge {
default void log(String msg) {
System.out.println("DHoge: " + msg);
}
}
public interface DHoge2 {
default void log(String msg) {
System.out.println("DHoge2: " + msg);
}
}
public class HogeImpl implements DHoge, DHoge2 {
@Override
public void log(String msg) {
DHoge.super.log(msg); //エラー(Stringを持つ重複したdefaultメソッドlogはDHoge2,DHogeから継承されている)
//IHoge.super.log(msg); //明示的に参照はOK
}
}
インターフェースと抽象クラスどっちを使う?
- インターフェース優先
- クラスはObjectクラスをrootとしいたツリー階層になっている
- インターフェース:クラスツリーから独立
- 抽象クラス:クラスツリーの一部を構成
- 型階層は現実全ての表現ではない
- インターフェースなら
- 型階層から独立しているので特定の機能を割り込ませることができる
- 基底クラスによらず新たなインターフェースを実装可能
- 多重継承可能
- 抽象クラスの場合、
- 関係クラスを洗い出し全部の上位クラスとなる位置に入れる必要
- 途中に必要ない機能があっても継承を拒否できない
- 採用するときはクラスがどのメソッドを持つか(クラスの振る舞い)より、実装に目を向けるべき
*派生クラスでの共通処理
- 実装処理の骨格
- ex: java.ioパッケージのWriterクラスでは
BufferWriter
,OutputStreamWriter
,PrintWriter
の基底クラスでXWriter
枠組みを提供
- インターフェースなら