検索
連載

オブジェクト指向の肝、ポリモーフィズムとインタフェースをAIに聞きながら学ぼうAIアシスト時代のJavaプログラミング入門(7)

対話型AI(人工知能)にアドバイスを受けながら進めるJavaプログラミングの入門連載。今回は、ポリモーフィズム(多態性)を、インタフェースとともに学習します。使いこなせれば便利ながらも、言葉の響きからも難解とされがちなポリモーフィズムを、親しみやすい事例から理解しましょう。

Share
Tweet
LINE
Hatena
「AIアシスト時代のJavaプログラミング入門」のインデックス

連載:AIアシスト時代のJavaプログラミング入門

本連載のサンプルコードをGitHubで公開しています。こちらからダウンロードしてみてください。


まだまだあるオブジェクト指向の機能

 対話型AI(人工知能)にアドバイスを受けながら進めるJavaプログラミングの入門連載「AIアシスト時代のJavaプログラミング入門」前回は、Javaをオブジェクト指向言語たらしめる機能として、カプセル化と継承について学びました。いずれも、「現実社会のモノや概念をプログラムで表現する」ために必須の機能ということが分かりました。

 今回は、前回Visual Studio Code(以降、VS Code)からGitHub Copilot(以降、Copilot)に投げた質問「オブジェクト指向についてメソッドやコンストラクタ以外の主な機能を重要度順に教えて。サンプルコードは不要です。」で得られた、その他の回答に注目していきます。まず、その回答を振り返ってみます(図1)。

図1 「オブジェクト指向についてメソッドやコンストラクタ以外の主な機能を重要度順に教えて。」の回答(再掲)
図1 「オブジェクト指向についてメソッドやコンストラクタ以外の主な機能を重要度順に教えて。」の回答(再掲)

 「まとめ」も再掲すると、2番目の「ポリモーフィズム」が前回扱っていない内容です。

  • 最も重要なのは「カプセル化」で、データの保護と管理が基本です。
  • 次に「継承」と「ポリモーフィズム」が、コードの再利用性や柔軟性を高めます。
  • 他の機能は、設計や用途に応じて適切に使い分けることが重要です。

 今回は、この「ポリモーフィズム」からオブジェクト指向を深掘りしていきましょう。

ポリモーフィズム(Polymorphism)

 「ポリモーフィズム」とは、日常生活では(おそらく)使わない言葉なので、機能のイメージもしにくいのではないでしょうか。図1の回答を見ると、「まとめ」に「コードの再利用性と柔軟性」とある他に、以下のように説明されています。

  1. 同じメソッド名や操作で異なる動作を実現する仕組み
    • 例:メソッドのオーバーライドやインタフェースの実装
  2. メリット:柔軟性のあるコード設計が可能

【補足】「インタフェース」への統一について

 AIの回答においては「インターフェース」が登場しますが、本文中では@ITのルールに合わせて「インタフェース」に統一しています。意味としては変わりませんのでご了承ください。

 1.については、前回で学習した内容です。「同じメソッド名や操作で異なる動作を実現する」というのは、「オーバーライド」という機能で実現されていました(「例:」にも示されていますね)。前回の例では、同じmoveというメソッドをDogクラスとCatクラスで呼び出すと、それぞれ以下のような動きとなりました。

  • Dogクラスのmoveメソッド→ポチが走ります。
  • Catクラスのmoveメソッド→タマが移動します。

 これは、DogクラスとCatクラスの親クラスであるAnimalクラスではmoveメソッドを「〜が移動します。」としていたのに、Dogクラスでは「〜が走ります。」となるようにオーバーライドしたからでした。これにより、moveメソッドをDogクラスとCatクラスで異なる動きにできたわけです。

 しかしながら、このような動きの違いは継承によってできたわけで、ポリモーフィズムとの関係がよく分かりません。さらに、「例:」には「インタフェース」という新しい言葉も登場しています。

 2.に示されているメリットも、1.の問題がクリアにならないと具体的にイメージしにくいかもしれません。何をもって「柔軟性」があるというのでしょうか。そこで、ポリモーフィズムについて掘り下げる質問を改めて投げてみることにしましょう。「ポリモーフィズムについてもう少し詳しく。」と投げてみました(図2)。

図2 ポリモーフィズムについてもう少し詳しく。」の回答
図2 ポリモーフィズムについてもう少し詳しく。」の回答

【補足】「コードは不要です。」は省略できる

 前回の流れでチャットビューを使うと、「コードは不要です。」が生きていて、ひとまずはコードなしで説明してくれます。回答にコードが出てきてしまったら、質問文に「コードは不要です。」を付加してみてください。

ポリモーフィズムのポイント

 図2では、ポリモーフィズムのポイントがまず示されています。「インタフェース(型)」も出てきていますが、ひとまず脇に置いておき、ポイントを見てみましょう。

  1. 共通の型で扱える
  2. 動的なメソッド呼び出し
  3. 拡張性と柔軟性の向上

 3.の「柔軟性」についてはたびたび出てきているので、これは最後に検討するとして、まずは最初の1.と2.です。「共通の型」って何? という疑問が湧くと思うので、その説明を読んでみましょう。

・親クラス型やインタフェース型の変数で、さまざまな子クラスのインスタンスを扱うことができます。
例:Animal型の変数にDogやCatのインスタンスを代入できる。

 例に、すごいことが書いてあります。前回の「継承」の解説では触れていなかったのですが、実は

子クラスのインスタンスは親クラスの変数に代入できる

のです。前回の「継承」の例で「多態性の例」とあったコードに少しだけ触れましたが、それが相当します。前回の図から、該当箇所のみを切り出して再掲します(図3)。

図3 「多態性の例」のコード
図3 「多態性の例」のコード

 図3の赤字にあるように、「子クラスのインスタンスは親クラスの変数に代入でき」「子クラスのメソッドが呼ばれる」のです。型はAnimalでも、実際のインスタンスの型(この場合はDog)に沿って振る舞うというわけですね。これがCatならCatに沿って振る舞います。これが、図2におけるポイントの「2. 動的なメソッド呼び出し」に説明されている内容です。ポリモーフィズムの和訳である「多態性」という用語は、ここから来ています。Animalが「多」くの「態」(さま)を持つ「性」質を備えるわけです(図4)。

図4 ポリモーフィズム(多態性)
図4 ポリモーフィズム(多態性)

ポリモーフィズムのコード

 ポリモーフィズムについて深めるために、このコードを踏み台に今回用のプロジェクトを作成しましょう。これまでは、第1回で紹介した手順でプロジェクトを作成していましたが、今回はプロジェクト作りもCopilotに任せてしまいましょう。

 VS Codeを、前回紹介した手順でAgentモードに切り替えてください。そして、「inheritanceプロジェクトを複製してpolymorphismプロジェクトを作って。」と投げてみましょう。ワークスペース内に自動的にpolymorphismプロジェクトが作成され、ひとまず内容は前回作成したinheritanceプロジェクトと同じになります(図5)。

図5 「inheritanceプロジェクトを複製してpolymorphismプロジェクトを作って。」の回答
図5 「inheritanceプロジェクトを複製してpolymorphismプロジェクトを作って。」の回答

【補足】変更されたファイル

 Agentモードの操作で変更されたファイルおよびフォルダには、緑色の□に●の入ったアイコンが表示されます(図6)。対応するファイルはチャットビューの下部に表示されるので確認できます。

図6 変更されたファイルのアイコン
図6 変更されたファイルのアイコン

 ビルドして実行すればinheritanceプロジェクトと同じ結果となりますが、その前にコードを少し書き換えてもらいましょう。「他に追加や修正したい点があればご指示ください。」と言ってくれていますしね。遠慮なくいきましょう。

 エディタビューにApp.javaが開かれている状態で、プロンプトパネルに「App.java」と正体で表示されていることを確認してください(図7)。もし斜体になっている場合には、ファイル名右の[+]をクリックしてください。

図7 プロンプトパネルにApp.javaが表示されている状態
図7 プロンプトパネルにApp.javaが表示されている状態

 ここで、「多態性の例にCatクラスも追加して。」と投げてみます(図8)。

図8 「多態性の例にCatクラスも追加して。」の回答
図8 「多態性の例にCatクラスも追加して。」の回答

 インデントが少しおかしいのですが、既存のコードが削除され、DogクラスとCatクラスのインスタンスを格納する2つのAnimal型変数が宣言されました。実行結果がコメントに表示されてしまっていますが、改めて実行して確かめましょう(図9)。

図9 polymorphismプロジェクトの実行結果
図9 polymorphismプロジェクトの実行結果

 ポリモーフィズムに関係ないコードの結果も混じっていますが、最後の2行が該当する出力です。同じAnimal型の変数ですが、DogクラスのインスタンスとCatクラスのインスタンスで結果が変わっています。Dogクラスではmoveメソッドをオーバーライドしているので「〜が走ります。」となりますが、CatクラスではオーバーライドされていないのでAnimalクラスのmoveメソッドすなわち「〜が移動します。」になります。このように、実際のインスタンスに合わせて振る舞うのが多態性というわけです。

 さて、ここで疑問が湧く人もいるかと思います。mainメソッドの冒頭ではDogクラスとCatクラスのインスタンスをそのままDog型、Cat型の変数に入れているので、これでいいんじゃないかと。確かに、それぞれのクラスのメソッドを呼ぶだけなら、わざわざAnimalクラスの変数を使う必要もありませんね。ここで、図2の「ポリモーフィズムの主なポイント」にあった「拡張性と柔軟性」です。この当たりを具体的に示してもらうことで、この疑問を解決しましょう。

ポリモーフィズムによる拡張性と柔軟性

 ストレートに、「ポリモーフィズムの拡張性と柔軟性が分かる例を追加して。」と投げてみました(図10)。

図10 「ポリモーフィズムの拡張性と柔軟性が分かる例を追加して。」の結果
図10 「ポリモーフィズムの拡張性と柔軟性が分かる例を追加して。」の結果

 エディタビューでコードが改変されます。改変の内容は、

  1. 新しいBirdクラスの追加とmoveメソッドのオーバーロード、独自のsingメソッドの追加
  2. mainメソッドでAnimal型の配列を作成して、異なる型のインスタンスのmoveメソッドを呼び出し

となっています(図11)。

図11 「ポリモーフィズムの拡張性と柔軟性が分かる例を追加して。」で改変されたコード
図11 「ポリモーフィズムの拡張性と柔軟性が分かる例を追加して。」で改変されたコード

 1.についてはこれまでの知識で読み解けると思います。2.では何をやっているか見るために、まずは実行していることにしましょう。その前に、mainメソッドの中にいろんなメソッド呼び出しがたまってきてしまっているので、例を追加した部分より前をコメントアウトしてしまいます。該当範囲を指定して、[Ctrl]+[/](Windows)/[Command]+[/](macOS)キーを押すだけと簡単です。実行してみると、図13のようになりました。

図13 「ポリモーフィズムの拡張性と柔軟性が分かる例を追加して。」の実行結果
図13 「ポリモーフィズムの拡張性と柔軟性が分かる例を追加して。」の実行結果

 これを踏まえて、追加のコードを図解してみたのが図14です。

図14 「ポリモーフィズムの拡張性と柔軟性が分かる例を追加して。」のコード
図14 「ポリモーフィズムの拡張性と柔軟性が分かる例を追加して。」のコード

 コードのポイントは(1)3つのクラスのインスタンスで、Animal型の配列を作り、(2)それを拡張for文で表示し、(3)Birdクラスのsingメソッドを呼び出す、この3つです。これで、4行結果が表示されているわけです。なお、配列や拡張for文を忘れてしまったという人は、それぞれ第2回第4回を振り返ってください。

 (1)と(2)でやっていることは、配列も拡張for文も使わずに、それぞれのクラスのインスタンスを生成して変数に入れて、それにmoveメソッドを呼び出せば、同様のことが行えます。しかしながらこの方法は、プログラムを拡張しようとしてPigクラスやLizardクラスを作っていったら、それらの変数を用意して呼び出すコードを追加していくという、考えるだけで面倒な作業が想像できますね。

 現状の方法だと、(新しいクラスの定義は別として)配列に新しいクラスのインスタンス生成を追加するだけで、拡張for文には全く手を付ける必要はないのです。拡張のしやすさ、変更への追従のしやすさ、これが「拡張性と柔軟性」というわけです。これは、Animal型を受け取る(返す)メソッドを設計する場合にも同じことが言えます。

ポリモーフィズム=万能ではない

 このようにポリモーフィズムはクラスの継承やメソッドのオーバーライドを生かした、いかにもオブジェクト指向的な機能ですが、万能ではありません。図14のコードの最後では、Animal型の変数からBirdクラス独自のsingメソッドを呼び出していますが、このときはAnimal型をBird型に型変換(キャスト)して、変数はBird型です! というように宣言してからsingメソッドを呼び出しています。

 つまり、ポリモーフィズムを利用して子クラスのメソッドを呼び出す場合、そのメソッドは親クラスにも定義されている必要があります。親クラスは全ての子クラスを知っているわけではないので、存在しないメソッドを直接呼ぶことはできないのです。存在しないメソッドを呼び出すには、この例のように「実際にはBird型である」と宣言(キャスト)することで、はじめてBird独自のメソッドを利用できるようになります。

 もしキャスト先が誤っていて、実際にはBird型でなかった場合、そのメソッドは呼び出せず、「例外(エラー)」が発生します。この部分は開発者が責任を持って、型が正しく一致するようにコードを書く必要があるわけです。

こんな質問をしてみたい!

 最後に、「型変換」(キャスト)が登場しました。このように、Javaではある型を別の型に変換することができます。ただし、何でもかんでも変換できるわけではなく、一定のルールが存在します。Javaの型変換について聞いてみると、今回のようなことが可能な理由が判明するかもしれませんよ!

インタフェース(Interface)

 次は、5番目に重要とされている「インタフェース」です。冒頭の図1では、以下のように説明されています。

  1. クラスが実装すべきメソッドの仕様を定義する仕組み
  2. メリット:異なるクラス間で共通の動作を保証

 メソッドの仕様を定義するならクラスと同じとも言えそうですが、「クラスが実装すべき」がポイントのようです。ところで、インタフェースという用語は、図1の4番目「抽象化」(Abstraction)にも「抽象クラス」というものとともに登場しています。抽象ということがポイントのようですが、これを知るためにインタフェースについて掘り下げる質問を改めて投げてみることにしましょう。「インタフェースについてもう少し詳しく。コードは不要です。」と投げてみました(図15)。

図15 「インタフェースについてもう少し詳しく。」の回答
図15 「インタフェースについてもう少し詳しく。」の回答

 上記でも示された「仕様」を定めるものと示されました。インタフェース自体には具体的な処理は含まれず、メソッドの仕様のみが宣言されているものと読み取れます。特徴が4つ示されましたが、仕様のみを定めるとはいったいどのようなことなのか、これまでと同様に具体的なイメージで示してもらいましょう。「インタフェースの具体的なイメージをコードなしで。」と投げてみました(図16)。

図16 「インタフェースの具体的なイメージをコードなしで。」の回答
図16 「インタフェースの具体的なイメージをコードなしで。」の回答

 インタフェースを、「プリンタ」で例えてくれました。かなりシンプルな説明で、これでも十分理解できると思いますが、さらに簡略化すると、このような感じでしょうか。

  • インタフェースとは「やるべきことリスト」である
  • 具体的に何をやるかはインタフェースを使うクラスが実現する

 ここではプリンタに例えられましたが、ポリモーフィズムの例えで登場した動物でも同様です。「移動する」(moveメソッド)ということだけインタフェースで決めておき、どのように移動するかはそれぞれのクラスで決めるというわけです(図17)。

図17 インタフェース
図17 インタフェース

インタフェースの目的

 インタフェースという用語は、既に学習したポリモーフィズムの中でもたびたび登場してきたので、両者には密接な関係がありそうだということは分かりますね。そこで、分かりやすいように動物の例を使って、インタフェースの例を提示してもらいましょう。polymorphismプロジェクトを作ったときと同じように、プロジェクトを複製して必要な修正を加えてもらうことにします。

 Agentモードになっていることを確認して、「polymorphismプロジェクトを複製してinterfaceプロジェクトを作って。」と投げてみます。すると何と、プロジェクトを複製するばかりか、インタフェースを使ったコードに書き換えてくれました(図18)。

図18 「polymorphismプロジェクトを複製してinterfaceプロジェクトを作って。」の回答
図18 「polymorphismプロジェクトを複製してinterfaceプロジェクトを作って。」の回答

 せっかくコードを作ってくれたので、実際のコードを眺めながらインタフェースについて理解を深めていくとします。

インタフェースのコード

 インタフェースを使ったコードは、継承のコードによく似ています。というより、インタフェースが継承を前提とした機能なので、当然といえば当然です。異なるのは、クラス定義の前に、図19のようにインタフェースの定義があることです。

図19 インタフェースを定義するコード
図19 インタフェースを定義するコード

 インタフェースの定義では、classキーワードではなくinterfaceを使います。そして、その中にメソッドの定義を入れていきますが、メソッドの処理内容は入れません。インタフェースには、どのようなメソッドがあるかという情報のみを入れていくのは前述の通りです。ここでは、moveメソッドがあることだけを表しています。

 次は、クラスの定義を見てみます。クラスは3つありますが、だいたい似たような内容なので、最初のDogクラスを見てみます(図20)。

図20 インタフェースをクラスに実装するコード
図20 インタフェースをクラスに実装するコード

 これまでのクラス定義と変わらないように見えますが、クラス名の後に「implements Animal」と続いている点が異なりますね。implements(実装する)とあるように、インタフェースを実装する場合には「implements」キーワードに続けてインタフェース名を指定します。これで、クラスDogはインタフェースAnimalを実装する、なのでmoveメソッドの処理内容を記述する必要がある、ということになります。

 @overrideアノテーションの後に、moveメソッドの定義があります。繰り返しになりますが、インタフェースにあるメソッドは、必ずクラスの方で実装しなければなりません。もしも実体がなかったら、呼び出されたときに何をしたらいいか分かりませんからね。

 インタフェースを使う場合も、独自のメソッド(Dogクラスならbarkメソッド)を定義できます。これは、Catクラス、Birdクラスでも同じです。

 最後に、mainメソッドのコードを見てみたいと思いますが、これはポリモーフィズムにおけるものと全く変わりません(図21)。

図21 インスタンスを生成して呼び出すコード
図21 インスタンスを生成して呼び出すコード

 キーワード的には同じAnimalですが、ポリモーフィズムの例ではAnimalはクラス、ここではAnimalはインタフェースです。インタフェース型の変数を宣言し、そこにクラスのインスタンスを入れることができます。その変数を使い、インタフェースにあるメソッドを呼び出せる点も同じです。

 ポリモーフィズムもインタフェースも、今回で紹介したことは最も基本的な事柄です。引っ掛かったところがあればAIに質問を投げて、学習の幅を広げていってください。

こんな質問をしてみたい!

 本稿では触れませんでしたが、インタフェースと似た機能に「抽象クラス」があります。抽象クラスもインタフェースと同様に、継承した側で実装しなければならないメソッドを宣言します。その構文やインタフェースとの使い分けを聞いてみると、インタフェースの意義や継承とポリモーフィズムへの理解がさらに深まるかもしれませんよ!

まとめ

 今回は、前回に引き続きオブジェクト指向の要であるポリモーフィズムとインタフェースについてAIに聞きながら学習しました。

 次回は、オブジェクト指向プログラミングの締めくくりとして、ジェネリクスと列挙型、そしてレコードクラスを学習していきます。

筆者紹介

WINGSプロジェクト 山内直

WINGSプロジェクト所属のテクニカルライター。出版社秀和システムを経てフリーランスとして独立。ライター、編集者、開発者、講師業に従事。屋号は「たまデジ。」。

たまデジ。 | たまプラーザで生活、仕事する。(https://naosan.jp/

WINGSプロジェクト

有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティー(代表山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手掛ける。2021年10月時点での登録メンバーは55人で、現在も執筆メンバーを募集中。興味のある方は、どしどし応募頂きたい。著書、記事多数。

サーバーサイド技術の学び舎 - WINGS(https://wings.msn.to/
RSS(https://wings.msn.to/contents/rss.php
X: @WingsPro_info(https://x.com/WingsPro_info
Facebook(https://www.facebook.com/WINGSProject


Copyright © ITmedia, Inc. All Rights Reserved.

ページトップに戻る