函數式接口和Stream流的完美搭配

1.函數式接口

函數式接口:有且僅有一個抽象方法的接口

1.2 函數式接口作爲方法的參數

/*
定義一個類(RunnableDemo),在類中提供兩個方法
	一個方法是:startThread(Runnable r) 方法參數Runnable是一個函數式接口
	一個方法是主方法,在主方法中調用startThread方法
*/

public class RunnableDemo {
    public static void main(String[] args) {
        //匿名內部類
        startThread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"啓動了");
            }
        });
        
        //Lambda表達式
        startThread(()->System.out.println(Thread.currentThread().getName()+"啓動了"));
       
    }

    private static void startThread(Runnable r){
        //Thread t = new Thread(r,"stratThread");
        //t.start();
        new Thread(r,"stratThread").start();
    }
}
運行結果:stratThread啓動了

如果方法的參數是函數式接口,我們可以使用Lambda表達式作爲參數傳遞

  • startThread(()->System.out.println(Thread.currentThread().getName()+“啓動了”));

1.3 函數式接口作爲方法的返回

/*
    定義一個類(ComparatorDemo),在類中提供兩個方法
        一個方法是:Comparator<String> getComparator()    方法返回值Comparator是一個函數式接口
        一個方法是主方法:在主方法中調用getComparator()方法

    需求:比較字符串的長度,按照長短來排序
 */

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

public class ComparatorDemo {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("aaaa");
        list.add("bbb");
        list.add("sss");
        list.add("c");
        list.add("cddf");

        System.out.println(list); //排序前
        Collections.sort(list,getComparator());//第一個參數是集合對象,第二個參數是比較器
        System.out.println(list); //排序後
    }

    //當函數式接口作爲方法的返回值時,返回的是接口的實現類對象
    private static Comparator<String> getComparator() {
        //使用匿名內部類返回一個Comparator對象
        /*Comparator c = new Comparator<String>() {
            @Override
            public int compare(String s1, String s2) {
                return s1.length() - s2.length();
            }
        };
        return c;*/
        
       /* return new Comparator<String>() {
            @Override
            public int compare(String s1, String s2) {
                return s1.length() - s2.length();
            }
        };*/
        //Lambda表達式
        return (s1, s2) -> s1.length() - s2.length();
    }
}

1.4 常用函數式接口

1.4.1 Supplier接口

Supplier接口主要是用來生產數據的,包含一個無參的方法

  • T get(): 獲得結果
  • 該方法不需要參數,它會按照某種實現邏輯(有Lambda表達式實現)返回一個數據。
  • Supplier接口也被稱爲生產型接口,如果我們指定了接口的泛型什麼類型,那麼接口中的get方法就會返回什麼類型的數據給我們使用。
import java.util.Arrays;
import java.util.function.Supplier;

public class SupplierDemo {
    public static void main(String[] args) {
        Integer[] arr = {12,43,22,67,34,99};

        //使用匿名內部類
        /*Integer max = getMax(new Supplier<Integer>() {
            @Override
            public Integer get() {
                Arrays.sort(arr);
                return arr[arr.length - 1];
            }
        });
        */
        //Lambda表達式
        Integer max = getMax(()->{
            Arrays.sort(arr);
            return arr[arr.length - 1];
        });
        System.out.println(max);
    }

    //使用Supplier接口作爲參數,獲取數組中的最大值
    private static Integer getMax(Supplier<Integer> s){
        return s.get();
    }
}
運行結果: 99

1.4.2 Consumer接口

Consumer:包含兩個方法

  • void accept(T t):對給定的參數執行此操作
  • default Consumer andThen(Consumer after):返回一個組合的Consumer,依次執行此操作,然後執行after操作
  • Consumer接口也被稱爲消費型接口,它消費的數據的數據類型由泛型指定

使用accept(T t)

public class ConsumerDemo {
    public static void main(String[] args) {
        String[] strings = {"路飛,19","索隆,22","山治,21","娜美,19"};

        //使用匿名內部類
        /*useString(strings, new Consumer<String>() {
            @Override
            public void accept(String s) {
                String[] split = s.split(",");
                System.out.println("姓名;"+split[0]+",年齡:"+split[1]);
            }
        });*/

        //Lambda表達式
        useString(strings,s-> System.out.println("姓名;"+s.split(",")[0]+",年齡:"+s.split(",")[1]));
        
    }
    /**
     * @param strarr
     * @param con
     */
    private static void useString(String[] strarr, Consumer<String> con){
        //遍歷數組獲取元素,用於消費
        for (String s : strarr) {
            con.accept(s);//將String[] strarr集合中的元素依次取出,用於消費
        }
    }
}
運行結果:姓名;路飛,年齡:19
        姓名;索隆,年齡:22
        姓名;山治,年齡:21
        姓名;娜美,年齡:19

使用Consumer andThen(Consumer after)

public class ConsumerDemo {
    public static void main(String[] args) {
        String[] strings = {"路飛,19","索隆,22","山治,21","娜美,19"};
        //使用兩個Consumer接口對象的方法
        //匿名內部類
       /* useString2(strings, new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.print("姓名;"+s.split(",")[0]);
            }
        }, new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(",年齡:"+s.split(",")[1]);
            }
        });
*/
        //Lambda表達式
        useString2(strings,
                   s ->System.out.print("姓名;"+s.split(",")[0]),
                   s -> System.out.println(",年齡:"+s.split(",")[1])
                  );
    }

    /**
     * @param strarr
     * @param con1
     * @param con2
     * 使用 andThen 方法將 Consumer 對象串聯起來能夠連續執行,消費同一個對象
     */
    private static void useString2(String[] strarr,Consumer<String> con1,Consumer<String> con2){
        for (String s : strarr) {
            con1.andThen(con2).accept(s);//使用兩個Consumer對象對同一個s進行操作
        }
    }
}

1.4.3 Predicate接口

Predicate接口用於對參數進行判斷是否滿足指定條件

Predicate: 常用的四個方法

  • boolean test(T t ): 對給定的參數進行判斷(判斷邏輯由Lambda表達式實現),返回一個布爾值
  • default Predicate negate():返回一個邏輯的否定,對應邏輯非
  • default Predicate and(Predicate other): 返回一個組合的判斷,對應短路與
  • default Predicate or(Predicate other): 返回一個組合的判斷,對應短路或

① test、negate方法使用

public class Demo {
    public static void main(String[] args) {

      /*  boolean result = usePredicate("hello", new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s.length()>8;
            }
        });*/

        boolean b1 = usePredicate("hello", s -> s.length()>8);
        System.out.println(b1);

        boolean b2 = usePredicate("hellowrold", s -> s.length() < 15);
        System.out.println(b2);
    }
    //

    //判斷字符串是否滿足條件
    private static boolean usePredicate(String str,Predicate<String> p){
//        boolean test = p.test(str);
//        return p.test(str);
        return p.negate().test(str);
    }
}
運行結果: true
    	 false

② and(Predicate other)、or(Predicate other)方法使用

public class Demo2 {
    public static void main(String[] args) {

        boolean b = checkString("hello", s -> s .length() > 8, s -> s.length() < 15);
        System.out.println(b);
    }

    //同一個字符串給出兩個不同的判斷條件,最後把這兩個判斷的結果做邏輯與運算和邏輯或運算
    private static boolean checkString(String s, Predicate<String> p1, Predicate<String> p2) {
//        boolean b1 = p1.test(s);
//        boolean b2 = p2.test(s);
//        return b1&&b2;
        //使用and、or方法
//        return p1.and(p2).test(s);
        return p1.or(p2).test(s);
    }
}
運行結果:true

練習

  • String[] strArr = {“林青霞,30”, “柳巖,43”, “張曼玉,35”, “貂蟬,28”, “王祖賢,33”};
  • 字符串數組中有多條信息,請通過Predicate接口的拼裝將符合條件的字符串篩選到集合ArrayList中,並遍歷集合
  • 同時滿足如下要求:姓名長度大於2,年齡大於33
public class PredicateDemo1 {
    public static void main(String[] args) {
        String[] strArr = {"林青霞,30", "柳巖,43", "張曼玉,35", "貂蟬,28", "王祖賢,33"};

        //調用usePredicate方法
        //匿名內部類
       /* usePredicate(strArr, new Predicate<String>() {
            @Override
            public boolean test(String s) {//判斷姓名長度是否大於3
                return s.split(",")[0].length() > 3;
            }
        }, new Predicate<String>() {
            @Override
            public boolean test(String s) {//判斷年齡是否大於33
                return Integer.parseInt(s.split(",")[1]) > 33;
            }
        });*/

        //Lambda表達式
        usePredicate(strArr, 
                     s -> s.split(",")[0].length() > 2, //篩選姓名長度大於2的
                     s -> Integer.parseInt(s.split(",")[1]) > 33 //篩選年齡大於33的
                    );

    }

    private static void usePredicate(String[] strings, Predicate<String> pre1, Predicate<String> pre2) {
        //用於將存放滿足條件的數據
        ArrayList<String> list = new ArrayList<>();

        //遍歷字符串數組
        for (String string : strings) {
            boolean test = pre1.and(pre2).test(string);	//判斷是否滿足條件
            if (test) {//如果test爲true,那麼將strings放入list中
                list.add(string);
            }
        }

        //遍歷ArrayList
        for (String s : list) {
            System.out.println(s);
        }
    }
}
運行結果:	張曼玉,35

1.4.4 Function接口

Function<T , R >接口通常用於對參數進行處理,轉換(處理邏輯由Lambda表達式實現),然後返回一個新的值常用的兩個方法

  • R apply(T t):將此函數應用於給定的參數
  • default Function andThen(Function after):返回一個組合函數,首先將該函數應用於輸入,然後將after函數應用於結果
public class Demo2 {
    public static void main(String[] args) {
        //定義一個方法,把一個字符串裝換成int型,輸出
//        convert("100",s->Integer.parseInt(s));

        //定義一個方法,把一個int型數據加上一個值後轉成字符串,輸出
//        convert(12,integer -> String.valueOf(integer+21));

        //定義一個方法,把一個字符串轉換成int型,把int型的數據加上一個整數後,轉爲字符串輸出
        convert("12", s -> Integer.parseInt(s), i -> String.valueOf(i + 21));
    }

    //定義一個方法,把一個字符串轉換成int型,把int型的數據加上一個整數後,轉爲字符串輸出
    private static void convert(String s, Function<String, Integer> f1, Function<Integer, String> f2) {
//        Integer i = f1.apply(s);
//        String ss = f2.apply(i);
//        System.out.println(ss);
        String apply = f1.andThen(f2).apply(s);
        System.out.println(apply);
    }

    //定義一個方法,把一個int型數據加上一個值後轉成字符串,輸出
    private static void convert(Integer i, Function<Integer, String> f) {
        String result = f.apply(i);
        System.out.println(result);
    }

    //定義一個方法,把一個字符串裝換成int型,輸出
    private static void convert(String s, Function<String, Integer> f) {
        Integer result = f.apply(s);
        System.out.println(result);
    }
}


2.Stream流

2.1 Stream流的生成方法

  • Collection體系的集合可以使用默認方法stream()生成流

    ​ default Stream stream()

  • Map體系的集合間接地生成流

  • 數組可以通過Stream接口的靜態方法of(T…values)生成流

public class Demo2 {
    public static void main(String[] args) {
        //Collection體系可以使用默認方法stream()生成流
        List<String> list = new ArrayList<>();
        Stream<String> listStream = list.stream();

        Set<String> set = new HashSet<String>();
        Stream<String> setStream = set.stream();

        //Map體系的集合間接地生成流
        Map<String,Integer> map = new HashMap<>();
        Stream<String> keyStream = map.keySet().stream();
        Stream<Integer> vlaueStream = map.values().stream();

        Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();

        //數組可以通過Stream接口的靜態方法of(T...values)生成流
        String[] strArray = {"hello","world","java"};
        Stream<String> strArray1 = Stream.of(strArray);
        Stream<String> strArray2 = Stream.of("hello", "world", "java");


    }
}


2.2 Stream流常見中間操作方法

2.1.1 filter(Predicate p)

Stream filter (Predicate predicate):用於對流中的數據進行過濾

​ Predicate 接口中的方法 Boolean test(T t):對給定的參數進行判斷,返回一個布爾值

public class Demo1 {
    public static void main(String[] args) {
        ArrayList<String> names = new ArrayList<>();
        names.add("張三");
        names.add("張曼玉");
        names.add("張無忌");
        names.add("林青霞");
        names.add("張開泰");
        names.add("王五");
		//篩選除姓張的
        ArrayList<String> zhangs = new ArrayList<>();
        for (String name : names) {
            if(name.startsWith("張")){
                zhangs.add(name);
            }
        }
		//在姓張的基礎上篩選出姓名長度等於3的
        ArrayList<String> three = new ArrayList<>();
        for (String zhang : zhangs) {
            if(zhang.length()==3){
                three.add(zhang);
            }
        }
		//輸出最終結果
        for (String s : three) {
            System.out.println(s);
        }

        System.out.println("----------");
        //使用stream流編程
        names.stream().filter(s->s.startsWith("張")).filter(s->s.length()==3).forEach(System.out::println);
        /**
         * names.stream(): 生成stream流對象
         * filter(s->s.startsWith("張")):過濾出以“張”開頭的字符串
         * filter(s->s.length()==3):在上一個條件的基礎上,過濾出名字長度爲三的字符串
         * forEach(System.out::println):使用方法引用輸出最終結果
         */

    }
}


2.1.2 limit(long n)

Stream limit(long n):返回截取的前 n 個元素組成的流

2.1.3 skip(long n)

Stream skip(long n):跳過指定參數個數的元素,返回剩下元素組成的流

public class Demo3 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("張三");
        list.add("李四");
        list.add("王五");
        list.add("趙六");
        list.add("宋七");
        list.add("劉九");

        //輸出前4個元素
        list.stream().limit(4).forEach(System.out::println);

        System.out.println("----------");
        //跳過前2個,輸出剩餘的
        list.stream().skip(2).forEach(System.out::println);

        System.out.println("----------");
        //跳過前2個,輸出剩餘元素中的前2個
        list.stream().skip(2).limit(2).forEach(System.out::println);
    }
}
運行結果:	 張三
            李四
            王五
            趙六
            ----------
            王五
            趙六
            宋七
            劉九
            ----------
            王五
            趙六

2.1.4 concat(Stream a, Stream b)

Static Stream concat(Stream a, Stream b):合併a和b兩個流爲一個流

2.1.5 distinct()

Stream distinct():返回該流的不同元素(根據 Object.equals(Object o))組成的流

public class Demo4 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("張三");
        list.add("李四");
        list.add("王五");
        list.add("趙六");
        list.add("宋七");
        list.add("劉九");

        //需求1:取出前4個元素組成一個流
        Stream<String> stream1 = list.stream().limit(4);

        //需求2:跳過前2個,剩餘的組成一個流
        Stream<String> stream2 = list.stream().skip(2);

        //需求3:將上面的兩個流組合成一個流,並輸出
//        Stream.concat(stream1, stream2).forEach(System.out::println);

        //需求4:合併需求1和需求2的流,並去除重複元素輸出
        Stream.concat(stream1, stream2).distinct().forEach(System.out::println);

    }
}

2.1.6 sorted()

Stream sorted(): 返回由此流的元素組成的流,自然排序

Stream sorted(Comparator comparator):返回由此流的元素組成的流,根據提供的Comparator進行排序

public class Demo5 {
    public static void main(String[] args) {
        ArrayList<String> list  = new ArrayList<>();
        list.add("aas");
        list.add("acbds");
        list.add("sccd");
        list.add("cbffe");
        list.add("scce");

        //需求1:按照自然排序輸出
//        list.stream().sorted().forEach(System.out::println);

        //需求2:按照字符串長度排序
//        list.stream().sorted((s1, s2) -> s1.length()-s2.length()).forEach(System.out::println);

        //需求3:按照長度排序,如果長度相同,那麼再比較字母
        list.stream().sorted((s1,s2)->{
            int num = s1.length()-s2.length();
            return num==0?s1.compareTo(s2):num;
        }).forEach(System.out::println);
    }
}
運行結果:	 aas
            sccd
            scce
            acbds
            cbffe

2.1.7 map(Function m)

Stream map(Function m): 返回由給定函數應用於此流的元素的結果組成的流

  • Function 接口中的方法 apply(T t)

2.1.8 mapToInt(ToIntFunction m)

IntStream mapToInt(ToIntFunction m): 返回一個IntStream 其中包含將給定函數應用於此流的元素的結果

IntStream接口

  • int sum() 返回此流中元素的總和。
public class Demo6 {
    public static void main(String[] args) {
        //創建一個集合
        ArrayList<String> list = new ArrayList<>();
        list.add("2");
        list.add("3");
        list.add("4");
        list.add("5");
        list.add("6");
        list.add("7");

        //需求1:將字符串轉變成整數在控制檯輸出
//        list.stream().map(s->Integer.parseInt(s)).forEach(System.out::println);
//        list.stream().map(Integer::parseInt).forEach(System.out::println);

        //int sum() 返回此流中元素的總和。
        int sum = list.stream().mapToInt(Integer::parseInt).sum();
        System.out.println(sum);
        //list.stream()生成一個stream流
        //mapToInt(Integer::parseInt) 返回一個IntStream流
        //sum()調用IntStream流的特有方法求和,返回一個整型
    }
}


2.3 Stream流的常見終結操作

  • void forEach(Consumer action): 遍歷元素並執行相應的操作

    ​ Consumer 接口中的方法 void accept(T t):對給定的參數執行操作

  • long count(): 返回此流中的元素個數

public class Demo7 {
    public static void main(String[] args) {
        ArrayList<String > list = new ArrayList<>();
        list.add("林青霞");
        list.add("張曼玉");
        list.add("王祖賢");
        list.add("趙敏");
        list.add("張天愛");

        //需求1:把集合中的元素輸出
//        list.stream().forEach(System.out::println);

        //需求2:統計集合中姓名以“張”開頭的個數,並把統計結果在控制檯輸出
        long count = list.stream().filter(s -> s.startsWith("張")).count();
        System.out.println(count);
    }
}
運行結果: 3

綜合練習

/*
    現有兩個ArrayList集合,分別存儲6名男演員名稱和女演員名稱,要求完成以下操作:
        男演員只要名字爲三個字的前三人
        女演員只要姓林的並且不要第一個
        把過濾後的男演員姓名和女演員姓名合併到一起
        把上一步操作後的元素作爲構造方法的參數創建演員對象,遍歷數據
            演員類Actor已經提供,裏面有一個成員變量,一個帶參構造,以及get、set方法
 */

import java.util.ArrayList;
import java.util.stream.Stream;

class Actor {
    private String name;
    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Actor{" +
                "name='" + name + '\'' +
                '}';
    }

    public void setName(String name) {
        this.name = name;
    }

    public Actor(String name) {
        this.name = name;
    }
}

public class Test_02 {
    public static void main(String[] args) {
        ArrayList<String> maleList = new ArrayList<>();
        ArrayList<String> femaleList = new ArrayList<>();

        maleList.add("周潤發");
        maleList.add("周星馳");
        maleList.add("張國榮");
        maleList.add("葛優");
        maleList.add("吳京");
        maleList.add("李連杰");

        femaleList.add("王祖賢");
        femaleList.add("林心如");
        femaleList.add("林青霞");
        femaleList.add("趙敏");
        femaleList.add("鞏俐");
        femaleList.add("林黛玉");

        //男演員只要名字爲三個字的前三人
        Stream<String> manStream = maleList.stream().filter(s -> s.length() == 3).limit(3);
        
        //女演員只要姓林的並且不要第一個
        Stream<String> womanStream = femaleList.stream().filter(s -> s.startsWith("林")).skip(1);

        //把過濾後的男演員姓名和女演員姓名合併到一起
        Stream<String> concatStream = Stream.concat(manStream, womanStream);

        //把上一步操作後的元素作爲構造方法的參數創建演員對象,遍歷數據
        concatStream.map(s->new Actor(s)).forEach(System.out::println);
//        concatStream.map(Actor::new).forEach(a-> System.out.println(a.getName()));
    }
}
運行結果:	 Actor{name='周潤發'}
            Actor{name='周星馳'}
            Actor{name='張國榮'}
            Actor{name='林青霞'}
            Actor{name='林黛玉'}

2.4 Stream 流的收集操作

對數據使用Stream流的方式操作完畢後,我想把流中的數據收集到集合中,該怎麼辦?

stream流的收集方法

  • R collect(Collector collector)
  • 但是這個收集方法的參數是一個Collector 接口

工具類Collectors 提供了具體的收集方法

  • public static Collector toList(): 把元素收集到List集合中
  • public static Collector toSet(): 把元素收集到Set集合中
  • public static Collector toMap(Function keyMap, Function valueMap): 把元素收集到Map集合中
public class Demo8 {
    public static void main(String[] args) {
        //創建List集合
        List<String> list = new ArrayList<>();
        list.add("林青霞");
        list.add("張曼玉");
        list.add("王祖賢");
        list.add("趙敏");

        //需求1:得到名字長度爲3的流
        Stream<String> lenStream = list.stream().filter(s -> s.length() == 3);
        //需求2:把上一步得到的流中的數據收集到List集合中,並遍歷
        List<String> names = lenStream.collect(Collectors.toList());
        names.forEach(System.out::println);

        //創建Set集合
        Set<Integer> set = new HashSet<>();
        set.add(10);
        set.add(20);
        set.add(30);
        set.add(40);

        //需求3:得到數字大於20的流
        Stream<Integer> num = set.stream().filter(i -> i > 20);
        //需求4:把上一步得到的流中的數據收集到Set集合中,並遍歷
        Set<Integer> numStream = num.collect(Collectors.toSet());
        numStream.forEach(System.out::println);

        //定義一個字符串數組,每一個元素由姓名和年齡組合而成
        String[] strArray = {"魍魎,12","梅花十三,20","西涼,22","精衛,18"};

        //需求5:得到字符串中年齡大於18的流
        Stream<String> stringStream = Stream.of(strArray).filter(s -> Integer.parseInt(s.split(",")[1]) > 18);

        //需求6:把上一步中得到的流存儲到Map集合中,其中名字做鍵,年齡做值
        Map<String, String> student = stringStream.collect(Collectors.toMap(s -> s.split(",")[0], s -> s.split(",")[1]));

        //遍歷集合
        Set<Map.Entry<String, String>> entries = student.entrySet();
        entries.forEach(s-> System.out.println("姓名:"+s.getKey()+",年齡:"+s.getValue()));

    }
}
運行結果:	 林青霞
            張曼玉
            王祖賢
            40
            30
            姓名:西涼,年齡:22
            姓名:梅花十三,年齡:20

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章