函数式接口

众所周知,Java8提供了很多新的特性,Lambda表达式,函数式接口,Optional,新的日期类api。今天简单聊一下Stream的前世今生。

Lambda表达式我们现在已经用的很多了,而函数式接口则是为了支持Lambda表达式,Java8提供了很多内置的函数式接口,如Runnable,Comparator等是从原有的API升级来的,而有些是Java8新增的,如Consumer等。

@FunctionalInterfacepublic interface Runnable {    public abstract void run();}

类上有注解@FunctionalInterface就可以认为这是一个函数式接口,可以用在Lambda表达式中。Lambda表达式极大的简化了我们的编程

// jdk1.8之前new Thread(new Runnable() {    @Override    public void run() {        System.out.println("yes");    }}).start();// jdk1.8及以后new Thread(() -> System.out.println("yes")).start();

为了方便我们使用Lambda表达式,Java8提供了一些内置的函数式接口

函数式接口 方法 用途 Consumer 消费型接口 void accept(T t) 输入参数为T,没有返回 Supplier 供给型接口 T get() 返回R Function 函数型接口 R apply(T t) 输入参数为T,返回为R Predicate 判断型接口 boolean test(T t) 对象是否满足条件,true为满足,false为不满足

Java8为什么要新提供这些函数式接口呢?

我举个例子你就明白了。

@Data@AllArgsConstructorpublic class Person {    private String name;    private int age;    private int salary;}

员工对象为Person,此时老板发话了,给我找出年龄大于20的员工把,于是就有了下面的方法

public List filterByAge(List personList) {    List resultList = Lists.newArrayList();    for (Person person : personList) {        if (person.getAge() > 20) {            resultList.add(person);        }    }    return resultList;}

干的不错,再给我找一下工资大于2000的员工把。

public List filterBySalary(List personList) {    List resultList = Lists.newArrayList();    for (Person person : personList) {        if (person.getSalary() > 2000) {            resultList.add(person);        }    }    return resultList;}

再给我找一下,,,
老板等等,我需要优化一下这个实现。

你发现问题了吗?2个方法只有判断条件不同,其余的部分一模一样。而且可扩展性太差,该怎么优化呢?

额,我们可以定义一个如下的接口,判断的逻辑让接口的实现类去实现

public interface Predicate {    boolean test(T t);}
public List filter(List personList, Predicate predicate) {    List resultList = Lists.newArrayList();    for (Person person : personList) {        if (predicate.test(person)) {            resultList.add(person);        }    }    return resultList;}

上面的需求就可以用如下几行代码实现。

List filterByAgeList = filter(list, p -> p.getAge() > 20);List filterBySalaryList = filter(list, p -> p.getSalary() > 2000);

此时老板再加筛选需求也不怕了。

等等,我们定义的Predicate接口和Java8内置的函数式接口好像。
哈哈,基本上一模一样,因为类似的场景很多,所以Java8帮我们定义了一系列的接口。省的我们自己定义

函数式接口的使用

函数式接口 方法 用途 Consumer 消费型接口 void accept(T t) 输入参数为T,没有返回 Supplier 供给型接口 T get() 返回R Function 函数型接口 R apply(T t) 输入参数为T,返回为R Predicate 判断型接口 boolean test(T t) 对象是否满足条件,true为满足,false为不满足

@Testpublic void testCase1() {    // 10    consumeTask(10, (m) -> System.out.println(m));}public void consumeTask(int num, Consumer consumer) {    consumer.accept(num);}@Testpublic void testCase2() {    // AAA    System.out.println(strHandler("aaa", (str) -> str.toUpperCase()));}public String strHandler(String str, Function function) {    return function.apply(str);}

当然,为了方便我们的使用,还有很多其他的内置接口,看入参和返回值就能知道接口的作用

函数式接口 方法 BiFunction R apply(T t, U u) BiConsumer void accept(T t, U u) ToIntFunction int applyAsInt(T value) IntFunction R apply(int value)

Stream介绍

在Java8之前,如果我们想对集合进行操作还是比较麻烦的。Java8设计了Stream API来简化对集合的操作,Stream API的设计基于函数式编程和lambda表达式,行云流水似的编程方式给人带来新的体验。

Stream操作分为如下三个步骤

  1. 创建Stream:从数据源,例如集合,数组中获取一个流
  2. 中间操作:对数据进行处理
  3. 终止操作:执行中间操作,并产生结果。一般返回void或一个非流的结果

注意当不执行终止操作的时候,中间操作不会执行

List dataList = Arrays.asList(1, 2, 3, 4);// 没有输出dataList.stream().map(x -> {    System.out.println(x);    return x;});// 输出 1 2 3 4// 正常是换行,我这用空格代替了,下同dataList = dataList.stream().map(x -> {    System.out.println(x);    return x;}).collect(Collectors.toList());

创建Stream

// 1. Collection集合的stream()或者parallelStream()List list = Lists.newArrayList();Stream stream1 = list.stream();// 2. 调用Arrays.stream(T[] array)静态方法Integer[] array = {1, 2, 3};Stream stream2 = Arrays.stream(array);// 3. 调用Stream.of(T... values)静态方法Stream stream3 = Stream.of("aa", "bb", "cc");// 4. 调用Stream.iterate(final T seed, final UnaryOperator f),创建无限流// (x) -> x + 2 为函数式接口,传入x返回x+2,0为最开始的值Stream stream4 = Stream.iterate(0, (x) -> x + 2);// 一直输出 0 2 4 6 8 10 12 ...stream4.forEach(System.out::println);// 5. 调用调用Stream.generate(),创建无限流Stream stream5 = Stream.generate(() -> 10);// 一直输出10,你可以用Random等类随机生成哈stream5.forEach(System.out::println);

中间操作

筛选与切片

函数名 解释 filter 从流中排除某些元素 limit 使元素不超过指定数量 skip 跳过前n个元素,如果流中元素不超过n个,则返回一个空流 distinct 通过hashCode()和equals()去除重复元素

List list = Arrays.asList(1, 2, 3, 4);// 1 3list.stream().filter(x -> x % 2 == 1).forEach(System.out::println);// 3 4list.stream().skip(2).forEach(System.out::println);

看一下filter方法和forEach方法的定义

Stream.java

Stream filter(Predicate super T> predicate);void forEach(Consumer super T> action);

这不就是我门上面介绍的函数式接口吗?很多方法的入参其实就是一个函数式接口

映射

函数名 解释 map 接收一个函数作为参数,该函数被应用到每个元素上,并将其映射成一个新的元素 flatMap 接受一个函数作为参数,将流中的每一个值都转换成另一个流,然后将所有流连接成一个流

先看这2个方法的定义

Stream map(Function super T, ? extends R> mapper); Stream flatMap(Function super T, ? extends Stream extends R>> mapper);

map方法的入参和返回值可以为任意值
flatMap方法的入参为任意值,返回值必须为Stream

List list = Arrays.asList("abcd", "efgh");// [Ljava.lang.String;@7b3300e5 [Ljava.lang.String;@2e5c649list.stream().map(x -> x.split("")).forEach(System.out::println);// a b c d e f g hlist.stream().flatMap(x -> Arrays.stream(x.split(""))).forEach(System.out::println);

解释一下这个输出,x.split("")后为数组,所以第一个输出的为数组的地址
第二个x.split("")后为数组,然后将数组转为多个流,将多个流合并后输出

排序

函数名 解释 sorted() 自然排序,通过Comparable接口定义的规则来排序 sorted(Comparator) 定制排序,通过Comparator接口定义的规则来排序

List list = Arrays.asList("b", "a", "c");// a b clist.stream().sorted().forEach(System.out::println);// c b alist.stream().sorted((x, y) -> y.compareTo(x)).forEach(System.out::println);

终止操作

查找与匹配

函数名 解释 allMatch 是否匹配所有元素 anyMatch 是否至少匹配一个元素 noneMatch 是否没有匹配所有元素 findFirst 返回第一个元素 findAny 返回当前流中的任意元素 count 返回当前流中元素总个数 max 返回流中最大值 min 返回流中最小值

List list = Arrays.asList(1, 2, 3, 4);// false// 当list都为1时才会返回trueSystem.out.println(list.stream().allMatch(num -> num.equals(1)));// trueSystem.out.println(list.stream().anyMatch(num -> num.equals(1)));// 4System.out.println(list.stream().max((x, y) -> x.compareTo(y)).get());

归约

函数名 解释 reduce 归约,将流中元素反复结合起来得到一个值

List list = Arrays.asList(1, 2, 3, 4);int sum = list.stream().reduce(0, (x, y) -> x + y);// 10// 初始值为0,执行过程为// x = 0 y = 1// x = 1 y = 2// x = 3 y = 4 ...// 10// 10 System.out.println(sum);

收集

用collect方法来进行收集,方法定义如下

Stream.java

R collect(Supplier supplier,              BiConsumer accumulator,              BiConsumer combiner); R collect(Collector super T, A, R> collector);

当然我一般不自己实现这个接口,可以直接用Collectors工具类

@Data@AllArgsConstructorpublic class Student {    private String name;    private int age;}
List studentList = Arrays.asList(new Student("张三", 30),        new Student("李四", 20),        new Student("王五", 20));
List nameList = studentList.stream().map(Student::getName).collect(Collectors.toList());// [张三, 李四, 王五]System.out.println(nameList);Set ageSet = studentList.stream().map(Student::getAge).collect(Collectors.toSet());// [20, 30]System.out.println(ageSet);LinkedHashSet linkedHashSet =        studentList.stream().map(Student::getAge).collect(Collectors.toCollection(LinkedHashSet::new));// [30, 20]System.out.println(linkedHashSet);
// 总数long count = studentList.stream().collect(Collectors.counting());// 3System.out.println(count);// 平均值double ageAvg = studentList.stream().collect(Collectors.averagingDouble(Student::getAge));// 23.3System.out.println(ageAvg);// 总和int totalAge = studentList.stream().collect(Collectors.summingInt(Student::getAge));// 70System.out.println(totalAge);// 最大值Optional student = studentList.stream().collect(Collectors.maxBy((x, y) -> x.getAge() - y.getAge()));// Student(name=张三, age=30)System.out.println(student.get());// 按照年龄分组// 还可以多级分组,按照年龄分组后,再按照其他条件分组,不再演示Map> listMap = studentList.stream().collect(Collectors.groupingBy(Student::getAge));// {20=[StreamDemo.Student(name=李四, age=20), StreamDemo.Student(name=王五, age=20)], 30=[StreamDemo.Student(name=张三, age=30)]}System.out.println(listMap);

一些使用Demo

枚举值参数校验

项目中有很多单选项需要定义相关的枚举值,前端传入后需要校验这些值是否在枚举范围内

public enum MSG_TYPE {    IMAGE((byte) 0, "图片"),    TEXT((byte) 1, "文本");    public final byte value;    public final String name;    MSG_TYPE(byte value, String name) {        this.value = value;        this.name = name;    }}
// 模拟前端传入的参数为1boolean isExist = Arrays.stream(MSG_TYPE.values()).anyMatch(v -> v.value == 1);// trueSystem.out.println(isExist);isExist = Arrays.stream(MSG_TYPE.values()).anyMatch(v -> v.value == 5);// falseSystem.out.println(isExist);

调用远程服务前存对应关系

根据学生姓名获取学生的其他信息

  1. 先存学生姓名->学生的映射关系为nameMap
  2. 通过学生姓名批量获取学生信息
  3. 从nemeMap中根据其他服务返回的学生姓名拿到Student,然后填充信息到Student
List studentList = Lists.newArrayList();for (int i = 0; i  studentMap = studentList.stream().collect(Collectors.toMap(Student::getName, student -> student));System.out.println(studentMap);

当然还有很多其他的骚操作,不再详细介绍