0Pricing
Kotlin Academy · درس

التباين في موضع التصريح: ‏in وout

استخدم معاملات الأنواع المتغايرة (out) والمتعاكسة التغاير (in) بشكل صحيح.

التباين في موضع التصريح: ‏in وout درس مجاني في Kotlin Academy على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 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: لا يوجد موضع in

مع 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: لا يوجد موضع out

مع 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)
}

تطبيقي: نمط المستودع

استخدم التغاير مع المستودعات للقراءة فقط، والتباين العكسي مع المنافذ المخصّصة للكتابة فقط، لنمذجة تدفق بيانات واضح.

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، وChannel).

// 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» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Kotlin Academy، انتقل إلى CoddyKit PRO. تتضمن دورة Kotlin Academy 4 دروس في المجموع.

ماذا ستتعلم في «التباين في موضع التصريح: ‏in وout»؟

استخدم معاملات الأنواع المتغايرة (out) والمتعاكسة التغاير (in) بشكل صحيح. تتمرن على Kotlin Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Kotlin Academy؟

لا تُشترط خبرة سابقة. Kotlin Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.

كم من الوقت يستغرق درس «التباين في موضع التصريح: ‏in وout»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Kotlin Academy هذا؟

نعم. كل درس في Kotlin Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. الدوال العامة وقيود الأنواع باستخدام where
  2. التباين في موضع التصريح: ‏in وout
  3. إسقاط النجمة ومتى تستخدم *
  4. محو النوع ومعاملات النوع reified
← العودة إلى Kotlin Academy