Showing posts with label Java SE : 5 Streams API. Show all posts
Showing posts with label Java SE : 5 Streams API. Show all posts

September 10, 2016

Optionals

ရှေ့အခန်း Streams API ကို ရေးသားစဥ် Method တွေရဲ့ Return Type တွေမှာ java.util.Optional Object ကို အသုံးပြုနေတာကို တွေ့ရပါမယ်။ ဒီအခန်းမှာတော့ Optional အကြောင်းကို လေ့လာသွားပါမယ်။

Optional Class ဟာ တန်ဖိုးတစ်ခုကို သိမ်းပေးထားနိုင်တဲ့ Container Class ဖြစ်ပါတယ်။ သို့ပေမဲ့ တစ်ခါသိမ်းပေးထားတဲ့ တန်ဖိုးကို ပြောင်းလို့မရနိုင်ပါဘူး။ အဲ့ဒီအတွက် Immutable Class အမျိုးအစားဖြစ်ပါတယ်။

ထူးခြားချက်ကတော့ တန်ဖိုးရှိသလား မရှိသလားဆိုတဲ့ အပေါ်မှာမူတည်ပြီး လုပ်ဆောင်မှူ့တွေကို ပြောင်းပေးနိုင်တဲ့ အချက်ဖြစ်ပါတယ်။ အဲ့ဒီအတွက် ယခင် တန်ဖိုးဟာ Null ဖြစ်သလား မဖြစ်သလားဆိုတာကို if နဲ့ စစ်ပြီး လုပ်ဆောင်မှု့တွေကို ပြောင်းရေးနေတဲ့ နေရာမှာ အဲ့ဒီထူးခြားချက်ကို အသုံးပြုနိုင်မှာ ဖြစ်ပါတယ်။

ရေးသားနေတဲ့ ပရိုဂရမ်တွေမှာ Null Check ကို မေ့ပြီး မရေးမိရင် NullPointerException ကို Throw ဖြစ်နိုင်ပါတယ်။ ဒါပေမဲ့ နေရာတကာမှာ Null Check တွေ ရေးနေရတာဟာ တကယ်ကို အလုပ်ရှုပ်ပြီး Program Logic ကိုလဲ​ရှုပ်ထွေးစေပြီး၊ Code တွေကို ဖတ်တဲ့နေရာမှာလဲ​ ရှူပ်ထွေးစေပါတယ်။ ဒီ့အပြင် Test Case တွေကို ရေးသားတဲ့ နေရာမှာလဲ​ Null အတွက်နဲ့ Null မဟုတ်တဲ့အတွက် ဆိုပြီး ပိုရေးရမှာ ဖြစ်ပါတယ်။

ဒီနေရာမှာ အသုံးဝင်တာကတော့ Optional ဖြစ်ပါတယ်။

Optional Class ဟာလဲ Stream တွေလိုပဲ Generics Type အမျိုးအစားဖြစ်ပြီး၊ အသုံးပြုမဲ့ Type ကို Type Parameter အနေနဲ့ ရေးသားပေးရမှာ ဖြစ်ပါတယ်။ အဲ့ဒီအတွက် primitive type တွေဖြစ်တဲ့ int, double အစရှိသည်တို့ကို အသုံးပြုနိုင်မှာ မဟုတ်ပါဘူး။ အဲ့ဒီအစား Primitive Type တွေ အတွက် OptionalInt, OptionalDouble, OptionalLong Class တို့ကိုပြင်ထားပါတယ်။

ဥပမာအနေနဲ့ int [] တစ်ခုရဲ့ ပျမ်းမျှကို ရှာတဲ့ ပရိုဂရမ်တစ်ခုကို စဉ်းစားကြည့်ပါမယ်။ တကယ်လို့ IntStream Object ရဲ့ average method ကို သုံးမယ်ဆိုရင် Source ဖြစ်တဲ့ int [] က blanck ဖြစ်နေရင် Average တန်ဖိုးကို ရှာနိုင်မှာ မဟုတ်ပါဘူး။ အဲ့လို အခါမျိုးမှာ ပျမ်းမျှတန်ဖိုးကို 0.0 လို့ပြန်ပေးရမှာ ဖြစ်ပါတယ်။

လက်တွေ့ရေးကြည့် ရအောင်။



အထက်ပါ ကုဒ်ရဲ့ လိုင်းနံပါတ် ၉ မှာ၊ IntStream Object ရဲ့ average method ကို ခေါ်မယ်ဆိုရင် OptionalDouble Object ကို ပြန်ရပါမယ်။ Average တန်ဖိုးကိုပိုင်တဲ့ Optional အမျိုးအစား Object ဖြစ်ပါတယ်။ OptionalDouble ရဲ့ getAsDouble method ကို သုံးပြီး double တန်ဖိုးကို ထုတ်ယူနိုင်ပါတယ်။

ဒါပေမဲ့ အထက် နမူနာထဲမှာလို Array အလွတ်ကို ပေးပြီး Avreage တန်ဖိုးကို ရှာမယ်ဆိုရင်တော့ Average တန်ဖိုးကို ရရှိမှာ မဟုတ်ပါဘူး။ အဲ့ဒီအတွက် Average ကို ဖေါ်ပြမည့် တန်ဖိုးလဲ​ပါမှာ မဟုတ်ပါဘူး။ အဲဒီလို အခါမျိုးမှာ getAsDouble ကို သုံးရင် NoSuchElementException ကို ဖြစ်ပွားစေမှာပါ။

အဲ့ဒီလို အခါမျိုးမှာ Line 10 မှာ ရေးထားသလို orElseGet ကို အသုံးပြုမယ်ဆိုရင် တန်ဖိုးမပါရင် 0.0 ကို ပြန်ပေးမယ်ဆိုပြီး သတ်မှတ်လို့ရပါတယ်။ ဤနည်းအားဖြင့် တန်ဖိုးမရှိတဲ့ အခါမှာ ဖြစ်ပွားတတ်တဲ့ ပြဿနာများကို ရှောင်ရှားနိုင်ပါမယ်။ Optional Class မှာ အထက်ပါ Function များ အပြင် အခြား အသုံးဝင်သော Feature များကိုလဲ​ပြင်ဆင် ထားပါသေးတယ်။ ဆက်လက်ပြီး လေ့လာကြည့်ကြရအောင်။


Creation

Optional Object ကို ရယူလိုတဲ့ အခါမှာ အောက်ပါ Static Method များကို အသုံးပြုနိုင်ပါတယ်။

Method
Description
empty()
တန်ဖိုး မပါသော Optional Object ကို instantiate လုပ်လိုတဲ့ အခါမှာ အသုံးပြုနိုင်ပါတယ်
of(T value)
null မဟုတ်သော တန်ဖိုးကို ပိုင်ဆိုင် စေလိုသော Optional Object ကို instantiate လုပ်လိုတဲ့ အခါမှာ အသုံးပြုနိုင်ပါတယ်
ofNullable(T value)
null လဲ ဖြစ်နိုင်သလို တန်ဖိုးလဲ ရှိခြင်ရှိနိုင်သော Optional Object ကို Instantiate လုပ်လိုတဲ့ အခါမှာ အသုံးပြုနိုင်ပါတယ်

empty() ကို သုံးရင်တော့ တန်ဖိုးမပါသော အလွတ် Optional Object တစ်ခုကို ဆောက်လုပ်နိုင်မှာ ဖြစ်ပါတယ်။ တန်ဖိုးကို သတ်မှတ်ပြီး Optional Object ကို ဆောက်လုပ်ချင်တယ်ဆိုရင်တော့ of နဲ့ ofNullable method တို့ကို သုံးနိုင်မှာဖြစ်ပါတယ်။ သူတို့ နှစ်ခုကွာခြားတာကတော့ of method ရဲ့ Argument ကို null သတ်မှတ်လို့ မရတာဖြစ်ပါတယ်။ တကယ်လို့ of method ရဲ့ Argument ကို null လို့သတ်မှတ်မိပါက NullPointerException ကို ဖြစ်ပေါ်စေမှာ ဖြစ်ပါတယ်။

OptionalInt, OptionalDouble နဲ့ OptionalLong တို့မှာလဲ အလားတူ Method များကို ပံ့ပိုးပေးထားသော်လဲ၊ Primitive Type တွေမှာ null တန်ဖိုးဟာ ရှိစရာ အကြောင်းမရှိတဲ့ အတွက် ofNullable method ကိုတော့ ရေးသားမထားပါဘူး။


Getting Values


Optional Object က ပိုင်ဆိုင်တဲ့ တန်ဖိုးကို ရယူနိုင်ဖို့အတွက် Method ၄ မျိုး ပြင်ဆင်ထားပါတယ်။ Primitive တွေ အတွက် Optional တွေမှာတော့ getAsDouble တို့လို သီးခြား Method တွေကိုလဲ​ပြင်ဆင်ထားပါတယ်။

Method
Description
get()
ပိုင်ဆိုင်တဲ့ တန်ဖိုးကို Return လုပ်ပါမယ်။ တကယ်လို့ တန်ဖိုးမရှိခဲ့ရင် NoSuchElementException ကို ဖြစ်ပေါ်စေပါမယ်။
orElse(T value)
ပိုင်ဆိုင်တဲ့ တန်ဖိုးကို Return လုပ်ပါမယ်။ တကယ်လို့ တန်ဖိုးမရှိခဲ့ရင် Argument မှာ ရေးသားထားတဲ့ Value ကို Default တန်ဖိုး အနေနဲ့ Return လုပ်ပါမယ်။
orElseGet(Supplier<? extends T> supplier)
ပိုင်ဆိုင်တဲ့ တန်ဖိုးကို Return လုပ်ပါမယ်။ တကယ်လို့ တန်ဖိုးမရှိခဲ့ရင် Argument မှာ ရေးသားထားတဲ့ Supplier ကနေ ထုတ်ပေးမဲ့ တန်ဖိုး ကို Default တန်ဖိုး အနေနဲ့ Return လုပ်ပါမယ်။
Functional Interface
ဖြစ်တဲ့အတွက် Lambda Expression နဲ့ လဲ ရေးသားနိုင်ပါမယ်။
<X extends Throwable> orElseThrow(
   Supplier<? extends X> exceptionSupplier)
ပိုင်ဆိုင်တဲ့ တန်ဖိုးကို Return လုပ်ပါမယ်။ တကယ်လို့ တန်ဖိုးမရှိခဲ့ရင် Argument မှာ ရေးသားထားတဲ့ Supplier ကနေ ထုတ်ပေးမဲ့ တန်ဖိုး ကို Exception ကို Throw လုပ်ပါမယ်။

နမူနာ အနေနဲ့ ကျောင်းသား အမှတ် စာရင်းထဲကနေ အနိမ့်ဆုံး အမှတ်ရသူ ကျောင်းသားကို ရှာဖွေပြီး၊ အဲ့ဒီကျောင်းသားရဲ့ ပျမ်းမျှ အမှတ်ဟာ ၄၀ အောက်ကို ရောက်နေရင် Runtime Exception ကို ဖြစ်ပေါ်စေတဲ့ Program တစ်ခုကို ရေးကြည့်ပါမယ်။

အထက်ပါ နမူနာမှာ ကျောင်းသား List ကို Stream အဖြစ်ပြောင်းပြီး၊ Average အမှတ် ငယ်ရာမှ ကြီးရာသို့ Sort လုပ်ခိုင်းပါတယ်။ ပြီးနောက် အရင်ဆုံးတွေ့တဲ့ Student ကို ရှာခိုင်းပါတယ်။ findFirst method ရဲ့ Return Type ဟာ Optional<Student> ဖြစ်ပါတယ်။ တဖန် အမှတ် 40 နှင့် အထက်ဖြစ်သလားကို စစ်ပါတယ်။ တကယ်လို့ မဟုတ်ခဲ့ရင် Optional မှာ Student ကို သိမ်းပေးထားမှာ မဟုတ်ပါဘူး။

အဲ့ဒီလို အခါမျိုးမှာ RuntimeException ကို ဖြစ်ပေါ်စေမည့် Supplier ကို ရေးသားထားခြင်းအားဖြင့် တကယ်လို့ Average အမှတ် အနည်းဆုံးကျောင်းသားရဲ့ Average အမှတ်ဟာ ၄၀ အောက်ကို ရောက်နေရင် RuntimeException ကို ဖြစ်ပေါ်စေမှာ ဖြစ်ပါတယ်။


Other Methods

Method
Description
ifPresent(Consumer<T> consumer)
တန်ဖိုးရှိပါက အဆိုပါတန်ဖိုးအား အသုံးပြုပြီး လုပ်ဆောင်နိုင်ရန် Consumer Interface Object ကို ရယူနေပါတယ်။ 
filter(Predicate<T> predicate)
တန်ဖိုးအား စစ်ပေးနိုင်ရန် အတွက် Predicate Interface ကို ရယူနေပါတယ်။ Predicate နဲ့ စစ်လိုက်တဲ့ ရလဒ်ဟာ True ဖြစ်ပါက တန်ဖိုးပါတဲ့ Optional ကို ရရှိမှာ ဖြစ်ပြီး၊ False ဆိုရင်တော့ တန်ဖိုးမပါတဲ့ Optional ကို ရရှိမှာ ဖြစ်ပါတယ်
map(Function<? supper T, ? extend U> mapper)
Optional တစ်ခုမှ အခြားသော တန်ဖိုး အမျိုးအစားကို ပိုင်သော Optional အဖြစ်ပြောင်းပေးနိုင်ပါတယ်
flatMap(Function<T, Optional<U>> mapper
map နှင့် ဆင်တူသော်လည်း၊ Function ရဲ့ ပြောင်းပေးရမဲ့ ပုံစံက အခြားသော Optional တစ်ခု ဖြစ်နေခြင်း ဖြစ်ပါတယ်


Optional ကို ဘယ်လိုနေရာတွေမှာ သုံးမှာလဲ

Optional တွေဟာ Null Check အတွက်ရေးသားရတဲ့ ကုဒ်တွေကို ရှောင်ရှားနိုင်အောင် ရေးသားဖို့ အတွက် အသုံးဝင်ကြောင်းကို ရှေ့မှာလဲ ဖေါ်ပြခဲ့ပြီးပါပြီ။ ဒါပေမဲ့ တွေ့ကရာနေရာမှာ Optional ကို သုံးလို့ကောင်းတာ မဟုတ်ပါဘူး။ ကဲ ဒါဖြင့် ဘယ်လိုနေရာတွေမှာ သုံးကြမလဲ။

ပုံမှန်အားဖြင် Java မှာ Variable ၅ မျိုးရှိပါတယ်။ အဲ့ဒါတွေကတော့ Local Variable, Instance Variable, Static Variable, Method Arguments နဲ့ Return Value တို့ဖြစ်ကြပါတယ်။

အဲ့ဒီအထဲမှာ Local Variable, Instance Variable နဲ့ Static Variable တွေမှာ Optional ကို သုံးတာဟာ ဘာမှ ထူးခြားမှာ မဟုတ်ပါဘူး။ Optional ရဲ့ကောင်းကွက်ကို အသုံးချနိုင်မှာ မဟုတ်ပါဘူး။

ကျွန်တော့် အနေနဲ့ကတော့ Optional ကို သုံးမယ်ဆိုရင် Method Return Type မှာ သုံးသင့်တယ်လို့ထင်ပါတယ်။ ဒါမှ အဲ့ဒီ Method ကို သုံးတဲ့ နေရာမှာ Null Check ကို ရေးစရာမလိုပဲ Logic တွေကို ခွဲပြီး ရေးနိုင်မှာ ဖြစ်ပါတယ်။ Streams API မှာလဲ null ကို return ပြန်နိုင်တဲ့ Method တွေရဲ့ Return Type နေရာမှာ Optional ကို သုံးထားတာကို တွေ့ရပါတယ်။

ဒါကြောင့်မို့လို့ null ကို ပြန်စရာ အကြောင်းမရှိတဲ့ Method တွေရဲ့ Return Type တွေမှာ Optional ကို သုံးစရာ အကြောင်းမရှိပါဘူ။

Reference
http://itpro.nikkeibp.co.jp/atcl/column/15/120700278/032200009/

ဆက်ပါဦးမယ်။
မင်းလွင်



April 19, 2015

Parallel Stream - Part 2

ပြီးခဲ့သော အခန်းဆက်ဖြင့် Parallel Stream အား အသုံပြုပုံကို ဖော်ပြခဲ့ပါသည်။ Parallel Stream အား အသုံးပြုခြင်းအားဖြင့် လွယ်ကူစွာပင် Parallel Processing ကို အသုံးပြုနိုင်မည်ဖြစ်သော်လည်း၊ သတိမမူပဲ အသုံးပြုပါက မလိုလားအပ်သော ပြဿနာများကို ဖြစ်စေနိုင်ပါသည်။ ထို့ကြောင့် ဤအခန်းတွင် Parallel Stream အား အသုံးပြုရာတွင် သတိပြုသင့်သော အခြေခံ အချက်များကို ဖော်ပြသွားပါဦးမည်။


Overhead

Parallel Stream အား အသုံးပြုရာတွင် Sequential Stream နှင့်စာလျင် Overhead ဖြစ်စေမည့် Process များ အတော်များပါသည်။ ထို့ကြောင့် အသုံးပြုမည့် Element ပမာဏ နည်းပါးပါက Parallel Stream ကို အသုံးပြုခြင်းက Performance ကို ကျဆင်းစေတတ်ပါသည်။

CPU ၏ Core အရေအတွက်ပေါ်တွင်မူတည်မည် ဖြစ်သော်လဲ၊ အနည်းဆုံး Element အရေအတွက် ၁၀၀၀၀ လောက်မှ Parallel Stream အား အသုံးပြုရသည့် အကျိုးကို သိမြင်နိုင်မည်ဖြစ်ပါသည်။

ထို့ကြောင့် အသုံးပြုရင်း Performance ပိုင်းဆိုင်ရာတွင်ထူးခြားမှု့မရှိဟု မြင်ပါက Element ၏ အရေအတွက်ကို ပြန်လည် စမ်းစစ်သင့်ပါသည်။


Because of Fork and Join

ကျွန်တော်တို့ အောက်ပါ ကုဒ်များကို Run ကြည့် ပါမည်။
    public static void main(String[] args) {

        BiConsumer<String, Supplier<OptionalDouble>> tester = (message, job) -> {
            LocalDateTime start = LocalDateTime.now();
            OptionalDouble result = job.get();
            LocalDateTime end = LocalDateTime.now();
            
            System.out.printf("%s , Result : %s, Time : %d%n", 
                    message, 
                    result, 
                    Duration.between(start, end).toNanos());
        };
        
        // range
        tester.accept("Test 1", () -> {
            return IntStream
                    .range(0, 1_000_000)
                    .parallel()
                    .filter(a -> a % 2 == 0)
                    .average();
        });
        
        // iterate
        tester.accept("Test 2", () -> {
            return IntStream
                    .iterate(0, a -> a + 1 )
                    .limit(1_000_000)
                    .parallel()
                    .filter(a -> a % 2 == 0)
                    .average();
        });
    }
အထက်ပါ နမူနာ၏ စာကြောင်း ၁၆နှင့် ၂၅တို့၏ Statement များသည် သုညမှ ၁၀၀၀၀၀၀ အကြားရှိ စုံကိန်းများ၏ ပျမ်းမျှတန်ဖိုးကို ရှာဖွေနေသည်မှာ အတူတူပင်ဖြစ်၏။ မတူညီသည်မှာ ၎င်းတို့အား စတင်စေသည့် နေရာပင်ဖြစ်၏။ စာကြောင် ၁၆တွင် range ဖြင့်စတင်ကာ စာကြောင်း ၂၅ တွင် iterate ဖြင့်စတင်ပါသည်။

ရလဒ်မှာအောက်ပါအတိုင်းဖြစ်ပါသည်။
Test 1 , Result : OptionalDouble[499999.0], Time : 33000000
Test 2 , Result : OptionalDouble[499999.0], Time : 118000000

နှစ်ခုလုံး၏ အဖြေမှာ အတူတူပင်ဖြစ်သော်လည်း၊ Test 2 အားလုပ်ဆောင်ရာတွင် Test 1 ထက် သိသိသာသာ ကြာမြင့်သည်ကို တွေ့ရပါသည်။

အဘယ့်ကြောင့် ဆိုသော် Parallel Stream အား လုပ်ဆောင်စေရာတွင် မူလအရင်းအမြစ်အတွင်းရှိ Element များအား အစိတ်စိတ် အပိုင်းပိုင်းခွဲ၍ အပြိုင်လုပ်ဆောင်စေပါသည်။ အစိတ်စိတ် အပိုင်းပိုင်းခွဲရာတွင် Fork And Join အား အသုံးပြုနေပြီး အတွင်းတွင် divide and conquer algorithm အား အသုံးပြု နေပါသည်။ အဆိုပါ Algorithm သည် မူလ Problem အား အသေးငယ်ဆုံး အနေအထား အထိ နှစ်ပိုင်းခွဲ အဖြေရှာ၍ ရလဒ်စုစုပေါင်းအား ရှာဖွေပါသည်။

ထို့ကြောင့် iterate ကဲ့သို့ အဆုံးမရှိသော ရင်းမြစ်များသည် စုစုပေါင်းပမာဏအား ရှာဖွေရန်ခက်ခဲပြီး၊ Divide And Conquer Algorithm တွင် အသုံးပြုရန်ခက်ခဲပါသည်။ ထို့ကြောင့် Test 2 ၏ Performance မှာ Test 1 ထက် ဆိုးရွားနေခြင်းဖြစ်ပါသည်။

iterate အပြင် generate သည်လည်း မူလအရေအတွက်ကို ခန့်မှန်းရခက်ခဲပါသဖြင့် Parallel Stream များတွင် အသုံးမပြုသင့်ပေ။ 

ထို့အပြင် File များမှတိုက်ရိုက် ပြုလုပ်သော Stream များအားလည်း Parallel Stream တွင် အသုံးမပြုသင့်ပါ။ ဥပမာအားဖြင့် Files#lines နှင့် BufferedReader#lines method များသည် ဖိုင်လ် အတွင်းရှိ စာကြောင်းများအား တစ်ကြောင်းစီ ဖတ်၍ Stream အတွင်းသို့ ထည့်သွင်းနေသောကြောင့် Process မဆုံးမခြင်း စုစုပေါင်းပမာဏအား မသိနိုင်ပါ။ ထို့ကြောင့် File များအား အသုံးပြုရာတွင် အရင်ဆုံး Files#readLines ဖြင့် စာကြောင်းများအား ဖတ်ယူပြီးမှ Parallel Stream အဖြစ်အသုံးပြုသင့်ပါသည်။


Accessing External Variable


ဒီတစ်ခေါက်တော့ ဉာဏ်စမ် ပဟေဠိလေးဖြင့် စကြည့်ပါမည်။
public class SumTest {
    
    public static void main(String[] args) {
        
        SumTest sum = new SumTest();
        List<Integer> list = IntStream.rangeClosed(0, 10_000_000)
                .mapToObj(a -> new Integer(a))
                .collect(Collectors.toList());
        
        BiConsumer<List<Integer>, 
            Function<List<Integer>, Long>> tester = (data, function) -> {
                LocalDateTime start = LocalDateTime.now();
                long result = function.apply(data);
                LocalDateTime end = LocalDateTime.now();
                
                System.out.format("%d : %d%n", Duration.between(start, end).toNanos(), result);
        };
        
        tester.accept(list, sum::getSum1);
        tester.accept(list, sum::getSum2);
        tester.accept(list, sum::getSum3);
    }
    
    private long sum;
    
    long getSum1(List<Integer> list) {
        sum = 0L;
        
        list.parallelStream().forEach(a -> sum += a);
        
        return sum;
    }
    
    private LongAdder adder;
    
    long getSum2(List<Integer> list) {
        adder = new LongAdder();

        list.parallelStream().forEach(a -> adder.add(a));
        
        return adder.longValue();
    }
    
    long getSum3(List<Integer> list) {
        return list.parallelStream().mapToLong(a -> a.longValue()).sum();
    }

}

အထက်ပါကုဒ်များသည် အလုပ်လုပ်ပါမည်လော။  getSum1, getSum2 နှင့် getSum3 တို့တွင် မည်သည့် method ၏ ရလဒ်သည် အမှန်ကန်ဆုံးနှင့် Performance အကောင်းဆုံးဖြစ်မည်နည်း။


getSum1 method


သော့ချက်မှာ စာကြောင်း ၂၇ရှိ sum variable ဖြစ်သည်။ စာကြောင်း ၂၉ ရှိ Lambda Expression အတွင်းမှ Reference လုပ်နေသောကြောင့် final ဖြစ်ရန်လိုအပ်ပါသည်။ သို့ရာတွင် စာကြောင်း ၂၄ တွင် Private member အဖြစ် Declare လုပ်ထားပြန်သဖြင့် Lambda Expression အတွင်းမှ ဆက်သွယ် အသုံးပြုနိုင်ပမည်။ ထိုကြောင့် Error မတက်ပဲ အလုပ်လုပ်မည်ဖြစ်သည်။
သို့သော်လဲ အသုံးပြုနေသည်မှာ Parallel Stream ဖြစ်ပြီး long type မှာ Thread Safe  မဖြစ်သောကြောင့် အဖြေမှာမှန်မည်မဟုတ်။
ထို့ကြောင့် Parallel Stream အား အသုံးပြုသောအခါ Thread Safe မဖြစ်သော Variable များအား ရှောင်ရှားသင့်ပါသည်။


getSum2 method


အဆိုပါ method သည် Thread Safe ဖြစ်စေရန် Java SE 8 တွင် အသစ်ဖြည့်စွက်ထားသော LongAdder Objecct အားအသုံးပြုထားပါသည်။ Thread Safe ဖြစ်သည်မှာ မှန်သော်လဲ၊ ပြင်ပရှိ Object တစ်ခုထဲအား Thread များမှ တစ်ပြိုင်ထဲ Access လုပ်ခြင်းသည် Bottle Neck ကို ဖြစ်ပွါးစေ၏ Performance ကို ကျဆင်းစေနိုင်ပါသည်။
ထို့ကြောင့် Parallel Stream များအတွင်းမှ တတ်နိုင်သလောက် ပြင်ပ Variable များ အား ဆက်သွယ် အသုံးပြုခြင်းကို ရှောင်ရှားသင့်ပါသည်။

getSum3 method


အထက်ပါ မက်သတ်အတွင်းတွင် External Variable များအား အသုံးမပြုပဲ Parallel Stream တွင်း၌သာ စုစုပေါင်းအားရှာဖွေစေပါသည်။ ရလဒ်မှာ အောက်ပါအတိုင်းဖြစ်ပါသည်။
94000000 : 15028796145232
79000000 : 50000005000000
56000000 : 50000005000000

getSum1 ၏ရလဒ်သည် မှားယွင်း၍ Performance မှာ အဆိုးရွားဆုံးဖြစ်ပါသည်။
getSum2 ၏ရလဒ်သည် မှန်ကန်သော်လည်း Performance မှာ သိပ်မကောင်းပါ။
getSum3 ၏ရလဒ်သည် မှန်ကန်ပြီး Performance မှာလည်း အကောင်းဆုံးဖြစ်သည်ကို တွေ့ရပါသည်။


ဆက်ပါဦးမည်။လေးစားစွာဖြင့်
မင်းလွင်

April 18, 2015

Parallel Stream

ပြီးခဲ့သော အခန်းများတွင် Stream API အကြောင်းကို မိတ်ဆက်ခဲ့ပါသည်။ Stream API သည် Stream အတွင်းရှိ Element များအား Pipe Line Operation ဖြင့်ဆောင်ရွက်စေနိုင်သော API တစ်ခု ဖြစ်ပါသည်။ Stream API တွင် ယခု တိုင် Java Programming Language တွင်ပါဝင်ခြင်း မရှိခဲ့သေးသော Internal Iteration Controls ကို အသုံးပြုလာခဲသည်။ ဤအချက်သည် Parallel Operation ကို လွယ်ကူစွာ ဆောင်ရွက်လာစေနိုင်ပါသည်။

Parallel Operation ဆိုသည်မှာ လုပ်ဆောင်မှု့တစ်ခုကို Tread တစ်ခုဆောင်ရွက်နေစဥ် အခြားသော ဆောင်ရွက်မှု့များကို ဆောင်ရွက်စေခြင်းဖြစ်ပါသည်။ ဘာ့ကြောင့် ဒီလိုအချိန်မှာ Parallel Operation အကြောင်းကို ဆောင်ရွက်လာကြသနည်း။ ကျွန်တော်တို့ လက်ရှိ အသုံးပြုနေသော Computer များသည် ယခင် ၁၀နှစ်ကျော်ကနှင့် စာလျင် လွန်စွာတိုးတက်လာခဲပါသည်။ ယခင်က Pan 2, 3, 4 ဆိုလျင် ဟုတ်လှခြည်လားဟု တွေးမိမည်ဆိုသော်လဲ၊ Core i 3, 5, 7 ဖြင့် Core တွေများပြားလာခဲ့ပါသည်။ Memory ဆိုလျင်လဲ အနည်းဆုံး 2G, 4G, 8G နှင် 16G အထိသုံးလာကြ၏။ လုပ်ငန်းသုံးများဆိုပါက CUP ကို ပင် အလုံးပေါင်းများစွာ အသုံးပြုလာကြ၏။

Programming Language ဘက်ကတော့ Hardware တိုးတက်မှု့နှင်စာလျင် တိုးတက်မှု့ နည်းပါးပါသေးသည်။ Hardware များ၏​ စွမ်းရည်အား အပြည့်အဝ အသုံးခြနိုင်အောင်ပြင်ဆင်မှု့နည်းပါးပါသေးသည်။ Java Programming သည် ကနဦးဗားရှင်း ကတည်းက Multi Thread ကို အသုံးပြုနိုင်ခဲ့သော်လဲ၊ CPU ၏ Core အားလုံးအား ပြည့်ပြည့်ဝဝ အသုံးခြလာနိုင်ခဲ့သည်တော့ မဟုတ်။ ထို့ကြောင့် Java SE 7 ကတည်းက စပြီး Parallel Operation များကို အသုံးပြုနိုင်ရန် ပြင်ဆင်မှု့များကို စတင်ပြင်ဆင်လာခဲ့၏။

Parallel Operation ၏ အခြေခံသဘောတရားမှာ အလွန်ကြီးမားသော ပြဿနာတစ်ခုရှိပါက အစအဆုံးအား တစ်ယောက်တည်း အစအဆုံးဖြေရှင်းနေသည်ထက်စာလျင်၊ ပြဿနာအား သေးငယ်သော ပြဿနာများ အဖြစ် အပိုင်းပိုင်းခွဲကာ လူအများနှင့် စုပေါင်းဖြေရှင်း၍ အဖြေစုစုပေါင်းအားရှာဖွေသည်က ပို၍မြန်ဆန်လွယ်ကူနိုင်စေမည်ဖြစ်သည်။ ထိုကြောင့် Java SE 7 တွင် Fork And Join အား ဖြည့် စွက်လာခဲ့ပြီး Thread အများဖြင့် ဆောင်ရွက်လာစေနိုင်ခဲ့ပါသည်။




သို့ရာတွင် Fork လုပ်ရာတွင် မည်ကဲ့သို့ခွဲမည်ဆိုသည်ကပြဿနာတစ်ခုဖြစ်လာပါသည်။ မည်ကဲသို့ ခွဲမည်ဆိုသည်ကို ပရိုဂရမ်မာများက ကိုယ့်ဟာကို ရေးသားရန်လို အပ်နေပါသေးသည်။ Java SE 7 အထိ Java Programming တွင် Iteration Process များအား for, while တို့အား အသုံးပြု၍ Control လုပ်ကြရပါသည်။ အဆိုပါ Iteration အမျိုးအစားအား External Iteration Control ဟု ခေါ်ဆိုပါသည်။ External Iteration Control များသည် API မှ Operation များကို ဖြည့်စွက်ရန်ခက်ခဲခဲ့ပါသည်။

ထို့ကြောင့် Java SE 8 အရောက်တွင် Collection Framework နှင့် Stream API တို့တွင် Internal Iteration Control များအား ဖြည့်စွက်လာခဲ့ပါသည်။ ထို့အပြင် Stream API တွင် Parallel Stream ကို လဲ ဖြည့်စွက်လာနိုင်ပြီး API ဘက်မှ Parallel Operation အား အပြည့်အဝပံ့ပိုးလာနိုင်ခဲ့ပါသည်။

တစ်ခုသတိပြုရန်မှာ Parallel Operation ကို နေရာတကာ အသုံးပြု၍ကောင်းသည်မဟုတ်။ ဥပမာအား ဖြင့် တစ်ဦးတည်း ဖြေရှင်းနိုင်သော ပြဿနာအား လူအများကို ခွဲနေပါမှမလိုအပ်ပဲကြံ့ကြာနေပါမည်။ ထို့ကြောင့် သင့်တော်သော Operation များကိုသာ Parallel Operation နှင့်လုပ်ဆောင်သင့်ပါသည်။


Creating Parallel Stream


Parallel Stream ဟု ဆိုသော်လဲ ၎င်းအတွက်သီးခြား Interface ကို အသုံးပြုနေခြင်း မဟုတ်။ Stream တွင် အသုံးပြုခဲ့သော Stream, IntStream တို့ကို သာ အသုံးပြုနေပါသည်။ ထူးခြားသည်မှာ Stream အားစတင်စေသော နေရာတွင်ဖြစ်ပါသည်။

Parallel Stream အားရရှိလိုပါက Collection#parallelStream method နှင့် Stream#parallel method ကို အသုံးပြုနိုင်၏။ ထို့အပြင် Parallel Stream မှ သာမန် Stream ကို ရယူလိုပါက squential method အား အသုံးပြုနိုင်ပါသည်။
  List<String> list = Arrays.asList("a", "b", "c");
  Stream<String> pStream1 = list.parallelStream();
  
  IntStream pStream2 = IntStream.range(1, 10).parallel();


Ordering of Parallel Stream


Parallel Stream များသည် Stream အတွင်းရှိ Element များအား တစ်ပြိုင်နက်တည်း လုပ်ဆောင်မှု့များအား ဆောင်ရွက်စေနိုင်ရန်အတွက် Multi Thread များကို အသုံးပြုဆောင်ရွက်နေပါသည်။ ထို့ကြောင့်သာမန်အားဖြင့် နဂိုမူလ အစီအစဥ်အား ထိမ်းသိမ်းပေးထားနိုင်မည်မဟုတ်။ အရင်ပြီးဆုံးသည့် Thread အတွင်းမှ Element သည်နောက်ဆုံးသို့ အရင်ရောက်ရှိလာမည်ဖြစ်ပါသည်။
 public static void main(String[] args) {
  IntStream.range(1, 10)
   .parallel().filter(a -> a % 2 == 1)
   .forEach(System.out::println);;
 }

အထက်ပါကုဒ်များအား Run ကြည့်ပါက 1,3,5,7,9 ဟု အစီအစဥ်အတိုင်းထွက်မည်မဟုတ်။ အစီအစဥ်အတိုင်း ထွက်ပေါ်စေလိုပါက forEach method အစား forEachOrdered အားအသုံးပြုရမည်ဖြစ်သည်။


Converting To Map


Stream မှ Map Object သို့ပြောင်းလဲရန် Collectors Class ၏ groupingBy နှင့် mapTo မက်သတ်တို့ကို အသုံးပြုနိုင်သည်။ Parallel Stream များတွင်လဲ အဆိုပါ မက်သတ်များကို အသုံးပြုနိုင်ပါသည်။ သို့ရာတွင် groupingByConcurrent နှင့် toConcurrentMap တို့ကိုလဲအသုံးပြုနိုင်ပါသည်။ အဆိုပါ မက်သတ်များ၏​ Return Type သည် java.urtil.concurrent.ConcurrentMap Interface ဖြစ်သည်။

ဤကဲ့သို့ ConcurrentMap အား အသုံးပြုခြင်းအားဖြင့် Parallel Stream လုပ်ဆောင်ချက် Performance အား ပိုမိုကောင်းမွန်စေမည် ဖြစ်ပါသည်။
    static class Student {
        private long id;
        private String name;
        private String address;
        private String township;
        
        // getters and setters
        
    }
    
    public static void main(String[] args) {
        List<Student> list = getStudents();
        
        ConcurrentMap<String, List<Student>> map = list.parallelStream()
                .collect(Collectors.groupingByConcurrent(a -> a.getTownship()));
    }

အထက်ပါနမူနာသည် Student List မှ township တန်ဖိုးတူသည့် Map အားရယူနေခြင်းဖြစ်ပါသည်။ groupingBy မက်သတ်အား အသုံးပြုပါက ရလဒ်မှာ အတူတူပင်ဖြစ်သော်လဲ Parallel Stream ဖြစ်သောကြောင့် groupingByConcurrent ကို အသုံးပြုခြင်းက Performance ကို ပိုမိုကောင်းမွန် စေပါသည်။


ဆက်ပါမည်။ ကျေးဇူးတင်စွာဖြင့်
မင်းလွင်

April 11, 2015

Stream API - Part 3

အခုတလော ရက်ဆက်ဆိုသလို ဘလော့ရေးဖြစ်နေပါတယ်။ မရေးဖြစ်ပဲ ရေးလက်စ ကျန်နေတာ ကြာပြီဖြစ်တဲ့ အခန်းဆက်တွေကို ဦးစားပေးရေးနေဖြစ်ခဲ့ပါတယ်။ အခုဆိုရင် အခြေခံဒေတာဘေစ် အကြောင်းကလဲ ပြီးခါနီးပါပြီ။ JavaFX Days အကြောင်းလဲ နောက်ပိုင်းကို ရောက်နေပါပြီ။ Java SE 8 အကြောင်းလဲ နောက်ပိုင်းနားကပ်နေပြီဖြစ်ပါတယ်။

ဒီနေ့ဘာရေးရင်ကောင်းမလဲ အတော် လေးစဥ်းစားရခက်ခဲ့ပါတယ်။ 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 အကြောင်းအား နောက်တွင်ဆက်လက်ဖေါ်ပြပါဦးမည်။


ဆက်ပါဦးမည်။ လေးစားစွာဖြင့်
မင်းလွင်

April 8, 2015

Stream API - Part 2

ပြီးခဲ့တဲ့ ဘလော့ဖြင့် Stream API အား စတင်စေခြင်း အကြောင်းကို မိတ်ဆက်ခဲ့ပါပြီ။ ရေးရင်းည အတော်နက်လာတာနဲ့ ပြီးအောင် မရေးဖြစ်ခဲ့ဘူး။ ဒီနေ့တော့ Stream Operation ထဲက Intermediate Operation အကြောင်းကို ဆက်ရေးပါဦးမယ်။ နောက်တစ်ခေါက်မှပဲ Terminal Operation အကြောင်းကို ရေးပြီးအဆုံသတ်ပါမယ်။


Intermediate Operation


Stream Object တစ်ခုကနေ နောက် Stream တစ်ခုကို ဖြစ်ပေါ်စေတဲ့ လုပ်ဆောင်ချက်တွေကို Intermediate Operation တွေဖြစ်ကြတယ်။ အလွယ်ဆုံးမှတ်ရမယ်ဆိုရင် Stream Interface ရဲ့ Stream Object တစ်ခုခုကို Return လုပ်ပေးနိုင်တဲ့ Instance Method တွေဟာ Intermediate Operation ကို လုပ်ဆောင်ပေးနိုင်ကြတယ်။ ထင်ရှားတဲ Method တွေကတော့ အောက်ပါ Method တွေဖြစ်ကြတယ်။


Name Description
filter Stream အတွင်းမှာရှိတဲ့ Element များကို စီစစ်ပြီး အသုံးပြုလိုတဲ့အခါမျိုးမှာ အသုံးပြုနိုင်တယ်
map မူလ Stream ရဲ့ Element တွေကို အခြားသော Type သို့ပြောင်းပြီး Stream အဖြစ်ရယူလိုတဲ့အခါမျိုးမှာ သုံးနိုင်ပါတယ်
flatMap Stream Object ရဲ့ Element တွေဟာ Collection တွေဖြစ်ကြပြီး၊ အဲ့ဒီ Collection ထဲက Element တွေကို Stream အဖြစ်ရယူလိုတဲ့ အခါ အသုံးပြုနိုင်ပါတယ်
distinct Stream ထဲမှာရှိတဲ့ တစ်ခုထက်ပိုတဲ့ တန်ဖိုးတူ Element တွေကို ဖြုတ်ထုတ်ထားတဲ့ Stream ကို လိုချင်ရင် အသုံးပြုနိုင်ပါတယ်
sorted Stream ထဲမှာရှိတဲ့ Element တွေကို Sort လုပ်ထားတဲ့ Stream အဖြစ်သုံးလိုတဲ့အခါ အသုံးပြုနိုင်ပါတယ်
limit Stream ထဲမှာရှိတဲ့ Element တွေကို Parameter မှာပါတဲ့ အရေအတွက်တန်ဖိုးအတိုင်း limit လုပ်ပြီးသုံးလိုတဲ့အခါမျိုးမှာ သုံးနိုင်ပါတယ်
skip Stream ထဲမှာရှိတဲ့ Element တွေကို Parameter မှာပါတဲ့ အရေအတွက်တန်ဖိုးအထိ skip လုပ်ပြီး သုံးလိုတဲ့ အခါမှာ သုံးနိုင်ပါတယ်




Filter Operation


Filter Operation ကို လုပ်ဆောင်စေလိုရင် Stream#filter method ကို အသုံးပြုနိုင်ပါတယ်။ filter method ဟာ Predicate Interface Object ကို Parameter အဖြစ်ရယူနေပါတယ်။ Predicate Interface ဟာ One Method Interface (Functional Interface) ဖြစ်ပြီး၊ Anonymous Class၊ Lambda Expression ဒါမှမဟုတ် Method Reference တို့ကို အစားထိုးအသုံးပြုနိုင်မှာဖြစ်တယ်။ ဒီနမူနာထဲမှာတော့ Lambda Expression ကို အသုံးပြုသွားပါမယ်။

ဥပမာအားဖြင့် IntStream ထဲက စုံကိန်းတွေကြီးကိုပဲ Filter လုပ်ချင်တယ်ဆိုရင် အောက်ပါအတိုင်း filter လုပ်ရပါမယ်။
    public static void main(String[] args) {
        IntStream evenStream = IntStream.range(1, 10).filter(a -> a % 2 == 0);
        int sum = evenStream.reduce(0, (a, b) -> a+b);
        System.out.println(sum);
    }

အပေါ်မှာ ဖေါ်ပြထားတဲ့ filter method ထဲက a -> a % 2 == 0 ဆိုတာက Predicate ကို အစားထိုးနေတဲ့ Lambda Expression ပါပဲ။ Stream ထဲမှာ ပါတဲ့ Element တွေဟာ filter method ထဲကို တစ်ခုချင်း ရောက်လာပြီး၊ အဲ့ဒီ့ကိန်းတွေကို 2 နဲ့စားလို့ ရတဲ့ အကြွင်းဟာ သုညဖြစ်တာ မှန်တယ်ဆိုရင် အနေက်ဘက်ကို ဆက် စီးသွားမှာ ဖြစ်တယ်။

နောက်ပြီး Student Object ထဲက အသက် ၂၀ ကျော်ပြီး ရန်ကုန် မှာနေတဲ့ အမျိုးသား ကျောင်းသားတွေကို လိုချင်ရင် အောက်ပါအတိုင်းရေးသားနိုင်မှာဖြစ်ပါတယ်။
        studentStream()
            .filter(a -> a.getAge() > 20 
                    && a.getGender().equals(Gender.male) 
                    && a.getAddress().contains("Yangon"))
                .forEach(a -> System.out.println(a));

အထက်ပါကုဒ်အတွင်းတွင် filter method အတွင်း Predicate Interface အတွက် Lambda Expression အား ရေးသားထားပြီး၊ အတွင်းတွင် ရေးသားထားသော အချက်အလက်များနှင့်ကိုက်ညီမှသာ၊ နောက် Stream သို့ ဆက်သွားစေမည်ဖြစ်ပါသည်။



Map Operation


Map Operation အား Stream#map method ဖြင့် လုပ်ဆောင်စေနိုင်ပြီး၊ Function<T, R> interface object အား Argument အဖြစ် ရယူပါသည်။ Function Interface သည်၊ Abstract Method တစ်ခုတည်းသာ ပါဝင်သော Functional Interface ဖြစ်ပါသည်။ ထို့ကြောင့် Lambda Expression ဖြင့် လည်း လုပ်ဆောင်ချက်များကို ဖြည့်စွက်ပေးနိုင်မှာ ဖြစ်ပါတယ်။

ဖြည့်စွက်ပေးရန်လိုသော Method တွင် Parameter တစ်ခု နှင့် Return Type တစ်ခုပါဝင်ရပါမည်။ မူလ Stream မှ Element များသည် Parameter အဖြစ် ဝင်ရောက်လာမည် ဖြစ်ပြီး၊ ပြုပြင်ပြောင်းလဲပြီး ပုံစံအသစ်ဖြင့် Return လုပ်ပေးရပါမည်။

အောက်ပါနမူနာအား ကြည့်ပါမည်။

    public static void main(String[] args) {
        try (Stream<String> stream = Files.lines(Paths.get("sample.txt"))) {
            
            Stream<String[]> otherStream = stream.map(a -> a.split(","));

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

အထက်ပါနမူနာတွင် sample.txt ဖိုင်အတွင်းမှ စာကြောင်းများမှ Stream<String> အားစတင်စေကာ၊ map method ဖြင့် Stream<String> မှ Stream<String[]> သို့ပြောင်းလဲပေးပါသည်။ map method ၏ Parameter သည် Function<String, String[]> ၏ Object အား Lambda Expression နှင့်အစားထိုးထားခြင်းဖြစ်ပါတယ်။ Lambda Expression ၏ a Parameter နေရာတွင် Stream<String> ၏ String တစ်ခုစီ ဝင်ရောက်လာပြီး၊ method body နေရာတွင် a.split(",") ဖြင့် String[] အဖြစ်ပြောင်းကာ Return လုပ်နေပါသည်။



FlatMap Operation


FlatMap သည်လည်း map ကဲ့သို့ပင် မူလ Stream အား အခြားသော Stream တစ်ခုသို့ပြောင်းပေးနိုင်ပါသည်။ မတူညီသည်မှာ အသုံးပြုနေသော Argument ဖြစ်ပါသည်။ Map သည် Argument အဖြစ် Function<? super T, ? extends R> ကို အသုံးပြုသော်လဲ၊ flatMap သည် Function<? super T, ? extends Stream<? extends R>> ကို အသုံးပြုပါသည်။

Map တွင်အသုံးပြုသော Function ၏ Return Type သည် နောက် Stream ၏ Element Type ဖြစ်သော်လဲ၊ FlatMap တွင် အသုံးပြုသော Function ၏ Return Type သည် နောက် Stream အမျိုးအစား ဖြစ်ပါသည်။ နောက်ဆုံးတွင် ထို Stream များ၏ Element များပါဝင်သော Stream အား ပြန်လည်ရရှိမည် ဖြစ်သည်။

public class FlatMapSample {

    static class Member {
        private String name;
        private List<String> skills;
        
        public Member(String name, String ... skills) {
            this.name = name;
            this.skills = Arrays.asList(skills);
        }

        // getter and setter
        
    }
    
    public static void main(String[] args) {
        List<Member> team = new ArrayList<>();
        
        team.add(new Member("Mg Mg", 
            "Java SE", "JPA", "JSP", "Servlet"));
        team.add(new Member("Ag Ag", 
            "MySQL", "JDBC", "Hibernated", "Spring Framework"));
        
        List<String> skills = team.stream()
                .flatMap(a -> a.skills.stream())
                .collect(Collectors.toList());
        
    }
}

အထက်ပါနမူနာတွင် flatMap ၏ Parameter ဖြစ်သော Lambda Expression ၏ method body အတွင်းတွင် a.skills.stream() ဟု Stream<String> အား ပြန်ပေးနေပါသည်။ ဤနည်းအားဖြင့် flatMap လုပ်ဆောင်ချက်အား လုပ်ဆောင်ပြီချိန်တွင် Member Object များ၏ skills များ အတွင်းရှိ String များ ပါဝင်သော Stream<String> အဖြစ်ပြောင်းလဲသွားမည် ဖြစ်ပါသည်။



Others Intermediate Operations


အခြားသော Operation များ ဖြစ်ကြသော distinct, sortd, skip နှင့် limit တို့သည် နာမည်ကြားရုံနှင့် မည်သို့ အလုပ်လုပ်မည်ကို ခန့်မှန်းနိုင်မည် ဖြစ်သောကြောင့် ဤနေရာတွင် နမူနာအနေနှင့် တိုက်ရိုက်ရေးသား သွားပါမည်။
    public static void main(String[] args) {
        DoubleStream.generate(Math::random)
            .mapToInt(a -> Double.valueOf(a * 100).intValue())
            .distinct()
            .skip(10)
            .limit(20)
            .sorted()
            .forEach(System.out::println);
    }
အထက်ပါနမူနာတွင် DoubleStream#generate ဖြင် Random Number များအား Generate လုပ်ကာ Stream အဖြစ်ထုတ်ပေးနေ ပါသည်။ ထို့နောက်တွင် ထို Double Element အား 100 ဖြင့်မြှောက်ကာ Integer အဖြစ်ပြောင်းပါသည်။ ထို့နောက် distinct method ဖြင့် တန်ဖိုးတူကိန်းများအား တစ်ခုထက် ပိုမပါအောင်စစ်ထုတ်ပါသည်။

တဖန် skip အား အသုံးပြု၍ ပထမဦးဆုံး ၁၀လုံးအား skip လုပ်ပါမည်။ ထို့နောက် ကိန်း၂၀အထိ litmit လုပ်ပါသည်။ နောက်တွင် sort လုပ်၍​ တစ်ခုချင်းအား System Output Writer ဖြင့် ရိုက်ထုတ်နေခြင်းဖြစ်ပါသည်။

ဤဘလော့ဖြင့် Stream API ၏ Intermediate Operation များ အကြောင်းကို လေ့လာခဲ့ပါသည်။ filter၊ map နှင့် flatMap တို့သည် Functional Interface များ အား အသုံးပြုထားသောကြောင့် လိုအပ်သလို လုပ်ဆောင်ချက်များအား ထည့်သွင်းအသုံးပြုနိုင်မည်ဖြစ်ပါသည်။

နောက်ရက်များမှ Stream API ၏​ Terminal Operation များ အကြောင်းကို ရေးသားပါဦးမည်။


လေးစားစွာဖြင့်
မင်းလွင်

April 7, 2015

Stream API

Java SE 8 အကြောင်းမရေးဖြစ်ခဲ့သည်မှာ ကြာပြီဖြစ်သည်။ Java Developer Class မှာ SE အတန်းနှစ်ခုကို ဝင်သင်နေတဲ့အတွက် အတော်လေးကို အချိန်ပေးရပါတယ်။ ဒီကြားထဲ ဂျပန်ကလာတဲ့ ပရိုဂျက်အချို့ကိုလည်း ဝင်ရေးလိုက်၊ Estimation တွေလုပ်လိုက်နဲ့ သိပ်ပြီး ဘလောဂ်ကို မရေးဖြစ်ဘူးဖြစ်နေတယ်။ ရေးလက်စ Java SE စာအုပ်ကိုလည်းအဆုံးသတ်ချင်တဲ့အတွက် လိုနေတဲ့ အခန်းလေးတွေကို လိုက်ဖြည့်နေရတယ်။

ဒီတစ်ခေါက်တော့ Java SE 8 ရဲ့ Stream API အကြောင်းကို ရေးလိုက်ပါဦးမယ်။ Stream API ဆိုတာ Lambda Expression နဲ့အတူ Java SE 8 ရဲ့ API အသစ်တစ်ခု ဖြစ်ပါတယ်။ Java SE 8 ကို တစ်ခုလုံး ခြုံကြည့်လိုက်တဲ့အခါမှာ Concurrency ကို အဓိကထားပြီး ပြုပြင်လာတာကို တွေ့ရတယ်။ Collection တွေကို အရင်တုန်းကဆိုရင် အပြင်ကနေ Iteration ကို Control လုပ်နေရတဲ့အတွက် Concurrent Program တွေမှာ ခွဲပြီး ခိုင်းဖို့ဟာ အစဉ်မပြေခဲ့ဘူး။ အဲ့ဒီအတွက် Internal Iteration တွေကို သုံးဖို့ကြိုးစားလာတာကို တွေ့ရတယ်။

အမှန်ဆို လက်ရှိ Collection ထဲကို Internal Iteration ကို ထည့်ပြီး၊ လိုအပ်တဲ့ Concurrent Feature တွေကို ထည့်လိုက်ရင်ရမယ်ထင်ပေမယ့် ပိုပြီးရှင်းအောင်ဆိုပြီး Stream API ကို သတ်သတ်ခွဲထုတ်ခဲ့တာကို တွေ့ရတယ်။ ဒီဘလောဂ်မှာ Stream API ဆိုတာဘာလဲ။ Stream API နဲ့ ဘာတွေလုပ်လို့ရတယ် ဆိုတာကို လေ့လာကြည့်ပါမယ်။


Stream API ဆိုတာဘာလဲ


Collection ကဲ့သို့ Data တွေကို Stream ကဲ့သို့ စီးဆင်းစေပြီး လိုအပ်တဲ့လုပ်ဆောင်ချက်တွေကို လုပ်ဆောင်စေနိုင်တဲ့ API တစ်မျိုး ဖြစ်ပါတယ်။ Stream API ရဲ့ အဓိကကျတဲ့အပိုင်းဟာ Stream Interface ဖြစ်ပြီး ၎င်းမှာ လုပ်ဆောင်ချက်တွေကို အစီအစဉ်အလိုက် တစ်ခုပြီးတစ်ခု လုပ်ဆောင်စေနိုင်တဲ့ Serial Stream နဲ့၊ လုပ်ဆောင်ချက်တွေကို အပြိုင်အဆိုင်လုပ်ဆောင်စေနိုင်တဲ့ Parallel Stream ဆိုပြီးရှိပါတယ်။ Concurrent Program တွေမှာဆိုရင် Parallel Stream ကို အသုံးပြုရမှာ ဖြစ်ပါတယ်။ Concurrent Program တွေကို ရေးသားရာမှာလည်း Parallel Stream ကို သုံးလိုက်ရုံနဲ့ အလွယ်တကူရေးသားနိုင်မှာဖြစ်ပါတယ်။ အတွင်းပိုင်းမှာ အသုံးပြုနေတာကတော့ Java SE 7 မှာ ပါဝင်ခဲ့တဲ့ Fork And Join ပဲဖြစ်ပါတယ်။

Stream တစ်ခုရဲ့ လုပ်ဆောင်ပုံကိုကြည့်မယ်ဆိုရင် သူ့ဆီမှာ အပိုင်း ၃ပိုင်းရှိပါတယ်။

  1. Stream Creation
  2. Intermediate Operations
  3. Terminal Operations
Stream ရဲ့ထူးခြားချက်ဟာ သူတို့တွေရဲ့ လုပ်ဆောင်ချက်တွေကို Argument တွေအနေနဲ့ ရယူနေတာဖြစ်တယ်။ Stream ရဲ့ Operators တွေမှာ One Method Interface (Functional Interface) တွေကို Argument တွေအနေနဲ့ ရယူပြီး၊ ၎င်းတို့ကို Lambda Expression နဲ့ ရေးသားလို့ရပါတယ်။

တစ်ခုသတိပြုရန်မှာ Stream များသည် သုံးစွဲကုန်ဆုံးနိုင်သော Object များ ဖြစ်ကြပါသည်။ ထို့ကြောင့် Terminal Operation အထိ အသုံးပြုပြီသော Stream များအား နောက်တစ်ကြိမ် အသုံးပြုနိုင်တော့မည်မဟုတ်ပါ။

Stream အား အသုံးမပြုမှီကနှင့် Stream အား အသုံးပြုနိုင်သည်နှင့် မည်သို့ကွာခြားသည်ဆိုသည်ကို လက်တွေ့ လေ့လာကြည့် ပါမည်။ Integer 1 မှ 100 အထိ စုစုပေါင်းအား ရှာဖွေကြည့်ပါမည်။
 public static void main(String[] args) {
  int sum = 0;
  
  for(int i=1; i <= 100; i ++) {
   sum += i;
  }
  
  System.out.println(sum);
 }
Stream အား အသုံးမပြုမှီက for ဖြင့် loop ပတ်ကာ sum variable အားပေါင်းယူမည် ဖြစ်သည်။
 public static void main(String[] args) {
  int sum = IntStream.rangeClosed(1, 100).sum();
  System.out.println(sum);
 }
Stream အား အသုံးပြုမည်ဆိုပါက IntStream ၏​ rangeClosed method ဖြင့် Stream အား စတင်စေကာ၊ sum method ဖြင့် စုစုပေါင်းအား ရှာဖွေနိုင်မည်ဖြစ်သည်။ အမှန်ဆိုပါက Operation များအား တစ်ခုထက်မက လုပ်ဆောင်စေလိုသော အခါ Stream ၏ အသုံးဝင်ပုံအား ပို၍မြင်သာမည် ဖြစ်ပါသည်။ နောက်အခန်းများဖြင့် Stream ၏ အသုံးဝင်ပုံအား လေ့လာသွားပါဦးမည်။


Stream Creation


Stream များအား စတင်စေရန် လုပ်ဆောင်ပေးနိုင်သော အပိုင်းဖြစ်ပါသည်။ Stream များအား Collection များ၊ File များမှသော်၎င်း၊ Range Numbers များနှင့် Stream Class များ၏ generate နှင့် iterate method များအား အသုံးပြု၍သော်၎င် စတင်စေနိုင်ပါသည်။ Stream Creation Operation များ၏ return type များသည် Stream တစ်ခုခု ကို ဖြစ်ပေါ်စေမည် ဖြစ်ပါသည်။

Collection Object မျာ၏ stream ဒါမှမဟုတ် parallelStream method အား အသုံးပြုပါက Stream Object အား ပြန်လည်ရရှိမည် ဖြစ်ပါသည်။
  List<Integer> list = Arrays.asList(1,2,3,4,5);
  Stream<Integer> stream = list.stream();
  Stream<Integer> pStream = list.parallelStream();

Text File တစ်ခု အတွင်:ရှိ စာကြောင်:မျာ:အာ: တစ်ကြောင်:စီ Stream Object အနေနှင့် ရယူလိုသောအခါ Files Class ၏ lines method အာ:အသုံးပြုနိုင်ပါသည်။
  try {
   Stream<String> strStream = Files.lines(Paths.get("sample.txt"));
   strStream.forEach(System.out::println);
   strStream.close();
  } catch (IOException e) {
   e.printStackTrace();
  }
အထက်နမူနာမှာ ကြည့်မယ်ဆိုရင် Text File တစ်ခု အတွင်းက စာကြောင်းတွေကို Files Utility Class ရဲ့ lines static method ကနေ Stream<string> Object အဖြစ်ရယူနိုင်တာကို တွေ့ရပါမယ်။ အဲ့ဒီ Method မှာသုံးနေတဲ့ Argument ကတော့ NIO2 ရဲ့ Path Object ပဲဖြစ်ပါတယ်။ NIO2 အကြောင်းကို တော့ Java SE 7 အကြောင်း အခန်းဆက်မှာ ဖေါ်ပြခဲ့ပါပြီ။ သူကလဲ အတော်လေးကို အသုံးဝင်တဲ့ API တစ်ခု ဖြစ်ပါတယ်။

နောက်ပြီး Stream API ထဲမှာ Custom Object တွေကို Stream အဖြစ် အသုံးပြုဖို့ Stream Interface အပြင်၊ Primitive Type တွေကို အသုံးပြုဖို့ သီးခြား Interface တွေလဲပါလာပါတယ်။ သူတို့တွေကတော့ IntStream, LongStream နဲ့ DoubleStream တို့ပါပဲ။ အဲ့ဒီ ထဲက IntStream နဲ့ LongStream Interface တွေရဲ့ static method ဖြစ်တဲ့ range နဲ့ rangeClosed method တွေကိုသုံးရင် သက်ဆိုင်ရာ Stream Object အသီးသီးကို ရရှိစေမှာ ဖြစ်ပါတယ်။

range နဲ့ rangeClosed တို့ရဲ့ကွာခြားချက်ကတော့ သူတို့ရဲ့ ဒုတိယ Parameter ကို Stream အတွင်း ပါဝင်စေမလား၊ မပါဝင်စေမလား ဆိုတဲ့အချက်ပါပဲ။ range အားအသုံးပြုပါက ဒုတိယ Parameter ဟာ Stream အတွင်းပါဝင်ခြင်းမရှိပဲ၊ rangeClosed အား အသုံးပြုပါက ဒုတိယ Parameter အထိ Stream အတွင်း ပါဝင်လာမှာ ဖြစ်ပါတယ်။
 public static void main(String[] args) {
  
  IntStream intStream1 = IntStream.range(1, 10);
  intStream1.forEach(System.out::println);
  
  IntStream intStream2 = IntStream.rangeClosed(1, 10);
  intStream2.forEach(System.out::println);
 }
အထက်ပါကုဒ် တွေကို Class တစ်ခုထဲ ကူးရေးပြီး Run ကြည့်ရင် intStream1 မှာတော့ 1 ကနေ 9 အထိပါလာတာကို တွေ့ရပြီး၊ intStream2 မှာတော့ 1 ကနေ 10 အထိ ပါလာတာကို တွေ့ရပါမယ်။

နောက်ပြီး Stream Interface တွေရဲ့ of method ကို သုံးရင်လဲ varage argument ကနေ သက်ဆိုင်ရာ Stream Object ကို ရယူနိုင်ပါလိမ့်မယ်။ Arrays.asList ကို သုံးပြီး Collection ကနေ Stream Object ကို ရယူနေမဲ့အစား၊ Stream.of ကို သုံးလိုက်တာ ပိုပြီးရှင်းပါတယ်။
 public static void main(String[] args) {
  Stream<String> stream1 = Arrays.asList("a", "b", "c").stream();
  Stream<String> stream2 = Stream.of("a", "b", "c");
  
  stream1.forEach(System.out::println);
  stream2.forEach(System.out::println);
 }

stream1 နဲ့ stream2 object ကို ကြည့်ရင် stream2 ကပိုရှင်းပါတယ်။

နောက်ပြီး generate method ကို သုံးမယ် ဆိုရင် ကြိုက်တဲ Supplier ကနေ Stream ကို generate လုပ်နိုင်မှာဖြစ်တယ်။
 public static void main(String[] args) {
  DoubleStream stream = DoubleStream.generate(Math::random).limit(20);
  stream.forEach(System.out::println);
 }

ပို၍လိုအပ်သလို Stream Object တွေကို လို အပ်ပါက iterate method ကို အသုံးပြုနိုင်မှာ ဖြစ်ပါတယ်။
 public static void main(String[] args) {
  IntStream.iterate(1, a -> a + 2).limit(10).forEach(System.out::println);
  Stream.iterate("a", a -> a + "a").limit(10).forEach(System.out::println);
 }
အပေါ်က IntStream ကတော့ 1 ကနေ 19 အထိ မကိန်းတွေကြီး Stream အဖြစ်ရရှိမှာ ဖြစ်ပြီး၊ နောက် Stream တစ်ခုကတော့ a စာလုံးတွေ တစ်လုံးစီ ၁၀လုံးတိတိ တိုးလာစေမှာ ဖြစ်ပါတယ်။

ဒီကနေ့ ဘလော့မှာတော့ Stream ဆိုတာဘာလဲ၊ ပြီတော့ Stream မှာ ဘယ်လို Operation တွေရှိလဲ၊ ပြီတော့ Stream တွေကို ဘယ်လို Create လုပ်ရတယ် ဆိုတဲ့ အပိုင်းကို လေ့လာခဲ့ပါတယ်။ နောက်ရက်များတွင်လဲ ဆက်လက်ပြီ Stream API ရဲ့ Intermediate Operation နဲ့ Terminal Operation တွေအကြောင်းကို ဖေါ်ပြသွားပါမယ်။

ဆက်ပါဦးမည်၊ လေးစားစွာဖြင့်
မင်းလွင်