Kotlin / السلوك القابل للاستخدام والحدود المنمطة / الدرس 8
استخدام الأنواع العامة دون وعود نوعية غير آمنة
افهم معاملات الأنواع، وثبات الحاويات المعدلة، وout للمنتج وin للمستهلك، وحدود فحص الأنواع الممحوة.
في هذه الصفحة
أهداف الدرس
- اشرح لماذا لا تُعد قائمة نصوص قابلة للتعديل قائمة قابلة للتعديل لأي كائن.
- اقرأ تباين المنتج والمستهلك بالنظر إلى العمليات التي يتيحها العقد.
يحفظ معامل النوع علاقة بين القيم
يعبّر التصريح العام عن علاقة بين بعض أنواع المدخلات والمخرجات دون تثبيت نوع محدد واحد. تخزن Box<T> قيمة من نوع T، ويمكن لدالة تعيد أول عنصر من List<T> أن تحافظ على نوع عناصر المستدعي. ليست الأنواع العامة وسيلة لتجنب التفكير في العمليات التي تدعمها القيمة. أضف قيدا عندما تتطلب الخوارزمية قدرة مثل المقارنة. قد يشمل معامل نوع غير مقيد أنواعا قابلة للخلو، بينما يستبعد قيد مثل T : Any القيمة null. اختر القيود المطابقة للخوارزمية الفعلية بدلا من إجراء تحويلات قسرية داخلها.
تحمي الحاوية المعدلة القراءة والكتابة معا
لو أمكن معاملة MutableList<String> على أنها MutableList<Any>، لاستطاع مستدع إدخال عدد صحيح وكسر وعد القائمة الأصلية بأنها نصية. لذلك تتطلب قابلية التعديل والأنواع الفرعية عناية. تكون List المقروءة متغايرة في نوع عنصرها لأن واجهتها لا تقبل عناصر جديدة اعتباطية عبر معامل النوع. ولا تعني القراءة فقط ثباتا عميقا. ينسخ المثال النصوص صراحة إلى قائمة أوسع، فيحفظ عقد عناصر الحاوية الأصلية بدلا من فرض مرجع مشترك غير آمن باستخدام تحويل قسري.
fun main() {
val names = mutableListOf("Mira", "Salim")
val readView: List<Any> = names
val wider = mutableListOf<Any>()
wider.addAll(names)
wider.add(7)
println(readView.size)
println(wider.joinToString("|"))
}
2
Mira|Salim|7
المخرجان المتوقعان هما 2 ثم Mira|Salim|7. يضاف العدد الصحيح إلى القائمة الأوسع المنفصلة فقط، ولا يغير تحويل غير متحقق نوع العناصر الذي تعد به القائمة الأصلية. هذا مصدر غير مترجم.
تنتج out القيم وتستهلكها in
يسمح التصريح باستخدام out بوضع معامل النوع في مواضع الخرج المسموحة فقط، فيجعل المنتج متغايرا بأمان. وتسمح in بمواضع الدخل المسموحة وتدعم التغاير العكسي للمستهلك. فكّر في العمليات: يستطيع مصدر نصوص خدمة قارئ يتوقع أي كائن، ويستطيع مستهلك يقبل أي كائن خدمة شيفرة لا ترسل إلا نصوصا. لا تطبق هذه القاعدة المختصرة آليا على خاصية تقرأ وتكتب. فالخاصية القابلة للتعديل تستخدم نوعها في الاتجاهين، ولذلك لا تفي بوعد الاقتصار على الخرج دون قيود إضافية.
عبّر عن عقد منتج ضيق
تعيد واجهة Source قيمة ولا تقبل أي قيمة من نوع T. لذلك يمكن إسناد Source<String> إلى Source<Any> دون تمكين المستدعي من كتابة قيمة غير متوافقة داخل المصدر. التنفيذ محلي ولا يملك عمر مورد. افصل في الواجهات الحقيقية بين علاقة الأنواع والسلوك التشغيلي، مثل الحجب أو الفشل أو نقل الملكية؛ فالتباين لا يقول شيئا عن تلك الخصائص. تستطيع إسقاطات النوع عند الاستخدام تقييد عمليات نوع عام قائم عندما لا يناسب تغيير تصريحه، لكن وضوح تصميم الواجهة يسبق التعقيد في مناورات الأنواع.
interface Source<out T> { fun next(): T }
class NameSource : Source<String> {
override fun next(): String = "Mira"
}
fun main() {
val names: Source<String> = NameSource()
val values: Source<Any> = names
println(values.next())
}
Mira
السطر المتوقع هو Mira. يتيح Source<Any> قراءة قيمة بوصفها Any، لكنه لا يوفّر عملية لإدخال كائن اعتباطي في NameSource. لم يُنفّذ المثال بالمترجم.
لا تستعيد فحوص التشغيل كل معاملات النوع
يحد محو النوع على JVM كثيرا من فحوص معاملات الأنواع أثناء التشغيل. إثبات أن القيمة List لا يثبت أن كل عناصرها String. يسمح الإسقاط النجمي بفحص الحاوية دون ادعاء معرفة معاملها المخفي؛ وتحقق من العناصر عند عبور حد غير منمّط. تدعم معاملات reified مع inline بعض عمليات النوع وقت التشغيل عند موضع الاستدعاء، لكنها لا تجعل معاملات الأنواع العامة المتداخلة قابلة للفحص في كل الحالات. تجنب التحويلات غير المتحققة التي تؤجل الفشل إلى قارئ بعيد. فضّل تحويلا متحققا يبني نتيجة صحيحة النوع ويبلّغ صراحة عن العناصر غير الصالحة.
الأخطاء الشائعة وإصلاحها
يفشل تحويل قائمة غير متحقق بعد زمن عند قراءتها.
- لماذا يحدث؟
- اختلط فحص شكل الحاوية بالتحقق من نوع كل عنصر فيها.
- كيف تصلحه؟
- افحص العناصر وحوّلها عند الحد غير المنمّط الذي تدخل منه.
- كيف تتجنب تكراره؟
- تجنب التحويلات غير المتحققة في واجهات التحويل العامة.
يُستخدم معامل out في خاصية تقبل الكتابة.
- لماذا يحدث؟
- يستهلك التصريح معامل النوع وينتجه، فلا يلتزم بقيد الخرج وحده.
- كيف تصلحه؟
- افصل الواجهة أو أزل التباين الذي لا تدعمه عملياتها.
- كيف تتجنب تكراره؟
- راجع مواضع القراءة والكتابة قبل إضافة معدل تباين.
تستدعي دالة عامة عمليات لا يوفّرها T.
- لماذا يحدث؟
- لم تُعبَّر القدرة المطلوبة بقيد على معامل النوع.
- كيف تصلحه؟
- أضف أضيق قيد مطلوب، أو اقبل السلوك في معامل دالي.
- كيف تتجنب تكراره؟
- صمّم حول العمليات المدعومة بدلا من التحويل القسري إلى أصناف محددة.
اختبر فهمك
1. لماذا يكون نسخ names إلى MutableList<Any> أأمن من تحويل names نفسها إلى هذا النوع؟
اعرض الحل المشروح
للنسخة حاوية مستقلة تقبل قيما أوسع صراحة. لا يستطيع إدخال عدد صحيح فيها إفساد عقد عناصر MutableList<String> الأصلية. أما التحويل فيجعل مرجعين يختلفان حول المحتوى المسموح للكائن القابل للتعديل نفسه، وقد يؤدي إلى فشل نوعي لاحقا. وقد تظل كائنات العناصر مشتركة بين القائمتين، لذا فنسخ الحاوية ليس استنساخا عميقا.
2. هل تستطيع Source<out T> أن تعد بأن next غير حاجبة؟
اعرض الحل المشروح
لا. يقيّد التباين مواضع الأنواع والاستبدال الآمن، ولا يحدد الجدولة أو سلوك الموارد. يمكن لمنتج حاجب ومنتج ثابت صرف أن يتشاركا شكل النوع هذا. وثّق دلالات التنفيذ والأخطاء والعمر بصورة منفصلة، أو اختر واجهة غير متزامنة إذا كان التطبيق يحتاجها.
أهم ما تعلمته
- تحفظ الأنواع العامة العلاقات، ويقيّد التباين العمليات ليبقى الاستبدال آمنا.
- تحقق من البيانات عند حدود محو النوع بدلا من توكيد نوع عناصر مخفي.