「アミーゴ、クジラは好きですか?」

「クジラ? いいえ、聞いたこともありません。」

「それは牛のようなものですが、体が大きくなっただけで泳ぎます。ちなみに、クジラは牛から生まれました。ええと、少なくとも共通の祖先を持っています。それは問題ではありません。」

ポリモーフィズムとオーバーライド - 1

「聞いてください。OOP のもう 1 つの非常に強力なツールであるポリモーフィズムについてお話したいと思います。これには 4 つの機能があります。」

1) メソッドのオーバーライド。

ゲーム用に「Cow」クラスを作成したと想像してください。たくさんのメンバー変数とメソッドがあります。このクラスのオブジェクトは、歩く、食べる、寝るなどのさまざまな動作を実行できます。牛も歩くときに鈴を鳴らします。クラス内のすべてを細部に至るまで実装したとします。

ポリモーフィズムとオーバーライド - 2

その後、突然顧客が、すべてのアクションが海で行われ、主人公がクジラである新しいレベルのゲームをリリースしたいと言いました。

あなたは Whale クラスの設計を開始し、それが Cow クラスとわずかに異なるだけであることに気づきました。どちらのクラスも非常に似たロジックを使用するため、継承を使用することにします。

Cow クラスは親クラスとして最適です。必要な変数とメソッドがすべてすでに含まれています。クジラの泳ぐ能力を追加するだけです。しかし、問題があります。クジラには足、角、鐘が付いています。結局のところ、Cow クラスはこの機能を実装しています。何ができるでしょうか?

ポリモーフィズムとオーバーライド - 3

メソッドのオーバーライドが役に立ちます。新しいクラスで必要なことを正確に実行しないメソッドを継承した場合は、そのメソッドを別のメソッドに置き換えることができます。

ポリモーフィズムとオーバーライド - 4

これはどのように行われるのでしょうか? 子孫クラスでは、変更するメソッドを (親クラスと同じメソッド シグネチャを使用して)宣言します。次に、メソッドの新しいコードを作成します。それでおしまい。親クラスの古いメソッドが存在していないかのようです。

仕組みは次のとおりです。

コード 説明
class Cow
{
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a cow");
}
}class Whale extends Cow
{
public void printName()
{
System.out.println("I'm a whale");
}
}
Cow ここでは、と の 2 つのクラスを定義します Whale。 Whaleを継承します Cow

クラス はメソッドWhale をオーバーライドします printName();

public static void main(String[] args)
{
Cow cow = new Cow();
cow.printName();
}
このコードは、画面に「私は牛です」と表示します。
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printName();
}
このコードは画面に「私はクジラです」と表示します

を継承してCowオーバーライドするとprintNameWhaleクラスには実際に次のデータとメソッドが含まれます。

コード 説明
class Whale
{
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a whale");
}
}
私たちは古い方法については何も知りません。

「正直に言うと、それを期待していました。」

2) しかし、それだけではありません。

「クラスに 、他の 2 つのメソッドを呼び出す , メソッドがCow あるとします 。その場合、コードは次のように機能します。」printAll

画面には次のように表示されます:
私は白です、
私はクジラです

コード 説明
class Cow
{
public void printAll()
{
printColor();
printName();
}
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a cow");
}
}

class Whale extends Cow
{
public void printName()
{
System.out.println("I'm a whale");
}
}
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printAll();
}
画面には次のように表示されます:
私は白です、
私はクジラです

Cow クラスの printAll () メソッドが Whale オブジェクトに対して呼び出される場合、Cow の printName() メソッドではなく、Whale の printName() メソッドが使用されることに注意してください。

重要なのは、メソッドが記述されているクラスではなく、メソッドが呼び出されるオブジェクトの型 (クラス) です。

"そうか。"

「継承およびオーバーライドできるのは、非静的メソッドのみです。静的メソッドは継承されないため、オーバーライドできません。」

継承を適用​​してメソッドをオーバーライドした後の Whale クラスは次のようになります。

コード 説明
class Whale
{
public void printAll()
{
printColor();
printName();
}
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a whale");
}
}
継承を適用​​してメソッドをオーバーライドした後の Whale クラスは次のようになります。私たちは古いprintName方法については何も知りません。

3) 型キャスト。

さらに興味深い点があります。クラスは親クラスのすべてのメソッドとデータを継承するため、このクラスのオブジェクトは親クラス(および親の親など、Object クラスに至るまで)の変数によって参照できます。次の例を考えてみましょう。

コード 説明
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printColor();
}
画面には「
私は白人です」と表示されます。
public static void main(String[] args)
{
Cow cow = new Whale();
cow.printColor();
}
画面には「
私は白人です」と表示されます。
public static void main(String[] args)
{
Object o = new Whale();
System.out.println(o.toString());
}
画面には「
Whale@da435a」と表示されます。
toString() メソッドは Object クラスから継承されます。

「いいものですね。でも、なぜこれが必要なのでしょうか?」

「これは貴重な機能です。これが非常に価値があることが後でわかります。」

4) 遅延バインディング (動的ディスパッチ)。

以下にその様子を示します。

コード 説明
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printName();
}
画面には「
私はクジラです」と表示されます。
public static void main(String[] args)
{
Cow cow = new Whale();
cow.printName();
}
画面には「
私はクジラです」と表示されます。

どの特定のprintNameメソッド (Cow クラスまたは Whale クラス) を呼び出すかを決定するのは変数の型ではなく、変数によって参照されるオブジェクトの型であることに注意してください。

Cow変数にはWhaleオブジェクトへの参照が格納され、Whaleクラスで定義されたprintNameメソッド呼び出されます。

「そうですね、分かりやすくするためにそれを追加したわけではありません。」

「ええ、それはそれほど明白ではありません。この重要なルールを覚えておいてください。」

変数に対して呼び出すことができるメソッドのセットは、変数の型によって決まります。ただし、どの特定のメソッド/実装が呼び出されるかは、変数によって参照されるオブジェクトの型/クラスによって決まります。

"私が試してみます。"

「これには常に遭遇するので、すぐに理解でき、決して忘れることはありません。」

5) 型キャスト。

キャストは、参照型、つまりクラスに対して、プリミティブ型とは異なる動作をします。ただし、拡大変換と縮小変換は参照型にも適用されます。次の例を考えてみましょう。

ワイドニングコンバージョン 説明
Cow cow = new Whale();

古典的な拡大変換。これで、Whale オブジェクトの Cow クラスで定義されたメソッドのみを呼び出すことができます。

コンパイラでは、Cow 型で定義されたメソッドを呼び出す場合にのみCow 変数を使用できます。

ナロー化変換 説明
Cow cow = new Whale();
if (cow instanceof Whale)
{
Whale whale = (Whale) cow;
}
型チェックを伴う古典的な縮小変換。Cow 型のCow変数には、Whale オブジェクトへの参照が格納されます。
これが当てはまることを確認し、(拡張) 型変換を実行します。これは、型キャストとも呼ばれます。
Cow cow = new Cow();
Whale whale = (Whale) cow; //exception
オブジェクトの型チェックを行わずに、参照型の縮小変換を実行することもできます。
この場合、cow変数が Whale オブジェクト以外を指している場合、例外 (InvalidClassCastException) がスローされます。

6) さて、おいしいものをどうぞ。元のメソッドを呼び出します。

継承されたメソッドをオーバーライドするときに、完全に置き換えたくない場合があります。時には、それに少しだけ追加したいこともあります。

この場合、実際には、新しいメソッドのコードで同じメソッドを、基本クラス上で呼び出す必要があります。Java を使用すると、これが可能になります。やり方は次のとおりです super.method()

ここではいくつかの例を示します。

コード 説明
class Cow
{
public void printAll()
{
printColor();
printName();
}
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a cow");
}
}

class Whale extends Cow
{
public void printName()
{
System.out.print("This is false: ");
super.printName();

System.out.println("I'm a whale");
}
}
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printAll();
}
画面には次のように表示されます:
私は白です
これは誤りです: 私は牛です
私はクジラです

「うーん。まあ、勉強になりました。ロボットの耳が溶けそうになりました。」

「はい、これは単純なものではありません。これはあなたが遭遇するであろう最も難しい資料の一部です。教授は、まだ何か理解できない場合は、他の著者の資料へのリンクを提供すると約束しました。ギャップ。」