Kotlin: دوال كوتلن وتعبيرات Lambda
آخر تحديث: 2026-08-26
الدوال هي مواطنات من الدرجة الأولى في كوتلن — الدوال الرتبة الأعلى + lambdas + inline تتيح لتشارلي بناء خطوط أنابيب معالجة بيانات OrderProcessor بأسلوب وظيفي، مما يقلل الكود بنسبة 60%.
1. ما ستتعلمه
- تعريفات الدوال: المعلمات الافتراضية، الوسائط المسماة، أجسام التعبير
- الدوال الرتبة الأعلى: الدوال كمعلمات وقيم إرجاع
- بنية lambda:
{ it -> ... }واختصارit - اصطلاح lambda الخلفي:
orders.filter { ... } - الدوال المضمنة
inline: القضاء على تكلفة lambda في وقت التشغيل
2. قصة مطور حقيقية
(1) مشكلة: منطق معالجة طلبات مكرر
في فريق تشارلي، كل مطور كتب منطق التصفية/التحويل/التجميع الخاص به — 100+ سطر من الكود المكرر منتشر عبر 5 خدمات. تغيير شرط تصفية واحد يعني التغيير في 5 أماكن.
(2) الدوال الرتبة الأعلى تقضي على التكرار
// معالج طلبات عام - دالة واحدة تحل محل 5 نسخ
fun <T> processOrders(
orders: List<Order>,
filter: (Order) -> Boolean,
transform: (Order) -> T
): List<T> = orders.filter(filter).map(transform)
// الاستخدام: كل موقع استدعاء سطران بدلاً من 20
val highValueIds = processOrders(orders, { it.total > 1_000 }, { it.id })
val vipCustomers = processOrders(orders, { it.total > 10_000 }, { it.customer })
الدوال الرتبة الأعلى تجعل "ما يتغير" معلمات، لذا "ما يبقى ثابتًا" يُكتب مرة واحدة فقط.
3. تعريفات الدوال
(1) الدوال الأساسية
// جسم كتلة
fun calculateTax(total: Double, rate: Double = 0.08): Double {
return total * rate
}
// جسم تعبير
fun calculateTax(total: Double, rate: Double = 0.08) = total * rate
// إرجاع Unit (void)
fun logOrder(order: Order) {
println("Processing ${order.id}")
}
(2) المعلمات الافتراضية والوسائط المسماة
// المعلمات الافتراضية - تقضي على تحميل الطرق
fun createOrder(
id: String,
total: Double,
status: String = "PENDING",
currency: String = "USD",
taxRate: Double = 0.08
): Order = Order(id, total, status, currency)
// وسائط مسماة - استدعِ أي معلمة بالاسم
val order1 = createOrder("ORD-001", 299.99)
val order2 = createOrder("ORD-002", 1_500.00, taxRate = 0.10)
val order3 = createOrder("ORD-003", 45.50, currency = "EUR", status = "CONFIRMED")
(3) المعلمات الافتراضية مقابل تحميل الطرق في جافا
| البعد | تحميل جافا | المعلمات الافتراضية في كوتلن |
|---|---|---|
| حجم الكود | N طريقة | طريقة واحدة |
| القابلية للصيانة | تغيير N أماكن لمعامل واحد | تغيير مكان واحد |
| مرونة الاستدعاء | ترتيب موضعي فقط | وسائط مسماة، أي تركيبة |
| التوافق | جافا تستدعي مباشرة | يحتاج @JvmOverloads |
4. الدوال الرتبة الأعلى
الدوال الرتبة الأعلى تأخذ دوال كمعلمات أو تُرجع دوال — حجر الأساس للبرمجة الوظيفية في كوتلن.
(1) أنواع الدوال
// بنية نوع الدالة
val transform: (Order) -> String = { order -> order.id }
val predicate: (Order) -> Boolean = { it.total > 1_000 }
val aggregator: (Double, Order) -> Double = { acc, order -> acc + order.total }
val supplier: () -> Order = { Order("DEFAULT", 0.0, "PENDING") }
// الدالة كمعامل
fun processOrders(orders: List<Order>, transform: (Order) -> String): List<String> {
return orders.map(transform)
}
(2) الدالة كقيمة إرجاع
// إرجاع دالة
fun getDiscountStrategy(tier: String): (Double) -> Double = when (tier) {
"VIP" -> { total -> total * 0.80 } // خصم 20%
"GOLD" -> { total -> total * 0.90 } // خصم 10%
else -> { total -> total } // بدون خصم
}
val discount = getDiscountStrategy("VIP")
val finalPrice = discount(1_000.00) // 800.0
(3) دالة رتبة أعلى + سلسلة استدعاء lambda
flowchart LR
A[Orders] --> B[filter]
B --> C[map]
C --> D[sortedBy]
D --> E[fold]
E --> F[Result]
5. تعبيرات Lambda
(1) بنية lambda
// بنية كاملة
val total = orders.map({ order: Order -> order.total })
// استدلال النوع
val total = orders.map({ order -> order.total })
// كلمة it للمعامل الواحد
val total = orders.map({ it.total })
// اصطلاح lambda الخلفي
val total = orders.map { it.total }
// lambda متعددة المعاملات
val result = orders.fold(0.0) { acc, order -> acc + order.total }
(2) مستويات تبسيط بنية lambda
| المستوى | البنية | الوصف |
|---|---|---|
| كاملة | { order: Order -> order.total } |
نوع صريح |
| مبسطة 1 | { order -> order.total } |
استدلال النوع |
| مبسطة 2 | { it.total } |
معامل واحد مع it |
| lambda خلفي | orders.map { it.total } |
lambda خارج الأقواس |
(3) أنماط lambda الشائعة
// let: تحويل واستهلاك
val formatted = order.total.let { "\$$it USD" }
// also: تأثير جانبي دون تغيير القيمة
val order = createOrder("ORD-001", 299.99).also {
println("Created order: ${it.id}")
}
// apply: تهيئة الكائن
val order = Order("ORD-001", 0.0, "PENDING").apply {
// يمكن استخدامه فقط مع خصائص var
}
// run: تنفيذ كتلة وإرجاع النتيجة
val summary = order.run {
"Order $id: \$${total} USD ($status)"
}
6. اصطلاح lambda الخلفي
عندما يكون المعامل الأخير للدالة lambda، يمكن كتابته خارج الأقواس — هذه أهم سكر بنوي في كوتلن.
(1) الاستخدام الأساسي
// التعريف
fun processOrders(orders: List<Order>, callback: (Order) -> Unit) { ... }
// الاستدعاء: lambda خلفي
processOrders(orders) { order ->
println(order.id)
}
// إذا كان lambda المعامل الوحيد، احذف الأقواس
orders.forEach { println(it.id) }
(2) lambda الخلفي كأساس لـ DSLs
// هكذا تعمل DSLs
fun order(id: String, init: OrderBuilder.() -> Unit): Order {
val builder = OrderBuilder(id)
builder.init()
return builder.build()
}
// الاستخدام: lambda الخلفي يجعله يُقرأ كتهيئة
val myOrder = order("ORD-001") {
total(299.99)
status("CONFIRMED")
customer("Alice")
}
7. الدوال المضمنة
تُترجَل lambdas إلى كائنات فئات مجهولة على JVM، مما ينشئ نسخًا جديدة في كل استدعاء. inline يقضي على هذه التكلفة.
(1) أساسيات inline
// بدون inline: Lambda ينشئ كائنًا في كل استدعاء
fun process(order: Order, block: (Order) -> String): String {
return block(order)
}
// مع inline: كود Lambda يُدرج في موقع الاستدعاء
inline fun process(order: Order, block: (Order) -> String): String {
return block(order)
}
(2) مقارنة تأثير inline
// بعد الإدراج، هذا:
process(order) { it.id }
// يصبح (مفهوميًا):
run { order.id }
(3) inline مقابل noinline مقابل crossinline
| المُعدِّل | التأثير | حالة الاستخدام |
|---|---|---|
inline |
كود lambda مُدرج في موقع الاستدعاء | Lambdas المستدعاة بشكل متكرر |
noinline |
يمنع الإدراج (يبقى ككائن) | lambda يحتاج للتخزين/التمرير |
crossinline |
مُدرج لكن يمنع الإرجاع غير المحلي | lambda في سياق تنفيذ مختلف |
(4) معاملات النوع reified
// عادي: النوع يُمحى في وقت التشغيل
fun <T> isType(value: Any): Boolean = value is T // خطأ
// reified: inline يحافظ على معلومات النوع
inline fun <reified T> isType(value: Any): Boolean = value is T // موافق
isType<String>("Hello") // true
isType<Int>(42) // true
isType<Int>("42") // false
8. مثال كامل: خط أنابيب وظيفي لـ OrderProcessor
// ============================================
// OrderProcessor - خط أنابيب وظيفي
// الميزة: معالجة الطلبات بدوال رتبة أعلى
// ============================================
data class Order(val id: String, val total: Double, val status: String, val customer: String)
// معالج خط أنابيب عام
inline fun <T, R> List<T>.pipeline(
filter: (T) -> Boolean,
transform: (T) -> R,
crossinline postProcess: (R) -> Unit
): List<R> {
return this.filter(filter).map(transform).also { results ->
results.forEach(postProcess)
}
}
// مصنع استراتيجية الخصم
fun getDiscountStrategy(tier: String): (Double) -> Double = when (tier) {
"VIP" -> { total -> total * 0.80 }
"GOLD" -> { total -> total * 0.90 }
"SILVER" -> { total -> total * 0.95 }
else -> { total -> total }
}
fun main() {
val orders = listOf(
Order("ORD-001", 299.99, "CONFIRMED", "Alice"),
Order("ORD-002", 15_000.00, "CONFIRMED", "Bob"),
Order("ORD-003", 2_500.00, "PENDING", "Charlie"),
Order("ORD-004", 45.50, "CANCELLED", "Alice"),
Order("ORD-005", 8_000.00, "CONFIRMED", "Bob")
)
// خط الأنابيب: تصفية المؤكدة → تطبيق خصم VIP → تسجيل
val discount = getDiscountStrategy("VIP")
val processed = orders.pipeline(
filter = { it.status == "CONFIRMED" },
transform = { it.copy(total = discount(it.total)) },
postProcess = { println(" Processed: ${it.id} -> \$${it.total} USD") }
)
// تجميع النتائج
val totalRevenue = processed.sumOf { it.total }
val avgOrder = processed.map { it.total }.average()
println("\nRevenue: \$$totalRevenue USD | Avg: \$${"%.2f".format(avgOrder)} USD")
// تركيب وظيفي مع let/also/run
val report = orders
.filter { it.status != "CANCELLED" }
.groupBy { it.customer }
.mapValues { (_, list) ->
list.sumOf { it.total }
}
.also { println("\nRevenue by customer:") }
.map { (customer, total) -> "$customer: \$$total USD" }
.joinToString("\n ")
println(" $report")
}
المخرجات:
Processed: ORD-001 -> $239.992 USD
Processed: ORD-002 -> $12000.0 USD
Processed: ORD-005 -> $6400.0 USD
Revenue: $18639.992 USD | Avg: $6213.330666666667 USD
Revenue by customer:
Alice: $299.99 USD
Bob: $23000.0 USD
Charlie: $2500.0 USD
9. أمثلة عملية سريعة
▶ مثال: دوال أساسية
// دالة بدون قيمة مرجعة
fun greet(name: String): Unit {
println("Hello, $name!")
}
// دالة بقيمة مرجعة
fun add(a: Int, b: Int): Int {
return a + b
}
// دالة بجسم تعبير
fun multiply(a: Int, b: Int) = a * b
// معامل افتراضي
fun greetWithPrefix(name: String, prefix: String = "Mr.") {
println("$prefix $name")
}
greet("Alice")
println("Add: ${add(3, 5)}")
println("Multiply: ${multiply(4, 7)}")
greetWithPrefix("Bob")
greetWithPrefix("Charlie", "Dr.")
**المخرجات:
Hello, Alice!
Add: 8
Multiply: 28
Mr. Bob
Dr. Charlie
▶ مثال: المعاملات المسماة والاختيارية
fun createOrder(
id: String,
items: List<String>,
urgent: Boolean = false,
notes: String? = null
): String {
val status = if (urgent) "URGENT" else "NORMAL"
val noteStr = notes?.let { " [$it]" } ?: ""
return "$id ($status)$noteStr: ${items.joinToString(", ")}"
}
// معاملات موضعية
println(createOrder("ORD-001", listOf("كتاب", "قلم")))
// معاملات مسماة (ترتيب مرن)
println(createOrder(urgent = true, id = "ORD-002", items = listOf("حاسوب")))
// معامل اختياري
println(createOrder("ORD-003", listOf("هاتف"), notes = "هش"))
**المخرجات:
ORD-001 (NORMAL): كتاب, قلم
ORD-002 (URGENT): حاسوب
ORD-003 (NORMAL) [هش]: هاتف
▶ مثال: Lambda الأساسية
// lambda بسيط
val greet: (String) -> Unit = { name -> println("Hello, $name!") }
greet("Alice")
// lambda مع معامل واحد → كلمة it ضمنية
val double: (Int) -> Int = { it * 2 }
println("Double 5: ${double(5)}")
// lambda مع معاملات متعددة
val sum: (Int, Int) -> Int = { a, b -> a + b }
println("Sum 3+7: ${sum(3, 7)}")
// lambda مع نوع إرجاع صريح
val divide: (Double, Double) -> Double = { a, b ->
if (b == 0.0) 0.0 else a / b
}
println("10/2: ${divide(10.0, 2.0)}")
println("10/0: ${divide(10.0, 0.0)}")
**المخرجات:
Hello, Alice!
Double 5: 10
Sum 3+7: 10
10/2: 5.0
10/0: 0.0
▶ مثال: دوال ذات معاملات lambda
// دالة تقبل lambda كمعامل
fun operate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
return operation(a, b)
}
// تمرير lambda
val result1 = operate(10, 5, { x, y -> x + y })
println("10 + 5 = $result1")
val result2 = operate(10, 5) { x, y -> x * y } // lambda خارج الأقواس
println("10 * 5 = $result2")
// دالة مخصصة تأخذ lambda
fun List<Int>.customFilter(predicate: (Int) -> Boolean): List<Int> {
val result = mutableListOf<Int>()
for (item in this) {
if (predicate(item)) result.add(item)
}
return result
}
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val evens = numbers.customFilter { it % 2 == 0 }
val bigOnes = numbers.customFilter { it > 5 }
println("Evens: $evens")
println("Big ones: $bigOnes")
**المخرجات:
10 + 5 = 15
10 * 5 = 50
Evens: [2, 4, 6, 8, 10]
Big ones: [6, 7, 8, 9, 10]
▶ مثال: Trailing lambda و it الضمني
val names = listOf("Alice", "Bob", "Charlie", "Dave")
// forEach مع it ضمني
names.forEach { println("Hello, $it!") }
// filter مع شرط
val longNames = names.filter { it.length > 4 }
println("Long names: $longNames")
// map مع تحويل
val upper = names.map { it.uppercase() }
println("Uppercase: $upper")
// سلسلة عمليات
val result = names
.filter { it.startsWith("A") || it.startsWith("C") }
.map { "Mr. $it" }
.sorted()
println("Filtered chain: $result")
// lambda مع معاملين واسمين
val pairs = listOf(1 to "one", 2 to "two", 3 to "three")
pairs.forEach { (num, str) ->
println("$num = $str")
}
**المخرجات:
Hello, Alice!
Hello, Bob!
Hello, Charlie!
Hello, Dave!
Long names: [Alice, Charlie]
Uppercase: [ALICE, BOB, CHARLIE, DAVE]
Filtered chain: [Mr. Alice, Mr. Charlie]
1 = one
2 = two
3 = three
▶ مثال: Function references و inline
// مرجع دالة بـ ::
val numbers = listOf(-3, -1, 2, 5, -7, 8, 0)
val positives = numbers.filter { it > 0 }
val positivesViaRef = numbers.filter(::isPositive)
fun isPositive(n: Int): Boolean = n > 0
println("Positives: $positives")
println("Via reference: $positivesViaRef")
// inline function - تحسين الأداء عند استخدام lambda
inline fun measureTime(action: () -> Unit): Long {
val start = System.nanoTime()
action()
return System.nanoTime() - start
}
val elapsed = measureTime {
// محاكاة عمل ما
var sum = 0
for (i in 1..100000) sum += i
println("Sum 1-100000: $sum")
}
println("Elapsed: ${elapsed / 1_000_000} ms")
**المخرجات:
Positives: [2, 5, 8]
Via reference: [2, 5, 8]
Sum 1-100000: 705082704
Elapsed: 0 ms
▶ مثال: Higher-order functions مخصصة
// دالة repeat تنفيذ N مرة
inline fun repeat(times: Int, action: (Int) -> Unit) {
for (i in 0 until times) action(i)
}
println("---")
repeat(3) { i -> println("Iteration $i") }
// دالة use للأمان مع الموارد
inline fun <T> use(resource: T, block: (T) -> Unit) {
try {
block(resource)
} finally {
println("Cleaning up resource")
}
}
data class Connection(val name: String) {
fun execute(query: String) = println("[$name] Executing: $query")
}
use(Connection("DB-1")) { conn ->
conn.execute("SELECT *")
conn.execute("UPDATE")
}
**المخرجات:
---
Iteration 0
Iteration 1
Iteration 2
[DB-1] Executing: SELECT *
[DB-1] Executing: UPDATE
Cleaning up resource
❓ أسئلة شائعة
it كمعامل ضمني وreturn يتصرف بشكل مختلف (إرجاع return في lambda غير محلي، إرجاع return في الدالة المجهولة محلي). استخدم lambdas في معظم الحالات.it مقابل تسمية صريحة؟it عندما يكون جسم lambda 1-2 سطر والسياق واضح؛ استخدم أسماء صريحة للأجسام الأطول أو lambdas المتداخلة لتحسين القراءة.inline؟inline أفضل للأجسام الصغيرة حيث يُستدعى lambda مباشرة. إدراج الدوال الكبيرة يزيد حجم الكود. معظم دوال المكتبة القياسية الرتبة الأعلى هي inline.reified في الفئات العادية؟reified يعمل فقط على معاملات نوع الدوال inline، لأن الإدراج يسمح للمُترجِم بحفظ معلومات النوع في موقع الاستدعاء. معاملات نوع الدوال العادية تُمحى في وقت التشغيل.let/also/apply/run؟let لتحويل القيم، also للتأثيرات الجانبية، apply لتهيئة الكائنات (يُرجع this)، run لتنفيذ كتلة (يُرجع النتيجة). تذكر: apply يُرجع this، run يُرجع نتيجة lambda.📖 ملخص
- المعلمات الافتراضية + الوسائط المسماة تقضي على تحميل الطرق في جافا — دالة واحدة تتعامل مع جميع تركيبات الاستدعاء
- الدوال الرتبة الأعلى تجعل "ما يتغير" معلمات، مما يقضي على المنطق المكرر
- بنية lambda من الكامل إلى الموجز:
{ x:Type -> ... }→{ x -> ... }→{ it -> ... }→ lambda خلفي - اصطلاح lambda الخلفي هو الأساس البنوي لـ Kotlin DSLs
inlineيقضي على تكلفة تخصيص كائنات lambda؛reifiedيحافظ على معلومات النوع العامlet/also/apply/runهي دوال نطاق شائعة، لكل منها غرض مميز
📝 تمارين
- أساسي (⭐): اكتب دالة
calculateTotalبمعلمات افتراضية واستدعِها بوسائط مسماة. تلميح:fun calc(price: Double, tax: Double = 0.08) - متوسط (⭐⭐): نفّذ
processAndTransformبدوال رتبة أعلى، تقبل شرط تصفية ودالة تحويل، وتُرجع قائمة نتائج. تلميح:<T> fun List<T>.process... - تحدي (⭐⭐⭐): نفّذ دالة
filterByType<Any>ترشّح عناصر نوع محدد من قائمة أنواع مختلطة. تلميح:inline fun <reified T> List<Any>.filterByType(): List<T>