Kotlin: شرح الأدوية في كوتلن

آخر تحديث: 2026-08-26

الأدوية في كوتلن أكثر أماناً من جافا — تباين موقع التصريح يتيح لتشارلي تحديد التباين المشترك وقت التعريف بدلاً من تكرار الرموز البديلة في كل موقع استخدام.

1. ما ستتعلمه


2. قصة مهندس حقيقية

(1) نقطة الألم: جحيم الرموز البديلة للأدوية في جافا

يكتب تشارلي List<? extends Order> لقوائم الطلبات المقروءة فقط و List<? super Order> لقوائم الكتابة فقط في جافا. كل موقع استخدام يتطلب تكرار الرموز البديلة، مما يجعل الكود غير مقروء تقريباً.

(2) حل تباين موقع التصريح في كوتلن

JAVA
// جافا: تكرار الرمز البديل في كل موقع استخدام
void process(List<? extends Order> orders) { }
void addAll(List<? super Order> target) { }

// كوتلن: تصريح التباين مرة واحدة في موقع التعريف
class Repository<out T : Order> {  // تباين مشترك في التعريف
    fun getAll(): List<T>  // T يظهر فقط في موقع 'out'
}
// لا حاجة للرموز البديلة في موقع الاستخدام!

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


3. أساسيات الأدوية

(1) الأصناف والدوال العامة

KOTLIN
// صنف عام
class Box<T>(val value: T) {
    fun unwrap(): T = value
}

val intBox = Box(42)         // T يستنتج كـ Int
val strBox = Box("Hello")   // T يستنتج كـ String

// دالة عامة
fun <T> singletonList(item: T): List<T> = listOf(item)

// عام مع قيد نوع
fun <T : Comparable<T>> maxOf(a: T, b: T): T = if (a > b) a else b

// قيود متعددة
fun <T> process(item: T) where T : CharSequence, T : Comparable<T> {
    // T هو CharSequence و Comparable في نفس الوقت
}

(2) مشكلة التباين: لماذا نحتاج out/in

KOTLIN
// هذا يبدو معقولاً لكنه خطأ نوع
val strings: List<String> = listOf("Hello", "World")
// val objects: List<Any> = strings  // خطأ في كوتلن (صحيح في مصفوفات جافا)

// لماذا؟ إذا سُمح:
// objects.add(42)  // سيؤدي لفساد قائمة النصوص!
// strings[0].length  // ClassCastException وقت التشغيل

4. تباين موقع التصريح

(1) out (التباين المشترك): منتج فقط، لا مستهلك أبداً

KOTLIN
// المنتج: T يظهر فقط في موقع 'out' (نوع الإرجاع)
interface Repository<out T> {
    fun findById(id: String): T
    fun findAll(): List<T>
    // fun save(item: T)  // خطأ: T في موقع 'in'
}

// الآن هذا يعمل:
val orderRepo: Repository<Order> = OrderRepositoryImpl()
val anyRepo: Repository<Any> = orderRepo  // صحيح! تباين مشترك

(2) in (التباين العكسي): مستهلك فقط، لا منتج أبداً

KOTLIN
// المستهلك: T يظهر فقط في موقع 'in' (نوع المعامل)
interface Filter<in T> {
    fun test(item: T): Boolean
    fun filter(items: List<T>): List<T>
    // fun getResult(): T  // خطأ: T في موقع 'out'
}

// الآن هذا يعمل:
val orderFilter: Filter<Order> = object : Filter<Order> {
    override fun test(item: Order) = item.total > 1_000
    override fun filter(items: List<Order>) = items.filter { test(it) }
}
val anyFilter: Filter<Any> = orderFilter  // لا يمكن التعيين - اتجاه التباين العكسي

(3) قواعد التباين نظرة سريعة

المُعدِّل المعنى T يظهر في اتجاه النوع الفرعي التشبيه
out تباين مشترك فقط في موقع الإرجاع Producer<Sub> نوع فرعي من Producer<Base> جافا ? extends
in تباين عكسي فقط في موقع المعامل Consumer<Base> نوع فرعي من Consumer<Sub> جافا ? super
بلا ثابت أي موقع لا علاقة نوع فرعي جافا T

(4) مخطط تدفق التباين

100%
flowchart TD
    subgraph "التباين المشترك (out)"
        A1[منتج من Sub] -->|"IS-A"| A2[منتج من Base]
    end
    subgraph "التباين العكسي (in)"
        B1[مستهلك من Base] -->|"IS-A"| B2[مستهلك من Sub]
    end
    subgraph "ثابت"
        C1[صندوق من Sub] -.->|"NOT IS-A"| C2[صندوق من Base]
    end

5. تباين موقع الاستخدام (إسقاط النوع)

عندما لا يمكنك تحديد التباين في موقع التصريح، اسقطه في موقع الاستخدام.

KOTLIN
// إسقاط موقع الاستخدام: عندما يكون موقع التصريح ثابتاً
fun copyFrom(source: Array<out Any>, dest: Array<in Any>) {
    for (i in source.indices) {
        dest[i] = source[i]
    }
}

// مكافئ لجافا:
// void copyFrom(Object[] source, Object[] dest)

(1) مقارنة تباين موقع الاستخدام

السيناريو جافا كوتلن
معامل للقراءة فقط <? extends T> <out T>
معامل للكتابة فقط <? super T> <in T>
تباين مشترك في التصريح غير مدعوم out T
تباين عكسي في التصريح غير مدعوم in T

6. الإسقاط النجمي

عندما يكون النوع العام غير معروف، استخدم الإسقاط النجمي للوصول الآمن.

KOTLIN
// List<*> = قائمة من نوع غير معروف (آمن للقراءة، ليس للكتابة)
val unknownList: List<*> = listOf("Hello", 42, 3.14)
val first: Any? = unknownList[0]  // صحيح - يرجع Any?
// unknownList.add("New")  // خطأ - لا يمكن الكتابة

// مقارنة
fun printSize(list: List<*>) {
    println("Size: ${list.size}")  // صحيح - الحجم لا يعتمد على T
}

// Array<*> مختلف
val array: Array<*> = arrayOf("A", "B")
// array[0] = "C"  // خطأ - لا يمكن الكتابة في Array<*>
val elem: Any? = array[0]  // صحيح - يمكن القراءة

(1) قواعد الإسقاط النجمي

النوع مكافئ الإسقاط النجمي قابل للقراءة قابل للكتابة
Foo<out T> Foo<out Any?> ✅ (Any?)
Foo<in T> Foo<in Nothing> ✅ (Nothing = غير قابل للكتابة)
Foo<T> (ثابت، T حد أعلى) Foo<out Any?> ✅ (Any?)
Foo<T> (T حد أعلى T : Upper) Foo<out Upper> ✅ (Upper)

7. معاملات النوع reified

(1) محو النوع و reified

KOTLIN
// العام العادي: النوع يُمحى وقت التشغيل
fun <T> isA(value: Any): Boolean = value is T  // خطأ: لا يمكن الفحص لنوع ممحو

// reified: الدالة المضمنة تحافظ على النوع في موقع الاستدعاء
inline fun <reified T> isA(value: Any): Boolean = value is T  // صحيح!

// الاستخدام
isA<String>("Hello")  // true
isA<Int>(42)          // true
isA<Int>("42")        // false

// عملي: تصفية حسب النوع
inline fun <reified T> Iterable<*>.filterIsInstance(): List<T> {
    return filter { it is T }.map { it as T }
}

(2) حالات استخدام reified العملية

KOTLIN
// إلغاء تسلسل JSON بدون معامل Class
inline fun <reified T> Json.decodeFromString(json: String): T

// بدلاً من:
// json.decodeFromString(Order::class.java, jsonString)

// فقط:
// json.decodeFromString<Order>(jsonString)

8. مثال كامل: مستودع طلبات آمن الأنواع

KOTLIN
// ============================================
// OrderProcessor - مستودع آمن الأنواع مع الأدوية
// الميزة: مستودع عام مع التباين
// ============================================

open class Order(val id: String, val total: Double)
class BulkOrder(id: String, total: Double, val minQty: Int) : Order(id, total)
class PriorityOrder(id: String, total: Double, val priority: String) : Order(id, total)

// مستودع التباين المشترك: ينتج T فقط
interface ReadOnlyRepository<out T : Order> {
    fun findById(id: String): T?
    fun findAll(): List<T>
}

// كاتب التباين العكسي: يستهلك T فقط
interface WriteRepository<in T : Order> {
    fun save(item: T)
    fun saveAll(items: List<T>)
}

// المستودع الكامل: ثابت (يقرأ ويكتب)
interface OrderRepository<T : Order> : ReadOnlyRepository<T>, WriteRepository<T>

// تطبيق في الذاكرة
class InMemoryOrderRepository<T : Order> : OrderRepository<T> {
    private val storage = mutableMapOf<String, T>()

    override fun findById(id: String): T? = storage[id]
    override fun findAll(): List<T> = storage.values.toList()
    override fun save(item: T) { storage[item.id] = item }
    override fun saveAll(items: List<T>) { items.forEach { save(it) } }
}

// تصفية عامة مع التباين العكسي
class OrderFilter<in T : Order>(private val predicate: (T) -> Boolean) {
    fun test(item: T): Boolean = predicate(item)
    fun filter(items: List<T>): List<T> = items.filter(predicate)
}

inline fun <reified T : Order> List<Order>.filterByType(): List<T> {
    return this.filterIsInstance<T>()
}

fun main() {
    val orders = listOf(
        Order("ORD-001", 299.99),
        BulkOrder("ORD-002", 5_000.00, 100),
        PriorityOrder("ORD-003", 15_000.00, "VIP"),
        BulkOrder("ORD-004", 8_000.00, 50),
        PriorityOrder("ORD-005", 2_500.00, "GOLD")
    )

    // التباين المشترك: مستودع BulkOrder قابل للتعيين لمستودع Order
    val bulkRepo: ReadOnlyRepository<BulkOrder> = InMemoryOrderRepository()
    val orderRepo: ReadOnlyRepository<Order> = bulkRepo  // صحيح: تباين مشترك

    // reified: تصفية حسب النوع وقت التشغيل
    val bulkOrders: List<BulkOrder> = orders.filterByType<BulkOrder>()
    println("Bulk orders: ${bulkOrders.map { it.id }}")

    val priorityOrders: List<PriorityOrder> = orders.filterByType<PriorityOrder>()
    println("Priority orders: ${priorityOrders.map { it.id }}")

    // تصفية عامة
    val highValueFilter = OrderFilter<Order> { it.total > 1_000 }
    val highValue = highValueFilter.filter(orders)
    println("High value orders: ${highValue.map { it.id }}")
}

المخرجات:

TEXT 📖 للعرض فقط
Bulk orders: [ORD-002, ORD-004]
Priority orders: [ORD-003, ORD-005]
High value orders: [ORD-002, ORD-003, ORD-004, ORD-005]

9. أمثلة عملية سريعة

▶ مثال: دالة عامة بسيطة

KOTLIN
// دالة عامة - تعمل مع أي نوع
fun <T> identity(value: T): T = value

println("Int: ${identity(42)}")
println("String: ${identity("Hello")}")
println("Double: ${identity(3.14)}")
println("List: ${identity(listOf(1, 2, 3))}")

// دالة تأخذ قائمة وتعيد العنصر الأول
fun <T> firstOrNull(list: List<T>, default: T? = null): T? =
    if (list.isEmpty()) default else list[0]

println("First: ${firstOrNull(listOf(1, 2, 3))}")
println("Empty default: ${firstOrNull(emptyList(), default = -1)}")

// دالة عامة مع قيود
fun <T : Number> sumList(list: List<T>): Double {
    return list.sumOf { it.toDouble() }
}

println("Sum Int: ${sumList(listOf(1, 2, 3, 4, 5))}")
println("Sum Double: ${sumList(listOf(1.5, 2.5, 3.0))}")

**المخرجات:

TEXT 📖 للعرض فقط
Int: 42
String: Hello
Double: 3.14
List: [1, 2, 3]
First: 1
Empty default: -1
Sum Int: 15.0
Sum Double: 7.0

▶ مثال: فئة عامة (Generic Class)

KOTLIN
// Box فئة عامة تحتفظ بقيمة من أي نوع
class Box<T>(val content: T) {
    fun describe(): String = "Box contains: $content (${content!!::class.simpleName})"

    fun <R> map(transform: (T) -> R): Box<R> = Box(transform(content))
}

val intBox = Box(42)
val strBox = Box("Hello Kotlin")

println(intBox.describe())
println(strBox.describe())

// map يحول النوع
val stringifiedBox = intBox.map { "Number: $it" }
val doubledBox = intBox.map { it * 2 }

println(stringifiedBox.describe())
println("Doubled: ${doubledBox.content}")

// Pair عام
class Pair<A, B>(val first: A, val second: B) {
    fun <C> combine(other: Pair<A, C>): Triple<A, B, C> = Triple(first, second, other.second)
}

val pair = Pair("name", 42)
val combined = pair.combine(Pair("name", "extra"))
println("Combined: $combined")

**المخرجات:

TEXT 📖 للعرض فقط
Box contains: 42 (Int)
Box contains: Hello Kotlin (String)
Box contains: Number: 42 (String)
Doubled: 84
Combined: (name, 42, extra)

▶ مثال: قيوود النوع (Type Bounds)

KOTLIN
// قيد T : Number - يقبل فقط أنواع الأرقام
fun <T : Number> average(numbers: List<T>): Double {
    if (numbers.isEmpty()) return 0.0
    var sum = 0.0
    for (n in numbers) sum += n.toDouble()
    return sum / numbers.size
}

println("Int avg: ${average(listOf(1, 2, 3, 4, 5))}")
println("Double avg: ${average(listOf(1.5, 2.5, 3.5))}")

// قيد مركب: T يجب أن يكون Number و Comparable
fun <T> findMax(items: List<T>): T? where T : Number, T : Comparable<T> {
    return items.maxOrNull()
}

println("Max int: ${findMax(listOf(3, 1, 4, 1, 5, 9, 2, 6))}")
println("Max double: ${findMax(listOf(1.5, 3.14, 2.71))}")

// قيد مع interface
interface Describable {
    fun describe(): String
}

data class User(val name: String, val age: Int) : Describable {
    override fun describe() = "$name ($age)"
}

fun <T> describeAll(items: List<T>) where T : Describable {
    items.forEach { println(it.describe()) }
}

describeAll(listOf(User("Alice", 30), User("Bob", 25)))

**المخرجات:

TEXT 📖 للعرض فقط
Int avg: 3.0
Double avg: 2.5
Max int: 9
Max double: 3.14
Alice (30)
Bob (25)

▶ مثال: التباين out و in

KOTLIN
// Producer<out T>: ينتج T فقط (مثل List<T>)
interface Producer<out T> {
    fun produce(): T
}

// Consumer<in T>: يستهلك T فقط (مثل Comparable<T>)
interface Consumer<in T> {
    fun consume(value: T)
}

class StringProducer : Producer<String> {
    override fun produce(): String = "Hello"
}

class AnyConsumer : Consumer<Any> {
    override fun consume(value: Any) = println("Consumed: $value")
}

// Producer<Cat> يمكن استخدامه كـ Producer<Animal> (covariance)
val stringProducer: Producer<String> = StringProducer()
val anyProducerFromString: Producer<Any> = stringProducer  // OK بسبب out

// Consumer<Animal> يمكن استخدامه كـ Consumer<Cat> (contravariance)
val anyConsumer: Consumer<Any> = AnyConsumer()
val stringConsumer: Consumer<String> = anyConsumer  // OK بسبب in

println(anyProducerFromString.produce())
stringConsumer.consume("Test value")

**المخرجات:

TEXT 📖 للعرض فقط
Hello
Consumed: Test value

▶ مثال: reified type parameters

KOTLIN
// reified يحافظ على معلومات النوع في وقت التشغيل (مع inline فقط)
inline fun <reified T> isType(value: Any): Boolean = value is T

println("42 is Int: ${isType<Int>(42)}")
println("'Hello' is String: ${isType<String>("Hello")}")
println("42 is String: ${isType<String>(42)}")

// دالة لفحص النوع و cast آمن
inline fun <reified T> safeCast(value: Any): T? = value as? T

val maybeInt: Any = "42"  // String, not Int
val result: Int? = safeCast<Int>(maybeInt)
println("Cast result: $result")

// استخدام عملي: EventBus بسيط
inline fun <reified T> handle(event: T) {
    println("Handling event of type ${T::class.simpleName}: $event")
}

handle(42)              // Int
handle("Hello")          // String
handle(listOf(1, 2, 3))  // List

**المخرجات:

TEXT 📖 للعرض فقط
42 is Int: true
'Hello' is String: true
42 is String: false
Cast result: null
Handling event of type Int: 42
Handling event of type String: Hello
Handling event of type List: [1, 2, 3]

▶ مثال: دوال الامتداد العامة

KOTLIN
// دالة امتداد عامة على List
fun <T> List<T>.secondOrNull(): T? = if (size >= 2) this[1] else null
fun <T> List<T>.thirdOrNull(): T? = if (size >= 3) this[2] else null

val numbers = listOf(10, 20, 30, 40)
println("Second: ${numbers.secondOrNull()}")
println("Third: ${numbers.thirdOrNull()}")
println("Empty: ${emptyList<Int>().secondOrNull()}")

// دالة عامة تأخذ تحويل
fun <T, R> List<T>.mapToPairs(transform: (T) -> Pair<R, T>): Map<R, T> {
    val result = mutableMapOf<R, T>()
    for (item in this) {
        val (key, value) = transform(item)
        result[key] = value
    }
    return result
}

val users = listOf("Alice", "Bob", "Charlie")
val firstCharMap = users.mapToPairs { it.first() to it }
println("First char map: $firstCharMap")

// دالة عامة على Map
fun <K, V> Map<K, V>.swapKeysValues(): Map<V, K> {
    return entries.associate { (k, v) -> v to k }
}

val original = mapOf("a" to 1, "b" to 2, "c" to 3)
val swapped = original.swapKeysValues()
println("Swapped: $swapped")

**المخرجات:

TEXT 📖 للعرض فقط
Second: 20
Third: 30
Empty: null
First char map: {A=Alice, B=Bob, C=Charlie}
Swapped: {1=a, 2=b, 3=c}

▶ مثال: الأنواع العامة المتداخلة

KOTLIN
// فئة عامة بأكثر من معامل نوع
class KeyValueStore<K, V> {
    private val data = mutableMapOf<K, V>()

    fun put(key: K, value: V) {
        data[key] = value
    }

    fun get(key: K): V? = data[key]

    fun getOrDefault(key: K, default: V): V = data.getOrDefault(key, default)

    fun keys(): Set<K> = data.keys
    fun values(): Collection<V> = data.values
    fun entries(): Set<Map.Entry<K, V>> = data.entries

    inline fun <R> mapValues(transform: (V) -> R): KeyValueStore<K, R> {
        val result = KeyValueStore<K, R>()
        for ((k, v) in data) {
            result.put(k, transform(v))
        }
        return result
    }
}

// استخدام
val store: KeyValueStore<String, Int> = KeyValueStore()
store.put("age-alice", 30)
store.put("age-bob", 25)

println("Alice age: ${store.get("age-alice")}")
println("Charlie: ${store.getOrDefault("age-charlie", 0)}")
println("Keys: ${store.keys()}")

// mapValues تُرجع نوعًا جديدًا
val doubledStore = store.mapValues { it * 2 }
println("Doubled Bob: ${doubledStore.get("age-bob")}")

**المخرجات:

TEXT 📖 للعرض فقط
Alice age: 30
Charlie: 0
Keys: [age-alice, age-bob]
Doubled Bob: 50

❓ أسئلة شائعة

س هل يمكن استخدام out و in على نفس معامل النوع في وقت واحد؟
ج لا. معامل النوع يمكن أن يكون فقط out أو in أو ثابتاً. إذا ظهر T في كل من موقع الإدخال والإخراج، يجب أن يكون ثابتاً.
س هل الأدوية في كوتلن لا تزال موجودة وقت التشغيل؟
ج مُمحاة في الغالب (مثل جافا). لكن inline + reified يمكنه الحفاظ على معلومات النوع. الدوال المضمنة تتوسع في موقع الاستدعاء، لذا معلومات النوع متاحة وقت الترجمة.
س ما الفرق بين الإسقاط النجمي * و Any؟
ج List<*> تعني "نوع العنصر غير معروف"، قابل للقراءة فقط كـ Any؟؛ List<Any> تعني "نوع العنصر هو Any"، يمكنك إضافة أي قيمة. الأول أكثر أماناً.
س متى أستخدم تباين موقع التصريح مقابل موقع الاستخدام؟
ج فضّل تباين موقع التصريح — قرر وقت تصميم API ما إذا كان T للقراءة فقط (out) أو للكتابة فقط (in). تباين موقع الاستخدام هو الملاذ الأخير.
س هل لـ reified تكلفة أداء إضافية؟
ج لا تكلفة إضافية وقت التشغيل. بما أن الدوال المضمنة تتوسع وقت الترجمة، فحوصات نوع reified تحدث وقت الترجمة بدون عمل إضافي وقت التشغيل.
س ما الفرق بين <T : Upper> و where T : Upper؟
ج القيد المفرد يستخدم اختصار <T : Upper>؛ القيود المتعددة يجب أن تستخدم صياغة where.

📖 ملخص


📝 تمارين

  1. مبتدئ (⭐): نفّذ Box<T> عام واختبر توافق التعيين مع معاملات أنواع مختلفة. تلميح: هل يمكن تعيين Box<Int> إلى Box<Any>؟
  2. متوسط (⭐⭐): عرّف ReadOnlyRepository<out T> و WriteRepository<in T>، أظهر علاقات تعيين التباين المشترك والتباين العكسي. تلميح: List<BulkOrder>List<out Order>
  3. متقدم (⭐⭐⭐): نفّذ دالة spotNull<reified Any> تصفّي حسب النوع ثم تحوّله. تلميح: استخدم filterIsInstance<T>

← السابق | التالي →

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%