لماذا يبكي مبرمجو Java عند الانتقال إلى Kotlin؟
\n\nتخيل: أنت تكتب كودًا بلغة Java، تفتح ملفًا، تقرأ البيانات، ثم... تنسى إغلاقه. ألم مألوف؟ في Java، تم اختراع بنية try-with-resources التي تستدعي تلقائيًا close(). ولكن عندما ينتقل المطورون إلى Kotlin، غالبًا ما يصابون بالحيرة: "أين هو try (Resource res = ...) المفضل لدي؟".
مفسد: لا يوجد try-with-resources كبنية لغوية منفصلة في Kotlin. وهذه أخبار جيدة. بدلاً منها، تقدم اللغة حلاً أكثر أناقة وإيجازًا - دالة use. دعنا نفهم كيف يعمل هذا ولماذا سيصبح كودك أنظف.
المشكلة: يجب إغلاق الموارد
\n\nأي مورد خارجي - ملف، اتصال شبكة، قاعدة بيانات - يتطلب إدارة يدوية. إذا فتحت FileInputStream ولم تغلقه، فقد يتسرب النظام (تسرب ذاكرة)، وسيظل الملف مقفلاً للعمليات الأخرى.
في Java، يتم حل هذه المشكلة بطريقتين:
\n\n- \n
- الطريقة القديمة: كتلة
finallyمع استدعاء صريح لـclose(). \n - الطريقة الجديدة:
try-with-resources(ظهرت في Java 7). \n
ذهب Kotlin إلى أبعد من ذلك: يستخدم دوال التوسيع و اللامبدات لجعل الكود ليس فقط آمنًا، بل جميلًا أيضًا.
\n\nكيف يبدو try-with-resources في Java؟
\n\nللمقارنة، لنلق نظرة على كود Java الكلاسيكي:
\n\ntry (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {\n String line = br.readLine();\n System.out.println(line);\n} catch (IOException e) {\n e.printStackTrace();\n}\n// br يُغلق تلقائيًا\n\n\nكل شيء جيد، ولكن هناك تفاصيل:
\n- \n
- يجب تعريف المتغير داخل أقواس
try. \n - يتم دعم الفئات التي تطبق
AutoCloseableفقط. \n - يصبح الكود ضخمًا إذا كان هناك عدة موارد. \n
نهج Kotlin: دالة use
\n\nفي Kotlin، للعمل الآمن مع الموارد، تُستخدم دالة use. تم تعريفها كامتداد للواجهة Closeable (و AutoCloseable للتوافق مع Java).
إليك كيف يبدو نفس المثال في Kotlin:
\n\nimport java.io.BufferedReader\nimport java.io.FileReader\n\nfun main() {\n BufferedReader(FileReader("file.txt")).use { reader ->\n val line = reader.readLine()\n println(line)\n }\n // reader يُغلق تلقائيًا\n}\n\n\nنظيف، موجز، بدون try-catch زائد؟ ليس تمامًا. use بحد ذاته لا يلتقط الاستثناءات. يضمن فقط استدعاء close() في نهاية الكتلة. إذا كنت بحاجة لمعالجة الخطأ، أضف try-catch من الخارج:
try {\n BufferedReader(FileReader("file.txt")).use { reader ->\n val line = reader.readLine()\n println(line)\n }\n} catch (e: IOException) {\n println("خطأ في قراءة الملف: ${e.message}")\n}\n\n\nكيف يعمل use تحت الغطاء؟
\n\nلفهم السحر، لنلقي نظرة على المصادر الأصلية لـ Kotlin. دالة use معرفة في المكتبة القياسية تقريبًا كالتالي:
public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {\n var exception: Throwable? = null\n try {\n return block(this)\n } catch (e: Throwable) {\n exception = e\n throw e\n } finally {\n when {\n exception == null -> close()\n else -> try {\n close()\n } catch (closeException: Throwable) {\n // يكتم الاستثناء، ويضيفه إلى الاستثناء الرئيسي\n exception.addSuppressed(closeExcep