【Java】多态性
多态是指通过一个统一的接口来使用不同的对象。
-
- 同じ名前のメソッドで異なる挙動を実現
-
- Triangle,RectangleクラスでShapeクラスを継承し、同名のgetAreaメソッドを定義
-
- 同じShape型の変数でもオブジェクトの型が違うので、オブジェクト型に応じて基底/派生クラスのメソッドが呼ばれる
-
- →異なる機能を同じ名前で呼ぶことができる!
-
- メリット:
保守性に優れる
機能の差し替えはインスタンスの差し替えのみ
理解しやすい
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();
}
public class Triangle extends Shape {
public Triangle(double width, double height) {
super(width, height);
}
//三角形の面積取得
@Override
public double getArea() {
return this.width * this.height / 2;
}
}
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修飾子をつける
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;
}
}
接口成员 (Jié
- 抽象メソッド、定数フィールド、defaultメソッド、staticメソッド(Java8以降)
常数字段
インターフェースはインスタンス化できないのでインターフェースフィールドは定義不可
フィールドは無条件にpublic static final
冗長なので以下のように書く
interface MyApp{
String TITLE = "不死鳥の騎士団";
double NUMBER = 5;
}
静态字段(Java8及以上版本)
- 既存のインターフェースに関連して何らかの機能をまとめておきたい時
默认方法(Java8及以上版本)
- 実装クラス側で明示的に実装されない場合既定で採用される実装
public interface Loggable {
default void log(String msg) {
System.out.println("Log: " + msg);
}
}
//logメソッドをオーバーライドしない
public class LoggableImpl implements Loggable {
}
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);
}
}
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); //エラー
}
}
默认方法重复
-
- 実装を持つのでメソッド名、引数、戻り値全部合致した場合もエラー
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枠組みを提供