ความแปรผัน ณ จุดประกาศ: in และ out
ใช้พารามิเตอร์ชนิดแบบโคแวเรียนต์ (out) และคอนทราแวเรียนต์ (in) อย่างถูกต้อง
ความแปรผัน ณ จุดประกาศ: in และ out เป็นบทเรียน Kotlin Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Kotlin Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Kotlin Academy มีบทเรียนทั้งหมด 4 บทเรียน
ปัญหาความแปรผัน
ใน Kotlin List<String> เป็นชนิดย่อยของ List<Any> เพราะ List ประกาศด้วย out T หากไม่มีคำกำกับความแปรผัน ความสัมพันธ์นี้จะไม่เกิดขึ้น
val strings: List<String> = listOf("a", "b")
val anys: List<Any> = strings // OK because List<out T>
// MutableList<String> is NOT a subtype of MutableList<Any>:
// val m: MutableList<Any> = mutableListOf("x") // compile error
fun main() { println(anys) }ความแปรผันร่วมด้วย out
out T หมายความว่าคลาสสามารถ สร้าง ค่า T ได้เท่านั้น (ส่งคืนค่า) และไม่สามารถรับค่าเหล่านั้นได้ ทำให้ Producer<Dog> เป็นชนิดย่อยของ Producer<Animal>
interface Producer<out T> {
fun produce(): T
}
class DogProducer : Producer<String> {
override fun produce() = "Woof!"
}
fun sound(p: Producer<Any>) = println(p.produce())
fun main() {
val dog = DogProducer()
sound(dog) // OK: Producer<String> is a subtype of Producer<Any>
}ความแปรผันตรงข้ามด้วย in
in T หมายความว่าคลาสสามารถ รับ ค่า T ได้เท่านั้น (รับเป็นพารามิเตอร์) และไม่สามารถสร้างค่าเหล่านั้นได้ ทำให้ Consumer<Animal> เป็นชนิดย่อยของ Consumer<Dog>
interface Consumer<in T> {
fun consume(item: T)
}
class Printer : Consumer<Any> {
override fun consume(item: Any) = println(item)
}
fun feedDog(consumer: Consumer<String>) = consumer.consume("Dog")
fun main() {
val printer = Printer()
feedDog(printer) // OK: Consumer<Any> is a subtype of Consumer<String>
}ข้อจำกัดของ out: ห้ามใช้ในตำแหน่งรับค่า
เมื่อใช้ out T จะไม่สามารถใช้ T ในตำแหน่งพารามิเตอร์ (in) ได้ คอมไพเลอร์จะบังคับใช้ข้อจำกัดนี้
interface ReadOnly<out T> {
fun get(): T // OK: out position
// fun set(t: T) {} // Error: T in in-position violates out variance
}
class Box<out T>(private val value: T) : ReadOnly<T> {
override fun get() = value
}
fun main() {
val box: ReadOnly<Any> = Box<String>("hello")
println(box.get())
}ข้อจำกัดของ in: ห้ามใช้ในตำแหน่งส่งค่า
เมื่อใช้ in T จะไม่สามารถใช้ T ในตำแหน่งผลลัพธ์ (out) ได้
interface WriteOnly<in T> {
fun set(t: T) // OK: in position
// fun get(): T {} // Error: T in out-position violates in variance
}
class Sink<in T> : WriteOnly<T> {
override fun set(t: T) = println("Received: $t")
}
fun main() {
val sink: WriteOnly<String> = Sink<Any>()
sink.set("hello")
}ตัวอย่างจากไลบรารีมาตรฐานของ Kotlin
Comparable<in T> มีความแปรผันตรงข้าม: Comparable<Number> สามารถเปรียบเทียบชนิดย่อยของ Number ได้ทุกชนิด ส่วน List<out T> มีความแปรผันร่วม: รายการแมวถือเป็นรายการสัตว์ได้
fun sortNumbers(list: List<Number>) = list.sortedWith(compareBy { it.toDouble() })
fun main() {
val ints: List<Int> = listOf(3, 1, 2)
val sorted = sortNumbers(ints) // OK: List<Int> is List<Number>
println(sorted)
}การไม่แปรผัน: MutableList
MutableList<T> ไม่แปรผัน เพราะสามารถทั้งสร้างและรับค่า T ได้ จึงไม่มีความสัมพันธ์แบบชนิดย่อยระหว่างชนิดข้อมูล T ที่แตกต่างกัน
fun addNumber(list: MutableList<Number>) { list.add(1.5) }
fun main() {
val ints = mutableListOf<Int>(1, 2)
// addNumber(ints) // compile error: MutableList<Int> is not MutableList<Number>
val nums = mutableListOf<Number>(1, 2)
addNumber(nums) // OK
println(nums)
}ความแปรผัน ณ จุดใช้งานเป็นทางเลือก
เมื่อไม่สามารถเปลี่ยนคลาสได้ ให้ใช้ความแปรผัน ณ จุดใช้งาน โดยใช้ out ที่จุดเรียกใช้เพื่อฉายชนิดข้อมูลให้มีความแปรผันร่วม
fun copy(from: MutableList<out Any>, to: MutableList<Any>) {
for (item in from) to.add(item)
}
fun main() {
val src = mutableListOf("a", "b", "c")
val dest = mutableListOf<Any>()
copy(src, dest)
println(dest)
}ภาคปฏิบัติ: รูปแบบ Repository
ใช้ความแปรผันร่วมกับ Repository แบบอ่านอย่างเดียว และใช้ความแปรผันตรงข้ามกับ Sink แบบเขียนอย่างเดียว เพื่อจำลองการไหลของข้อมูลที่เป็นระเบียบ
interface Repository<out T> {
fun findAll(): List<T>
fun findById(id: Int): T?
}
interface Writer<in T> {
fun save(entity: T)
}
interface ReadWriteRepo<T> : Repository<T>, Writer<T>
// ReadWriteRepo<User> is neither sub nor super of ReadWriteRepo<Admin>แนวทางเลือกความแปรผัน
เลือกความแปรผันตามวิธีใช้ T: out = ตัวสร้าง (ส่งคืน T เท่านั้น), in = ตัวรับ (รับ T เท่านั้น), ไม่แปรผัน = ทำได้ทั้งสองอย่าง (MutableList, ช่องสัญญาณ)
// Quick mental model:
// out T: source/producer — List, Flow, Sequence
// in T: sink/consumer — Comparable, Continuation
// invariant: read+write — MutableList, Channel, MutableStateFlow
fun main() {
val nums: List<Number> = listOf(1, 2, 3) // List is out
println(nums)
}คลาสที่ไม่แปรผันพร้อมฟังก์ชัน out
แม้ในคลาสที่ไม่แปรผัน คุณก็ยังใช้ out ณ จุดใช้งานกับพารามิเตอร์ฟังก์ชันเฉพาะได้
class Stack<T>(private val items: MutableList<T> = mutableListOf()) {
fun push(item: T) = items.add(item)
fun pop(): T? = items.removeLastOrNull()
}
fun printAll(stack: Stack<out Any>) {
// can only read, not write
println(stack.pop())
}
fun main() {
val s = Stack<String>()
s.push("hello")
printAll(s)
}ตรวจสอบความเข้าใจ
out T บนพารามิเตอร์ชนิดข้อมูลหมายความว่าอย่างไร
สรุปทบทวน
out T (ความแปรผันร่วม) อนุญาตให้เกิดความสัมพันธ์แบบชนิดย่อยเมื่อมีการสร้างค่า in T (ความแปรผันตรงข้าม) อนุญาตให้เกิดความสัมพันธ์แบบชนิดย่อยเมื่อมีการรับค่า ชนิดข้อมูลที่ไม่แปรผัน (ทั้งอ่านและเขียน) ไม่มีความสัมพันธ์แบบชนิดย่อยระหว่าง T ที่แตกต่างกัน
คำถามที่พบบ่อย
บทเรียน “ความแปรผัน ณ จุดประกาศ: in และ out” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “ความแปรผัน ณ จุดประกาศ: in และ out” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Kotlin Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Kotlin Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “ความแปรผัน ณ จุดประกาศ: in และ out”
ใช้พารามิเตอร์ชนิดแบบโคแวเรียนต์ (out) และคอนทราแวเรียนต์ (in) อย่างถูกต้อง คุณปฏิบัติ Kotlin Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Kotlin Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน Kotlin Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน
บทเรียน “ความแปรผัน ณ จุดประกาศ: in และ out” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน Kotlin Academy นี้ได้ไหม
ได้ บทเรียน Kotlin Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- ฟังก์ชัน Generic และข้อจำกัดชนิดด้วย where
- ความแปรผัน ณ จุดประกาศ: in และ out
- Star Projection และการใช้ *
- การลบชนิดและพารามิเตอร์ชนิด reified