Menu
Coddy logo textTech

מחלקות גנריות

חלק מהיחידה תכנות מונחה עצמים במסלול ה-Kotlin של Coddy. שיעור 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

מחלקות גנריות הן בדרך כלל מחלקות למכולות. מימוש אחד של 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

אתגר

קל

כתבו את המחלקה הגנרית History<T>(limit) ששומרת את 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 אונליין