ویژگیهای جدید Stream API در جاوا 9
در این نوشته ویژگیهای جدید اضافه شده به Stream API در جاوا 9 را مرور خواهیم کرد.
متدهای takeWhile/dropWhile
فرض کنید میخواهیم با این الگوریتم یک استریم بسازیم: عضو اول استریم یک کاراکتر “*” باشد و اعضای بعدی از چسباندن یک “*” دیگر به عضو قبلی تولید شوند و این کار تا آن جا ادامه پیدا کند که طول عضو کمتر از 10 باشد.
این کار در جاوا 8 چگونه انجام میشد؟ میتوانستیم از یکی از متدهای limit یا allMatch استفاده کنیم که کاربرد اصلی آنها چیز دیگری است. با استفاده از جاوا 9 کد به شکل زیر نوشته میشود:
Stream<String> stream = Stream.iterate("", s -> s + "*") .takeWhile(s -> s.length() < 10);
متد takeWhile نمونهای از جنس واسط Predicate دریافت میکند که میتوان با استفاده از آن چک کرد طول عضو به ده رسیده است یا نه. بسته به «مرتب» بودن یا نبودنِ استریمی که takeWhile روی آن اجرا میشود، با این متد میتوان «طولانیترین پیشوند[1]» یا «زیر مجموعه[2]»ای از این اعضا را به دست آورد.
طولانیترین پیشوند: یک توالی پیوسته از المانهای استریم است که در شرط موجود در Predicate صدق میکنند. اولین المان توالی، اولین المان استریم اصلی است و المان بعد از آخرین المان توالی، عضوی است که در شرط Predicate صدق نمیکرده است.
زیر مجموعه: مجموعهای از المانها (و نه همۀ المانها)یی است که در شرط داده شده صدق میکنند.
پس از تعریف این دو اصلاح میتوانیم عملکرد dropWhile را به این شکل شرح دهیم:
dropWhile دقیقا عکس عملکرد takeWhile را دارد. اگر المانهای استریم مرتب باشند، dropWhile ابتدا طولانیترین پیشوند را حذف میکند و بعد باقی ماندۀ استریم را برمیگرداند.
اگر استریم مرتب نباشد، ابتدا یک زیرمجموعه که در شرط Predicate صدق میکند را حذف میکند و بعد ادامۀ استریم را برمیگرداند.
به عنوان مثال اگر بخواهیم پنج المان اول استریمی که تولید کردهایم را حذف کنیم، میتوانیم بنویسیم:
stream.dropWhile(* -> !s.contains("*****"));
به صورت خلاصه، dropWhile تا زمانی که المان در شرط داده شده صدق میکند، حذف میشود و پس از این که یک المان در شرط صدق نکرد، تمام المانهای بعدی برگردانده میشوند.
متد iterate
با استفاده از متد iterate ای که در جاوا 8 معرفی شده بود میتوانستیم یک توالی مرتب و بیپایان بسازیم. به عنوان مثال یک توالی بینهایت از اعداد زوج را به این شکل میتوان چاپ کرد:
Stream.iterate(0, num->num+2).forEach(System.out::println);
ویژگی جدید یک نسخۀ overload شده از همین متد است که میتواند یک توالی محدود بسازد. در این متد با اضافه کردن یک شرط در پارامترهای تابع میتوان مشخص کرد تولید المانهای استریم چهموقع به اتمام برسد. استفاده از آن بسیار راحت است:
Stream.iterate(0, i -> i < 10, i -> i + 1) .forEach(System.out::println);
کاری که تکه کد فوق انجام میدهد، توسط for به شکل زیر قابل انجام است:
for (int i = 0; i < 10; ++i) { System.out.println(i); }
متد ofNullable
گاهی مجبور میشویم استریمی از یک سری شی بسازیم که ممکن است یک یا چند مورد از این اشیا null باشند. اگر بخواهیم nullها را از استریم حذف کنیم مجبور میشویم از if یا عملگر ternary استفاده کنیم. به این مثال توجه کنید:
List<Integer> intList = new ArrayList<>(); intList.add(1); intList.add(2); intList.add(null); intList.add(4); intList.stream() .flatMap(num -> num == null ? Stream.empty() : Stream.of(num)) .forEach(System.out::println);
برای این که مجبور نباشیم از کدهای تکراری برای چک کردن null بودن یا نبودن اشیا استفاده کنیم، متدی به نام ofNullable به کلاس Stream اضافه شده است. کد فوق، با استفاده از این متد به شکل زیر قابل بازنویسی است:
intList.stream() .flatMap(num -> Stream.ofNullable(num)) .forEach(System.out::println);
که خروجی زیر را تولید میکند:
1 2 4
ویژگیهای دیگری مانند Collectors.filtering و Collectors.flatMapping در نسخۀ جدید اضافه شده است که برای مطالعۀ بیشتر میتوانید به این لینک مراجعه کنید.