対話型AI(人工知能)にアドバイスを受けながら進めるJavaプログラミングの入門連載。今回は、オブジェクト指向の要といえるカプセル化と継承を学習します。クラス構造の隠蔽と公開、クラスを再利用する方法について理解しましょう。
この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。
本連載のサンプルコードをGitHubで公開しています。こちらからダウンロードしてみてください。
対話型AI(人工知能)にアドバイスを受けながら進めるJavaプログラミングの入門連載「AIアシスト時代のJavaプログラミング入門」。前回は、Javaにおけるオブジェクト指向の機能として、クラスとインスタンス化、メソッドやコンストラクタについて学びました。「現実社会のモノや概念をプログラムで表現する」のがオブジェクト指向であることも学びました。
しかしながら、オブジェクト指向とは果たしてこのような単純なものであるのか? という疑問も湧きます。現時点において多くのプログラミング言語で採用され、広く使われているのがオブジェクト指向です。クラスやメソッドといった基本的な機能に加えて、「現実社会を表現する」にふさわしい機能がまだまだあるに違いありません。
今回は、ここからJavaのオブジェクト指向をさらに掘り下げていきます。そのための下地として、Visual Studio Code(以降、VS Code)からGitHub Copilot(以降、Copilot)に「オブジェクト指向についてメソッドやコンストラクタ以外の主な機能を重要度順に教えて。サンプルコードは不要です。」と投げてみました。そこで得られた回答が図1です。
前回までの回答を見ると、親切にサンプルコードも提示してくれることが多かったのですが、まずはどのような機能があるか知りたいので、サンプルコードは不要と付け加えました。何度か同じように質問することで、いずれ学習して、最初はサンプルコードを交えずに回答してくれるようになります。
全部で10個、たくさんの項目を得られました。「重要度順に」と付けたので、ランキング順に並べてくれました。字面的にも難しそう、聞いたこともないような片仮名語が並んでいて、先に進むのがためらわれそうですね。ここはまず、オブジェクト指向にはこのような機能があるんだ程度に押さえておけば大丈夫です。「まとめ」にも、以下のように締めくくられていますね。
最も重要とされるものから、順を追って深掘りしていきましょう。今回は、「カプセル化」と「継承」です。
まずは「カプセル化」です。図1上の回答を見ると、「まとめ」に「データの保護と管理」とある他に、以下のように説明されています。
1.については、前回で学習した内容ですね。「データ(フィールド)とそれを操作するメソッドを1つのクラスにまとめる」というのは、オブジェクト指向の基本であるクラスの役割として説明した通りです。クラスは、カプセル化の機能も持っているわけです。すると、続く「外部から直接アクセスできないようにする」の部分が気になります。前回のサンプルでは、Catクラスにmeowメソッドを実装して呼び出していましたが、外部からアクセスできないとこのような使い方はできません。
2.に示されているメリットも、1.の問題がクリアにならないと具体的にイメージしにくいようです。ですので、カプセル化について掘り下げる質問を改めて投げてみることにしましょう。「カプセル化についてもう少し詳しく。サンプルコードは不要です。」と投げてみました。そこで得られた回答が図2です。
図2から、カプセル化の目的を抜き出してみると以下のようにまとめられます。
一つ一つ理解するのは大変なのですが、要は「データの存在は外部から隠して、「窓口」としてのメソッドを公開する」ということのようです。そして、そのメソッドはゲッター、セッターと呼ばれるようです。まずは、フィールドは隠し、メソッドは公開すると単純に理解しておけばよさそうです。
では、どのようにフィールドを隠し、メソッドを公開するのでしょうか。これも回答が示されています。
privateとかpublicとか聞いたことがないキーワードが出現していますね。では、これらの意味を探るために、具体的な例を実際のコードとして生成してもらいましょう。カプセル化のサンプル用に、プロジェクトencapsulateを作成してください。プロジェクトの作り方を忘れてしまった人は、第1回を参照してください。
今回は、CopilotのAgentモードを使って、クラスファイルも自動生成してみます。VS Codeのチャットビュー下にある「Ask」をクリックして、プルダウンから「Agent」を選択するか[Command]+[Shift]+[I](macOS)/[Ctrl]+[Shift]+[I](Windows)キーを押してください(チャット履歴は消去されます)。そして、「encapsulateプロジェクトにCatクラスファイルをカプセル化を考慮して作成して。」と投げてみます。チャットビューには、作成あるいは変更されるファイルが示され、エディタビューには実際のコードが現れます(図3)。
このように、Agentモードを使うと簡単な指示でファイルを作ってもらったり、プロジェクトも作成してもらうことが可能です。もちろん、Askモードのような使い方もできるので、いろいろ試してみるとよいでしょう。
Agentモードは、従来のAskモード、Editモードに対して新しく追加されたモードで、その名の通り「代理人」のようにさまざまな処理を自動で実行してくれる機能です。特徴は、ここでも示したようにかなりざっくりとした質問(指示)でも受け入れてくれる点です。作成済みのファイルについても要望を伝えれば修正を施してくれるので、AIと対話しながらの作業といったことが可能です。
作成されたCatクラス(Cat.javaファイル)は自動で開かれているので、早速中身を見てみます。気になるprivateはというと、フィールドnameとフィールドageに付けられています(図4)。
privateの役割は、既に書かれているように「そのフィールドをクラス内部からしかアクセスできないようにする」ことです。つまり、nameやageは、外部から見たり変更することができなくなります。これによって存在を隠し、不用意に変更されたりすることを防ぐわけです。カプセル化の名の通り、カプセルで包んで見えなくするのですね。
privateによって見えなくなってしまったフィールドは、どのように読み取って変更すればよいのかというと、これも説明に登場していたゲッターやセッターと呼ばれるメソッドを使います。ゲッターやセッターのコードも作成されていて、getNameやgetAgeがゲッター、setNameやsetAgeがセッターです。ゲッターを通じてフィールドの値を取り出し、セッターによってフィールドに値を設定するのです。これらのメソッドにはpublicが付けられており、クラス外部から利用できるようにします(図5)。
このように、大ざっぱに、隠したいものにはprivateを、見せたいものにはpublicを指定します。これが、「アクセス修飾子を適切に使う」で示されている内容です。privateやpublicはアクセスをコントロールする役割なので、アクセス修飾子というわけです(図6)。
さて、ここでのゲッターやセッターは、それぞれフィールドの値を取得、あるいは設定するだけなので、これならフィールドをpublicにしても同じではないか? という疑問も湧くでしょう。そこで、図2の最後「例で考えるポイント」です。ここには、フィールドを直接公開する場合の問題点が示されていますが、セッターを適切に実装することで意図しない値の設定などからガードできるということも示されています。以下は、ゲッターとセッターでできることの一例です。
具体的なコードはインラインチャットで追加できると思いますが、負の年齢というのはあり得ないので、そのような場合には無視したり、エラーとしたりすることができるでしょう。また、ゲッターでは年齢から生年に変換して返すということもできます。
ゲッターとセッターは基本的には単なるメソッドなので、どのような名前でも大丈夫そうですが、ここでの例を見る限り何かルールがありそうです。Copilotは特に言及していませんでしたが、ゲッターとセッターの命名規則について聞いてみると、なぜそのようにするのかがはっきりするかもしれませんよ!
次は、2番目に重要とされている「継承」です。冒頭の図1上では、以下のように説明されています。
継承の名の通り、親クラスとされるクラスの機能つまりフィールドやメソッドは、子クラスに引き継ぐことができるということです。これがいったいどのようなことで、どのような意義があるのか、継承について掘り下げる質問を改めて投げてみることにしましょう。「継承についてもう少し詳しく。サンプルコードは不要です。」と投げてみました。そこで得られた回答が図7です。
またまた、たくさんの項目が回答されました。しかしながら、カプセル化と違って、継承というのは動作を説明されてもいまひとつイメージしにくいのではないでしょうか? そこで、質問を変えてみます。「継承の具体的なイメージをコードなしで。」と投げてみました。オブジェクト指向というのは現実社会のメタファですから、実在の何かで例えてくれることを期待します。そこで得られた回答が図8です。
継承を、「動物」で例えてくれました。かなりシンプルな説明で、これでも十分理解できると思いますが、さらに簡略化すると、こんな感じでしょうか。
生物の系統樹のように、根っこの部分の生物をあらゆるクラスの親クラスとすれば、そこから派生していく生物を子クラスとしていく、そんなイメージになります。子クラスは、親クラスの性質を受け継ぎ、新たな性質を備えたり、親の性質を置き換えたりすることができます(図9)。なお、図8にある通り、親クラスは「スーパークラス」ともいい、子クラスは「サブクラス」ということもあります。
動物に例えることで、継承が大まかにイメージできたことと思います。では、そもそもなぜそのような機能が用意されているかということを掘り下げましょう。図1上の回答においても、図7の回答においても、ほぼ同様のことが書かれています。それが、「コードの再利用」です。どうやら、継承においてはこの「コードの再利用」が重要そうです。そこで、「継承におけるコードの再利用について。」と投げてみました。そこで得られた回答が図10です。
この回答を基に、簡単にまとめてみましょう。
1. 共通機能の集約
複数のクラスで共通する機能があるとき、それを親クラスで実装すれば、それぞれの子クラスで個別に実装する必要がなくなるということですね。これは明らかに便利で効率がよさそうです。
2. 一元管理
仮に、それぞれのクラスで同じ機能を実装していったとしたら、あるクラスの変更を他のクラスにも同じように反映する必要が出てきます。漏れや間違いが発生すると、クラスによって動作が異なるということになってしまいます。これでは具合が悪いですね。同じような機能は親クラスにまとめて、子クラスではそれを使うだけというようにすれば、仮に機能に変更があっても親クラスだけを修正すれば済みます。
3. コードの簡素化
親クラスにある機能をそのまま使うのであれば、子クラスには改めて機能を実装する必要がないので、子クラスのコードが簡素になりますね。親クラスの機能をそのまま使わないにしても、使える部分だけでも使えばそれでも十分に簡素化できそうです。コードを書く量を減らせれば、それだけバグの発生する可能性も減らせるので安全といえそうです。
4. 一貫性の確保
複数のクラスでデータの保存をそれぞれ実装したら、微妙に違った形式になってしまって具合の悪いことになりがちです。親クラスで統一した方法を用意しておけば、どの子クラスから保存しても同じ形式にできますね。
コードの再利用には、開発効率と保守性を向上させたり、バグの発生を防いだりといったメリットがあることが分かります。
では、どのように継承を実現するのでしょうか。ここまでの回答にはこれが一切なかったので、ここで聞いてみましょう。「継承の具体的な方法を教えて。」と投げてみました。そこで得られた回答が図11です(「コードなしで」と断る必要がなくなっていました)。
項目がたくさんあるのですが、まずは1.と2.に着目しましょう。それぞれ、親クラスと子クラスの定義方法について書かれています。
1. 親クラス(スーパークラス)の定義
説明を読むと、共通のプロパティとメソッドを定義するという他は、これまで出てきたクラス定義と変わらなさそうです。
プロパティという初出の用語が登場していますが、ここではフィールドと同じようなものと思って構いません。値の読み出しや設定が可能なもの全般をプロパティと呼ぶ、と理解しておきましょう。
2. 子クラス(サブクラス)の定義
子クラスは、extendsキーワードを使って定義するとあります。親クラスから引き継ぐメンバーについての記述もありますが、それは後で改めましょう。早速、extendsキーワードを使ったクラス定義の構文を知りたいところです。少し飛びますが、図11の下部にある「例(コードなしの説明)」の箇所をコードありで示してもらうことにしましょう。
ここにもメンバーという初出の用語が登場していますが、これはフィールドやメソッドを全てまとめたものと思って構いません。クラスにはさまざまなメンバーがいる、というイメージです。これに倣って、フィールドをメンバー変数と呼ぶこともあります。
「例のコードを具体的にお願い。」と投げてみました。Animalクラス、Dogクラス、Catクラスと、mainメソッドのコードが示されるので、それらをinheritanceプロジェクトを作ってソースファイルとして保存しましょう。全てApp.javaファイルに入れてしまって構いません。mainメソッドのクラスだけ、MainからAppに変更しておきます。
まずは継承元となるAnimalクラスです(図12)。
Animalクラスのコードは、前回で学んだ内容で十分理解できるものですが、1カ所だけ親クラスならではの記述があります。それがprotectedキーワードです。図2において、アクセス修飾子としてprivate、public、protectedが挙げられていましたが、そこではprotectedを使いませんでした。このprotectedは、外部には見せたくないが、子クラスにだけは見ることを許す、という意味です。privateだと自クラスの中でしか見えないので、継承した子クラスが使えないからです。もちろん、子クラスにも見せたくないものをprivateにするのは変わりません。
では、継承先はどうなるのか、Dogクラスを見てみます(図13)。Catクラスも似たようなものなので、各自でのぞいてみてください。なお、DogクラスではAnimalクラスのメソッドを置き換える「オーバーライド」という処理が入っています。
注目すべきは、クラス定義に使われているextendsキーワードです。これを、「class XXX」に続けることで、継承でクラス定義することを表します。extendsの名の通り、「拡張する」というわけですね。
その他は、通常のコンストラクタ、メソッド定義と変わりませんが、幾つか見慣れないコードがありますね。一つは、コンストラクタの中にある「super(name);」と、もう一つはmoveメソッドの前にある「@Override」です。
superは、親クラスのコンストラクタを呼び出すときに使います。Dogクラスのコンストラクタを見ると、superを呼び出しているだけです。これなら親クラスのコンストラクタが継承されるからわざわざDogクラスにはコンストラクタはいらないのでは? と思われるでしょう。Copilotは特に触れていませんでしたが、Javaでは「コンストラクタは継承されない」という性質があるのです。このため、子クラス側でもコンストラクタを定義し、必要なら親クラスのコンストラクタに処理を委ねるのです。この特別なメソッド呼び出しがsuperです。
@Overrideは「アノテーション」と呼ばれるものの一つで、続くメソッド定義が親クラスのものを置き換えるものであることを示します。overrideとは上書きの意味で、親クラスのメソッドを上書きして隠してしまいます。メソッドがオーバーライドされると、親クラスのメソッドではなく子クラスのメソッドが呼ばれるようになります。オーバーライドによって、子クラスに合わせた動きとなるメソッドとすることができるようになるわけです。
クラスをインスタンス化して、呼び出す側のコードも見てみましょう。その前に、プログラムを実行して結果を確認してみます(図14)。
これを踏まえて、呼び出し側のコードすなわちmainメソッドを見てみます(図15)。
注目すべき点は幾つかあります。まず、子クラスであるDogとCatのインスタンスから親クラスであるAnimalのmoveメソッドを呼び出していますが、Dogクラスではオーバーライドしたために挙動が変わっている点です。Catクラスでは基のmoveメソッドの挙動となっているので、これからオーバーライドの働きが理解できますね。
最後に、「多態性の例」とありますが、これは図1上などでも登場していた「ポリモーフィズム」を指します。これも、オブジェクト指向では非常に重要な概念です。コードの意味については次回にインタフェースとともに取り上げます。
カプセル化も、継承も機能は豊富です。今回、説明しきれなかった機能がたくさんあるので、Copilotの回答を読んでみて、さらに質問するなどして学習を深めていってください。
継承の説明を読むと、設計が大事とくどく書かれています。何が何でも継承関係にすればよいというわけではないというのは分かりますが、アンチパターンというものを聞いてみると、どのようなケースで継承を使うべきかがより深くイメージできるかもしれませんよ!
今回は、オブジェクト指向の要であるカプセル化と継承について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.