検索
連載

GPT-5 miniに聞き学ぶ、オブジェクト指向の実用機能ジェネリクスとコレクションAIアシスト時代のJavaプログラミング入門(7)

対話型AI(人工知能)にアドバイスを受けながら進めるJavaプログラミングの入門連載。引き続き、オブジェクト指向プログラミングの機能である、ジェネリクスとコレクションを学習します。特定のデータ型に依存しないクラスの考え方、それを利用したユーティリティーであるコレクションを理解しましょう。

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

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

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


オブジェクト指向の実用的な用途

 対話型AI(人工知能)にアドバイスを受けながら進めるJavaプログラミングの入門連載「AIアシスト時代のJavaプログラミング入門」前回までで、Javaをオブジェクト指向言語たらしめる機能を一通り学びました。しかしながら、「あくまでも例なのは分かるが、犬とか猫とか動物とかではなく、もっと実用的な用途を知りたい。」と考える方もいるのではないでしょうか? そこで今回からは、オブジェクト指向の延長として、実用的な用途に踏み込んでいきます。

【補足】GPT-5 miniとGrok Code Fast 1

 Visual Studio Code(以降、VS Code)バージョン1.103において、OpenAIの新しいAIモデルGPT-5が利用可能になりました。プレミアムリクエストを消費しないGPT-5 miniも利用可能なので、今回からこのモデルを使っていきます。なお、初めてGPT-5 miniを選択すると、図1のようにGPT-5 miniを有効にするかどうか聞かれるので、[Enable]をクリックして有効にしてください。

図1 GPT-5 miniを有効にする
図1 GPT-5 miniを有効にする

 なお、xAIによるGrok Code Fast 1も同様に利用可能です。有効化が必要な点も同じですので、興味のある人は試してみるとよいでしょう。

 VS CodeからGitHub Copilot(以降、Copilot)に、新たに「Javaのオブジェクト指向機能を使った実用的な機能を知りたい。コードは不要です。」と投げてみました(図2)。

図2 「Javaのオブジェクト指向機能を使った実用的な機能を知りたい。コードは不要です。」の回答
図2 「Javaのオブジェクト指向機能を使った実用的な機能を知りたい。コードは不要です。」の回答

 カプセル化、継承、ポリモーフィズム、抽象化といった、これまでに学習した内容に加えて、はじめて目にするキーワードが追加でたくさん登場しました。改めて、Javaのオブジェクト指向の奥深さがうかがい知れますね。中には技法的なものも混じっているのですが、今回は7番目の「ジェネリクス(型安全な再利用)」をピックアップします。

ジェネリクス(Generics)

 「ジェネリクス」とは、「一般的な」「総括的な」という意味です。「ポリモーフィズム」同様に日常生活にあまり登場しませんが、「ジェネリック医薬品」など目にすることがありますね。図2の回答を見ると、以下のように説明されています。

・コレクションやユーティリティーを型安全に再利用。APIの誤用をコンパイル時に検出。

 「再利用」は、一貫してオブジェクト指向のメリットとしてのキーワードなのでひとまず置いておき、ここでは「型安全」と「コレクション」「ユーティリティー」に注目した方がよさそうです。「API」とはApplication Program Interfaceの略ですが、ここではメソッドのことと思っておいて構いません。

【補足】記載ルールについて

 今回も前回と同様に、AIの回答と@ITの記載ルールが異なる場合、本文中では@ITのルールを優先しています。

コレクションとは

 コレクション(Collection)という用語自体は、第4回で拡張for文を扱う際にちらっと登場しました。とすると、配列と共通点がありそうです。そこで、まずはコレクションについて聞いてみましょう。「コレクションの目的を説明して。初心者でも分かるように専門用語はできるだけ控えて簡潔にコードなしで。」と投げてみました(図3)。

図3 「コレクションの目的を説明して。初心者でも分かるように専門用語はできるだけ控えて簡潔にコードなしで。」の回答
図3 「コレクションの目的を説明して。初心者でも分かるように専門用語はできるだけ控えて簡潔にコードなしで。」の回答

【補足】GPT-5 miniへの注文は細かく

 今回使用しているGPT-5 miniは、前回まで使ってきたGPT-4oと比較すると、より詳細に網羅的に、という傾向があるようです。そのため、ざっくりした質問(プロンプト)では回答量が多く専門用語も多出して入門者には難しいと判断し、本稿での質問は注文を多くして回答を絞ることにしました。

 「コレクションとは」に示されている内容が、コレクションの目的をひとことで表しています。

複数のデータ(値や物の情報)を一かたまりで扱うための仕組み。箱やリストのように、たくさんのものをまとめて管理するために使う

 コレクションの名の通り、複数のデータをかたまりで扱うための機能、ということです。ますます配列のようなものということになりますね。ただし「目的」を見ていくと、「データをまとめて保存する」のように配列に共通する機能の他に、「追加・削除・取り出しが簡単にできる」とあるように、配列にはない機能を持っていそうです。配列は要素数が固定で、追加や削除といった操作はありませんでした。

 「例え」を見ると、買い物リストや住所録への応用が考えられるとあります。なかなか便利そうで実用的な機能がコレクションというわけです(図4)。

図4 コレクション
図4 コレクション

 コレクションにはどのような種類があるのかも聞いてみました。全部を列挙されると困りそうなので、「代表的なコレクションを用途別に一文で説明しながら列挙して。」とよく使いそうなものだけを挙げてもらうことにしました(図5)。

図5 代表的なコレクション
図5 代表的なコレクション

 それでも9個も挙げてくれましたが、先頭に近いものがよく使うものと思ってよさそうです。ListとArrayListの関係など、どのように使うのかは、後ほど具体的なコードとして見ていくことにしましょう。

  • List(例:ArrayList):順番を保って同じ値を何度でも入れられ、位置や順序で扱いたいときに使う
  • Set(例:HashSet):同じ要素を重複させたくないときに使う
  • Map(例:HashMap):キーと値の組でデータを保存し、キーで素早く値を取り出したいときに使う

コレクションとジェネリクスの関係

 コレクションの姿が見えてきましたが、ジェネリクスとの関連は? という点について、さらなる質問で聞いてみましょう。「コレクションとジェネリクスの関係を説明して。初心者でも分かるように専門用語はできるだけ控えて簡潔にコードなしで。」と投げてみました(図6)。

図6 「コレクションとジェネリクスの関係を説明して。初心者でも分かるように専門用語はできるだけ控えて簡潔にコードなしで。」の回答
図6 「コレクションとジェネリクスの関係を説明して。初心者でも分かるように専門用語はできるだけ控えて簡潔にコードなしで。」の回答

 「概要(簡潔)」の2行目に書かれている内容が、ジェネリクスの機能を一つ、端的に表しています。

ジェネリクスは「そのコレクションが何の種類のものを入れるかをあらかじめ決める仕組み」。

 配列には、それが何の配列かを表すint[]などの型がありましたが、それに相当するものがジェネリクスということのようです。「関係(初心者向け)」の1行目に「コレクションに入るものの種類を明確にできる(例:文字列だけ、数値だけ)」とあるので、同じように文字列(String)、数値(intなど)を指定できるということですね。2行目以降では、メリットとして安全性や再利用性について書かれているので、これらについては後ほどコードを見ながら明らかにしていきましょう。

 「たとえ」に書かれている、以下が分かりやすいです(図7)。

「リンゴだけ入る箱」を用意しておくと、間違えてナシを入れられないし、中身を取り出すときにリンゴだと安心して扱える、というイメージ。

図7 ジェネリクス
図7 ジェネリクス

コレクションを使うコード

 このへんで、具体的なコードを示してもらい、コレクションを通じたジェネリクスの利用イメージを深めていくことにしましょう。使うコレクションは、最初に出てきたArrayListにします。ここからはVS CodeのAgentモードを活用して、「basicプロジェクトからgenericsプロジェクトを作り、ArrayListの簡単なサンプルコードを入れて。」と投げて一連の作業を実行してもらいました。プロジェクトが作成され、App.javaファイルにサンプルコードが生成されます。

【補足】コマンドの実行

 Agentモードでプロジェクトを作成すると、生成したコードが正しくコンパイルできるか、期待通りの出力となるかどうかを確認するために、コマンド実行の許可を要求されることがあります(図8)。その場で実行してもよいですし、後で実行しても構いません。その場で実行すると出力例も得られるので、後で自分で実行する手間が省けます。

図8 コマンドの実行
図8 コマンドの実行

 注目すべきはApp.javaです。開いて内容を確認してもよいですが、コードを基に説明してもらうことにします。「生成されたApp.javaのコードを、専門用語を使って説明して。コードの説明以外は不要です。」と投げてみました。これまでが割とふんわりした回答を期待していたのに対し、ここからは具体的に用語を使ってくれることを期待します。ただ、GPT-5 miniの傾向から、補足的な説明を加えてくると思われるので、ひとまずはコードに絞って解説してもらいます(図9)。

図9 「生成されたApp.javaのコードを、専門用語を使って説明して。コードの説明以外は不要です。」の回答
図9 「生成されたApp.javaのコードを、専門用語を使って説明して。コードの説明以外は不要です。」の回答

 やや長いですが、処理単位ごとに箇条書きで要点が列挙されます。これを読むと、コードのどの部分で何をやっているか分かりますね。これを踏み台に、対応するコードも見ていくことにしましょう(図10)。

図10 生成されたApp.javaのコード
図10 生成されたApp.javaのコード

 以下、図10の赤字を補足する形で、図9の回答も使いながらコードを説明します。mainメソッドやprintlnメソッドなど既出の内容は省きます。

・import文

 java.util.Listとjava.util.ArrayListについて簡単に説明してくれています。Listがインタフェース、ArrayListが実装クラスというわけです。このへんは前回で学習した内容ですね。java.util.Listといった形式やimport文って何? という疑問が湧くと思いますが、後続の回のテーマにもなっているので、詳しくはそちらで説明します。ここではListとArrayListを使うための文、と理解しておけば十分です。

・ジェネリクスによる型指定(List<String> names = new ArrayList<>();)

 コレクションはクラスなので、newによってインスタンス化するのはこれまで同様です。ここには、重要なキーワードが登場しています。List<String>が「ジェネリック型」で、<...>の中のStringは「型パラメーター」というようです。ジェネリクスを使った型だからジェネリック型、ジェネリック型が扱う型を指定するから型パラメーターというわけですね。ジェネリック型では、「<>」で扱う型を指定します。

 重要なのは、型パラメーターで指定するString以外の値は入らない、これで型安全が保証されているということです。また、ArrayList<>の型パラメーターは空っぽですが、これはList<String>から自明なので、第2回で学習した「型の推論」が働いています。「<>」は、その見た目から「ダイヤモンド演算子」と呼ばれます。

・要素の追加(names.add("Alice"); 等)

・型チェックの例示コメント(names.add(123); // コンパイルエラー)

 自明なのか触れられていませんが、コレクションはインスタンス化した時点では要素の数はゼロ、すなわち空です。であるので、要素は追加していく必要があります。これに使うのがaddメソッドです。上記のように、型パラメーターで指定するString以外の値は入らないので、数値型である「123」を入れようとするとコンパイルエラーとなります。ソースコードの段階で、型の間違いを防いでくれるというわけです。

・要素数取得と反復(names.size()と拡張forループ)

・要素取得(names.get(0))

 コレクションでは、配列と同様に拡張for文で全要素を走査できます。これも配列と同様にインデックスで要素にアクセスできます(getメソッドを利用)。配列と異なり、要素の数は変動するので、要素数を取得する手段がsizeメソッドとして用意されています。

ジェネリック型の定義

 ここまではArrayListというジェネリック型を利用するコードを見てきましたが、オブジェクト指向の学習的には、どのように定義するのかも知りたいところです。そこで、「ArrayListの定義のコードを大ざっぱに知りたい。」と投げてみました。コードが長めに提示されるので、説明部分を切り取ったのが図11です。

図11 「ArrayListの定義のコードを大ざっぱに知りたい。」の回答
図11 「ArrayListの定義のコードを大ざっぱに知りたい。」の回答

 「ポイント(短く)」にはいろいろ説明がありますが、それも踏まえてコードに説明を加えてみたのが図12です。全部を説明し切れないので、クラスそのものの定義やコンストラクタ、登場済みのaddやgetなどのメソッドに絞っています。

図12 SimpleArrayListのコード
図12 SimpleArrayListのコード

 注目したいのは、クラス定義に型パラメーターを指定していることです。これにより、クラスがジェネリック型になるわけです。ここで、型パラメーターは「<E>」となっており、これはクラス定義中で使う仮の型です。実際の型は利用時に指定されますが、定義時には実際の型は分からないので、このように仮の名前としておくわけです。実際、addメソッドやgetメソッドでは、引数と戻り値の型はEとなっています。

 このように、実際にどのような型で使われるか? ということと関係なくクラスを定義できるので、型ごとにクラスを定義する必要がなくなります。ジェネリクスの再利用性とは、ここから来ているのが分かりますね(図13)。

図13 ジェネリクスと再利用性
図13 ジェネリクスと再利用性

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

 ここでは、コレクションを題材にジェネリクスについて学習してきました。実は、ジェネリクスはクラスに限ったものではありません。ジェネリクスのクラス以外の適用について聞いてみると、ジェネリクスの理解の幅が広がるかもしれませんよ!

その他のコレクション

 ジェネリクスとはどういうものか、コレクションがジェネリクスをどのように利用するかを見てきました。既に、コレクションの一つであるList(ArrayList)については簡単な利用コードを見てきましたが、ここで他のコレクションにも注目してみましょう。これらのコレクションはジェネリック型なので、サンプルで示された型以外にも使えるということを押さえておきましょう。

Set(HashSet)

 改めてList(ArrayList)について振り返ってみると、「順番を保って同じ値を何度でも入れられ、位置や順序で扱いたいときに使う」コレクションでした。つまり、配列的なものがListであるというわけです。Set(HashSet)はどうでしょうか? 「同じ要素を重複させたくないときに使う」とあるので、役割はListと異なりそうです。

 これを確かめるために、「basicプロジェクトからcollectionプロジェクトを作り、HashSetの簡単なサンプルコードを入れて。」と投げて一連の作業を実行してもらいました。プロジェクトが作成され、App.javaファイルにサンプルコードが生成されます。作成途中で、サンプルの要点が既に示されているので、これをあらかじめ抜粋しておきます(図14)。

図14 HaspSet利用サンプルの要点
図14 HaspSet利用サンプルの要点

 これを踏まえて、コードに説明を入れてみたのが図15です。

図15 HaspSet利用のコード
図15 HaspSet利用のコード

 SetインタフェースとhashSetクラスの関係など、Listと構造は同じであり、インスタンスの生成についても同様です。また、addメソッドで要素を追加するのも同じですが、異なるのは重複を許さない(重複する追加は無視される)ことです。これが、Setの性質である「要素を重複させない」に通じるのですね。

 この他、sizeメソッド、containsメソッド、拡張for文、removeメソッドの例が示されていますが、これらはListと同じです。異なるのは、拡張for文では順序が保証されないということです。つまり、入れた順に出てくるというわけではないということですね。これは、順序を保証するListとの大きな違いとなるので覚えておきましょう。

 なお、回答では特に触れられていませんでしたが、Listのgetメソッドのように要素を個別で取り出すことはできません。順序がないので、場所の指定のしようがないためですね。その代わり、containsメソッドで存在は確認できるので、それをもって取り出すといった操作と見なすこともできるでしょう。

【補足】Hashとは

 HashSetと、次で紹介するHashMapは、Hash(ハッシュ)という仕組みで要素の場所を管理しています。ハッシュとは、ある値によって一意に定まる計算方法およびその結果のことで、値が異なればハッシュも異なるという性質を持っています。これらのコレクションでは、このハッシュを利用して要素への高速なアクセスを実現しています。

Map(HashMap)

 お次はMap(HashMap)はどうでしょうか。「キーと値の組でデータを保存し、キーで素早く値を取り出したいときに使う」とあります。値とは別に「キー」が登場しているので、これが重要な役割を果たしそうです。これを確かめるために、「collectionプロジェクトにHashMapの簡単なサンプルコードを追加して、要点もお願い。」と投げてサンプルコードを追加してもらいました。既存のApp.javaファイルにサンプルコードが追加されます。作成途中で、サンプルの要点が既に示されているので、これをあらかじめ抜粋しておきます(図16)。

図16 HaspMap利用サンプルの要点
図16 HaspMap利用サンプルの要点

 これを踏まえて、コードに説明を入れてみたのが図17です。

図17 HaspMap利用のコード
図17 HaspMap利用のコード

 こちらもHashSetのサンプルと似ていますが、幾つか違うところがありますね。

 一つは、インスタンス生成時の型パラメーターが、<String, Integer>と2つあることです。SimpleArrsyListでは要素の型だけが必要なので型パラメーターは1つだけでしたが、HashMapのようにキーと値の型が必要な場合には型パラメーターは2つになるわけですね。最初のStringがキーの型に、Integerが値の型に相当します。

 もう一つは、要素の追加にはaddメソッドでなくputメソッドを使う点です。このとき、最初の引数にはキーを、2番目の引数には値を指定しています。特に示されていませんでしたが、同じキーでputメソッドを呼び出すと、同じキーの値ができるわけではなく値が書き換えられます。このへんの振る舞いはSetに似ていますね。

 値の取り出しはgetメソッドで、キーのみを指定します。containsメソッドでキーの存在を確認することもできますね。なお、拡張for文による要素の取り出しは、少し変わった形式になります。Mapでは要素をキーと値のペアで扱うので、それを受け取るためにEntryという特別なクラスを使います。Entryクラスのインスタンスからは、getKeyメソッド、getValueメソッドで、キーと値をそれぞれ取り出せます。

 最後に、リスト、セット、マップの特徴をまとめてみたのが図18です。

図18 リスト、セット、マップの特徴
図18 リスト、セット、マップの特徴

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

 ここでは、SetとHashSet、MapとHashMapについてコードを通じてその動きを学習しました。ArrayListも含めて、コレクションには他にもいろいろな実装があるようです。ArrayList、HashSet、HashMap以外のコレクションについて聞いてみると、コレクションに求められる機能や性能の違いが分かり、コレクションひいてはジェネリクスの理解の幅が広がるかもしれませんよ!

まとめ

 今回は、オブジェクト指向プログラミングの実用的な例として、ジェネリクスとコレクションを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.

ページトップに戻る