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 တွေအကြောင်းကို ဖေါ်ပြသွားပါမယ်။

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

No comments:

Post a Comment