函数式接口和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 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() {
            @Override
            public int compare(String s1, String s2) {
                return s1.length() - s2.length();
            }
        };
        return c;*/
        
       /* return new Comparator() {
            @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() {
            @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() {
            @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() {
            @Override
            public void accept(String s) {
                System.out.print("姓名;"+s.split(",")[0]);
            }
        }, new Consumer() {
            @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() {
            @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() {
            @Override
            public boolean test(String s) {//判断姓名长度是否大于3
                return s.split(",")[0].length() > 3;
            }
        }, new Predicate() {
            @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接口通常用于对参数进行处理,转换(处理逻辑由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

你可能感兴趣的:(Java,编程技巧,Java新特性,Stream流操作)