Menu
flag Ar iconالعربيةdown icon

Java Optional: تجنّب null و NullPointerException

ما هو java.util.Optional، وكيف تنشئ واحدًا، وكيف تقرأ قيمته بأمان باستخدام map وfilter وorElse وifPresent بدلًا من فحوصات null.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

المشكلة التي يحلّها Optional

الدالة التي قد لا تملك إجابة كان أمامها دائمًا خيار محرج في Java: أن تُعيد null وتأمل أن يتذكّر المستدعي الفحص. وهو في العادة لا يتذكّر، فتكون النتيجة NullPointerException يظهر بعيدًا عن الدالة التي أعادت null.

Optional<T> صندوق صغير إمّا أن يحتوي قيمة وإمّا أن يكون فارغًا صراحةً. فبإعادة Optional<User> بدلًا من User، تقول الدالة للمستدعي "قد لا يجد هذا شيئًا" في توقيعها ذاته - ويوجّهه المترجِم نحو معالجة تلك الحالة.

إنشاء Optional

هناك ثلاث دوال مصنع، واختيار الصحيحة منها مهم:

  • Optional.of(value) - يجب أن تكون القيمة غير null، وإلّا رمى NullPointerException على الفور.
  • Optional.ofNullable(value) - فارغ إن كانت القيمة null، وحامل لها فيما عدا ذلك. استخدمه لتغليف شيء قد يكون null.
  • Optional.empty() - optional فارغ.

من الأخطاء الشائعة اللجوء إلى Optional.of(x) مع قيمة قد تكون null - فذلك يُفسد الغرض ويرمي الاستثناء نفسه الذي كنت تحاول تجنّبه. وعند الشكّ، استخدم ofNullable.

قراءة القيمة بأمان

ما إن يصبح بحوزتك optional، يكون الهدف إخراج القيمة دون افتراض وجودها. الأداة الفظّة هي get()، وينبغي ألّا تستخدمها تقريبًا أبدًا:

Optional<String> name = Optional.empty();
String value = name.get();   // يرمي NoSuchElementException - مجرد NPE متنكّر

بدلًا من ذلك، وفّر قيمة بديلة أو تفاعل مع وجود القيمة. يُعيد orElse قيمة افتراضية عند الفراغ؛ ويُشغّل ifPresent شيفرة فقط عندما توجد قيمة:

استخدم orElseGet(supplier) بدلًا من orElse حين يكون بناء القيمة الافتراضية مكلفًا - فالـ supplier لا يُنفَّذ إلّا إذا كان الـ optional فارغًا فعلًا. أمّا وسيط orElse فيُقيَّم دائمًا، حتى حين لا يكون لازمًا.

‏orElseThrow لـ "يُفترض أن يوجد هذا"

أحيانًا يكون الفراغ خطأً حقًّا - قيمة إعداد مطلوبة، أو مستخدم يُفترض وجوده في قاعدة البيانات. يحوّل orElseThrow الـ optional الفارغ إلى استثناء واضح من اختيارك:

هذا أوضح في القراءة بكثير من كتلة if (opt.isPresent())، ويجعل الفشل صريحًا عند النقطة التي يحدث فيها.

التحويل باستخدام map و filter

العائد الحقيقي هو التسلسل. يطبّق map دالةً على القيمة فقط إن كانت موجودة ويترك الـ optional الفارغ فارغًا - فتحوّل دون أن تلمس null إطلاقًا. ويُسقط filter القيمة إن لم تجتَز اختبارًا.

إن كانت دالة التحويل نفسها تُعيد Optional، فاستخدم flatMap بدلًا من map لتتجنّب Optional<Optional<T>> المربك. وهذا يعكس تمييز map/flatMap الذي رأيته مع الـ streams - فالـ Optional يتصرّف إلى حدّ كبير كأنه stream من عنصر واحد أو لا عنصر.

أين ينتمي Optional (وأين لا)

Optional مصمَّم ليكون نوعًا مُعادًا للدوال التي قد لا تنتج نتيجة بصورة مشروعة - والأمثلة النموذجية هي Stream.findFirst() وعمليات البحث على نمط Map والتحليل (parsing). استخدمه هناك فتوثّق واجهة برمجتك ثغراتها بنفسها.

وهو ليس مخصّصًا لـ:

  • الحقول. فهو غير قابل للتسلسل (serializable) ويضيف كائنًا لكل حقل. استخدم مراجع عادية وتحقّق في الباني (constructor).
  • معاملات الدالة. إذ يضطر المستدعي عندئذٍ إلى تغليف الوسائط بـ Optional.of(...)، وهو أكثر ضجيجًا من تحميل زائد (overload) أو معامل يقبل null.
  • المجموعات. أعِد List فارغة، لا Optional<List>. فالمجموعة الفارغة تعني أصلًا "لا شيء".

حين يُعامَل بوصفه نوعًا مُعادًا، يحوّل Optional أخطاء null الصامتة إلى تنبيهات في وقت الترجمة لمعالجة الحالة المفقودة.

التالي: الاستثناءات

يعالج Optional حالة "قد لا توجد قيمة" اليومية بنظافة، لكن بعض حالات الفشل استثنائية حقًّا - ملف لا يُفتح، شبكة تنقطع، مدخل يتعذّر تحليله. تنمذج Java تلك الحالات بالاستثناءات، وذلك هو موضوع الصفحة التالية.

الأسئلة الشائعة

ما هو Optional في Java ولماذا نستخدمه؟

Optional<T> هو حاوية إمّا أن تحمل قيمة وإمّا أن تكون فارغة. وُجد ليجعل "قد لا توجد قيمة" أمرًا صريحًا في نوع القيمة المُعادة من الدالة، فيُدفع المستدعي إلى معالجة الحالة الفارغة بدلًا من نسيان فحص null والوقوع في NullPointerException أثناء التشغيل. استخدمه بالأساس كنوع مُعاد للدوال التي قد لا تنتج نتيجة، مثل بحث لا يجد شيئًا.

ما الفرق بين Optional.of و Optional.ofNullable؟

Optional.of(x) يرمي NullPointerException فورًا إذا كان x يساوي null - استخدمه حين تعلم أن القيمة ليست null. أمّا Optional.ofNullable(x) فيُعيد Optional فارغًا حين يكون x يساوي null، وواحدًا حاملًا للقيمة فيما عدا ذلك - استخدمه حين قد تكون القيمة null. وOptional.empty() يعطيك optional فارغًا مباشرة.

هل ينبغي أن أستدعي Optional.get() لقراءة القيمة؟

تجنّب get() المجرّد - فهو يرمي NoSuchElementException إذا كان الـ optional فارغًا، وما هو إلّا NullPointerException باسم آخر. فضّل orElse أو orElseGet أو orElseThrow أو ifPresent أو map كي تُعالَج الحالة الفارغة دائمًا. وإن كان لا بدّ من الفحص أولًا، فاحترس باستخدام isPresent()، لكنّ الأساليب الوظيفية أنظف.

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن