إعادة تصميم برنامج Kotlin

في هذا الدرس التطبيقي حول الترميز، ستتعرّف على كيفية تحويل الرمز من Java إلى Kotlin. وستتعرّف أيضًا على اصطلاحات لغة Kotlin وكيفية التأكد من التزامها بالرمز البرمجي الذي تكتبه.

يناسب هذا الدرس التطبيقي أي مطوّر برامج يستخدم لغة Java التي تفكر في نقل مشاريعها إلى لغة Kotlin. سنبدأ ببعض صفوف جافا التي ستحوِّلها إلى لغة Kotlin باستخدام IDE. وبعد ذلك، سنلقي نظرة على الرمز المُحوَّل ونرى كيف يمكننا تحسينه من خلال جعله أكثر بساطة وتجنُّب الأخطاء الشائعة.

ما ستتعرَّف عليه

ستتعلّم كيفية تحويل لغة البرمجة Java إلى لغة Kotlin. بعد ذلك، ستتعلّم الميزات والمفاهيم التالية بلغة Kotlin:

  • التعامل مع إمكانية قبول القيم الفارغة
  • تطبيق التعرّف على فردين
  • دروس البيانات
  • سلاسل المعالجة
  • مشغّل Elvis
  • تدمير
  • المواقع وخصائص الدعم
  • الوسيطات التلقائية والمعلمات المُعنونة
  • التعامل مع المجموعات
  • دوال الإضافات
  • الدوال والمعلّمات ذات المستوى الأعلى
  • let وapply وwith وrun كلمة رئيسية

الافتراضات

يجب أن تكون على دراية بلغة Java.

الأشياء التي تحتاج إليها

إنشاء مشروع جديد

إذا كنت تستخدم IntelliJ IDEA، أنشئ مشروع Java جديدًا باستخدام Kotlin/JVM.

إذا كنت تستخدم "استوديو Android"، يمكنك إنشاء مشروع جديد بدون أي نشاط.

الرمز

سننشئ عنصر نموذج User وفئة Repository فردية تعمل مع عناصر User وتعرض قوائم المستخدمين وأسماء المستخدمين المنسّقة.

أنشِئ ملفًا جديدًا باسم User.java ضمن app/java/<yourpackagename> والصقه في الرمز التالي:

public class User {

    @Nullable
    private String firstName;
    @Nullable
    private String lastName;

    public User(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

}

بناءً على نوع مشروعك، يمكنك استيراد androidx.annotation.Nullable إذا كنت تستخدم مشروع Android أو org.jetbrains.annotations.Nullable بخلاف ذلك.

أنشئ ملفًا جديدًا باسم Repository.java والصق الرمز التالي:

import java.util.ArrayList;
import java.util.List;

public class Repository {

    private static Repository INSTANCE = null;

    private List<User> users = null;

    public static Repository getInstance() {
        if (INSTANCE == null) {
            synchronized (Repository.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Repository();
                }
            }
        }
        return INSTANCE;
    }

    // keeping the constructor private to enforce the usage of getInstance
    private Repository() {

        User user1 = new User("Jane", "");
        User user2 = new User("John", null);
        User user3 = new User("Anne", "Doe");

        users = new ArrayList();
        users.add(user1);
        users.add(user2);
        users.add(user3);
    }

    public List<User> getUsers() {
        return users;
    }

    public List<String> getFormattedUserNames() {
        List<String> userNames = new ArrayList<>(users.size());
        for (User user : users) {
            String name;

            if (user.getLastName() != null) {
                if (user.getFirstName() != null) {
                    name = user.getFirstName() + " " + user.getLastName();
                } else {
                    name = user.getLastName();
                }
            } else if (user.getFirstName() != null) {
                name = user.getFirstName();
            } else {
                name = "Unknown";
            }
            userNames.add(name);
        }
        return userNames;
    }
}

يمكن أن ينفّذ IDE الخاص بك ترميزًا تلقائيًا للغة البرمجة Java في لغة Kotlin، لكنّه في بعض الأحيان يحتاج إلى القليل من المساعدة. سنطبّق هذا الأمر أولاً ثم نتناول الرمز الذي أُعيد تجديده لفهم كيفية وسبب إعادة إنتاجه بهذه الطريقة.

انتقل إلى ملف User.java وحوّله إلى Kotlin: شريط القوائم -> Code -> تحويل ملف Java إلى ملف Kotlin.

في حال مطالبة IDE بالتصحيح بعد التحويل، اضغط على Yes (نعم).

من المفترض أن يظهر لك رمز Kotlin التالي: :

class User(var firstName: String?, var lastName: String?)

لاحظ أن User.java تمت إعادة تسميته إلى User.kt. تحتوي ملفات Kotlin على الامتداد kt..

في صف Java User، كان لدينا خاصية واحدة: firstName وlastName. تضمّنت كل طريقة طريقة إرجاع القيمة والضبط، ما يجعل قيمتها قابلة للتغيير. كلمة رئيسية لـ Kotlin&#39 في المتغيّرات القابلة للتحويل هي var، لذا تستخدِم أداة التحويل var لكلٍّ من هذه المواقع. إذا كانت خصائص Java تحتوي على مقادير فقط، ستكون غير قابلة للتغيير وستُعلَن عنها على أنها val متغيّر. val تتشابه مع الكلمة الرئيسية final في Java.

من أهم الاختلافات بين لغة Kotlin وJava أنّ لغة Kotlin تحدّد صراحةً ما إذا كان المتغير يقبل قيمة فارغة. ويتم إجراء ذلك من خلال إلحاق `?` بإعلان النوع.

وبما أنّه تم وضع علامة على firstName وlastName للإشارة إلى أنّه لا يمكن ترك أي منها، وضعت أداة التحويل التلقائي علامة على الخصائص على أنها غير صالحة باستخدام String?. وفي حال إضافة تعليقات توضيحية على أعضاء Java بوصفهم غير فارغين (باستخدام org.jetbrains.annotations.NotNull أو androidx.annotation.NonNull)، سيتعرّف المُحوِّل على ذلك، وسيجعل الحقول غير فارغة بلغة Kotlin أيضًا.

سبق لك الانتهاء من تجديد البنية بشكل أساسي. ولكن يمكننا أن نكتب ذلك بطريقة أكثر إيحاءً. لنتعرّف على كيفية إجراء ذلك.

فئة البيانات

لا تحتفظ صف User إلا بالبيانات. تحتوي كلمة Kotlin على كلمة رئيسية للصفوف التي لها هذا الدور: data. بعد وضع علامة على هذا الصف كصف data، ستنشئ أداة التجميع تلقائيًا أدوات القياس والضبط إلينا. وستشتد أيضًا الدوال equals() وhashCode() وtoString().

لنبدأ الكلمة الرئيسية data في صف User:

data class User(var firstName: String, var lastName: String)

يمكن أن تتوفّر لغة Kotlin، مثل Java في الوضع الأساسي ومصمّم ثانوي واحد أو أكثر. المثال السابق في المثال أعلاه هو الملف الشخصي الأساسي لفئة المستخدم. وإذا كنت تحوّل صفًا من لغة Java يحتوي على أدوات إنشاء متعددة، ستنشئ أداة التحويل تلقائيًا العديد من أدوات الإنشاء في لغة Kotlin أيضًا. ويتم تحديدها باستخدام الكلمة الرئيسية constructor.

إذا أردت إنشاء مثيل لهذا الصف، يمكننا إجراء ذلك:

val user1 = User("Jane", "Doe")

المساواة

يتوفّر نوعَان من المساواة في لغة Kotlin:

  • تستخدم المساواة في البنية التنظيمية عامل التشغيل == وتطلب equals() لتحديد ما إذا كانت هناك مثيلان متساويان.
  • تستخدم المساواة المرجعية عامل التشغيل === وتتحقّق مما إذا كان مرجعان يشيران إلى العنصر نفسه.

سيتم استخدام الخصائص المحدّدة في طريقة الإنشاء الأساسية لفئة البيانات لعمليات التحقق من المساواة في البنية.

val user1 = User("Jane", "Doe")
val user2 = User("Jane", "Doe")
val structurallyEqual = user1 == user2 // true
val referentiallyEqual = user1 === user2 // false

في لغة Kotlin، يمكننا إسناد القيم التلقائية للوسيطات في استدعاءات الدالة. ويتم استخدام القيمة التلقائية عند حذف الوسيطة. وفي حال استخدام لغة البرمجة Kotlin، تكون أيضًا الدوال دالة، لذلك يمكننا استخدام الوسيطات التلقائية لتحديد أن القيمة التلقائية lastName هي null. لإجراء ذلك، ما عليك سوى تخصيص null للتطبيق lastName.

data class User(var firstName: String?, var lastName: String? = null)

// usage
val jane = User("Jane") // same as User("Jane", null)
val joe = User("John", "Doe")

يمكن تسمية مَعلمات الوظائف عند استدعاء الدوال:

val john = User(firstName = "John", lastName = "Doe") 

كحالة استخدام مختلفة، لنفترض أن firstName تحتوي على null كقيمة تلقائية، وlastName ليست له. وفي هذه الحالة، ونظرًا لأن المعلمة التلقائية ستسبق معلمة بدون قيمة تلقائية، سيتعين عليك استدعاء الدالة بوسيطات مُسَمّية:

data class User(var firstName: String? = null, var lastName: String?)

// usage
val jane = User(lastName = "Doe") // same as User(null, "Doe")
val john = User("John", "Doe")

قبل المضي قدمًا، عليك التأكّد من أنّ صفك في User هو data. لنحوّل الصف Repository إلى لغة Kotlin. يجب أن تظهر نتيجة الإحالة الناجحة التلقائية على النحو التالي:

import java.util.*

class Repository private constructor() {
   private var users: MutableList<User?>? = null
   fun getUsers(): List<User?>? {
       return users
   }

   val formattedUserNames: List<String?>
       get() {
           val userNames: MutableList<String?> =
               ArrayList(users!!.size)
           for (user in users) {
               var name: String
               name = if (user!!.lastName != null) {
                   if (user!!.firstName != null) {
                       user!!.firstName + " " + user!!.lastName
                   } else {
                       user!!.lastName
                   }
               } else if (user!!.firstName != null) {
                   user!!.firstName
               } else {
                   "Unknown"
               }
               userNames.add(name)
           }
           return userNames
       }

   companion object {
       private var INSTANCE: Repository? = null
       val instance: Repository?
           get() {
               if (INSTANCE == null) {
                   synchronized(Repository::class.java) {
                       if (INSTANCE == null) {
                           INSTANCE =
                               Repository()
                       }
                   }
               }
               return INSTANCE
           }
   }

   // keeping the constructor private to enforce the usage of getInstance
   init {
       val user1 = User("Jane", "")
       val user2 = User("John", null)
       val user3 = User("Anne", "Doe")
       users = ArrayList<Any?>()
       users.add(user1)
       users.add(user2)
       users.add(user3)
   }
}

لنتعرّف على ما يفعله المحوِّل التلقائي:

  • تمت إضافة حظر init (Repository.kt#L50)
  • أصبح الحقل static الآن جزءًا من الكتلة companion object (Repository.kt#L33)
  • قائمة users خالية من الكيانات نظرًا لعدم إنشاء الكائن بشكل مسبق في وقت الإعلان (Repository.kt#L7)
  • أصبحت طريقة getFormattedUserNames() الآن خاصية تُسمى formattedUserNames (Repository.kt#L11).
  • بنية تكرار قائمة المستخدمين (التي كانت في البداية جزءًا من getFormattedUserNames() بنية مختلفة عن بنية Java (Repository.kt#L15)

قبل المضي قدمًا، لنبدأ في تنظيم الرمز قليلاً. يمكننا ملاحظة أنّ المحوّل قد جعل قائمة users لدينا قائمة قابلة للتغيير وتحتوي على عناصر فارغة. على الرغم من أن القائمة يمكن أن تكون خالية فعليًا، فدعنا نقول أنه لا يمكن أن تحتفظ ببيانات المستخدمين الفارغين. لنفعل ما يلي:

  • إزالة ? من User? في تعريف النوع users
  • من المفترض أن تعرض getUsers القيمة List<User>?.

يتم أيضًا تقسيم المحوّل التلقائي بدون داعٍ إلى سطرين في تعريفات المتغير لمتغيرات المستخدمين والمتغيرات المحدّدة في مجموعة init. لنضع تعريف كل متغير في سطر واحد. في ما يلي الشكل الذي سيظهر به الرمز:

class Repository private constructor() {
    private var users: MutableList<User>? = null

    fun getUsers(): List<User>? {
        return users
    }

    val formattedUserNames: List<String?>
        get() {
            val userNames: MutableList<String?> =
                ArrayList(users!!.size)
            for (user in users) {
                var name: String
                name = if (user!!.lastName != null) {
                    if (user!!.firstName != null) {
                        user!!.firstName + " " + user!!.lastName
                    } else {
                        user!!.lastName
                    }
                } else if (user!!.firstName != null) {
                    user!!.firstName
                } else {
                    "Unknown"
                }
                userNames.add(name)
            }
            return userNames
        }

    companion object {
        private var INSTANCE: Repository? = null
        val instance: Repository?
            get() {
                if (INSTANCE == null) {
                    synchronized(Repository::class.java) {
                        if (INSTANCE == null) {
                            INSTANCE =
                                Repository()
                        }
                    }
                }
                return INSTANCE
            }
    }

    // keeping the constructor private to enforce the usage of getInstance
    init {
        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")
        users = ArrayList<Any?>()
        users.add(user1)
        users.add(user2)
        users.add(user3)
    }
}

حظر الإعداد

في لغة Kotlin، لا يمكن أن تحتوي أداة الإنشاء الأساسية على أي رمز، لذا يتم وضع رمز الإعداد في أجزاء init. الوظيفة هي نفسها.

class Repository private constructor() {
    ...
    init {
        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")
        users = ArrayList<Any?>()
        users.add(user1)
        users.add(user2)
        users.add(user3)
    }
}

يتعامل الكثير من رموز init مع خصائص الإعداد. ويمكن إجراء ذلك أيضًا في الإعلان عن الموقع. على سبيل المثال، في إصدار لغة Kotlin لفئة Repository، نلاحظ أنّه تم إعداد خاصيّة المستخدمين في التعريف.

private var users: MutableList<User>? = null

خصائص وخصائص staticKotlin's

في لغة Java، نستخدم الكلمة الرئيسية static للحقول أو الوظائف للإشارة إلى أنها تنتمي إلى صف دراسي ولكنها لا تنتمي إلى فئة معيّنة. ولهذا السبب أنشأنا الحقل الثابت INSTANCE في الصف Repository. مكافئ لغة Kotlin لهذا هو كتلة companion object. وستعلن هنا أيضًا الحقول الثابتة والدوال الثابتة. أنشأ المحوِّل الحقل INSTANCE ونقله هنا.

التعامل مع الحالات الفردية

ونظرًا لاحتياجنا إلى مثيل واحد فقط من الفئة Repository، استخدمنا نمط Singleton في Java. باستخدام Kotlin، يمكنك فرض هذا النمط على مستوى المجمِّع من خلال استبدال الكلمة الرئيسية class بـ object.

أزِل المُنشئ الخاص والكائن المصاحب واستبدل تعريف الفئة بـ object Repository.

object Repository {

    private var users: MutableList<User>? = null

    fun getUsers(): List<User>? {
       return users
    }

    val formattedUserNames: List<String>
        get() {
            val userNames: MutableList<String> =
                ArrayList(users!!.size)
        for (user in users) {
            var name: String
            name = if (user!!.lastName != null) {
                if (user!!.firstName != null) {
                    user!!.firstName + " " + user!!.lastName
                } else {
                    user!!.lastName
                }
            } else if (user!!.firstName != null) {
                user!!.firstName
            } else {
                "Unknown"
            }
            userNames.add(name)
       }
       return userNames
   }

    // keeping the constructor private to enforce the usage of getInstance
    init {
        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")
        users = ArrayList<Any?>()
        users.add(user1)
        users.add(user2)
        users.add(user3)
    }
}

عند استخدام الفئة object، يتم فقط استدعاء الدوال وخصائصها على العنصر مباشرةً، على النحو التالي:

val users = Repository.users

المحو

يسمح Kotlin بتدمير كائن في عدد من المتغيرات، باستخدام بنية تُسمى بيان التدمير. ننشئ متغيّرات متعددة ويمكننا استخدامها بشكل مستقل.

على سبيل المثال، تدعم فئات البيانات المحور حتى نتمكن من إتلاف الكائن User في حلقة for داخل (firstName, lastName). ويتيح لنا ذلك العمل مباشرةً مع القيم firstName وlastName. لنحدث حلقة for على النحو التالي:

 
for ((firstName, lastName) in users!!) {
       val name: String?

       if (lastName != null) {
          if (firstName != null) {
                name = "$firstName $lastName"
          } else {
                name = lastName
          }
       } else if (firstName != null) {
            name = firstName
       } else {
            name = "Unknown"
       }
       userNames.add(name)
}

عند تحويل الفئة Repository إلى لغة Kotlin، جعلت أداة التحويل التلقائي قائمة المستخدمين فارغة، لأنها لم يتم إعدادها إلى عنصر عند الإعلان عنه. بالنسبة إلى جميع استخدامات الكائن users، يتم استخدام عامل تشغيل التأكيد غير فارغ !!. وتحوّل أي متغيّر إلى نوع غير فارغ وتعطي استثناءً إذا كانت القيمة فارغة. باستخدام !!، أنت تخاطر باستبعاد الاستثناءات في وقت التشغيل.

وبدلاً من ذلك، ننصحك بالتعامل مع القيم الفارغة باستخدام إحدى الطرق التالية:

  • إجراء فحص فارغ ( if (users != null) {...} )
  • استخدام عامل تشغيل elvis ?: (يتم تناوله لاحقًا في الدرس التطبيقي حول الترميز)
  • استخدام بعض الدوال العادية بلغة Kotlin (سيتم تناولها لاحقًا في الدرس التطبيقي حول الترميز)

في هذه الحالة، نعرف أنه "ليس من الضروري أن تكون قائمة المستخدمين فارغة، نظرًا لأنّها يتم إعدادها بعد إنشاء العنصر مباشرةً، نتمكن من إنشاء الكائن مباشرةً عند الإعلان عنه.

عند إنشاء أمثلة لأنواع المجموعات، توفّر لغة Kotlin وظائف مساعدة تجعل الرمز أكثر وضوحًا ومرونة. آدِي نِستخدم MutableList لـ users:

private var users: MutableList<User>? = null

لتبسيط الأمر، يمكننا استخدام الدالة mutableListOf() وتوفير نوع عنصر القائمة وإزالة استدعاء دالة الإنشاء من الفئة init وإزالة تعريف النوع الصريح للسمة users.

private val users = mutableListOf<User>()

وقد غيّرنا أيضًا طريقة العرض في هذه الحالة لأنّ المستخدمين سيتضمنون مرجعًا غير قابل للتغيير إلى قائمة المستخدمين. يُرجى العِلم بأن المرجع غير قابل للتغيير، ولكن القائمة نفسها قابلة للتغيير (يمكنك إضافة عناصر أو إزالتها).

بعد إجراء هذه التغييرات، لم تعد خاصية users فارغة، ويمكننا إزالة جميع مواضع ورود !! غير الضرورية.

val userNames: MutableList<String?> = ArrayList(users.size)
for ((firstName, lastName) in users) {
    ...
}

ولأنّ متغيّر المستخدمين تم إعداده مسبقًا، علينا إزالة الإعداد من المجموعة init:

init {
    val user1 = User("Jane", "")
    val user2 = User("John", null)
    val user3 = User("Anne", "Doe")

    users.add(user1)
    users.add(user2)
    users.add(user3)
}

وبما أن كلاً من lastName وfirstName يمكن أن يكون null، علينا معالجة القيمة الفارغة عندما ننشئ قائمة بأسماء المستخدمين المنسَّقة. وبما أننا نريد عرض "Unknown" في حال عدم توفّر أي من الاسمَين، يمكننا جعل الاسم غير فارغ عن طريق إزالة ? من تعريف النوع.

val name: String

إذا كانت قيمة السمة lastName فارغة، يكون name هو firstName أو "Unknown":

if (lastName != null) {
    if (firstName != null) {
        name = "$firstName $lastName"
    } else {
        name = lastName
    }
} else if (firstName != null) {
    name = firstName
} else {
    name = "Unknown"
}

يمكن كتابة ذلك بشكل أكثر بساطة باستخدام عامل تشغيل Elvis ?:. سيعرض عامل التشغيل elvis التعبير على جانبه الأيسر إذا لم يكن فارغًا، أو التعبير على الجانب الأيمن، إذا كان الجانب الأيسر فارغًا.

لذلك في الرمز التالي، يتم عرض user.firstName إذا لم يكن فارغًا. إذا كان user.firstName فارغًا، سيعرض التعبير القيمة على اليسار، "Unknown":

if (lastName != null) {
    ...
} else {
    name = firstName ?: "Unknown"
}

يسهل استخدام لغة Kotlin في استخدام StringString النماذج. تسمح لك نماذج السلسلة بالرجوع إلى المتغيرات داخل تعريفات السلاسل.

عدَّل المُحوِّل التلقائي سلسلة الاسم الأول واسم العائلة للإشارة إلى اسم المتغيّر مباشرةً في السلسلة باستخدام الرمز $ ووضع التعبير بين { } .

// Java
name = user.getFirstName() + " " + user.getLastName();

// Kotlin
name = "${user.firstName} ${user.lastName}"

في الرمز، استبدل سلسلة السلاسل بـ:

name = "$firstName $lastName"

في لغة Kotlin if وwhen وfor وwhile هي تعبيرات، يتم عرض قيمة. يعرض IDE أيضًا تحذيرًا بأنه يجب إزالة المهمة الدراسية من if:

دعنا نتبع اقتراح IDE't ونرفع المهمة لكلتا العبارتين (if). سيتم تعيين السطر الأخير من عبارة if. وكما هو الحال، أصبح من الواضح أن الغرض الوحيد من ذلك الحظر هو إعداد قيمة الاسم:

name = if (lastName != null) {
    if (firstName != null) {
        "$firstName $lastName"
    } else {
        lastName
    }
} else {
   firstName ?: "Unknown"
}

بعد ذلك، سنتلقى تحذيرًا بشأن إمكانية الانضمام إلى بيان name مع المهمة. لنفعل ذلك أيضًا. يمكننا إزالة تعريفات نوع العنصر الفاضح إذا كان من الممكن استنتاج نوع متغيّر الاسم. تبدو formattedUserNames الآن على النحو التالي:

val formattedUserNames: List<String?>
   get() {
       val userNames: MutableList<String?> = ArrayList(users.size)
       for ((firstName, lastName) in users) {
           val name = if (lastName != null) {
               if (firstName != null) {
                   "$firstName $lastName"
               } else {
                   lastName
               }
           } else {
               firstName ?: "Unknown"
           }
           userNames.add(name)
       }
       return userNames
   }

لنلقِ نظرة فاحصة على دالة formattedUserNames التي تتناولها وتعرّف على كيفية جعلها أكثر جاذبية. في الوقت الحالي، ينفذ الرمز ما يلي:

  • إنشاء قائمة جديدة بالسلاسل
  • التكرار من خلال قائمة المستخدمين
  • إنشاء اسم منسق لكل مستخدم، استنادًا إلى الاسم الأول واسم العائلة للمستخدم
  • عرض القائمة التي تم إنشاؤها حديثًا
val formattedUserNames: List<String>
        get() {
            val userNames = ArrayList<String>(users.size)
            for ((firstName, lastName) in users) {
                val name = if (lastName != null) {
                    if (firstName != null) {
                        "$firstName $lastName"
                    } else {
                        lastName
                    }
                } else {
                    firstName ?: "Unknown"
                }

                userNames.add(name)
            }
            return userNames
        }

يقدم Kotlin قائمة شاملة بتحويلات المجموعات التي تجعل عملية التطوير أسرع وأكثر أمانًا من خلال توسيع إمكانات واجهة برمجة تطبيقات مجموعات Java. إحداها هي دالة map. تعرض هذه الدالة قائمة جديدة تحتوي على نتائج تطبيق دالة التحويل المحددة على كل عنصر في القائمة الأصلية. ولذلك، بدلاً من إنشاء قائمة جديدة وتكرار قائمة المستخدمين يدويًا، يمكننا استخدام الدالة map ونقل المنطق الذي كان لدينا في حلقة for داخل نص map. وفقًا للإعدادات التلقائية، يكون اسم عنصر القائمة الحالي المستخدَم في map هو it، ولكن يمكنك استبدال it باسم المتغيّر الخاص بك لتسهيل القراءة. في هذه الحالة، نسمِّي user:

    
val formattedUserNames: List<String>
        get() {
            return users.map { user ->
                val name = if (user.lastName != null) {
                    if (user.firstName != null) {
                        "${user.firstName} ${user.lastName}"
                    } else {
                        user.lastName ?: "Unknown"
                    }
                }  else {
                    user.firstName ?: "Unknown"
                }
                name
            }
        }

لتبسيط هذه العملية أكثر، يمكننا إزالة المتغيّر name تمامًا:

    
val formattedUserNames: List<String>
        get() {
            return users.map { user ->
                if (user.lastName != null) {
                    if (user.firstName != null) {
                        "${user.firstName} ${user.lastName}"
                    } else {
                        user.lastName ?: "Unknown"
                    }
                }  else {
                    user.firstName ?: "Unknown"
                }
            }
        }

تبيّن لنا أنّ المحوّل التلقائي استبدل الدالة getFormattedUserNames() بموقع يُسمّى formattedUserNames ويتضمّن دالّة مخصّصة. في الخيارات المتقدمة، تواصل أداة Kotlin إنشاء طريقة getFormattedUserNames() تعرض List.

في Java، سيتم عرض خصائص صفنا من خلال وظائف getter وsetter. يُتيح لنا Kotlin التمييز بشكل أفضل بين خصائص الصف والتعبير عن الحقول والوظائف والإجراءات التي يستطيع الصف تنفيذها باستخدام "الدوال". في هذه الحالة، ضِمن فئة Repository بسيطة جدًا، وهي لا تنفّذ أي إجراءات كي تتضمّن حقولاً فقط.

يتم الآن تشغيل المنطق الذي تم تشغيله في دالة Java getFormattedUserNames() عند استدعاء قيمة غيت formattedUserNames.

على الرغم من أنّنا لا نمتلك صراحةً حقلاً مطابقًا لخاصية formattedUserNames، إلا أنّ لغة البرمجة Kotlin تمنحنا حقل دعم تلقائيًا يُسمى field ويمكننا الوصول إليه إذا لزم الأمر من مُحدِّدات وأدوات ضبط مخصَّصة.

ولكن في بعض الأحيان، نريد إضافة بعض الوظائف الإضافية التي لا يوفرها حقل النسخ الاحتياطي التلقائي. لنستعرض مثالاً أدناه.

داخل صف Repository التابع لنا، ندرج قائمة من المستخدمين القابلين للعرض في الدالة getUsers() التي تم إنشاؤها من رمز Java:

fun getUsers(): List<User>? {
    return users
}

تكمن المشكلة في أنه بإرجاع users، يمكن لأي مستهلك في"مستودع المستودع"تعديل قائمة المستخدمين، وهي ليست فكرة جيدة. لنصلح هذه المشكلة باستخدام موقع إلكتروني احتياطي.

أولاً، لنعيد تسمية users إلى _users. أضِف الآن موقعًا غير قابل للتغيير بشكل علني يعرض قائمة من المستخدمين. لنُطلق عليه اسم users:

private val _users = mutableListOf<User>()
val users: List<User>
      get() = _users

مع هذا التغيير، تصبح الخاصية _users الخاصة الموقع الاحتياطي للموقع users العلني. وخارج الصف Repository، لن تكون قائمة _users قابلة للتعديل، لأنّ المستهلكين في الصف لا يمكنهم الوصول إلى القائمة إلا من خلال users.

الرمز الكامل:

object Repository {

    private val _users = mutableListOf<User>()
    val users: List<User>
        get() = _users

    val formattedUserNames: List<String>
        get() {
            return _users.map { user ->
                if (user.lastName != null) {
                    if (user.firstName != null) {
                        "${user.firstName} ${user.lastName}"
                    } else {
                        user.lastName ?: "Unknown"
                    }
                }  else {
                    user.firstName ?: "Unknown"
                }
            }
        }

    init {

        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")

        _users.add(user1)
        _users.add(user2)
        _users.add(user3)
    }
}

تتعرّف الآن السمة Repository على طريقة حساب اسم المستخدم المنسّق لعنصر User. وفي حال رغبتنا في إعادة استخدام منطق التنسيق نفسه في صفوف أخرى، نحتاج إلى نسخه ولصقه أو نقله إلى الصف User.

توفر لغة Kotlin إمكانية الإعلان عن الوظائف والخصائص خارج أي فئة أو كائن أو واجهة. على سبيل المثال، يتم تحديد دالة mutableListOf() التي استخدمناها لإنشاء مثيل جديد من List مباشرةً في Collections.kt من "المكتبة العادية".

في Java، يجب إنشاء فئة Util على الأرجح والتصريح عن هذه الوظيفة كدالة ثابتة عندما تحتاج إلى بعض وظائف الخدمات. في لغة Kotlin، يمكنك إعلان وظائف المستوى الأعلى بدون وجود صف. ومع ذلك، فإن لغة Kotlin توفر أيضًا إمكانية إنشاء وظائف الإضافة. وهذه الوظائف هي إضافات تمتد إلى نوع معيّن ولكن يتم الإعلان عنها خارج النوع. وبالتالي، لديهم اهتمام بهذا النوع من الإعلانات.

يمكن تقييد ظهور وظائف الإضافات وخصائصها باستخدام معدِّلات إذن الوصول. يؤدي ذلك إلى حصر الاستخدام على الصفوف التي تحتاج إلى الإضافات فقط، ولا يلوّث مساحة الاسم.

بالنسبة إلى الفئة User، يمكننا إما إضافة دالة إضافة تحسب الاسم المنسّق أو يمكننا الاحتفاظ بالاسم المنسّق في إحدى خصائص الإضافة. يمكن إضافته خارج الفئة Repository في الملف نفسه:

// extension function
fun User.getFormattedName(): String {
    return if (lastName != null) {
        if (firstName != null) {
            "$firstName $lastName"
        } else {
            lastName ?: "Unknown"
        }
    } else {
        firstName ?: "Unknown"
    }
}

// extension property
val User.userFormattedName: String
    get() {
        return if (lastName != null) {
            if (firstName != null) {
                "$firstName $lastName"
            } else {
                lastName ?: "Unknown"
            }
        } else {
            firstName ?: "Unknown"
        }
    }

// usage:
val user = User(...)
val name = user.getFormattedName()
val formattedName = user.userFormattedName

يمكننا بعد ذلك استخدام وظائف الإضافة وخصائصها كما لو كانت جزءًا من فئة User.

ونظرًا لأن الاسم المنسَّق هو خاصيّة المستخدِم وليست وظيفة في الفئة Repository، لنبدأ باستخدام السمة الإضافة. يظهر ملف Repository الآن على النحو التالي:

val User.formattedName: String
    get() {
        return if (lastName != null) {
            if (firstName != null) {
                "$firstName $lastName"
            } else {
                lastName ?: "Unknown"
            }
        } else {
            firstName ?: "Unknown"
        }
    }

object Repository {

    private val _users = mutableListOf<User>()
    val users: List<User>
      get() = _users

    val formattedUserNames: List<String>
        get() {
            return _users.map { user -> user.formattedName }
        }

    init {

        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")

        _users.add(user1)
        _users.add(user2)
        _users.add(user3)
    }
}

تستخدم مكتبة لغة البرمجة Kotlin وظائف التمديد لتوسيع وظائف عدة واجهات برمجة تطبيقات جافا؛ حيث يتم تنفيذ الكثير من الوظائف على Iterable وCollection كدوال إضافة. على سبيل المثال، دالة map التي استخدمناها في خطوة سابقة هي دالة إضافة على Iterable.

في رمز صف Repository، سنضيف العديد من كائنات المستخدم إلى قائمة _users. يمكن إجراء هذه الطلبات بشكل أكثر توافقًا مع وظائف النطاق.

لتنفيذ الرمز فقط في سياق عنصر معيّن، بدون الحاجة إلى الوصول إلى الكائن استنادًا إلى اسمه، أنشأت لغة Kotlin 5 دوال نطاق: let وapply وwith وrun وalso. قصيرة وقوية، وتشتمل هذه الدوال على مستقبِل (this)، وقد تتضمّن وسيطة (it) وقد تعرِض قيمة. ستحدد الخيار الذي تريد استخدامه استنادًا إلى ما تريد تحقيقه.

يمكنك الاطّلاع على ورقة الملاحظات الموجزة هذه لمساعدتك على تذكّر ما يلي:

وبما أنّنا نضبط عنصر _users في Repository، يمكننا جعل الرمز أكثر ارتباطًا باستخدام الدالة apply:

init {
    val user1 = User("Jane", "")
    val user2 = User("John", null)
    val user3 = User("Anne", "Doe")
   
    _users.apply {
       // this == _users
       add(user1)
       add(user2)
       add(user3)
    }
 }

في هذا الدرس التطبيقي حول الترميز، تناولنا الأساسيات التي تحتاج إليها للبدء في إعادة معالجة الرمز من Java إلى Kotlin. وتُعدّ عملية إعادة التصميم هذه عملية مستقلة عن النظام الأساسي للتطوير وتساعد في التأكد من أن الرمز الذي تكتبه يمثل أصلاً.

لغة Kotlin الاصطلاحية تجعل رمز الكتابة قصيرًا ولطيفًا. مع جميع الميزات التي توفرها لغة Kotlin، هناك العديد من الطرق لجعل رمزك أكثر أمانًا وأكثر إيجازًا وقابلية للقراءة. على سبيل المثال، يمكننا تحسين فئة Repository عن طريق إنشاء قائمة _users من خلال المستخدمين مباشرةً في التعريف، والتخلص من حظر init:

private val users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))

تناولنا مجموعة كبيرة من المواضيع، بدءًا من معالجة القيم الفارغة والفردية والسلاسل والمجموعات إلى مواضيع مثل وظائف الإضافات ودوال المستوى الأعلى والخصائص ووظائف النطاق. انتقلنا من فئتين من جافا إلى اثنين من لغة Kotlin التي تبدو الآن على النحو التالي:

User.kt

class User(var firstName: String?, var lastName: String?)

Repository.kt

val User.formattedName: String
    get() {
       return if (lastName != null) {
            if (firstName != null) {
                "$firstName $lastName"
            } else {
                lastName ?: "Unknown"
            }
        } else {
            firstName ?: "Unknown"
        }
    }

object Repository {

    private val _users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))
    val users: List<User>
        get() = _users

    val formattedUserNames: List<String>
        get() = _users.map { user -> user.formattedName }
}

في ما يلي مثال على TL؛ لـ "وظائف Java" وربطها بلغة Kotlin:

Java

كوتلين

كائن final

كائن val

equals()

==

==

===

الفئة التي تحتفظ بالبيانات فقط

صف واحد (data)

إعداد في عامل البناء

جارٍ الإعداد في المجموعة init

static حقول ودوال

الحقول والدوال المُعلَنة في companion object

درجة سينغلتون

object

لمعرفة المزيد من المعلومات حول لغة Kotlin وكيفية استخدامها على منصّتك، يمكنك الاطّلاع على المراجع التالية: