بنية البرمجة في لغة Kotlin

من موسوعة حسوب
اذهب إلى: تصفح، ابحث

تعريف الحزم (Package)

يُكتب توصيف الحزمة (package) في بداية الملف المصدريّ (source file) بالشكل الآتي:
package my.demo
import java.util.*
// ...
ولا يُشترط التوافق ما بين الحزمة (package) والمجلد الذي توجد فيه (directory)، إذ من الممكن أن تتوضع الملفات المصدريّة عشوائيًّا في نظام الملفات.

المزيد عن الحزم (packages).

تعريف الدوال (Function)

إن كانت الدالة بمتحولين من نوع Int وتعيد قيمةً بنوع Int أيضًا، فسيصبح تعريفها بالشكل:
fun sum(a: Int, b: Int): Int {
  return a + b
}
أما الشيفرة الآتية فهي لتعريف دالةٍ باسم sum تحتوي على تعبيرٍ (expression) دون التصريح عن نوع القيمة المُعادَة:
fun sum(a: Int, b: Int) = a + b
وإن لم تُعد الدالة أيّة قيمةٍ ذات معنى يصبح تعريفها على النحو الآتي (يكون النوع المعاد Unit افتراضيًا):
fun printSum(a: Int, b: Int): Unit {
  println("sum of $a and $b is ${a + b}")
}
ويمكن الاستغناء عن كتابة النوع المُعاد Unit ليصبح التعريف بالشكل:
fun printSum(a: Int, b: Int) {
  println("sum of $a and $b is ${a + b}")
}
المزيد عن الدوال (functions).

تعريف المتحولات (Variables)

لتعريف متحوِّلٍ محليٍّ بإسنادٍ لمرة واحدة فقط (أي للقراءة فقط ولا يمكن إعطاؤه قيمةً جديدة) تُستخدَم إحدى الصيغ الآتية وذلك بالاعتماد على الكلمة المفتاحية val:
val a: Int = 1  // إسناد مباشر

val b = 2   // يُعرَّف ضمنيًّا من النوع Int

val c: Int  // من الضروري تحديد النوع إن لم تُسند القيمة للمتحوّل مباشرةً

c = 3       // إسنادٌ لمتحوّل مُعرَّف مسبقًا
ولتعريف متحوِّلٍ بقيمةٍ قابلةٍ للتعديل (أي أنّه mutable) تُستخدَم الكلمة المفتاحية var بالصيغة الآتية:
var x = 5 // يُعرَّف ضمنيًا من النوع Int 

x += 1
وقد تقع المتحولات بمستوىً أعلى من البنية الحاليّة (top-level variables) كما في المثال الآتي الذي يقع فيه المتحولان PI و x في مستوى أعلى من الدالة incrementX:
val PI = 3.14
var x = 0

fun incrementX() {
  x += 1
}
المزيد عن الخاصّيات (properties) والحقول (fields).

التعليقات (Comments)

تدعم لغة Kotlin نوعين من التعليقات (كما هو الحال في لغتَي Java و JavaScript)؛ وهما التعليقات السطريّة والتعليقات متعددة الأسطر وتكون صياغتهما بالشكل الآتي:
//‎‎ تعليق بسطر واحد

/*‎‎ هذا التعليق
بأكثر من سطر */
وعلى عكس لغة Java يُمكن أن تتداخل التعليقات متعددة الأسطر (أي أن يقع تعليقٌ داخلَ تعليقٍ آخر).

المزيد عن توضيح آلية توثيق الشيفرات بلغة Kotlin.

استخدام قوالب السلاسل النصية (String Templates)

تكون صياغتها بالشكل الآتي:
var a = 1
//‎‎ اسم بسيط ضمن قالب نصيّ
val s1 = "a is $a"

a = 2
//‎‎ تعبيرٌ عشوائيّ ضمن قالب نصيّ
val s2 = "${s1.replace("is", "was")}, but now is $a"
المزيد عن قوالب السلاسل النصيّة (string templates).

استخدام التعابير الشرطية (Conditional Expressions)

توضِّح الدالة الآتية والتي تعيد القيمة الأكبر من بين القيمتين a وb طريقة صياغة الشرط if:
fun maxOf(a: Int, b: Int): Int {
  if (a > b) {
      return a
  } else {
      return b
  }
}
وكما يمكن استخدام الشرط if بصياغة تعبيريّة (expression) بالشكل الآتي:
fun maxOf(a: Int, b: Int) = if (a > b) a else b
المزيد عن استخدام الشرط if.

استخدام القيم التي تقبل القيمة الفارغة Null والتحقق منها

يُقال عن المتحول المرجعيّ (reference variable) بأنّه nullable إن كان يقبل القيمة null، وهو المتحول الذي يُتاح الوصول إلى الكائن من خلاله.

ستعيد الدالة الآتية القيمة null إن لم تحتوِ السلسلة النصيّة str على قيمةٍ صحيحةٍ (Int):
fun parseInt(str: String): Int? {
  // ...
}
أما الدالة الآتية (والتي تُظهِر ناتج جداء العددين الموجودين بالسلسلتين arg1 و arg2) فستعيد القيمة null:
fun printProduct(arg1: String, arg2: String) {
  val x = parseInt(arg1) //‎‎ تحويل السلسلة النصيّة إلى قيمة عددية تُخزّن في المتحول
  val y = parseInt(arg2)

  //‎‎ قد ينتج عن عملية الجداء حدوث خطأ في حال احتواء أحد المتحولين على قيمة null
  if (x != null && y != null) {
      println(x * y)
  }
  else {
      println("either '$arg1' or '$arg2' is not a number")
  }  
}
أو بالشكل:
// ...

if (x == null) {
  println("Wrong number format in arg1: '$arg1'")
  return
}
if (y == null) {
  println("Wrong number format in arg2: '$arg2'")
  return
}
println(x * y)
المزيد عن القيمة الفارغة null وأمان استخدامها.

التحقق من النوع (Type Check) والتحويلات التلقائية بين الأنواع (Casting)

يُستخدَم المعامل is للتحقُّق من أنّ قيمة التعبير البرمجي (expression) تتبع لأحد الأنواع المُعرَّفة في لغة Kotlin، وإذا ما تمّ التحقُّق من نوع القيمة غير القابلة للتغيير (immutable) للمتغيّرات المحليّة (local variable) أو من قيمة إحدى الخاصّيات (property) فلا حاجة حينئذٍ للتصريح عن تحويلها (casting)، كما هو واضح بالشيفرة الآتية:
fun getStringLength(obj: Any): Int? {
  if (obj is String) {
      //‎‎ سيُحوّل الكائن تلقائيًا في هذا الجزء إلى نوع String
      // ولا داعي لإجراء التحويل بشكل صريح
      return obj.length
  }
  //‎‎ وفي هذا الجزء سيبقى من النوع Any
  return null
}
أو بالشكل:
fun getStringLength(obj: Any): Int? {
  if (obj !is String) return null
  //‎‎ سيُحوّل الكائن obj 
  // تلقائيًا إلى نوع String 
  return obj.length
}
أو بشكلٍ آخر:
fun getStringLength(obj: Any): Int? {
  //‎‎ سيُحوّل الكائن الموجود على يمين المعامل تلقائيًا إلى النوع String
  if (obj is String && obj.length > 0) {
      return obj.length
  }
  return null
}
المزيد عن الأصناف (classes) والتحويل ما بين الأنواع (type casts).

استخدام حلقة for

تُكتب حلقة for بالشكل الآتي:
val items = listOf("apple", "banana", "kiwi")

for (item in items) {
  println(item)
}
إذ يُستخدَم المعامل in للمرور بكافة العناصر الموجودة في المجموعة items، ويوضِّح المثال الآتي استخدام حلقة for أيضًا ولكن بالاعتماد على فهارس (indices) العناصر بدلًا من العناصر بحدّ ذاتها:
val items = listOf("apple", "banana", "kiwi")
for (index in items.indices) {
  println("item at $index is ${items[index]}")
}
المزيد عن حلقة for.

استخدام حلقة while

يكون استخدامها وفق الصيغة الآتية:
val items = listOf("apple", "banana", "kiwi")
var index = 0

while (index < items.size) {
  println("item at $index is ${items[index]}")
  index++
}
إذ ستستمر الحلقة لحين الوصول إلى آخر عنصر في المجموعة (أي حتى وصول المتحول index إلى قيمة فهرس العنصر الأخير).

المزيد عن حلقة while.

استخدام التعبير when

يُستخدَم التعبير when للتحقُّق من أنّ قيمةً ما (value) أو أحدَ المتغيِّرات (variable) أو إحدى خاصّيات الكائن (properties) توافق إحدى الحالات المذكورة في التعبير  بالشكل الآتي:
fun describe(obj: Any): String =
when (obj) {
  1        -> "One"
  "Hello"    -> "Greeting"
  is Long    -> "Long"
  !is String -> "Not a string"
  else       -> "Unknown"
}
المزيد عن التعبير when.

استخدام المجالات (Ranges)

يُستخَدم المعامل in للتأكُّد من وقوع قيمةٍ ما ضمن المجال المُحدَّد، وهذا يتَّضِح في المثال الآتي:
val x = 10
val y = 9

if (x in 1..y+1) { // المجال يمتد من قيمة 1 وحتى قيمة y+1
  println("fits in range")
}
كما ويمكن استخدامه للتحقُّق من وقوع القيمة خارج هذا المجال، بالشكل الآتي:
val list = listOf("a", "b", "c")

if (-1 !in 0..list.lastIndex) { // المجال يمتد من 0 وحتى قيمة الفهرس الأخير
  println("-1 is out of range")
}

if (list.size !in list.indices) {
  println("list size is out of valid list indices range too")
}
ولتكرار عددٍ من التعليمات في مجالٍ مُحدَّدٍ، تصبح الصيغة بالشكل:
for (x in 1..5) { // يمتد المجال من 1 حتى 5
  print(x)
}
أما للتكرار المتسلسل وفق خطوة step:
for (x in 1..10 step 2) { //يبدأ المجال من القيمة 1 وينتهي بالقيمة 10 وبخطوةٍ مقدارها 2
  print(x)
}
println()
for (x in 9 downTo 0 step 3) { // يبدأ المجال بالقيمة 9 وينتهي بشكل تنازليّ عند القيمة 0 بخطوةٍ مقدارها 3
  print(x)
}
المزيد عن المجالات (ranges).

استخدام المجموعات (Collections)

يكون المرور على العناصر الموجودة في المجموعة بالشكل الآتي:
for (item in items) {
  println(item)
}
أما للتأكُّد من وجود عنصرٍ ما ضمن المجموعة باستخدام المعامل in فتصبح الصيغة بالشكل الآتي:
when {
  "orange" in items -> println("juicy")
  "apple" in items -> println("apple is fine too")
}
كما ويمكن استخدام تعابير lambda لتصنيف وتعيين المجموعات، كما في الشيفرة الآتية:
fruits
.filter { it.startsWith("a") }
.sortedBy { it }
.map { it.toUpperCase() }
.forEach { println(it) }
المزيد عن الدوال عالية المستوى (top-level functions) وتعابير lambdas.

إنشاء الأصناف (Classes) والكائنات الناتجة عنها (Objects)

يُوضِّح المثال الآتي إنشاء كائنَين من صنفَين مختلفين:
val rectangle = Rectangle(5.0, 2.0) // لا حاجة للكلمة المفتاحيّة new

val triangle = Triangle(3.0, 4.0, 5.0)
المزيد عن الأصناف classes والكائنات objects.

مصادر