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 များ အကြောင်းကို ရေးသားပါဦးမည်။


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

No comments:

Post a Comment