Kotlin: شرح الفئات والكائنات في كوتلن
آخر تحديث: 2026-08-26
فلسفة تصميم الفئات في كوتلن: المنشئ الأساسي + الخصائص في سطر واحد، كتل init تحل محل جسم المنشئ في جافا — تشارلي يُعرّف فئة Order في 3 أسطر أقوى من 30 سطر مكافئة في جافا.
1. ما ستتعلمه
- المنشئ الأساسي + الخصائص في سطر واحد
- كتل
init: منطق التحقق - الرؤية:
public/internal/protected/private - التهيئة المؤجلة:
lateinitوby lazy - تشارلي عمليًا: تصميم فئات مجال Order / OrderItem / Customer
2. قصة مهندس معمارية حقيقية
(1) مشكلة: جحيم المنشآت في جافا
في جافا، فئة Order الخاصة بتشارلي احتاجت 3 منشآت (بدون وسائط، الحقول المطلوبة، جميع الحقول)، بالإضافة إلى getters/setters/منطق التحقق — 80+ سطر لفئة مجال واحدة.
(2) حل المنشئ الأساسي في كوتلن
KOTLIN
// جافا: 80+ سطر لفئة مجال واحدة
// كوتلن: 3 أسطر مع التحقق
class Order(val id: String, var status: String, val total: BigDecimal) {
init { require(total >= BigDecimal.ZERO) { "Total must be non-negative" } }
}
المنشئ الأساسي + الخصائص + كتلة init مجتمعة — 80 سطر جافا → 3 أسطر كوتلن.
3. المنشئ الأساسي والخصائص
(1) المنشئ الأساسي في سطر واحد
KOTLIN
// المنشئ الأساسي مع الخصائص في سطر واحد
class Order(val id: String, var status: String, val total: Double)
// المكافئ في جافا سيكون 20+ سطر
(2) معاملات المنشئ مقابل الخصائص
KOTLIN
// val/var في المنشئ = خصائص
class Order(val id: String, // خاصية للقراءة فقط
var status: String, // خاصية قابلة للتغيير
total: Double) // مجرد معامل منشئ، ليست خاصية
// الاستخدام
val order = Order("ORD-001", "PENDING", 299.99)
println(order.id) // موافق - خاصية val
order.status = "PAID" // موافق - خاصية var
// order.total // خطأ - ليست خاصية
(3) المنشئ الأساسي مقابل المنشئ الثانوي
KOTLIN
class Order(val id: String, var status: String, val total: Double) {
// المنشئ الثانوي يجب أن يفوّض للأساسي
constructor(id: String) : this(id, "PENDING", 0.0)
// منشئ ثانوي آخر
constructor(id: String, total: Double) : this(id, "PENDING", total)
}
val order1 = Order("ORD-001") // ثانوي
val order2 = Order("ORD-002", 299.99) // ثانوي
val order3 = Order("ORD-003", "CONFIRMED", 1_500.00) // أساسي
(4) مقارنة المنشآت
| البعد | جافا | كوتلن |
|---|---|---|
| تعريف الخصائص + المنشئ | منفصلان | المنشئ الأساسي في سطر واحد |
| منشآت متعددة | تعريفات مستقلة | الثانوي يجب أن يفوّض للأساسي |
- معامل إلى خاصية | تعيين يدوي |
val/varتلقائي | | القيم الافتراضية | تحميل الطرق | المعلمات الافتراضية |
4. كتلة init
كتلة init تعمل فورًا بعد المنشئ الأساسي — تُستخدم للتحقق ومنطق التهيئة.
(1) الاستخدام الأساسي
KOTLIN
class Order(val id: String, var status: String, val total: Double) {
init {
require(total >= 0.0) { "Total must be non-negative, got $total" }
require(id.startsWith("ORD-")) { "Order ID must start with ORD-" }
}
// كتل init متعددة تنفذ بالترتيب
init {
println("Order $id created with total \$$total USD")
}
}
(2) ترتيب تنفيذ كتلة init
KOTLIN
class Example {
val a = println("1: property initialization")
init {
println("2: first init block")
}
val b = println("3: property initialization")
init {
println("4: second init block")
}
}
// المخرجات: 1, 2, 3, 4 (ترتيب الإعلان)
5. مُعدِّلات الرؤية
(1) أربع مستويات الرؤية
KOTLIN
class OrderProcessor {
// public (افتراضي): مرئي في كل مكان
fun process(order: Order) { ... }
// private: مرئي داخل هذه الفئة فقط
private fun validate(order: Order) { ... }
// protected: مرئي في هذه الفئة والفئات الفرعية
protected fun calculateTax(order: Order) { ... }
// internal: مرئي داخل نفس الوحدة
internal fun report() { ... }
}
(2) جدول مقارنة الرؤية
| المُعدِّل | داخل الفئة | فئة فرعية | نفس الوحدة | عام |
|---|---|---|---|---|
public |
✅ | ✅ | ✅ | ✅ |
internal |
✅ | ✅ | ✅ | ❌ |
protected |
✅ | ✅ | ❌ | ❌ |
private |
✅ | ❌ | ❌ | ❌ |
(3) اختلافات الرؤية بين جافا وكوتلن
| البعد | جافا | كوتلن |
|---|---|---|
| الرؤية الافتراضية | package-private | public |
| رؤية الوحدة | لا توجد | internal |
| رؤية الحزمة | package-private | لا توجد (استخدم internal بدلاً) |
| إعلانات المستوى الأعلى | public فقط | public / internal / private |
6. التهيئة المؤجلة
(1) lateinit var
KOTLIN
class OrderService {
// lateinit: وعد بالتهيئة قبل الاستخدام
lateinit var repository: OrderRepository
fun init(repo: OrderRepository) {
repository = repo
}
fun process(order: Order) {
// الوصول قبل التهيئة يرمي UninitializedPropertyAccessException
repository.save(order)
}
}
(2) by lazy
KOTLIN
class OrderProcessor {
// lazy: آمن للخيوط، يُهيَّأ عند أول وصول
val cache: OrderCache by lazy {
println("Initializing cache...")
OrderCache(maxSize = 10_000)
}
// lazy مع وضع قفل مخصص
val heavyResource by lazy(LazyThreadSafetyMode.PUBLICATION) {
loadHeavyResource()
}
}
(3) مقارنة lateinit مقابل by lazy
| البعد | lateinit |
by lazy |
|---|---|---|
| النوع | var (قابل للتغيير) |
val (للقراءة فقط) |
| توقيت التهيئة | تعيين يدوي | عند أول وصول |
| أمان الخيوط | لا | نعم (افتراضي) |
| القابلية للفراغ | مُعلن غير فارغ | مُعلن غير فارغ |
| الوصول قبل التهيئة | استثناء وقت التشغيل | لن يحدث |
| حالة الاستخدام | حقن إطار DI | خصائص حسابية مكلفة |
7. مخطط علاقات الفئات
classDiagram
class Order {
+val id: String
+var status: String
+val total: Double
+val items: List~OrderItem~
+fun addItem(item: OrderItem)
}
class OrderItem {
+val sku: String
+val quantity: Int
+val unitPrice: Double
+fun subtotal: Double
}
class Customer {
+val id: String
+val name: String
+val email: String?
+val address: Address?
}
class Address {
+val street: String
+val city: String
+val country: String
}
Order --> OrderItem : contains
Order --> Customer : belongs to
Customer --> Address : has
8. مثال كامل: نموذج مجال OrderProcessor
KOTLIN
// ============================================
// OrderProcessor - نموذج المجال
// الميزة: Order، OrderItem، Customer مع تحقق init
// ============================================
import java.math.BigDecimal
import java.math.RoundingMode
class Address(val street: String, val city: String, val country: String) {
override fun toString(): String = "$street, $city, $country"
}
class Customer(val id: String, val name: String, val email: String?) {
var address: Address? = null
fun getDisplayEmail(): String = email ?: "no-email"
override fun toString(): String = "Customer($id, $name, ${getDisplayEmail()})"
}
class OrderItem(val sku: String, val quantity: Int, val unitPrice: BigDecimal) {
init {
require(quantity > 0) { "Quantity must be positive, got $quantity" }
require(unitPrice >= BigDecimal.ZERO) { "Price must be non-negative" }
}
val subtotal: BigDecimal
get() = unitPrice.multiply(BigDecimal(quantity)).setScale(2, RoundingMode.HALF_UP)
}
class Order(
val id: String,
var status: String,
private val _items: MutableList<OrderItem> = mutableListOf()
) {
init {
require(id.startsWith("ORD-")) { "Order ID must start with ORD-" }
}
val items: List<OrderItem> get() = _items.toList()
val total: BigDecimal
get() = _items.fold(BigDecimal.ZERO) { acc, item -> acc.add(item.subtotal) }
fun addItem(item: OrderItem) {
_items.add(item)
}
val itemCount: Int get() = _items.size
// ضريبة محسوبة مؤجلة
val tax by lazy {
total.multiply(BigDecimal("0.08")).setScale(2, RoundingMode.HALF_UP)
}
override fun toString(): String = "Order($id, $status, ${itemCount} items, \$$total USD)"
}
fun main() {
val customer = Customer("CUST-001", "Alice", "alice@example.com").also {
it.address = Address("123 Main St", "New York", "US")
}
println(customer)
println("Address: ${customer.address}")
val order = Order("ORD-001", "PENDING")
order.addItem(OrderItem("SKU-WIDGET", 3, BigDecimal("9.99")))
order.addItem(OrderItem("SKU-GADGET", 1, BigDecimal("149.99")))
order.addItem(OrderItem("SKU-DOOHICKEY", 5, BigDecimal("4.50")))
println(order)
println("Subtotal: \$$${order.total} USD")
println("Tax: \$$${order.tax} USD")
println("Grand Total: \$$${order.total.add(order.tax)} USD")
order.status = "CONFIRMED"
println("Status updated: ${order.status}")
}
المخرجات:
TEXT
📖 للعرض فقط
Customer(CUST-001, Alice, alice@example.com)
Address: 123 Main St, New York, US
Order(ORD-001, PENDING, 3 items, $199.42 USD)
Subtotal: $$199.42 USD
Tax: $$15.95 USD
Grand Total: $$215.37 USD
Status updated: CONFIRMED
9. أمثلة عملية سريعة
▶ مثال: فئة بسيطة مع خصائص
KOTLIN
// تعريف فئة مع خصائص للقراءة فقط (val) وكتابة (var)
class Person(val name: String, var age: Int) {
// كتلة init للتحقق من الصحة
init {
require(age >= 0) { "Age cannot be negative: $age" }
}
// خاصية محسوبة
val isAdult: Boolean
get() = age >= 18
// طريقة
fun greet(): String = "Hello, my name is $name and I am $age years old"
// طريقة مع قيمة افتراضية
fun haveBirthday(years: Int = 1) {
age += years
println("$name is now $age years old")
}
}
val alice = Person("Alice", 30)
println(alice.greet())
println("Is adult: ${alice.isAdult}")
alice.haveBirthday()
println(alice.greet())
// try { Person("Bob", -5) } catch (e: Exception) { println("Error: ${e.message}") }
**المخرجات:
TEXT
📖 للعرض فقط
Hello, my name is Alice and I am 30 years old
Is adult: true
Alice is now 31 years old
Hello, my name is Alice and I am 31 years old
▶ مثال: المُنشآت (Constructors)
KOTLIN
class Order(val id: String, val total: Double) {
var status: String = "PENDING"
private var lastModified: Long = System.currentTimeMillis()
// كتلة init ثانوية
init {
require(total >= 0) { "Total cannot be negative" }
println("Order $id created with total $total")
}
// منشأ ثانوي: يفوض للمنشئ الأساسي
constructor(id: String) : this(id, 0.0) {
println("Created empty order $id")
}
// منشأ مع قيم افتراضية
constructor() : this("AUTO-${System.currentTimeMillis()}")
fun confirm() {
status = "CONFIRMED"
lastModified = System.currentTimeMillis()
}
fun info() = "Order $id: $status (\$$total USD)"
}
val o1 = Order("ORD-001", 299.99)
println(o1.info())
o1.confirm()
println(o1.info())
val o2 = Order("ORD-002")
println(o2.info())
val o3 = Order()
println(o3.info())
**المخرجات:
TEXT
📖 للعرض فقط
Order ORD-001 created with total 299.99
Order ORD-001: PENDING ($299.99 USD)
Order ORD-001: CONFIRMED ($299.99 USD)
Order ORD-002 created with total 0.0
Created empty order ORD-002
Order ORD-002: PENDING ($0.0 USD)
Order AUTO-1700000000000 created with total 0.0
Order AUTO-1700000000000: PENDING ($0.0 USD)
▶ مثال: الوراثة
KOTLIN
// فئة أساسية مفتوحة للوراثة
open class Employee(val name: String, val baseSalary: Double) {
open fun calculateBonus(): Double = baseSalary * 0.1
fun describe(): String = "$name earns \$${baseSalary + calculateBonus()} USD"
}
class Manager(name: String, baseSalary: Double, val teamSize: Int) : Employee(name, baseSalary) {
override fun calculateBonus(): Double {
return baseSalary * 0.1 + teamSize * 1000
}
}
class Developer(name: String, baseSalary: Double, val projects: Int) : Employee(name, baseSalary) {
override fun calculateBonus(): Double {
return baseSalary * 0.1 + projects * 500
}
}
val alice = Employee("Alice", 50000.0)
val bob = Manager("Bob", 70000.0, 5)
val charlie = Developer("Charlie", 60000.0, 3)
println(alice.describe())
println(bob.describe())
println(charlie.describe())
**المخرجات:
TEXT
📖 للعرض فقط
Alice earns $55000.0 USD
Bob earns $72000.0 USD
Charlie earns $64500.0 USD
▶ مثال: الواجهات (Interfaces)
KOTLIN
// واجهة تحدد عقد
interface PaymentProcessor {
fun processPayment(amount: Double): Boolean
fun refund(transactionId: String): Boolean
}
// فئة تنفذ الواجهة
class CreditCardProcessor(private val apiKey: String) : PaymentProcessor {
override fun processPayment(amount: Double): Boolean {
println("Processing credit card payment of \$$amount USD")
return true
}
override fun refund(transactionId: String): Boolean {
println("Refunding transaction $transactionId")
return true
}
}
class PayPalProcessor(private val clientId: String) : PaymentProcessor {
override fun processPayment(amount: Double): Boolean {
println("Processing PayPal payment of \$$amount USD")
return true
}
override fun refund(transactionId: String): Boolean {
println("PayPal refund for $transactionId")
return true
}
}
// استخدام الواجهة
fun checkout(processor: PaymentProcessor, amount: Double): Boolean {
return processor.processPayment(amount)
}
val cc = CreditCardProcessor("api-key-123")
val pp = PayPalProcessor("client-id-456")
checkout(cc, 299.99)
checkout(pp, 1500.00)
**المخرجات:
TEXT
📖 للعرض فقط
Processing credit card payment of $299.99 USD
Processing PayPal payment of $1500.0 USD
▶ مثال: فئات البيانات (Data Classes)
KOTLIN
// data class - توليد تلقائي لـ equals/hashCode/toString/copy
data class Product(val id: String, val name: String, val price: Double)
// إنشاء ونسخ
val p1 = Product("P001", "Laptop", 1299.99)
val p2 = p1.copy(price = 1199.99) // نسخ مع تغيير حقل واحد
println("p1: $p1")
println("p2: $p2")
println("Equal: ${p1 == p2}") // false (price مختلف)
// equals/hashCode
val p3 = Product("P001", "Laptop", 1299.99)
println("p1 == p3: ${p1 == p3}") // true
println("Hash p1 == Hash p3: ${p1.hashCode() == p3.hashCode()}")
// النسخ التدميري
val (id, name, price) = p1
println("Destructured: id=$id, name=$name, price=\$$price")
// قائمة بـ data class
val products = listOf(
Product("P001", "Laptop", 1299.99),
Product("P002", "Mouse", 29.99),
Product("P003", "Keyboard", 79.99)
)
val total = products.sumOf { it.price }
println("Total: \$$total USD")
**المخرجات:
TEXT
📖 للعرض فقط
p1: Product(id=P001, name=Laptop, price=1299.99)
p2: Product(id=P001, name=Laptop, price=1199.99)
Equal: false
p1 == p3: true
Hash p1 == Hash p3: true
Destructured: id=P001, name=Laptop, price=$1299.99
Total: $1409.97 USD
▶ مثال: Companion Objects و Static-like
KOTLIN
class Database private constructor(private val url: String) {
// companion object مثل static في جافا
companion object {
private val instances = mutableMapOf<String, Database>()
fun connect(url: String): Database {
return instances.getOrPut(url) { Database(url) }
}
const val MAX_CONNECTIONS = 100
}
fun query(sql: String): String = "[$url] Executing: $sql"
}
// استخدام
val db1 = Database.connect("localhost:5432")
val db2 = Database.connect("localhost:5432") // نفس المثيل (Singleton)
println("Same instance: ${db1 === db2}")
println(db1.query("SELECT * FROM users"))
println("Max connections: ${Database.MAX_CONNECTIONS}")
**المخرجات:
TEXT
📖 للعرض فقط
Same instance: true
[localhost:5432] Executing: SELECT * FROM users
Max connections: 100
▶ مثال: Getters و Setters المخصصة
KOTLIN
class Temperature {
var celsius: Double = 0.0
set(value) {
// التحقق من الصحة عند التعيين
require(value >= -273.15) { "Temperature cannot be below absolute zero" }
field = value
}
// خاصية محسوبة من أخرى
val fahrenheit: Double
get() = celsius * 9.0 / 5.0 + 32.0
val kelvin: Double
get() = celsius + 273.15
}
val temp = Temperature()
temp.celsius = 25.0
println("${temp.celsius}°C = ${temp.fahrenheit}°F = ${temp.kelvin}K")
temp.celsius = 100.0
println("${temp.celsius}°C = ${temp.fahrenheit}°F")
// try { temp.celsius = -300.0 } catch (e: Exception) { println("Error: ${e.message}") }
**المخرجات:
TEXT
📖 للعرض فقط
25.0°C = 77.0°F = 298.15K
100.0°C = 212.0°F
❓ أسئلة شائعة
س هل يمكن أن يتعايش المنشئ الأساسي والثانوي؟
ج نعم، لكن المنشآت الثانوية يجب أن تفوّض للأساسي باستخدام
this(...). المعلمات الافتراضية مُفضَّلة على المنشآت الثانوية.س هل يمكنني الحصول على كتل init متعددة؟
ج نعم، كتل init المتعددة تنفذ بترتيب الإعلان. يُوصى بدمجها في واحدة لتجنب الارتباك.
س هل يمكن استخدام lateinit للأنواع الأولية؟
ج لا. lateinit يعمل فقط مع الأنواع غير الأولية (أنواع الكائنات)، لأن الأولية لها قيم افتراضية. استخدم
by lazy أو أنواع فارغة + قيم افتراضية لـ Int/Double إلخ.س هل تهيئة by lazy آمنة للخيوط؟
ج نعم افتراضيًا (وضع SYNCHRONIZED). إذا كنت متأكدًا من الوصول بخيط واحد، استخدم
LazyThreadSafetyMode.NONE لأداء أفضل.س كيف تعمل رؤية internal في مشاريع Maven/Gradle متعددة الوحدات؟
ج internal يقيّد الرؤية داخل نفس وحدة Gradle أو وحدة Maven. وحدات مختلفة — حتى داخل نفس المشروع — لا يمكنها الوصول لأعضاء internal.
س لماذا لا يملك كوتلن package-private مثل جافا؟
ج كوتلن يستخدم
internal (رؤية على مستوى الوحدة) بدلاً من package-private. الرؤية على مستوى الحزمة يُساء استخدامها غالبًا في جافا؛ الرؤية على مستوى الوحدة تناسب هياكل المشاريع الحديثة بشكل أفضل.📖 ملخص
- المنشئ الأساسي +
val/varيُعرّف الخصائص في سطر واحد، مما يقضي على الكود الزائد getter/setter في جافا - كتل
initتحل محل جسم المنشئ في جافا للتحقق والتهيئة - أربع مستويات رؤية:
public(افتراضي) /internal/protected/private lateinitيناسب حقن إطار DI؛by lazyيناسب الحساب المؤجل- حقول دعم الخصائص تستخدم نمط
private var+ عرض للقراءة فقط عام للنسخ الدفاعي - المعلمات الافتراضية مُفضَّلة على المنشآت الثانوية للحفاظ على المنشآت نظيفة
📝 تمارين
- أساسي (⭐): عرّف فئة
Product(id: String, name: String, price: Double) مع كتلة init تتحقق منprice >= 0. تلميح:require(price >= 0) - متوسط (⭐⭐): صمم فئة
ServiceتستخدمlateinitلحقنRepositoryوby lazyلتهيئة ذاكرة تخزين مؤقت. تلميح:lateinit+by lazy - تحدي (⭐⭐⭐): نفّذ نموذج مجال طلب كامل مع Order/OrderItem/Customer، جميع التحققات في كتل init، والنسخ الدفاعي للعناصر. تلميح: راجع المثال الكامل في القسم 8