قبلاً چندین استفاده از کلاس Optional را پوشش دادهایم، به خصوص در مورد فراخوانی یک عملیات پایانی روی استریمی که عنصر همانی ندارد. این مورد آسان نیست چون نمیتوانید هیچ مقداری برگردانید، از جمله 0، و بازگرداندن null باعث میشود کد شما مجبور به مدیریت مقادیر null در جاهایی شود که نمیخواهید.
توانایی استفاده از کلاس Optional برای مواردی که نمیتوانید مقداری تولید کنید، فرصتهای زیادی برای الگوهای بهتر، به خصوص مدیریت بهتر خطاها فراهم میکند. این دلیل اصلی استفاده از شیءهای Optional است: آنها نشان میدهند یک متد ممکن است در شرایط خاصی نتیجه تولید نکند. ذخیرهی نمونهای از Optional در فیلد، لیست، نقشه یا ارسال آن به عنوان آرگومان متد آنچه Optional برای آن ساخته شده نیست.
به زبان ساده، Optional<T> کلاسی wrapper است که میتواند یک مرجع: Optional<T> یا یک مقدار: OptionalInt، OptionalLong و OptionalDouble دربر بگیرد. تفاوت با wrapper type های کلاسیک: Integer، Long، Double و غیره این است که شیء optional میتواند خالی باشد. چنین نمونهای هیچ چیزی دربر نمیگیرد.
کلاس Optional کلاسی final با سازندهی private است. پس تنها راه ایجاد نمونهی آن فراخوانی یکی از متدهای factory است. سه مورد وجود دارد.
Optional.empty() ایجاد کنید.Optional.of() دربر بگیرید. ارسال مرجع null به این متد مجاز نیست. در آن صورت NullPointerException دریافت خواهید کرد.Optional.ofNullable() دربر بگیرید. میتوانید مرجع null به این متد بدهید. در آن صورت، Optional خالی دریافت خواهید کرد.اینها تنها راههای ایجاد نمونهی این کلاس هستند. همانطور که میبینید، نمیتوانید مرجع null را با یک شیء optional دربر بگیرید. نتیجه این است که باز کردن یک Optional غیرخالی همیشه مرجع غیرnull برمیگرداند.
روشهای متعددی برای استفاده از یک optional و دسترسی به عنصر دربرگرفتهشده وجود دارد.
باز کردن یک optional برای دریافت محتوای آن باید با احتیاط انجام شود چون اگر optional خالی باشد
NoSuchElementExceptionایجاد میکند. مگر اینکه مطمئن باشید در optional شما عنصری وجود دارد، باید این عملیات را با ابتدا آزمایش کردن محافظت کنید.
دو متد برای بررسی خالی بودن Optional وجود دارد:
isPresent()isEmpty()، اضافهشده در Java SE 11.برای باز کردن Optional:
get(): deprecated شده چون شبیه getter به نظر میرسد اما میتواند NoSuchElementException ایجاد کند.orElseThrow(): الگوی ترجیحی از Java SE 10.orElseThrow(Supplier exceptionSupplier): همان کار قبلی با exception سفارشی.همچنین میتوانید سعی کنید محتوای یک شیء optional را بدست آورید و شیئی ارائه دهید که در صورت خالی بودن optional برگردانده شود:
orElse(T returnedObject).orElseGet(Supplier<T> supplier): supplier فقط در صورت نیاز فراخوانی میشود.در نهایت، میتوانید اگر این optional خالی است یک optional دیگر ایجاد کنید:
کلاس Optional الگوهایی ارائه میدهد تا پردازش شیءهای optional خود را با پردازش stream یکپارچه کنید. متدهایی دارد که مستقیماً معادل متدهای Stream API هستند: map()، filter() و flatMap().
این متدها Optional ای برمیگردانند با این قرارداد:
فرض کنید لیستی از نمونههای Customer با ویژگی id دارید. باید نام مشتری با شناسهی مشخص را پیدا کنید.
Name of customer with id 2: James
فرض کنید مجموعهای از مقالات دارید که باید پردازش کنید. یک مقاله عنوان، سال شروع و لیستی از نویسندگان دارد. نویسنده نام دارد.
باید بدانید کدام نویسندگان بیشترین مقالات را با هم نوشتهاند.
ایدهی اول شما میتواند ساختن استریمی از جفت نویسندگان برای یک مقالهی مشخص باشد. در واقع حاصلضرب دکارتی مجموعهی نویسندگان یک مقالهی مشخص است. اما به تمام جفتها نیاز ندارید.
بیایید دو record برای این مدل بنویسیم.
ایجاد متد factory در record PairOfAuthors به شما امکان میدهد نمونههای مجاز این record را کنترل کنید. برای نشان دادن اینکه این متد factory ممکن است نتواند نتیجه تولید کند، میتوانید آن را در Optional پیچید.
بیایید تابعی بنویسیم که Stream<PairOfAuthors> برای یک مقالهی مشخص ایجاد کند.
میتوانید با یک هیستوگرام که کلیدها جفت نویسندگان و مقادیر تعداد مقالاتی هستند که با هم نوشتهاند، بدانید کدام دو نویسنده بیشترین همکاری را داشتهاند.
The authors that published the most together are Patricia and Michael, they wrote 4 articles together.
میتوانید همین پردازش را برای هر سال انجام دهید. این کار با flatMapping() collector ممکن است.
الگوی امنتر فراخوانی نکردن orElseThrow() است. با ایجاد collector ای که Optional برمیگرداند، میتوانید از Optional.stream() و flatMap() برای حذف آرام Optional های خالی استفاده کنید.
Map 1
4 :: Optional[four]
3 :: Optional.empty
2 :: Optional[two]
1 :: Optional.empty
Map 2
2 :: two
4 :: four
ifPresent(Consumer consumer): اگر Optional خالی نباشد consumer فراخوانی میشود.ifPresentOrElse(Consumer consumer, Runnable runnable): اگر Optional خالی باشد Runnable فراخوانی میشود.قانون ۱: هرگز null برای متغیر Optional یا مقدار بازگشتی استفاده نکنید.
قانون ۲: هرگز orElseThrow() یا get() فراخوانی نکنید مگر مطمئن باشید Optional خالی نیست.
قانون ۳: جایگزینهایی به ifPresent()، orElseThrow() یا get() ترجیح دهید.
قانون ۴: Optional را برای جلوگیری از بررسی null بودن یک مرجع ایجاد نکنید.
قانون ۵: Optional را در فیلدها، پارامترهای متدها، collection ها و نقشهها استفاده نکنید.
قانون ۶: عملیاتهای حساس به همانی مثل برابری مرجع، hash code همانی و هماهنگسازی روی شیء optional اعمال نکنید.
قانون ۷: فراموش نکنید شیءهای Optional قابل serialization نیستند.
اکنون میتوانید آنها را در یک collector collectingAndThen() واحد ادغام کنید. میتوانید collector groupingBy() را به عنوان آرگومان اول و تابع finisher را به عنوان آرگومان دوم تشخیص دهید.
اکنون میتوانید الگوی کامل را با عملیات اولیه flatmap و این collector بنویسید.
با کمک collector flatMapping()، میتوانید این کد را با یک collector واحد بنویسید، با ادغام flatMap() میانی و collector پایانی. کد زیر معادل کد قبلی است.
یافتن دو نویسندهای که در هر سال بیشترین انتشار را داشتهاند، فقط به ارسال collector flatMapping() به عنوان downstream collector به groupingBy() مناسب بستگی دارد.
شاید به یاد داشته باشید که در عمق collector flatMapping() فراخوانی Optional.orElseThrow() وجود دارد. بررسی اینکه آیا این فراخوانی ممکن است ناموفق باشد در الگوی قبلی آسان بود، چون حدس زدن اینکه در این نقطه optional خالی باشد کار سادهای بود.
اکنون که از این collector به عنوان downstream collector استفاده کردیم، وضعیت متفاوت است. چگونه میتوان مطمئن شد که برای هر سال، حداقل یک مقاله با حداقل دو نویسنده وجود دارد؟ ایمنتر است این کد را در برابر هرگونه NoSuchElementException محافظت کنید.
آنچه در بافت اول الگوی قابل قبولی بود، اکنون بسیار خطرناکتر است. مقابله با آن شامل فراخوانی نکردن orElseThrow() در وهلهی اول است.
در آن صورت، collector به صورت زیر در میآید. به جای ایجاد یک جفت کلید-مقدار از جفت نویسندگان و یک عدد long، نتیجه را در یک شیء optional پیچیده میکند.
توجه کنید orElseThrow() دیگر فراخوانی نمیشود، که منجر به optional بودن در امضای collector میشود.
این optional همچنین در امضای collector flatMapping() ظاهر میشود.
استفاده از این collector برای ایجاد نقشهی جفت نویسندگان در هر سال، نقشهای از نوع Map<Integer, Optional<Map.Entry<PairOfAuthors, Long>>> ایجاد میکند، نوعی که به آن نیاز نداریم: داشتن نقشهای که مقادیرش optional های خالی هستند بیفایده و شاید پرهزینه است. این یک antipattern است. متأسفانه راهی وجود ندارد که قبل از محاسبهی این مقدار بیشینه حدس بزنید این optional خالی خواهد بود.
هنگامی که این نقشهی میانی ساخته شد، باید از شر optional های خالی خلاص شوید تا نقشهای که هیستوگرام مورد نیاز شما را نشان میدهد بسازید. از همان تکنیک قبلی استفاده خواهیم کرد: فراخوانی متد stream() روی شیءهای optional در flatMap() تا عملیات flatMap() optional های خالی را به آرامی حذف کند.
الگو به صورت زیر است.
تابع flatmap در این الگو را بررسی کنید. این تابع یک entry را میگیرد که مقدارش از نوع Optional<Map.Entry<PairOfAuthors, Long>> است و map() را روی این optional فراخوانی میکند.
اگر optional خالی باشد، این فراخوانی optional خالی برمیگرداند. سپس تابع mapping نادیده گرفته میشود. فراخوانی بعدی stream() استریم خالی برمیگرداند، که از استریم اصلی حذف میشود چون در یک فراخوانی flatMap() هستیم.
اگر مقداری در optional وجود داشته باشد، تابع mapping با این مقدار فراخوانی میشود. این تابع mapping یک جفت کلید-مقدار جدید با همان کلید و این مقدار موجود ایجاد میکند. این جفت کلید-مقدار از نوع Map.Entry<PairOfAuthors, Long> است و توسط متد map() در یک شیء optional پیچیده میشود. فراخوانی stream() استریمی با محتوای این optional میسازد که سپس توسط فراخوانی flatMap() باز میشود.
این الگو Stream<Map.Entry<Integer, Optional<Map.Entry<PairOfAuthors, Long>>>> با optional های خالی را به Stream<Map.Entry<Integer, Map.Entry<PairOfAuthors, Long>>> نگاشت میکند و تمام جفتهای کلید/مقداری که optional خالی داشتند را حذف میکند.
بازسازی نقشه را میتوان به صورت ایمن با collector toMap() انجام داد، چون میدانید که نمیتوان کلید یکسان دو بار در این استریم داشت.
میتوانید این الگو را در مثال زیر در عمل ببینید.
اجرای کد قبلی خروجی زیر را چاپ میکند.میتوانید ببینید optional های خالی از نقشهی نتیجه حذف شدهاند.
Map 1
4 :: Optional[four]
3 :: Optional.empty
2 :: Optional[two]
1 :: Optional.empty
Map 2
2 :: two
4 :: four
این الگو از سه نکتهی مهم Stream API و optional ها استفاده میکند.
Optional.map() که اگر روی optional خالی فراخوانی شود، optional خالی برمیگرداند.Optional.stream() که استریمی روی محتوای یک optional باز میکند. اگر optional خالی باشد، استریم بازگرداندهشده نیز خالی است. این امکان انتقال بیدرز از فضای optional به فضای استریم را فراهم میکند.Stream.flatMap() که استریمهای ساختهشده از optional ها را باز میکند و استریمهای خالی را به آرامی حذف میکند.کلاس Optional همچنین دو متد دارد که consumer را به عنوان آرگومان میگیرند.
ifPresent(Consumer consumer): این متد consumer ارائهشده را با محتوای این optional فراخوانی میکند، اگر محتوایی وجود داشته باشد. در واقع معادل متد Stream.forEach(Consumer) است.ifPresentOrElse(Consumer consumer, Runnable runnable): این متد اگر optional خالی نباشد همان کار متد قبلی را انجام میدهد. اگر خالی باشد، نمونهی ارائهشدهی Runnable را فراخوانی میکند.قانون ۱: هرگز null برای متغیر Optional یا مقدار بازگشتی استفاده نکنید.
قانون ۲: هرگز orElseThrow() یا get() فراخوانی نکنید مگر مطمئن باشید Optional خالی نیست.
قانون ۳: جایگزینهایی به ifPresent()، orElseThrow() یا get() ترجیح دهید.
قانون ۴: Optional را برای جلوگیری از بررسی null بودن یک مرجع ایجاد نکنید.
قانون ۵: Optional را در فیلدها، پارامترهای متدها، collection ها و نقشهها استفاده نکنید.
قانون ۶: عملیاتهای حساس به همانی مثل برابری مرجع، hash code همانی و هماهنگسازی روی شیء optional اعمال نکنید.
قانون ۷: فراموش نکنید شیءهای Optional قابل serialization نیستند.
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://dev.java/learn/