Menu
Coddy logo textTech

ジェネリッククラス

CoddyのKotlinジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 38/57。

クラスにも型パラメーターを持たせることができます。Box<T>は任意の型の値を保持し、各インスタンスで型が固定されます。つまり、Box<Int>はIntの値のみ受け入れます。

class Box<T>(var value: T) {
    fun describe() = "box with $value"
}

mainの中で:

val numberBox = Box(42)
val textBox = Box("hello")
numberBox.value += 1
println(numberBox.describe())
println(textBox.describe())
// numberBox.value = "x"   // エラー: String は Int ではない

出力:

box with 43
box with hello

コンテナは、典型的なジェネリッククラスです。1つの Stack<T> 実装ですべての要素型に対応でき、コンパイラーが何を入れ、何が取り出されるかをチェックします:

class Stack<T> {
    private val items = mutableListOf<T>()
    val size: Int
        get() = items.size

    fun push(item: T) { items.add(item) }
    fun pop(): T? = items.removeLastOrNull()
    fun peek(): T? = items.lastOrNull()
}

main の中では:

val stack = Stack<String>()
stack.push("a")
stack.push("b")
println(stack.pop())
println(stack.peek())
println(stack.size)

出力:

b
a
1

型引数は、Box(42)のようにコンストラクター引数がある場合はそこから推論されます。それ以外の場合は明示的に記述します: Stack<String>()。異なる型引数を持つインスタンスは異なる型なので、Stack<String>がStack<Int>と混同されることは決してありません。ジェネリッククラスの関数は、それ自身の型パラメーターを追加することもできます:

class Box<T>(val value: T) {
    fun <R> map(f: (T) -> R): Box<R> = Box(f(value))
}

mainの内部:

val length = Box("kotlin").map { it.length }
println(length.value + 1)

出力:

7

ジェネリックインターフェースも同じルールに従います。クラスは、ジェネリックな型として実装することも、特定の型引数に対して実装することもできます。

interface Repository<T> {
    fun add(item: T)
    fun all(): List<T>
}
class MemoryRepository<T> : Repository<T> {
    private val items = mutableListOf<T>()
    override fun add(item: T) { items.add(item) }
    override fun all() = items.toList()
}
class TagRepository : Repository<String> {
    private val tags = sortedSetOf<String>()
    override fun add(item: String) { tags.add(item.lowercase()) }
    override fun all() = tags.toList()
}

mainの内部では:

val ids = MemoryRepository<Int>()
ids.add(3); ids.add(1)
val tags = TagRepository()
tags.add("Kotlin"); tags.add("JVM")
println("${ids.all()} ${tags.all()}")

出力:

[3, 1] [jvm, kotlin]
challenge icon

チャレンジ

簡単

最後の limit 個の項目を記憶するジェネリッククラス History<T>(limit) を作成してください。add(item) は項目を追加し、limit 個を超えた場合は最も古い項目を削除します。latest は最も新しい項目、または null です。all() は最も古い項目から最も新しい項目までを、読み取り専用リストとして返します。次に、数値の履歴にのみ存在する拡張 History<Int>.average() を作成してください。これは保持されている数値の整数平均を返します(空の場合は 0)。

提供されたコードは、上限数、単語の行、数値の行を読み取ります。History<String> と History<Int> に値を格納し、保持された単語、最新の単語、保持された数値、それらの平均、および empty latest: null を出力します。

コードは History.kt に記述してください。Main.kt には提供された入出力コードが含まれており、編集できません。

自分で試してみよう

fun main() {
    // 提供された入出力コード: そのままにしておく
    val input = generateSequence(::readLine).toList()
    val limit = input[0].toInt()
    val words = History<String>(limit)
    for (w in input[1].split(" ")) words.add(w)
    val numbers = History<Int>(limit)
    for (n in input[2].split(" ")) numbers.add(n.toInt())
    println("words: ${words.all()}")
    println("latest word: ${words.latest}")
    println("numbers: ${numbers.all()}")
    println("average: ${numbers.average()}")
    println("empty latest: ${History<Char>(3).latest}")
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: Kotlinプレイグラウンド