Optional مع Streams
Optional مع Streams
يوجد في Streams عمليتان نهائيتان — findFirst() وfindAny() — لا تُعيدان قيمة ملموسة مباشرة، بل تُعيدان Optional<T>. يتناول هذا الدرس السبب وراء ذلك، وكيفية التعامل مع هذا الـOptional بأمان.
لماذا تُعيد عمليات البحث Optional؟
قد يكون الـstream فارغًا. لو طلبت "أعطني أول عنصر" وكان الـstream لا يحتوي أي عناصر، فلا توجد قيمة منطقية للإعادة. في إصدارات Java القديمة كان الحل هو إعادة null، وكان على كل كود يستدعي الدالة أن يتذكّر التحقق من null. يجعل Optional<T> احتمال الغياب صريحًا في نظام الأنواع — ويُجبر المُصرِّف على معالجته.
null.
findFirst() — بحث حتمي
تُعيد findFirst() أول عنصر في الـstream يجتاز عمليات الـfilter السابقة، مُغلَّفًا داخل Optional. "الأول" يعني العنصر الأول وفق ترتيب المصدر الأصلي (كقائمة List مثلًا).
يجد الـstream "Alice" أولًا ويتوقف — لا يفحص بقية القائمة. تعمل الـstreams بشكل كسول: تسحب العمليات النهائية فقط العناصر التي تحتاجها.
findAny() — بحث ملائم للمعالجة المتوازية
تُعيد findAny() أي عنصر يُحقق شروط الـfilter السابقة. في الـstream التسلسلي عادةً ما تُعيد العنصر الأول، لكن لا يوجد ضمان من JVM على ذلك. في الـstream المتوازي تُعيد أي عنصر تجده أي خيط تنفيذ أولًا، وهذا أسرع لأن الخيوط لا تحتاج الاتفاق على الترتيب.
findFirst() حين تحتاج نتيجة متوقعة وقابلة للتكرار (الاختبارات، المعالجة المرتّبة). استخدم findAny() حين تحتاج فقط أي تطابق وتستخدم stream متوازيًا لتحسين الأداء.
معالجة نتيجة Optional
تُعطيك كلتا العمليتين Optional<T>. توجد عدة طرق لفتح القيمة منه بأمان.
isPresent() / get()
أوضح أسلوب — لكنه الأكثر تفصيلًا:
get() دون التحقق أولًا بـisPresent() (أو استخدام أحد البدائل الأكثر أمانًا أدناه). إذا كان الـOptional فارغًا، يُلقي get() استثناء NoSuchElementException — وهو تمامًا ما صُمِّم Optional لمنعه.
orElse() — توفير قيمة افتراضية
تُعيد القيمة إن وُجدت، أو القيمة الاحتياطية التي تُمرّرها:
orElseGet() — قيمة افتراضية كسولة عبر Supplier
مثل orElse()، لكن القيمة الاحتياطية تُحسَب فقط عند الحاجة فعليًا. فضّل هذا حين يكون الاحتياطي مكلفًا (استعلام قاعدة بيانات، بناء كائن، إلخ):
orElseThrow() — الفشل الصريح
يُلقي استثناءً إذا كان الـOptional فارغًا. استخدمه حين يكون غياب القيمة خطأً برمجيًا حقيقيًا:
ifPresent() — تنفيذ تأثير جانبي عند الوجود فقط
ينفّذ Consumer عند احتواء الـOptional قيمة، ولا يفعل شيئًا عند فراغه:
سلسلة تحويلات Optional
يمتلك Optional نفسه دوال map() وfilter()، فيمكنك تحويل القيمة بداخله دون فتحه مبكرًا:
مرجع سريع
findFirst()— أول تطابق وفق ترتيب المصدر؛ للاستخدام التسلسلي الحتمي.findAny()— أي تطابق؛ يُفضَّل على الـstreams المتوازية للأداء.orElse(T)— قيمة افتراضية ثابتة.orElseGet(Supplier)— قيمة افتراضية كسولة، تُحسَب فقط عند الفراغ.orElseThrow(Supplier)— إلقاء استثناء حين يكون الغياب خطأً.ifPresent(Consumer)— تأثير جانبي عند وجود القيمة.map(Function)— تحويل القيمة مع البقاء داخل Optional.
إتقان هذه الأنماط يجعل كودك يُعبّر بوضوح عن نواياه: القيم الغائبة مُعالَجة صراحةً، وأخطاء NPE مُتجنَّبة بالتصميم، والبحث المتوازي سهل التعبير عنه.