Kotlin مقابل Java : أهم الفروقات التي يجب أن تعرفها - صفحات

Kotlin مقابل Java : أهم الفروقات التي يجب أن تعرفها

Kotlin هي لغة منوّعة ثباتيًا (statically typed)، وقد تم تطويرها من قبل JetBrains. وعلى غرار Java، صارت Kotlin أحد الخيارات المفضلة لتطوير تطبيقات Android. يبدو هذا جليا من حقيقة أنّ Android Studio صارت تدعم Kotlin كما تدعم Java.

 

Kotlin مقابل Java

السؤال هو: هل ينبغي أن أتحول من Java إلى Kotlin أم لا؟ حسنًا، الجواب يعتمد على تفضيلاتك واحتياجاتك. ولكن قبل أن تقرر، من المهم أن تفهم الفرق بين هاتين اللغتين.

 

الاستثناءات المفحوصة (Checked Exceptions)

أحد الاختلافات الرئيسية بين Java و Kotlin هو أنّ الأخيرة لا تتيح الاستثناءات المفحوصة. لذلك، ليست هناك حاجة لإمساك أيّ استثناءات أو التصريح بها.

مطورو Java الذين ملّوا استخدام الكتل try/catch في الشيفرات البرمجية، سيحبون الإعفاء الذي جاءت به Kotlin. أما المطورون الذين يرون أنّ الاستثناءات المفحوصة تسهل رصد الأخطاء وإنشاء شيفرات سليمة، فقد لا يحبون هذا التغيير.

 

الإيجاز

عند مقارنة صنفٍ من Java بمُقابلِه في Kotlin، يتضح لنا أنّ شيفرة Kotlin أوجز. فنفس العملية التي يقوم بها أحد أصناف Java، ستتطلب شيفرة أٌقل عند استخدام صنف من Kotlin. مثلًا، يمكن رؤية هذا بجلاء في التابع findViewByIds.

تسمح الإضافة Kotlin Android Extensions باستيراد مرجع إلى المعرض (View) في ملف النشاط (Activity file). مما يُمكّن من معاملة ذلك المعرض كما لو كان جزءًا من النشاط (Activity).

 

Coroutines

تستغرق العمليات التي تستهلك قدرات وحدة المعالجة المركزية وشبكة I/O‎ الكثير من الوقت. إذ تُعطَّل المهمةَ الفرعية (thread) إلى حين اكتمال العملية. ونظرًا لكون نظام Android أحادي المهام (single-threaded) بشكل افتراضي، ستُجمَّد واجهة المستخدم الخاصة بالتطبيق بالكامل بمجرد تعطيل المهمة الرئيسية.

يتمثل الحل التقليدي لهذه المشكلة في Java في إنشاء مهمة فرعية خلفية لإجراء العمليات التي تستغرق الكثير من الوقت. المشكلة في ذلك أنّ إدارة عدة مهام فرعية يُعقد الشيفرة، ويؤدي إلى كثرة الأخطاء.

تسمح Kotlin أيضًا بإنشاء مهام فرعية إضافية. إلا أنّها تقدم طريقة أفضل لإدارة العمليات المكثفة، وتُعرف هذه الطريقة باسم coroutines. المكوّنة Coroutines غير مكدسة (stackless)، لذلك فهي تتطلب ذاكرة أقل بالمقارنة مع المهام الفرعية.

بإمكان المكونات Coroutines تنفيد المهام الطويلة والمكثفة عن طريق تعليق التنفيذ دون تعطيل المهمة الفرعية، ثم استئناف التنفيذ في وقت لاحق. فهي تسمح بإنشاء شيفرات برمجية غير متزامنة (asynchronous) وغير مُعطِّلة (non-blocking)، والتي تبدو وكأنها متزامنة (synchronous).

استخدام المكونات coroutines لا يجعل الشيفرة واضحة وحسب، بل ويجعلها أوجز أيضًا. علاوة على ذلك، تسمح المكونات coroutines باستعمال أنماط إضافية من البرمجة غير المتزامنة وغير المُعطلة، مثل نمط مزامنة/انتظار (async/await).

 

أصناف البيانات

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

عادةً ما يحتاج المطور إلى تعريف الباني (constructor)، وعدة حقول لتخزين البيانات، والجالبات (getter functions) والمُعيِّنات (setter functions) لكل حقل من الحقول، إضافة إلى الدوال equals()‎ و hashCode()‎ و toString()‎.

تعتمد Kotlin مقاربة بسيطة للغاية في إنشاء مثل هذه الأصناف. فكل ما على المطور فعله هو تضمين الكلمة المفتاحية للبيانات (data keyword) في تعريف الصنف. وسيتولى المُصرّف (compiler) المهمة كاملة.

 

دوال التمديد (Extension Functions)

تسمح Kotlin للمطورين بتمديد الأصناف بوظائف جديدة عبر دوال التمديد. هذه الدوال، على الرغم من توفرها في لغات البرمجة الأخرى مثل C#‎، غير متوفرة في Java.

من السهل إنشاء دالة تمديد في Kotlin. إذ يمكن ذلك عن طريق إضافة اسم الصنف المراد تمديده كبادئة إلى اسم الدالة المُنشأة. ولكي تستدعي الدالة على نسخ (instances) الصنف المُمدّد، عليك استخدام الرمز ".".

 

الدوال عالية المستوى ودوال Lambdas

الدوال عالية المستوى (higher-order functions) هي تلك الدوال التي تقبل دوالًا كمعاملات، أو تُعيد دالة. كما تُعد دوال Kotlin من الدرجة الأولى، أي أنّه يمكن تخزينها في هياكل البيانات والمتغيرات، ويمكن تمريرها كوسائط إلى دوال أخرى من المستوى العالي، أو إعادتها كنتيجة. خلاصة الأمر هو أنه يمكنك معاملة الدوال كما تعامل الأنواع الأخرى.

باعتبارها لغة برمجة منوعة ثابتيًا (statically typed)، تستخدم Kotlin مجموعة من أنواع الدوال لتمثيل الدوال. كما أتت بمجموعة من التركيبات المتخصصة في ذلك، مثل تعبيرات lambda.

تُعرَف الدوال المجهولة (Anonymous functions) وتعبيرات lambda أيضًا بالدوال الحرفية (function literals). هذه الدوال لا يُصرّح بها، ولكنها تُمرر كتعبيرات فوريًا.

 

Implicit Widening Conversions

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

 

الدوال المُضمنة (Inline Functions)

تُعرف المتغيرات التي تُستخدم في متن الدالة (body of the function) باسم الإغلاقات (closures). يمكن أن يؤدي استخدام دوال المستوى العالي إلى إبطاء البرنامج. وتُعد كل دالة في Kotlin كائنًا، كما أنها تُمسك إغلاقًا (captures a closure).

تتطلب كل من الأصناف وكائنات الدوال تخصيص الذاكرة. إضافة إلى الاستدعاءات الافتراضية (virtual calls) التي قد تسبب الحمل الزائد (overhead) في وقتَ التنفيذ. يمكن تجنب مثل هذا الحمل الزائد عبر تضمين تعبيرات lambda في Kotlin. ومن أمثلة ذلك الدالة lock()‎.

على عكس Kotlin، لا تدعم Java الدوال المضمّنة. إلا أنّ المُصرِّف (compiler) الخاص بلغة Java قادر على إنجاز التضمين باستخدام تابع نهائي (final method). هذا لأنه لا يمكن إعادة تعريف التوابع النهائية في الأصناف الفرعية. كما أنّ استدعاء التابع النهائي سيُحلُّ في وقت التصريف (compile time).

 

الدعم الأصلي للتفويض

في عالم البرمجة، التفويض (Delegation) هو عملية قيام كائن مستقبِل (receiving object) بتفويض العمليات إلى كائن مفوَّض ثان. Kotlin تدعم مقاربة التركيب (composition) أكثر من دعمها لنمط تصميم الوراثة (inheritance design pattern)، حيث إنَها تدعم التفويض من الدرجة الأولى (first-class delegation)، والمعروف أيضا باسم التفويض الضمني (implicit delegation).

تفويض الأصناف يمثل بديلًا للوراثة في Kotlin، ويجعل من الممكن استخدام الوراثة المتعددة. كما أنّ خاصيات Kotlin المفوَّضة تجنّب التكرار في الشيفرات البرمجية.

 

الحقول غير الخاصة (Non-private Fields)

التغليف (Encapsulation) هو أحد التقنيات الضرورية لتحقيق قابلية الصيانة (maintainability). فعن طريق تغليف تمثيل الكائنات، يمكن تحديد كيفية تفاعل المُستدعِيات (callers) معه. علاوة على ذلك، من الممكن تغيير التمثيل دون الحاجة إلى تعديل المُستدعيات شريطة أن تظل الواجهة البرمجية (API) العامة دون تغيير.

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

لا توفر Kotlin ميزة الحقول غير الخاصة.

 

تأمين القيمة الفارعة (Null Safety)

يُعتبر الاستثناء NullPointerExceptions أحد أكثر المشكلات إزعاجًا لمطوري Java. تتيح Java للمطورين تعيين قيمة فارغة (null) لأي متغير. ومع ذلك، إذا حاولوا استخدام مرجع لكائن يحمل قيمة فارغة، فسيُطلق الاستثناء NullPointerException

بخلاف Java، جميع الأنواع غير قابلة للإفراغ (non-nullable) في Kotlin افتراضيًا. إذا حاول المطور تعيين أو إرجاع قيمة فارغة في شيفرة Kotlin، فسوف تفشل العملية في وقت التصريف (compile time). لكن هناك طريقة لتجاوز هذا العائق. فلتعيين قيمة فارغة لمتغير في Kotlin، يلزم التصريح بأنّ ذلك المتغير قابل للإفراغ (nullable). يمكن ذلك عن طريق إضافة علامة استفهام بعد اسم النوع، على سبيل المثال:

val number: Int? = null

وبالتالي، لا وجود للاستثناء NullPointerExceptions في Kotlin. وإذا واجهتَ مثل هذا الاستثناء في Kotlin، فعلى الأرجح أنك قمت بتعيين قيمة فارغة بشكل صريح (explicitly)، أو أنّك تستخدم شيفرات Java خارجية.

 

الأنواع الأولية (Primitive Types)

هناك 8 أنواع من البيانات الأولية، منها char و double و float و int. على عكس Kotlin، فإنّ المتغيرات التي تنتمي إلى الأنواع الأولية لا تُعد كائنات في Java. هذا يعني أنها ليست كائنًا مُستنسخًا (instantiated) من صنف أو بنية (struct).

 

تحويلات الأنواع الذكية (Smart Casts)

قبل تحويل نوع الكائنات في Java، من الضروري التحقق أولًا من النوع. هذا واجب حتى في الحالات التي تكون فيها عملية التحويل واضحة.

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

 

الأعضاء الثابتون (Static Members)

لا تقدم Kotlin مفهوم الأعضاء الثابتين. وفي لغة Java، تشير الكلمة المفتاحية static إلى أنّ العضو المحدد الذي استُخدمت الكلمة المفتاحية معه ينتمي إلى النوع نفسه، وليس إلى نسخة (instance) من ذلك النوع. يعني ذلك أنه لن تُنشأ أو تُتشارك عبر كافة نُسخ الصنف إلا نسخة واحدة فقط من ذلك العضو الثابت.

 

دعم البانِيات (Support for Constructors)

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

 

المعامل الثلاثي (Ternary Operator)

على عكس Kotlin، تتضمن Java المعامل الثلاثي. يعمل معامل Java الثلاثي وكأنه عبارة if. إذ يتألف من شرط يتم تقييمه إلى إحدى القيمتين true أو false.

علاوة على ذلك، لمعامل Java الثلاثي قيمتان، ولا تُعاد إلا واحدة منهما فقط، بناء على ما إذا كان الشرط صحيحًا أم خاطئًا.

إليك صياغة معامل Java الثلاثي:

(condition) ? (value1) : (value 2)

 

محارف البدَل الخاصة بالأنواع (Wildcard Types)

يمثل الرمز "؟" نوعًا مجهولا. ويُعرف باسم محرف البدل (wildcard). هناك استخدامات عدة لأحرف البدل، سواء في أنواع الحقول، أو المتغيرات المحلية، أو المعاملات.

بينما تسمح صياغة Java باستخدام أحرف البدل مع الأنواع، فإنّ Kotlin لا تسمح بذلك. مع ذلك، فإنها تقدم شيئين مختلفين؛ وهما، تباين موقع التصريح (declaration-site variance)، وإسقاطات النوع (type projections) كبديل لمحارف البدل الخاصة بالنوع.

 

الأداة Annotation Processing Libraries في Kotlin

إضافة إلى دعم إطارات ومكتبات Java الحالية، تدعم Kotlin أيضًا إطارات عمل Java المتقدمة التي تعتمد على معالجة التأشير (annotation processing).

استخدام مكتبات Java التي تستخدم معالجة التأشيرات في Kotlin مختلف قليلاً عن استخدام مكتبات Java التي لا تستخدم معالجة التأشيرات.

يجب تحديد التبعية (dependency) باستخدام الإضافة kotlin-kapt. بعد ذلك، يجب استخدام أداة معالجة التأشيرات (Kotlin Annotation processing) بدلاً من annotationProcessor.

 

لم تفهم بعد؟ إليك الحل!

بعض الميزات تكون أفضل في Kotlin، وبعضها الآخر أفضل في Java. وبالنسبة للمطورين غير المستعدين للتخلي عن أيّ من هاتين اللغتين الرائدتين في تطوير تطبيقات Android، فهناك حل لذلك.

بغض النظر عن الاختلافات بين هاتين اللغتين، يمكن الدمج بينهما. فكل من Java و Kotlin تُصرّفان إلى رُقامة (bytecode). هذا يعني أنه من الممكن استدعاء شيفرة Java من Kotlin، والعكس بالعكس.

هذه المرونة لها ميزتان. الميزة الأولى أنها تعمل على تسهيل البدء في Kotlin من خلال إقحام شيفرات Kotlin بشكل تدريجي في مشاريع Java. والميزة الثانية أنه يمكن استخدام كلا اللغتين في وقت واحد في أي مشروع لتطوير تطبيقات Android.

 

خلاصة

في البرمجة العامة متعددة الأغراض، فإنّ للغة Java اليد العليا. على الجانب الآخر، يتزايد عدد المطورين والمؤسسات التي تستخدم Kotlin لتسريع تطوير تطبيقات Android.

لكل من Java و Kotlin مزايا خاصة بها. النقاش حول أيهما أفضل قد بدأ للتو، ولا يبدوا أنه سينتهي في أيّ وقت قريب.

 

ترجمة - وبتصرف - للمقال Kotlin vs Java: [2019] Most Important Differences That You Must Know لصاحبها Vijay Khatri.

التعليقات

يجب تسجيل الدخول أو التسجيل لتتمكّن من التعليق