【Kotlin】レッスン3-6:ジェネリクスの基礎を理解しよう|型に依存しない関数を作成

ながみえ

一つ前のページでは関数のオーバーロードについて学習しました。

今回は ジェネリクスの基礎 について見ていきましょう。

Lesson1:基礎文法編
Lesson2:制御構造編
Lesson3:関数編

 ・Lesson3-1:関数の基本を理解しよう
 ・Lesson3-2:デフォルト引数とキーワード引数を理解しよう
 ・Lesson3-3:関数の戻り値を理解しよう
 ・Lesson3-4:真偽値を返す関数を理解しよう
 ・Lesson3-5:関数のオーバーロードを理解しよう
 ・Lesson3-6:ジェネリクスの基礎を理解しよう ◁今回はココ
 ・確認問題3-☆1:ブラックジャックゲームを作ろう
 ・確認問題3-☆2:丁半賭博ゲームを作ろう
 ・確認問題3-☆3:モンスターとのバトルゲームを作ろう
Lesson4:コレクション編
Lesson5:オブジェクト指向編

<<前のページ

学習記事一覧

次のページ>>

ジェネリクスの基本|柔軟で再利用可能なコードを

異なる型を扱う関数やクラスを一つの形で定義できる仕組みとして「ジェネリクス」が提供されています。

ジェネリクスを利用すると、型を抽象化することで柔軟性と再利用性の高いコードを作成することができます。

この章では、関数におけるジェネリクスの基礎を学び、簡単な例を通してその使い方を理解します。

ジェネリック関数の仕組みとは?|型を抽象化する方法

ジェネリクス とは、データ型を自由に変えて使える仕組みのことです。

通常、関数は特定の型に依存して作られますが、ジェネリクスを使うことで異なる型に対応可能な汎用的な関数を作ることができます。

たとえば次の例を考えてみましょう。

「1つの値を出力する関数」を作る場合、文字列を扱う関数や数値を扱う関数など、扱う型ごとに関数を作らなければなりません。

fun printInt(item: Int) {		// Int型の引数を持つ関数
    println(item)
}
fun printDouble(item: Double) {	// Double型の引数を持つ関数
    println(item)
}
fun printString(item: String) {	// String型の引数を持つ関数
    println(item)
}

この方法では扱う型が増えるたびに関数を新たに作成する必要があり、非常に効率が悪くなります。

しかしジェネリクスを使うと、次のように1つの関数で複数の型に対応できます。

fun <T> printItem(item: T) {	// T型の引数を持つジェネリック関数
    println(item)
}
  • <T> は「型パラメータ」を意味します。T は任意の型を表し、必要に応じて別の名前(例:EK)に変更できます。
  • item: T は、関数が任意の型の引数を受け取れることを示します。
  • この関数を使えばどんな型でも出力できる柔軟な関数を実現できます。

このような関数を ジェネリック関数 といい、メイン関数内から以下のように呼び出せます。

printItem(100)				// Int型
printItem(3.14)				// Double型
printItem("こんにちは")		// String型

ジェネリクスを使うことで、このように型に関係なく再利用可能な関数を簡単に作成できます。

ジェネリック関数の使用例|複数型への対応を実現する方法

ジェネリクスを使うことで、同じロジックを異なる型で再利用できる汎用的な関数を作成できます。

たとえば「2つの値を比較して同じかどうかを確認する関数」を考えてみましょう。

通常、型ごとに関数を作ると次のようになります。

fun compareInt(a: Int, b: Int): Boolean {
    return a == b
}
fun compareString(a: String, b: String): Boolean {
    return a == b
}

これでは扱う型ごとに新しい関数を作成する必要があります。

ジェネリクスを使えば、次のように1つの関数ですべての型に対応できます。

fun <T> compareItems(a: T, b: T): Boolean {	// ジェネリック関数
    return a == b
}

fun main() {
    println(compareItems(1, 1))				// true
    println(compareItems("Hello", "Hi"))	// false
    println(compareItems(3.14, 3.14))		// true
}

この例ではcompareItems 関数を使って整数、文字列、浮動小数点数を比較しています。

ジェネリクスを使うことで型に依存しない汎用的な関数を作ることが可能になります。

ジェネリクスのメリット|再利用性・安全性・設計効率の向上

ジェネリクスを使う主な利点は以下の通りです。

  1. コードの再利用性向上:型に依存しないため、汎用的なコードを書けます。
  2. 型安全性の向上:型キャストが不要になり、ランタイムエラーを防ぎます。
  3. 簡潔な設計:同じロジックを複数の型で使うための重複を減らせます。

複雑なシステムを設計する際には、ジェネリクスは必ず使用する機能です。

まとめ|ジェネリクスで安全かつ効率的な関数設計を

ジェネリクスを使うことで、さまざまな型に対応した安全かつ柔軟なコードを書くことができるようになります。

最初は少し難しく感じるかもしれませんが、実際にコードを書いていくうちにその便利さが実感できるはずです。

今後は、標準ライブラリにあるジェネリクスを使ったクラスや関数(例:ListやMap)を実際に触れながら、さらに理解を深めていきましょう!

ジェネリック関数の練習問題|複数型を出力・比較しよう

「ジェネリクス」を使って汎用的な関数を作成し、さまざまな型のデータを処理するプログラムを作成してください。

以下の要件に従ってコードを書いてください。

この問題の要件

以下の要件に従ってコードを完成させてください。

  1. 関数 printItem を作成すること。
    • ジェネリクス型 <T> を使用し、引数 item を出力する関数にすること。
    • 出力例:「入力された値: こんにちは」
  2. 関数 areEqual を作成すること。
    • ジェネリクス型 <T> を使用し、2つの引数 a と b が等しいかどうかを判定して返す関数にすること。
    • 出力例:「10と10は等しい: true」
  3. メイン関数で以下を実行すること:
    • printItem を使用し、異なる型(例:文字列、整数、小数)の値を出力すること。
    • areEqual を使用し、異なる型の値を比較して結果を出力すること。
  4. 出力を日本語で表現すること。
    • 例:「10と10は等しい: true」「入力された値: 123」

ただし、以下のような実行結果となること。

=== printItem 関数の例 ===
入力された値: こんにちは
入力された値: 123
入力された値: 3.14

=== areEqual 関数の例 ===
10と10は等しい: true
"Kotlin"と"Java"は等しい: false
3.14と3.14は等しい: true

この問題を解くヒント

1からコードを組み立てることが難しい場合は、以下のヒントを開いて参考にしましょう。

Q
ヒント1【コードの構成を見る】

正解のコードは上から順に以下のような構成となっています。
(※下記の□はコード内のインデントを表しています)

1:汎用的な値を出力するジェネリック関数printItemの定義
  □ 引数itemを受け取り、printlnで「入力された値: 」を出力
2:汎用的な値を比較するジェネリック関数areEqualの定義
  □ 引数aとbを比較し、結果を返す
3:main関数の定義
  □ printlnで「=== printItem 関数の例 ===」を出力
  □ printItem関数を呼び出し、「こんにちは」を出力
  □ printItem関数を呼び出し、123を出力
  □ printItem関数を呼び出し、3.14を出力
  □ printlnで「=== areEqual 関数の例 ===」を出力
  □ areEqual関数を呼び出し、10と10を比較し、結果をresult1に代入
  □ printlnで「10と10は等しい: 」とresult1の値を出力
  □ areEqual関数を呼び出し、「Kotlin」と「Java」を比較し、結果をresult2に代入
  □ printlnで「”Kotlin”と”Java”は等しい: 」とresult2の値を出力
  □ areEqual関数を呼び出し、3.14と3.14を比較し、結果をresult3に代入
  □ printlnで「3.14と3.14は等しい: 」とresult3の値を出力

Q
ヒント2【穴埋め問題にする】

以下のコードをコピーし、コメントに従ってコードを完成させて下さい。

// 汎用的な値を出力する関数 (ジェネリック関数)
fun <T> printItem(item: T) {
    /*【穴埋め問題1】
    ここにprintItem関数の中身を記述してください。「入力された値: $item」と出力するコードを書いてください。
    */
}

// 汎用的な値を比較する関数 (ジェネリック関数)
fun <T> areEqual(a: T, b: T): Boolean {
    /*【穴埋め問題2】
    ここにareEqual関数の中身を記述してください。引数aとbを比較し、結果を返すコードを書いてください。
    */
}

fun main() {

    // 値を出力するジェネリクス関数の例
    println("=== printItem 関数の例 ===")
    /*【穴埋め問題3】
    ここにprintItem関数を使用して「こんにちは」「123」「3.14」を出力するコードを書いてください。
    */

    // 値を比較するジェネリクス関数の例
    println("\n=== areEqual 関数の例 ===")
    /*【穴埋め問題4】
    ここにareEqual関数を使用して以下の結果を出力するコードを書いてください。
    ・10と10が等しい場合の結果
    ・"Kotlin"と"Java"が等しいかの結果
    ・3.14と3.14が等しいかの結果
    */
}

このヒントを見てもまだ回答を導き出すのが難しいと感じる場合は、先に正解のコードと解説を見て内容を理解するようにしましょう。

この問題の解答と解説

この問題の正解コードとその解説は以下の通りです。

クリックして開いて確認してください。

Q
正解コード
// 汎用的な値を出力する関数 (ジェネリック関数)
fun <T> printItem(item: T) {
    println("入力された値: $item")
}

// 汎用的な値を比較する関数 (ジェネリック関数)
fun <T> areEqual(a: T, b: T): Boolean {
    return a == b
}

fun main() {

    // 値を出力するジェネリクス関数の例
    println("=== printItem 関数の例 ===")
    printItem("こんにちは") // String型を渡す
    printItem(123)         // Int型を渡す
    printItem(3.14)        // Double型を渡す

    // 値を比較するジェネリクス関数の例
    println("\n=== areEqual 関数の例 ===")
    val result1 = areEqual(10, 10) // Int型の比較
    println("10と10は等しい: $result1")

    val result2 = areEqual("Kotlin", "Java") // String型の比較
    println("\"Kotlin\"と\"Java\"は等しい: $result2")

    val result3 = areEqual(3.14, 3.14) // Double型の比較
    println("3.14と3.14は等しい: $result3")
}
Q
正解コードの解説

コードをブロックごとに分割して解説します。

汎用的な値を出力する関数

fun <T> printItem(item: T) {
    println("入力された値: $item")
}
  • ジェネリック関数: この関数はどの型にも対応する汎用的な関数です。
  • 型パラメータ <T>: T は型を表すパラメータで、関数がどの型でも受け取れることを示しています。
  • 機能: 引数 item を受け取り、それを出力します。

汎用的な値を比較する関数

fun <T> areEqual(a: T, b: T): Boolean {
    return a == b
}
  • ジェネリック関数: この関数もジェネリクスを使用し、どの型にも対応します。
  • 戻り値の型: この関数は Boolean を返します。比較結果が等しければ true、そうでなければ false を返します。
  • 用途: 2つの値が等しいかを比較する機能を提供します。

メイン関数

fun main() {
    // ジェネリック関数の呼び出し例
    println("=== printItem 関数の例 ===")
    printItem("こんにちは") // 文字列型
    printItem(123)         // 整数型
    printItem(3.14)        // 小数型
}

printItemの呼び出し: ジェネリック関数 printItem を使用して、異なる型の値を出力しています。

    // areEqual関数の呼び出し例
    println("\n=== areEqual 関数の例 ===")
    val result1 = areEqual(10, 10) // 整数型の比較
    println("10と10は等しい: $result1")

    val result2 = areEqual("Kotlin", "Java") // 文字列型の比較
    println("\"Kotlin\"と\"Java\"は等しい: $result2")

    val result3 = areEqual(3.14, 3.14) // 小数型の比較
    println("3.14と3.14は等しい: $result3")
}

areEqualの呼び出し: 異なる型でジェネリック関数 areEqual を呼び出しています。

Q
サイト改善アンケート & ご指摘/ご質問

本サイトでは、みなさまの学習をよりサポートできるサービスを目指しております。
そのため、みなさまの「プログラミングを学習する理由」などを アンケート 形式でお伺いしています。

また記事の内容に関する ご指摘ご質問 もお待ちしています。

1.このサイトをどのように活用していますか?また、今後どのように活用したいですか?
5.気になっているサービス・商品があれば教えてください。
※ 特定の記事に関する内容の場合は、記事番号(レッスン〇-〇)をご記入願います。

<<前のページ

学習記事一覧

次のページ>>

記事URLをコピーしました