Try-with-resources في Kotlin: كيف تتخلص من finally إلى الأبد

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

لماذا يبكي مبرمجو Java عند الانتقال إلى Kotlin؟

\n\n

تخيل: أنت تكتب كودًا بلغة Java، تفتح ملفًا، تقرأ البيانات، ثم... تنسى إغلاقه. ألم مألوف؟ في Java، تم اختراع بنية try-with-resources التي تستدعي تلقائيًا close(). ولكن عندما ينتقل المطورون إلى Kotlin، غالبًا ما يصابون بالحيرة: "أين هو try (Resource res = ...) المفضل لدي؟".

\n\n

مفسد: لا يوجد try-with-resources كبنية لغوية منفصلة في Kotlin. وهذه أخبار جيدة. بدلاً منها، تقدم اللغة حلاً أكثر أناقة وإيجازًا - دالة use. دعنا نفهم كيف يعمل هذا ولماذا سيصبح كودك أنظف.

\n\n

المشكلة: يجب إغلاق الموارد

\n\n

أي مورد خارجي - ملف، اتصال شبكة، قاعدة بيانات - يتطلب إدارة يدوية. إذا فتحت FileInputStream ولم تغلقه، فقد يتسرب النظام (تسرب ذاكرة)، وسيظل الملف مقفلاً للعمليات الأخرى.

\n\n

في Java، يتم حل هذه المشكلة بطريقتين:

\n\n
    \n
  • الطريقة القديمة: كتلة finally مع استدعاء صريح لـ close().
  • \n
  • الطريقة الجديدة: try-with-resources (ظهرت في Java 7).
  • \n
\n\n

ذهب Kotlin إلى أبعد من ذلك: يستخدم دوال التوسيع و اللامبدات لجعل الكود ليس فقط آمنًا، بل جميلًا أيضًا.

\n\n

كيف يبدو try-with-resources في Java؟

\n\n

للمقارنة، لنلق نظرة على كود Java الكلاسيكي:

\n\n
try (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
\n\n

نهج Kotlin: دالة use

\n\n

في Kotlin، للعمل الآمن مع الموارد، تُستخدم دالة use. تم تعريفها كامتداد للواجهة Closeable (و AutoCloseable للتوافق مع Java).

\n\n

إليك كيف يبدو نفس المثال في Kotlin:

\n\n
import 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 من الخارج:

\n\n
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 معرفة في المكتبة القياسية تقريبًا كالتالي:

\n\n
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                

المدونات

توصيات الكتب