ဒီနေ့ဘာရေးရင်ကောင်းမလဲ အတော် လေးစဥ်းစားရခက်ခဲ့ပါတယ်။ Java SE 8 ရဲ့ Stream API အကြောင်းက နောက်ဆုံအပိုင်းကို ရောက်နေပါပြီ။ ဒါကို ပြီးအောင်ရေးပြီးတော့မှ အခြေခံ ဒေတာဘေစ်နဲ့ Java FX Days အကြောင်းကို ဆက်ပါမယ်။ Stream API အကြောင်းကို ရေးသားနေခဲ့သည်မှာ ၂ကြိမ် ရေးသားခဲ့ပြီဖြစ်ပါတယ်။ Stream ဆိုတာဘာလဲ။ Stream တွေကို ဘယ်လို စတင်စေမလဲ။ ပြီးတော့ Intermediate Operation တွေကို ဘယ်လို ရေးမလဲ အစရှိတဲ့ အကြောင်းတွေကို ယခင်အခန်းဆက်များနှင့် ဖေါ်ပြခဲ့ပါတယ်။ ဒီနေ့မှာတော့ Stream Operation ရဲ့ နောက်ဆုံးအပိုင်းဖြစ်တဲ့ Terminal Operation အကြောင်းကို ဆက်လက်ဖေါ်ပြသွားပါမည်။
Terminal Operations
Terminal Operation ဆိုသည်မှာ Stream တစ်ခု၏ နောက်ဆုံအပိုင်းဖြစ်ပြီး၊ Stream Operation အား အဆုံးသတ်စေသော Operation ဖြစ်ပါသည်။ ထို့ကြောင် Terminal Operation အားဖြစ်ပေါ်စေသော method များ ၏ Return Type သည် void သို့မဟုတ် Stream မဟုတ်သော အခြား Object တစ်မျိုးမျိုး ဖြစ်ပါမည်။
ကျွန်တော်တို့ အားလူးပြုပ်သုတ် လုတ်သည်ကို Stream Operation ဖြင့် ဆောင်ရွက်မည်ဟု စဥ်းစားကြည့်ပါမည်။ အားလူးအိတ်ထဲမှ အာလူးများအား တစ်လုံးခြင်းထုတ်ယူနေခြင်းသည် အာလူး Stream အား စတင်စေသော Operation ဖြစ်ပါမည်။
တဖန် အာလူးအပုပ်များအား ဖယ်ထုတ်ခြင်းသည် Filter လုပ်ခြင်းဖြစ်ပါမည်။ ထိုနောက် အာလူးအားပြုပ်၍ အခွံနွှာကာ ကြိတ်ခြင်းသည် Map လုပ်နေခြင်းနှင့် တူပါသည်။ ၎င်းတို့သည် Intermediate Operation ဖြစ်ပါသည်။ ပြုပ်လိုက်ပြီးသောအခါ အာလူးအစိမ်းများသည် အာလူးပြုပ်များ ဖြစ်လာပါမည်။ အခွံနွှာပြီးကြိတ်ပြီးသောအခါ အာလူးပြုပ်များသည် အာလူးကြိတ်ဖတ်များဖြစ်လာမည်ဖြစ်သည်။ ထို့ကြောင့် Intermediate Operation များသည် Stream တစ်ခုမှ အခြားသော Stream တစ်ခုသို့ပြောင်းလဲပေးနေပါသည်။
နောက်ဆုံး အာလူးကြိတ်ဖတ်များအား အရသာစပ်၍ ပန်းကန် အတွင်းထည့်လိုက်သော အခါ အာလူးသုတ် တစ်ပွဲရပါမည်။ ၎င်းသည် Stream အား အဆုံးသတ်စေသော Terminal Operation ဖြစ်ပြီး အာလူးသုတ်ဟူသော အခြားသော Object ကိုရရှိစေမည်ဖြစ်ပါသည်။
Terminal Operation တွင်ပါဝင်သော Method များ
Method | Description |
---|---|
forEach | Stream အတွင်းရှိ ဒေတာများအား တစ်ခုချင်းစီ အသုံးပြုနိုင်ပါသည် |
forEachOrdered | Parallel Stream အတွင်းရှိ ဒေတာများအား တစ်ခုချင်းစီ အသုံးပြုနိုင်ပါသည် |
toArray | Stream အတွင်းမှ ဒေတာများအား Array ပုံစံနှင့် ပြန်လည်ရယူနိုင်ပါသည် |
collect | Stream အတွင်းမှဒေတာများအား Container တစ်ခုဖြင့်စုစည်း၍ ပြန်လည် ရယူနိုင်ပါသည် |
reduce | Stream အတွင်ရှိ ဒေတာများအား လုပ်ဆောင်ချက်တစ်ခုဖြင့် တစ်ခုပြီးတစ်ခု စုစည်း၍ ပြန်လည်ရရှိစေနိုင်ပါသည် |
allMatch | Stream အတွင်းရှိ ဒေတာများအားလုံး Parameter အဖြစ်ရယူထားသော အချက်အလက်နှင့် ကိုက်ညီပါမှ True ကို ရရှိမည်ဖြစ်ပြီး၊ သို့မဟုတ်ပါက False ကို ရရှိမည် ဖြစ်သည် |
anyMatch | Stream အတွင်းရှိ ဒေတာများထဲမှ တစ်ခုခုက Parameter အဖြစ်ရယူထားသော အချက်အလက်နှင့် ကိုက်ညီပါမှ True ကို ရရှိမည်ဖြစ်ပြီး၊ သို့မဟုတ်ပါက False ကို ရရှိမည် ဖြစ်သည် |
nonMatch | Stream အတွင်းရှိ ဒေတာများအားလုံး Parameter အဖြစ်ရယူထားသော အချက်အလက်နှင့် ကိုက်ညီမှု့မရှိပါမှ True ကို ရရှိမည်ဖြစ်ပြီး၊ သို့မဟုတ်ပါက False ကို ရရှိမည် ဖြစ်သည် |
min / max | Stream အတွင်းမှ ဒေတာများထဲမှ အသေးဆုံးဒါမှမဟုတ် အကြီးဆုံး ဒေတာကို ရယူနိုင်ပါမည် |
count | Stream အတွင်းမှ ဒေတာ အရေအတွက်ကိုပြန်လည် ရရှိနိုင်ပါသည် |
findFirst | Stream အတွင်းမှ ပထမဦးဆုံး ဒေတာအား Optional Object အနေနှင့်သော်၎င်း၊ Stream အတွင် ဒေတာ တစ်ခုမှမရှိပါက Empty Optional Object တစ်ခုကို သော်၎င်းပြန်လည် ရရှိနိုင်ပါသည် |
findAny | Stream အတွင်းမှ ဒေတာတစ်ခုခုအား Optional Object အနေနှင့်သော်၎င်း၊ Stream အတွင် ဒေတာ တစ်ခုမှမရှိပါက Empty Optional Object တစ်ခုကို သော်၎င်းပြန်လည် ရရှိနိုင်ပါသည် |
အထက်ပါ Method တွေကတော့ Stream ရဲ့ Terminal Operation အနေနဲ့အသုံးပြုနိုင်သော Method များ ဖြစ်ကြပါတယ်။ toArray, count, allMatch, anyMatch, noneMatch, findFirst နဲ့ findAny တို့ကတော့ အမည်ကြားရုံနှင့် မည်သို့အသုံးပြုရမည်၊ မည်သို့ဖြစ်လိမ့်မည်ဆိုသည်ကို ခန့်မှန်းနိုင်မည်ဖြစ်သောကြောင့် နမူနာများအား မဖော်ပြတော့ပါ။ အနည်းငယ်ခက်ခဲသော အပိုင်းများကိုသာ နမူနာအဖြစ်ရေးသားသွားပါမည်။
collect
ကျွန်တော်တို့ Text File တစ်ခုအတွင်းတွင် User နှင့်ပတ်သက်သော အချက်အလက်များကိုရေးသားထားပြီ၊ အဆိုပါအချက်အလက်များအား ပရိုဂရမ်အတွင်းဖတ်ယူသည့် နမူနာတစ်ခုအား ရေးသားကြည့်ပါမည်။
public class StreamSample1 { static class User { private String name; private int age; private String address; public User(String ... data) { this.name = data[0]; this.age = Integer.parseInt(data[1]); this.address = data[2]; } // getters and setters } public static void main(String[] args) throws IOException { List<User> list = Files.lines(Paths.get("sample.txt")) .map(a -> a.split(",")) .map(User::new) .collect(Collectors.toList()); } }အထက်ပါနမူနာတွင် Files#lines အားအသုံးပြု၍ sample.txt အတွင်းရှိ စာကြောင်းများအား Stream<String> အဖြစ်ရယူပါသည်။ တဖန် map(a -> a.split(",")) ဖြင့် Stream<String[]> အဖြစ်ပြောင်းပါသည်။ ထို့နောက်တွင် map(User::new) ဖြင့် Stream<User> အဖြစ်ပြောင်းပါသည်။ နောက်ဆုံးတွင် collect(Collectors.toList()) ဖြင့် Stream<User> အတွင်းမှ User Object များအား List<User> အတွင်စုစည်း၍ပြန်လည်ရရှိစေခြင်းဖြစ်ပါသည်။
Stream API အား အသုံးပြုခြင်းအားဖြင့် ဤကဲ့သို့ ရိုးရှင်းစွာ ရေးသားနိုင်သည်ကို တွေ့နိုင်ပါသည်။
reduce
အထက်ပါနမူနာကိုပဲ ပြန်လည်အသုံးပြုပါမည်။ Text File အတွင်းရှိ User အချက်အလက်များ၏ အသက်စုစုပေါင်းကို ရှာဖွေမည့် ပရိုဂရမ်ဖြစ်ပါသည်။ အမှန်ဆိုပါက Stream<User> မှ mapToInt method အားအသုံးပြု၍ sum ကိုရှာ မည်ဆိုလဲ ဖြစ်နိုင်သော်လဲ ဤနေရာတွင် reduce ကို အသုံးပြုပါမည်။
public static void main(String[] args) throws IOException { int sum = Files.lines(Paths.get("sample.txt")) .map(a -> a.split(",")) .map(User::new) .map(a -> a.getAge()) .reduce(0, (a,b) -> a + b); System.out.println(sum); }အထက်ပါနမူနာတွင် စာကြောင်း ၆ တွင် map(a -> a.getAge()) အား အသုံးပြု၍ Stream<User> မှ Stream<Integer> အဖြစ်ပြောင်းယူပါသည်။ ထို့နောက်တွင် reduce(0, (a, b) -> a + b) အား အသုံးပြု၍ Stream<Integer> အတွင်းရှိ Integer အား တစ်ခုခြင်းပေါင်းပြီး စုစုပေါင်းအား တွက်ယူခြင်းဖြစ်ပါသည်။
reduce method ၏ ဖွဲ့စည်းပုံမှာ Stream<T>#reduce(T identity, BinaryOperator<T> accumulator):T ဖြစ်သည်။ Stream တွင်ပါဝင်သော Element Type အတိုင်း Return Type ကို ရရှိမည်ဖြစ်သည်။ reduce method တွင်အသုံးပြုနေသော Arguments မှာ ၂ ခုဖြစ်ပြီး၊ ပထမ တစ်လုံးမှာ Reduce Operation ၏ကနဦးတန်ဖိုးဖြစ်ပါသည်။
ဒုတိယ Argument မှာ BinaryOperator ၏ Object ဖြစ်ပြီး ၎င်းမှာ Functional Interface ဖြစ်ပါသည်။ implement လုပ်ရန်လိုအပ်သော method မှာ apply(T t, T u):T ဖြစ်သည်။ apply method ၏ argument မှာ ၂ ခု ရှိပြီး တစ်ခုတွင် ကနဦးတန်ဖိုး သို့မဟုတ် အရင်တစ်ခေါက်၏ ရလဒ်ကို အသုံးပြုပြီး နောက် Argument တစ်ခု အဖြစ် Stream အတွင်းရှိ လက်ရှိ အသုံးပြုမည့် Argument ကို အသုံးပြုပါမည်။ အထက်နမူနာတွင် method body အတွင်း၌ (a, b) -> a + b ဟု ရေးသားထားသောကြောင့် a + b ရလဒ်အား နောက် တစ်ကြိမ်၏ မူလတန်ဖိုးအဖြစ် အသုံးပြုမည်ဖြစ်သည်။ ဤနည်းအားဖြင့် Stream အတွင်းရှိ Element များအား တစ်ခုခြင်း reduce လုပ်၏ စုစုပေါင်းအား ရှာဖွေသွားခြင်းဖြစ်ပါသည်။
forEach vs forEachOrdered
forEach ရော forEachOrdered ပါ Argument အနေနှင့် Consumer<T> Object အား အသုံးပြုပြီး ၎င်းသည် Single Method Interface (SAM) တစ်ခုဖြစ်သောကြောင့် Lambda Expression နှင့် အသုံးပြုနိုင်ပါသည်။ Stream<T> Object အတွင်းရှိ Element များအား တစ်ခုခြင်းစီ လုပ်ဆောင်မှု့တစ်ခုအား ဆောင်ရွက်စေလို ပါက အသုံးပြုနိုင်ပါသည်။ Consumer<T> Interface ၏ implement လုပ်ရန်လို သော method မှာ consume(T t):void ဖြစ်ပြီး Argument အနေနှင်း Stream<T> ၏ Element တစ်ခုခြင်း ဤmethod အတွင်းသို့ရောက်ရှိလာပါမည်။ method အတွင်းတွင် မည်သို့ပြုလုပ်မည်ဆိုသည်ကို ရေးသားရပါမည်။
Return Type မှာ Void ဖြစ်သောကြောင့် အဆိုပါ Method များအား လုပ်ဆောင်ပြီးပါက Stream ပြီဆုံးမည် ဖြစ်ပါသည်။
သာမန် Stream Operation တွင်အသုံးပြုပါက ၎င်း Method တို့၏ ရလဒ်မှာ အတူတူပင်ဖြစ်၏။ သို့ရာတွင် Parallel Stream တွင် အသုံးပြုပါက တူညီမည်မဟုတ်ပါ။ Parallel Stream အတွင်း forEach ကို အသုံးပြုပါက နဂိုမူလ Stream ၏ Order အတိုင်း ဆောင်ရွက်နိုင်မည်ဟု အာမခံနိုင်မည်မဟုတ်။ forEachOrdered အားအသုံးပြုပါမှ နဂိုမူလ Order အတိုင်းဆောင်ရွက်နိုင်မည်ဖြစ်ပါသည်။ Parallel Stream အကြောင်းအား နောက်တွင်ဆက်လက်ဖေါ်ပြပါဦးမည်။
ဆက်ပါဦးမည်။ လေးစားစွာဖြင့်
မင်းလွင်
အကို
ReplyDeleteက်ေနာ္ဒီBlog ကိုတေန႔ကမွေတြ႕တာပါအရမ္းစိတ္ဝင္စားပါတယ္
အစကေနေလ့လာရမွာဆိုေတာ့ဘယ္ကစရမလဲမသိလို႔ေျပာေပးပါခင္ဗ်ာ