try-with-resources وواجهة AutoCloseable
try-with-resources وواجهة AutoCloseable
في كل مرة يفتح فيها كودك ملفًا أو اتصال شبكة أو اتصال قاعدة بيانات أو أي مورد خارجي آخر، يجب إغلاق ذلك المورد عند الانتهاء منه. نسيان إغلاق الموارد من أكثر الأخطاء شيوعًا في Java: يتسبب في تسرب الذاكرة، واستنزاف مقابض الملفات، وقفل الملفات بحيث لا يستطيع أي كود آخر فتحها.
قدّم Java 7 تعليمة try-with-resources لحل هذه المشكلة تلقائيًا. بدلًا من كتابة منطق الإغلاق بنفسك، تُعلن عن المورد داخل قوسَي try ويضمن Java إغلاقه — حتى لو رُمي استثناء.
المشكلة: تنظيف الموارد يدويًا عرضة للأخطاء
قبل try-with-resources، كان يجب إغلاق الموارد في كتلة finally. حتى الكود الحذِر كان يحتوي على أخطاء خفية:
هذا عشرة أسطر من الكود المكرر لإغلاق مورد واحد بأمان. إذا رمى close() استثناءً أيضًا، يُبتلع الاستثناء الأصلي — خطأ يصعب تتبعه جدًا.
الحل: try-with-resources
أعلِن عن المورد في القوسين بعد try. يستدعي Java تلقائيًا close() عند نهاية الكتلة، سواء خرجت بشكل طبيعي أو عبر استثناء:
يُعيد المُترجم كتابة هذا الكود بصورة تتعامل بشكل صحيح مع كل الحالات الطرفية، بما فيها الاستثناءات المكبوتة.
close() دائمًا — حتى عند رمي استثناء داخل كتلة try، وحتى إن لم تكن هناك كتلة catch على الإطلاق.
موارد متعددة في تعليمة واحدة
يمكنك الإعلان عن موارد متعددة مفصولة بفاصلة منقوطة. تُغلق بـترتيب عكسي للإعلان — آخر مورد فُتح هو أول من يُغلق:
واجهة AutoCloseable
تعمل try-with-resources مع أي كلاس يُنفّذ AutoCloseable (أو الواجهة الفرعية Closeable). تحتوي الواجهة على طريقة واحدة فقط:
جميع كلاسات I/O القياسية في Java (InputStream، OutputStream، Reader، Writer، Scanner، Connection، PreparedStatement، إلخ) تُنفّذ هذه الواجهة بالفعل. لهذا تعمل جميعها مع try-with-resources من دون أي إعداد إضافي.
كتابة كلاس AutoCloseable خاص بك
يمكنك جعل أي كلاس يعمل مع try-with-resources بتنفيذ AutoCloseable. مثال شائع هو كلاس يُغلّف اتصال شبكة أو ملفًا مؤقتًا:
الآن يمكنك استخدامه تمامًا مثل أي مورد مدمج:
close() أكثر من مرة دون رمي خطأ. استخدم علمًا منطقيًا (مثل open أعلاه) للحماية من الإغلاق المزدوج.
الاستثناءات المكبوتة
ماذا يحدث عندما ترمي كتلة try استثناءً ويرمي close() استثناءً أيضًا؟ في الكود القديم بكتلة finally، كان الاستثناء الثاني يُخفي الأول. تتعامل try-with-resources مع هذا بشكل صحيح: استثناء كتلة try هو الاستثناء الرئيسي، والاستثناء الصادر عن close() يُرفق به كـاستثناء مكبوت.
يمكنك استرجاع الاستثناءات المكبوتة بـgetSuppressed() التي تُعيد Throwable[]. لا تضيع أي معلومات.
قراءة ملف سطرًا بسطر — مثال واقعي
عمليًا، الاستخدام الأكثر شيوعًا لـtry-with-resources هو قراءة ملفات نصية بـBufferedReader:
BufferedReader يُغلق تلقائيًا FileReader المُغلَّف بداخله، لأن استدعاء close ينتقل عبر السلسلة. إعلان كليهما منفصلَين قد يُغلق القارئ الداخلي مرتين.
الخلاصة
try-with-resources هي الطريقة الحديثة والصحيحة لإدارة أي مورد يجب إغلاقه. أعلِن عن الموارد في قوسَي try()، ونفِّذ AutoCloseable في كلاساتك الخاصة، ودَع JVM يتولى التنظيف. ستحصل على كود أقصر وأخطاء أقل ومعالجة صحيحة للاستثناءات المكبوتة — كل ذلك مجانًا.