מחלקות גנריות
חלק מהיחידה תכנות מונחה עצמים במסלול ה-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]אתגר
קלכתבו את המחלקה הגנרית 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}")
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1מחלקות ואובייקטים
עבודה עם קבציםהגדרת מחלקותבנאים ראשייםבלוקי init ואימות נתוניםבנאים משנייםסיכום – כרטיס ספרייה8טיפוסים גנריים
פונקציות גנריותמחלקות גנריותאילוצי טיפוסיםקווריאנטיות עם in ו-outסיכום – תור עדיפויותתרגלו בעצמכם: Kotlin אונליין