Kotlin: متغيرات كوتلن ونظام الأنواع بعمق
آخر تحديث: 2026-08-26
نظام أنواع كوتلن ليس نسخة من جافا — فهو يقضي على التحويلات القسرية بالتحويلات الذكية، يبني جبر أنواع كامل بـ Any/Unit/Nothing، ويجعل نظام الأنواع يعمل لصالحك لا ضدك.
1. ما ستتعلمه
- الأنواع الأساسية:
Int/Double/Boolean/Char/String - التحويل الذكي: استخدم مباشرة بعد فحص
is - التحويلات الصريحة:
toInt()/toDouble()، بدون تضييق ضمني Any/Unit/Nothing— ثلاثة أنواع عليا- أسماء مستعارة للأنواع
typealiasلتحسين القراءة
2. قصة مطور حقيقية
(1) مشكلة: جحيم تحويل الأنواع في جافا
بوب كان يصون كود جافا لـ OrderProcessor، والتحويلات القسرية كانت في كل مكان: (Order)، (String). تفويت فحص instanceof واحد أطلق ClassCastException — 3-5 حوادث إنتاجية كل ربع سنة.
(2) حل التحويل الذكي في كوتلن
// جافا: مطوّل وعرضة للأخطاء
if (obj instanceof Order) {
Order order = (Order) obj; // يجب التحويل مرة أخرى
System.out.println(order.getId());
}
// كوتلن: التحويل الذكي يقضي على التحويل
if (obj is Order) {
println(obj.id) // لا حاجة لتحويل!
}
مُترجِم كوتلن يُدرج تحويلات آمنة تلقائيًا بعد فحوصات
is. بوب لم يكتب تحويلًا يدويًا مرة أخرى.
3. الأنواع الأساسية
(1) الأنواع الرقمية
كوتلن لا يكشف الأنواع الأولية لجافا (int/double) — كل شيء كائن — لكن المُترجِم يحسّنها إلى أنواع أولية على JVM.
val intValue: Int = 42 // 32 بت
val longValue: Long = 42L // 64 بت
val doubleValue: Double = 3.14 // 64 بت IEEE 754
val floatValue: Float = 3.14f // 32 بت IEEE 754
// شرطة سفلية للقراءة
val million = 1_000_000
val creditCard = 1234_5678_9012_3456L
val bytes = 0b11010010_01101001
| النوع | البت | النطاق | المقابل في جافا |
|---|---|---|---|
Byte |
8 | -128 ~ 127 | byte |
Short |
16 | -32768 ~ 32767 | short |
Int |
32 | -2³¹ ~ 2³¹-1 | int |
Long |
64 | -2⁶³ ~ 2⁶³-1 | long |
Float |
32 | IEEE 754 | float |
Double |
64 | IEEE 754 | double |
(2) الأنواع غير الرقمية
val bool: Boolean = true
val char: Char = 'A'
val str: String = "OrderProcessor"
// الحرف ليس رقمًا في كوتلن
// val code: Int = 'A' // خطأ
val code: Int = 'A'.code // موافق: 65
val charFromCode: Char = 65.toChar() // موافق: 'A'
4. التحويل الذكي
مُترجِم كوتلن يحوّل تلقائيًا المتغير إلى نوع أكثر تحديدًا بعد فحص النوع — بدون تحويل يدوي.
(1) التحويل الذكي الأساسي
fun process(input: Any) {
// بعد فحص 'is'، المُترجِم يحوّل ذكيًا
if (input is String) {
println(input.length) // تحويل ذكي إلى String
}
// بعد فحص '!is'
if (input !is String) return
println(input.length) // تحويل ذكي إلى String
}
// تحويل ذكي مع when
fun describe(obj: Any): String = when (obj) {
is String -> "String: ${obj.length} chars" // تحويل ذكي
is Int -> "Int: ${obj.dec()}" // تحويل ذكي
is List<*> -> "List: ${obj.size} items" // تحويل ذكي
else -> "Unknown"
}
(2) قيود التحويل الذكي
// تحذير: التحويل الذكي قد لا يعمل مع var أو getters مخصصة
var obj: Any = "Hello"
if (obj is String) {
// obj قد يتغير بين الفحص والاستخدام
// println(obj.length) // تحذير في بعض الحالات
}
// نهج آمن: استخدم val محلي
val safeObj = obj
if (safeObj is String) {
println(safeObj.length) // موافق - مضمون الاستقرار
}
(3) تحويل جافا مقابل التحويل الذكي في كوتلن
| البعد | جافا | كوتلن |
|---|---|---|
| البنية | (Type)obj |
تلقائي |
| الأمان | قد يسبب CPE في وقت التشغيل | ضمان في وقت الترجمة |
- حجم الكود | فحص + تحويل (خطوتان) | فحص = تحويل (خطوة واحدة) | | أمان الفارغ | قد يسبب NPE | يعالج الفارغ تلقائيًا |
5. تحويلات الأنواع الصريحة
كوتلن لا يدعم تحويلات التضييق الضمنية (مثلاً Long → Int) — جميع التحويلات يجب استدعاؤها صراحةً.
(1) دوال التحويل
val longVal: Long = 42L
val intVal: Int = longVal.toInt() // تضييق صريح
val doubleVal: Double = 3.99
val intFromDouble: Int = doubleVal.toInt() // اقتطاع: 3
// سلسلة إلى رقم
val parsed: Int = "42".toInt()
val parsedDouble: Double = "3.14".toDouble()
// رقم إلى سلسلة
val strVal: String = 42.toString()
(2) لماذا لا توجد تحويلات ضمنية؟
// هذا سيفقد بيانات بصمت - كوتلن يمنعه
val longValue: Long = 2_147_483_648L // يتجاوز Int.MAX_VALUE
// val intValue: Int = longValue // خطأ ترجمة
// يجب أن تكون صريحًا بشأن فقدان البيانات المحتمل
val intValue: Int = longValue.toInt() // موافق لكن قد يفيض
| نوع التحويل | جافا | كوتلن |
|---|---|---|
| توسيع (Int→Long) | ضمني | صريح toLong() |
| تضييق (Long→Int) | ضمني (مخاطر) | صريح toInt() |
| سلسلة→رقم | Integer.parseInt() |
"42".toInt() |
| رقم→سلسلة | "" + 42 |
42.toString() |
6. ثلاثة أنواع عليا
(1) مخطط هرم الأنواع
classDiagram
Any --> String
Any --> Int
Any --> Order
Any --> Unit
Any --> Nothing
class Any {
+equals()
+hashCode()
+toString()
}
class Unit {
<<singleton>>
}
class Nothing {
<<never returns>>
}
(2) Any: والد جميع الأنواع غير الفارغة
// Any هو جذر هرم أنواع كوتلن (مثل Object في جافا)
val obj: Any = "Hello"
val obj2: Any = 42
val obj3: Any = Order("ORD-001", 299.99, "CONFIRMED")
// Any لديه 3 طرق فقط: equals، hashCode، toString
// استخدم Any? للنوع الجذر الفارغ
(3) Unit: "لا قيمة إرجاع" مُنمَّط
// Unit مثل void في جافا، لكنه نوع حقيقي
fun logOrder(order: Order): Unit { // إرجاع Unit اختياري
println("Processing ${order.id}")
}
fun logOrder2(order: Order) { // مكافئ - Unit مستدل
println("Processing ${order.id}")
}
// Unit هو singleton - يمكن استخدامه كقيمة
val unitValue: Unit = Unit
(4) Nothing: لا يصل أبدًا
// Nothing يعني "هذا الكود لا يعود أبدًا"
fun fail(message: String): Nothing {
throw IllegalStateException(message)
}
// مفيد لعامل Elvis مع الاستثناءات
val customer = order.customer ?: fail("Customer required")
// الحلقة اللانهائية أيضًا تُرجع Nothing
fun infiniteLoop(): Nothing {
while (true) { /* لا يعود أبدًا */ }
}
(5) مقارنة ثلاثة أنواع عليا
| النوع | المعنى | حالة الاستخدام | المقابل في جافا |
|---|---|---|---|
Any |
والد جميع الأنواع غير الفارغة | مراجع عامة | Object |
Unit |
لا قيمة إرجاع ذات معنى | دوال التأثير الجانبي | void |
Nothing |
لا يصل النهاية أبدًا | رمي الاستثناءات / الحلقات اللانهائية | لا يوجد |
7. أسماء مستعارة للأنواع (typealias)
// تبسيط تواقيع الأنواع المعقدة
typealias OrderMap = Map<String, List<Order>>
typealias OrderProcessor = (List<Order>) -> List<String>
typealias USD = Double
// الاستخدام
val ordersByCustomer: OrderMap = mapOf(
"Alice" to listOf(Order("ORD-001", 299.99, "CONFIRMED"))
)
typealias Predicate<T> = (T) -> Boolean
val isHighValue: Predicate<Order> = { it.total > 10_000 }
// typealias لا ينشئ أنواعًا جديدة - مجرد اسم مستعار
val map: OrderMap = ordersByCustomer // نفس النوع
8. مثال كامل: معالجة آمنة الأنواع في OrderProcessor
// ============================================
// OrderProcessor - معالجة أحداث آمنة الأنواع
// الميزة: معالجة أحداث الطلب مع تحويل ذكي
// ============================================
typealias OrderId = String
typealias USD = Double
sealed class OrderEvent {
data class Created(val orderId: OrderId, val total: USD) : OrderEvent()
data class Paid(val orderId: OrderId, val amount: USD) : OrderEvent()
data class Shipped(val orderId: OrderId, val trackingCode: String) : OrderEvent()
data class Cancelled(val orderId: OrderId, val reason: String) : OrderEvent()
}
fun handleEvent(event: OrderEvent): String = when (event) {
is OrderEvent.Created -> {
// تحويل ذكي: event.orderId و event.total متاحان
val threshold = 10_000
val priority = if (event.total > threshold) "VIP" else "STANDARD"
"Order ${event.orderId} created (\$${event.total} USD) -> $priority"
}
is OrderEvent.Paid -> {
val tax = event.amount * 0.08
"Order ${event.orderId} paid: \$${event.amount} USD (tax: \$$tax USD)"
}
is OrderEvent.Shipped ->
"Order ${event.orderId} shipped: ${event.trackingCode}"
is OrderEvent.Cancelled ->
"Order ${event.orderId} cancelled: ${event.reason}"
}
fun main() {
val events: List<OrderEvent> = listOf(
OrderEvent.Created("ORD-001", 299.99),
OrderEvent.Created("ORD-002", 15_000.00),
OrderEvent.Paid("ORD-001", 299.99),
OrderEvent.Shipped("ORD-001", "TRK-ABC123"),
OrderEvent.Cancelled("ORD-003", "Customer request")
)
events.forEach { event ->
val result = handleEvent(event)
println(result)
}
// فحص النوع مع التحويل الذكي
val anyEvent: Any = events[0]
if (anyEvent is OrderEvent.Created) {
println("\nSmart cast works: ${anyEvent.orderId} costs \$${anyEvent.total} USD")
}
}
المخرجات:
Order ORD-001 created ($299.99 USD) -> STANDARD
Order ORD-002 created ($15000.0 USD) -> VIP
Order ORD-001 paid: $299.99 USD (tax: $23.999200000000002 USD)
Order ORD-001 shipped: TRK-ABC123
Order ORD-003 cancelled: Customer request
Smart cast works: ORD-001 costs $299.99 USD
9. أمثلة عملية سريعة
▶ مثال: val و var مع الاستدلال التلقائي
// استدلال النوع: المتغيرات لا تحتاج لتعليقات نوع صريحة
val orderId = "ORD-001" // String
val total = 299.99 // Double
val isPaid = true // Boolean
// var مع تغيير القيمة
var status = "PENDING"
status = "CONFIRMED"
// الأنواع الصريحة عند الحاجة
val explicitInt: Int = 42
val explicitDouble: Double = 3.14
val explicitString: String = "Hello"
println("orderId: $orderId (${orderId::class.simpleName})")
println("total: $total (${total::class.simpleName})")
println("status: $status")
**المخرجات:
orderId: ORD-001 (String)
total: 299.99 (Double)
status: CONFIRMED
▶ مثال: أنواع الأعداد الأساسية
// أنواع الأعداد الصحيحة
val byteVal: Byte = 127 // -128 إلى 127
val shortVal: Short = 32_000 // -32768 إلى 32767
val intVal: Int = 2_000_000_000 // الأكثر شيوعًا
val longVal: Long = 9_000_000_000L // لاحظ اللاحقة L
// أنواع الأعداد العشرية
val floatVal: Float = 3.14f // لاحظ اللاحقة f
val doubleVal: Double = 3.14159265358979
// التحويلات الصريحة
val intFromDouble = doubleVal.toInt() // 3 (اقتطاع)
val doubleFromInt = intVal.toDouble() // 2000000000.0
// Underscores في القيم الرقمية (للقراءة)
val million = 1_000_000
val pi = 3.14159_26535_89793
println("intFromDouble: $intFromDouble")
println("doubleFromInt: $doubleFromInt")
println("million: $million")
**المخرجات:
intFromDouble: 3
doubleFromInt: 2.0E9
million: 1000000
▶ مثال: String مع طرق شائعة
val order = "ORD-001"
// الطول، الوصول، القطع
println("Length: ${order.length}")
println("First 3: ${order.substring(0, 3)}")
println("Contains 001: ${order.contains("001")}")
// القوالب والتعبيرات
val total = 299.99
val msg = "Order $order: \$$total USD"
println(msg)
// متعدد الأسطر مع trimMargin
val report = """
|Order: $order
|Total: $total
|Status: PENDING
""".trimMargin()
println(report)
// مقارنة السلاسل
val a = "Alice"
val b = "alice"
println("Equal: ${a == b}") // false
println("Equal ignore case: ${a.equals(b, ignoreCase = true)}") // true
**المخرجات:
Length: 7
First 3: ORD
Contains 001: true
Order: ORD-001: $299.99 USD
Order: ORD-001
Total: 299.99
Status: PENDING
Equal: false
Equal ignore case: true
▶ مثال: Any و Unit و Nothing
// Any: النوع الجذر — كل الأنواع الأخرى ترث منه
fun describe(value: Any): String = "${value::class.simpleName}: $value"
println(describe(42))
println(describe("Hello"))
println(describe(listOf(1, 2, 3)))
// Unit: مثل void — نوع له قيمة واحدة فقط ()
fun logMessage(msg: String): Unit {
println("[LOG] $msg")
}
logMessage("تم معالجة الطلب")
// Nothing: دالة "لا تعود أبدًا"
fun failWith(message: String): Nothing {
throw IllegalStateException(message)
}
val x: Int = try {
"not a number".toInt()
} catch (e: NumberFormatException) {
failWith("Invalid input: $e")
}
**المخرجات:
Int: 42
String: Hello
List: [1, 2, 3]
[LOG] تم معالجة الطلب
Exception: Invalid input: ...
▶ مثال: تحويلات النوع الصريحة
// لا تحويل ضمني بين الأعداد — يجب استدعاء .toXxx صراحةً
val intVal: Int = 42
val longVal: Long = intVal.toLong()
val doubleVal: Double = intVal.toDouble()
val stringVal: String = intVal.toString()
// السلسلة إلى رقم
val parsed = "123".toInt()
val parsedDouble = "3.14".toDouble()
// نوع إلى نوع آمن
val maybeInt: Any = "123"
val safeInt = (maybeInt as? Int) ?: 0 // 0 إذا لم يكن Int
val unsafeInt = maybeInt as Int // يلقي ClassCastException إذا فشل
println("long: $longVal")
println("double: $doubleVal")
println("string: $stringVal")
println("parsed: $parsed (${parsed::class.simpleName})")
println("safeInt: $safeInt (since maybeInt is String)")
**المخرجات:
long: 42
double: 42.0
string: 42
parsed: 123 (Int)
safeInt: 0 (since maybeInt is String)
▶ مثال: typealias للأسماء المستعارة
// تعريف أسماء مستعارة للأنواع المعقدة
typealias OrderId = String
typealias OrderMap = Map<OrderId, Double>
typealias OrderHandler = (OrderId, Double) -> String
// الاستخدام
val prices: OrderMap = mapOf(
"ORD-001" to 299.99,
"ORD-002" to 1500.00
)
// دالة تأخذ اسم مستعار
val formatPrice: OrderHandler = { id, price ->
"$id: \$$price USD"
}
// استدعاء
prices.forEach { (id, price) ->
println(formatPrice(id, price))
}
**المخرجات:
ORD-001: $299.99 USD
ORD-002: $1500.0 USD
▶ مثال: late و lazy
// late: تهيئة متأخرة قابلة للتغيير
lateinit var config: String
fun initializeConfig() {
config = "production"
}
fun main() {
initializeConfig()
println("Config: $config")
}
// lazy: تهيئة عند الوصول الأول (مفيدة للحسابات المكلفة)
val expensiveValue: String by lazy {
println("Computing expensive value...")
"result-${System.currentTimeMillis()}"
}
println("Before access")
println("Value: $expensiveValue") // يتم التهيئة هنا
println("Value: $expensiveValue") // يُعاد نفس القيمة بدون إعادة التهيئة
**المخرجات:
Config: production
Before access
Computing expensive value...
Value: result-1700000000000
Value: result-1700000000000
❓ أسئلة شائعة
Int في كوتلن هو نفس int في جافا؟Int في كوتلن يُترجَل إلى int في جافا (نوع أولي)، ويُغلَّف تلقائيًا في Integer عند الحاجة لكائن. المطورون لا يحتاجون للقلق بشأن هذا الفرق.var وقد يُعدَّل بشكل متزامن، أو لديه getter مخصص، لا يمكن للمُترجِم ضمان أنواع متسقة بين الفحص والاستخدام — التحويل الذكي لن يُطبَّق.Nothing؟typealias نوعًا جديدًا؟typealias هو مجرد اسم مستعار للنوع — المُترجِم يستبدله بالنوع الأصلي. لأغلفة آمنة الأنواع حقًا، استخدم inline class / value class.Long→Int) تفقد بيانات بصمت وهي مصدر شائع للأخطاء. التحويلات الصريحة تجعل المطورين مدركين لفقدان البيانات المحتمل.Any وAny?؟Any هو والد جميع الأنواع غير الفارغة؛ Any? هو والد جميع الأنواع (بما فيها الفارغة). Any? = Any | null هو القمة الحقيقية لنظام أنواع كوتلن.📖 ملخص
- الأنواع الأساسية في كوتلن هي كائنات في الكود لكن المُترجِم يحسّنها إلى أنواع أولية على JVM
- التحويل الذكي يحوّل تلقائيًا بعد فحوصات
is، مما يقضي على التحويلات النوعية القسرية في جافا - جميع تحويلات الأنواع يجب أن تكون صريحة (
toInt()/toDouble())، بدون تضييق ضمني Anyهو جذر الأنواع غير الفارغة،Unitهو النسخة المنمطة من void،Nothingيعني لا يعود أبدًاtypealiasيوفر أسماء مستعارة مقروءة للأنواع المعقدة دون إنشاء أنواع جديدة- الشرطات السفلية الرقمية
1_000_000تحسن قراءة الأرقام الكبيرة
📝 تمارين
- أساسي (⭐): أعلن متغيرات من أنواع مختلفة (
Int،Double،Boolean،String) واطبع أنواعها::class. تلميح: استخدمval x = 42; println(x::class) - متوسط (⭐⭐): اكتب دالة تأخذ
Any، تستخدم التحويل الذكي لمعالجةString(اطبع الطول) وInt(اطبع المربع). تلميح:is String/is Int - تحدي (⭐⭐⭐): استخدم
typealiasوالفئات المغلقة لتصميم نظام أنواع أحداث طلب، معwhenيعالج جميع أنواع الأحداث بشكل شامل. تلميح: راجع المثال الكامل في القسم 8