java函数式编程之Collector、Optional、CompletableFuture详解

1. Stream.collect()

collect就是一个归约操作,就像reduce一样可以接受各种做法作为参数,将流中的元素累积成一个汇总结果

1.1 collect和reduce的区别

reduce不会修改累计值对象,而是直接把累计值和当前值做计算操作,产生一个新累计值对象传递下去,而collect每次都会做完操作后修改累计值对象,并把这个累计值对象传递下去,如下图,collect的累计值是一个Container,每次都把当前元素做完操作后,存入Container,并让Container一直往下传递,最后所有的结果都会汇总到Container中

1.2 collect vs reduce伪代码示意

1.3 collect(Supplier, Accumulator, Combiner)

1.3.1 Supplier

用于提供一个累计容器,也就是上图中用于存储元素的Container,通过调用Supplier的get方法得到

1.3.2 Accumulator

用于提供累计函数,即给定累计值和当前值的操作关系

1.3.3 Combiner

用于给定容器合并的规则

1.4 collect(Collector)

Collector就是把collect()方法中需要的函数封装成一个对象

2. Collector

2.1 Collector 要素

Supplier: 累积数据构造函数

Accumulator: 累积函数,同 reduce

Combiner: 合并函数 ,并行处理场合下用,同 reduce

Finisher: 对 累积数据 做最终 转换

//Collector 就是一个接口,带了几个静态方法,注意,接口也可以带default或者static方法
public interface Collector<T, A, R> {
    Supplier<A> supplier();
    BiConsumer<A, T> accumulator();
    BinaryOperator<A> combiner();
    Function<A, R> finisher();
    Set<Characteristics> characteristics();
    public static<T, R> Collector<T, R, R> of(Supplier<R> supplier,
                                              BiConsumer<R, T> accumulator,
                                              BinaryOperator<R> combiner,
                                              Characteristics... characteristics) {
        ....
    }

Collectors类就提供了一些api来创建Collector,如我们常用的toList源码如下

public static <T> Collector<T, ?, List<T>> toList() {
    return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
                               (left, right) -> { left.addAll(right); return left; },
                               CH_ID);
}

可以看出来返回的这个Collector的Supplier是ArrayList::new,accumulator是List::add,combiner是(left, right) -> { left.addAll(right); return left; },没有finisher

3. Collectors API

3.1 toList /toConcurrentMap toSet toCollection

把结果归集为一个集合

List<Integer> list = Arrays.asList(0, 1, 2, 3, 4);
List<Integer> nums = list.stream().filter(i -> i > 0).collect(Collectors.toList());

3.2 counting/ averagingXX / summingXX

counting:统计计数

List<Integer> list = Arrays.asList(0, 1, 2, 3, 4);
long count = list.stream().collect(Collectors.counting());
System.out.println(count);//5

averagingLong/int/Double:求平均

List<String> list = Arrays.asList("jack", "bob", "alice", "mark");
double avg = list.stream().collect(Collectors.averagingLong(String::length));
System.out.println(avg);//4.0

SummarizingDouble/Long/Int

为stream中的元素生成了统计信息,返回的结果是一个统计类

public static void main(String[] args) {
    List<String> list = Arrays.asList("jack", "bob", "alice", "mark");
    IntSummaryStatistics sum = list.stream().collect(Collectors.summarizingInt(String::length));
    System.out.println(sum);
    //打印结果:IntSummaryStatistics{count=4, sum=16, min=3, average=4.000000, max=5}
}

3.3 groupingBy:

对流中的内元素进行分组

  • groupingBy(Function) – 单纯分key存放成Map,默认返回HashMap
  • groupingBy(Function, Collector) - 分key后,对每个key的元素进行后续collect操作
  • groupingBy(Function, Suppiler, Collector) - 同上,允许通过Suppiler传入其他Map类型
public class Student {
    public Student(String name, Integer score) {
        this.name = name;
        this.score = score;
    }
    private String name;
    private Integer score;

    public String getName() {
        return name;
    }
    public Integer getScore() {
        return score;
    }

    public static void main(String[] args) {
        List<Student> students = Arrays.asList(new Student("zhangsan", 70),
                new Student("lisi", 60),
                new Student("wangwu", 70),
                new Student("mazi", 60),
                new Student("qiqi", 50));

        Map<Integer, List<Student>> collect = students.stream()
                .collect(Collectors.groupingBy(Student::getScore));
        System.out.println(collect);
          /**
         * 打印结果
         * {50=[com.zhaoyu.javabase.Student@4edde6e5], 
         * 70=[com.zhaoyu.javabase.Student@70177ecd, com.zhaoyu.javabase.Student@1e80bfe8], 
         * 60=[com.zhaoyu.javabase.Student@66a29884, com.zhaoyu.javabase.Student@4769b07b]}
         */
    }
}

downstream操作:可以嵌套多层Collector,如下,分组完之后还可以传入Collector无限操作

public static void main(String[] args) {
    List<Student> students = Arrays.asList(new Student("zhangsan", 70),
                                           new Student("lisi", 60),
                                           new Student("wangwu", 70),
                                           new Student("mazi", 60),
                                           new Student("qiqi", 50));

    Map<Integer, Map<Integer, List<Student>>> collect = students.stream()
        .collect(Collectors.groupingBy(Student::getScore, 
                                       Collectors.groupingBy(s -> s.getName().length())));
    System.out.println(collect);
    //分组之后还可以分组

}

3.4 partitioningBy

partitioningBy:按条件分成true和false两部分

public static void main(String[] args) {
    List<Student> students = Arrays.asList(new Student("zhangsan", 70),
            new Student("lisi", 60),
            new Student("wangwu", 70),
            new Student("mazi", 60),
            new Student("qiqi", 50));

    Map<Boolean, List<Student>> collect = students.stream()
            .collect(Collectors.partitioningBy(s -> {
                return s.getScore() > 60;
            }));
    System.out.println(collect);
    /**
     * {false=[com.zhaoyu.javabase.Student@70177ecd, com.zhaoyu.javabase.Student@1e80bfe8,
     * com.zhaoyu.javabase.Student@66a29884],
     * true=[com.zhaoyu.javabase.Student@4769b07b, com.zhaoyu.javabase.Student@cc34f4d]}
     */
}

3.5 mapping/reducing

mapping的功能上和stream.map差不多,但是这个支持downstream,也就是支持映射之后,做进一步操作

public static void main(String[] args) {
    List<Student> students = Arrays.asList(new Student("zhangsan", 70),
                                           new Student("lisi", 60),
                                           new Student("wangwu", 70),
                                           new Student("mazi", 60),
                                           new Student("qiqi", 50));

    Map<Integer, List<String>> collect = students.stream()
        .collect(Collectors.mapping(Student::getName, Collectors.groupingBy(String::length)));
    System.out.println(collect);
    //打印结果:{4=[lisi, mazi, qiqi], 6=[wangwu], 8=[zhangsan]}
}

reducing:对结果进行归集操作

Student student = students.stream().collect(Collectors.reducing((acc, cur) -> {
    if (acc.getScore() > cur.getScore()) {
        return acc;
    }
    return cur;
})).get();

4. Function composition 函数组合

函数组合就是把多个函数组合成一个函数来执行

Function<Integer,Integer> f1 = a -> a + 1;
Function<Integer,Integer> f2 = a -> a * 10;

andThen:

先执行andThen前面的函数,再执行后面的函数,相当于数学表达式:f1.andThen(f2) = f2(f1());

Function<Integer,Integer> fn = f1.andThen(f2);
Integer n = fn.apply(10);//先执行f1:10 + 1 = 11,再执行f2:11 * 10 = 110
System.out.println(n);//110

compose

先执行后面的函数,再执行前面的函数,相当于数学表达式:f1.compose(f2) = f1(f2());

Function<Integer,Integer> fc = f1.compose(f2);
Integer c = fc.apply(10);//先执行f2:10 * 10 = 100,再执行f1:100 = 1 = 101
System.out.println(c);//101

5. Optional

Optional是java8引入的一个用于解决空指针异常的类,相当于用Optional把对象包装起来,每次都只针对Optional进行操作,这样就能避免空指针异常的出现;

举个栗子:

没有Optional之前我们要一层一层判空

@Data
public class Person {
    private Pet pet;
    @Data
    public static class Pet {
        private String petName;
    }

    public static void main(String[] args) {
        System.out.println(getPetName(null));
    }

    public static String getPetName(Person person) {
        if (person != null) {
            Pet pet = person.getPet();
            if (pet != null) {
                String petName = pet.getPetName();
                return petName;
            }
        }
        return "defaultName";
    }
}

有了Optional之后,中间任意一层都可以为空

public static void main(String[] args) {
    System.out.println(getPetName(null));//打印结果defaultName
}
public static String getPetName(Person person) {
    String petName = Optional.ofNullable(person).map(Person::getPet)
        .map(Pet::getPetName).orElse("defaultName");
    return petName;
}

5.1 创建Optional

empty():

创建一个空的Optional对象,看源码

private static final Optional<?> EMPTY = new Optional<>();
public static<T> Optional<T> empty() {
    @SuppressWarnings("unchecked")
    Optional<T> t = (Optional<T>) EMPTY;
    return t;
}

Optional.of():

不允许传入null对象,否则会报空指针,应该明确对象不为null的时候才使用

Student stu = new Student("zhangsan", 70);
Optional.of(stu);

Optional.ofNullable()

允许传入null对象

Student stu = new Student("zhangsan", 70);
Optional.ofNullable(stu);

ofNullable源码可以看到如果传入null,会自动创建一个空的Optional对象

public static <T> Optional<T> ofNullable(T value) {
    return value == null ? empty() : of(value);
}

5.2 get()

可以取回对象实际值,值为null 的时候抛出异常,所以建议配合下面几个api一起用

5.3 orElse (T)

if x!= null return x else return T:如果实际值不为空,则返回实际值,否则返回orElse()方法中给定的值

Student stu = new Student("zhangsan", 70);
String name = Optional.ofNullable(stu.getName()).orElse("lisi");//如果stu.getName()则返回lisi

5.4 orElseGet(Supplier fn)

if x!=null return x else return fn:如果值不为空则返回实际值,否则返回函数fn的执行结果

Student stu = new Student("zhangsan", 70);
String name = Optional.ofNullable(stu.getName()).orElseGet(stu::getNickName);

如果实际值为空,则orElse和orElseGet效果差不多,但是如果实际值不为空,虽然都能获取到实际值,但是orElse中的调用还是会执行,而orElseGet的不会执行,所以建议用orElseGet,减少不必要的代码执行,提升性能

public static void main(String[] args) {
    Student stu = new Student("zhangsan", 70);
    //虽然stu不为空,但是createStu还是会被执行
    Student stu1 = Optional.of(stu).orElse(createStu("lisi", 50));
    //stu不为空,createStu不会被执行
    Student stu2 = Optional.of(stu).orElseGet(() -> createStu("wangwu", 50));
}

public static Student createStu(String name, Integer score) {
    System.out.println(name);
    return new Student(name, score);
}

5.5 ifPresent(fn) /isPresent

if x!= null fn:如果实际值不为空,则执行fn函数

Optional.of(stu).ifPresent(s -> System.out.println(s.name));;//如果stu不为空则输出姓名

isPresent():判断实际值是否为空

boolean present = Optional.of(stu).isPresent();//返回一个布尔值

6. map/flatMap

6.1 map

跟stream的map效果差不多也是可以对内容做映射操作

String petName = Optional.ofNullable(person).map(Person::getPet)
        .map(Pet::getPetName).orElse("defaultName");

6.2 flatMap

如果一个对象某个成员属性本身是Optional类型的,可能会出现Optional嵌套情况

@Data
public class Person {
    private Pet pet;

    //这里不知道pet是否会为空,所以用Optional包装一下,避免空指针出现
    public Optional<Pet> getPetOp() {
        return Optional.ofNullable(pet);
    }

    @Data
    public static class Pet {
        private String petName;
    }

    public static void main(String[] args) {
        Person person = new Person();
        System.out.println(person.getPetName(null));
    }
    public String getPetName(Person person) {
        //出现了Optional嵌套情况
        Optional<Optional<Pet>> pet = Optional.ofNullable(person).map(Person::getPetOp);
        return null;
    }
}

如果想获取到真实的Pet,需要用flatMap解掉一层Optional

public String getPetName(Person person) {
    Optional<Pet> petOptional = Optional.ofNullable(person).flatMap(Person::getPetOp);
    String petName = petOptional.map(Pet::getPetName).orElse("defaultName");
    return petName;
}

7. CompletableFuture

Future接口可以构建异步应用,但依然有其局限性。它很难直接表述多个Future 结果之间的依赖性,CompletableFuture正好可以满足这个要求,如何对两个或多个异步操作进行流水线和合并操作;

由于api非常多,可以通过以下方式来理解api的作用

  • accept: 接受参数是 Consumer
  • apply: 接受参数是 Function
  • handle:接受参数是BiFunction
  • runAfter : 接受参数是Runnable
  • Either/Both: 任一任务完成还是都完成
  • then: 等当前任务完成再执行另一个
  • async 后续任务是否异步执行

举个栗子

假如需要完成以下的几个异步调用:

service2依赖于service1的执行结果,service4依赖于service2和service3的执行结果

用传统的Future实现方式:

public class FutureExample {
    private static ExecutorService pool = Executors.newFixedThreadPool(16); 

    private static String service1() {
        try {
            Thread.sleep(1000 * 3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "service 1 result";
    }

    private static String service2(String retFromService1) {
        try {
            Thread.sleep(1000 * 3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return retFromService1 + " -> service2";
    }

    private static String service3() {
        try {
            Thread.sleep(1000 * 3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "service 3 result";
    }

    private static String service4(String service3Ret, String service2Ret) {
        return String.format("svr3:%s, svr2:%s", service3Ret, service2Ret);
    }

    public static String mainService() throws InterruptedException, ExecutionException {
        //service1和service3没依赖关系,可以分开执行
        Future<String> task1 = pool.submit(FutureExample::service1);
        Future<String> task3 = pool.submit(FutureExample::service3);
        String ret = task1.get();//service2依赖于service1的执行结果
        Future<String> task2 = pool.submit(()->service2(ret));
        //拿到service2和service3的执行结果
        String ret3 = task3.get();
        String ret2 = task2.get();
        return service4(ret3, ret2);//执行service4
    }
}

CompletableFuture实现方式:

public static String mainServiceWithNewAPI() throws InterruptedException, ExecutionException {
    //异步执行完service1,再执行service2
    CompletableFuture<String> cf2 = CompletableFuture
        .supplyAsync(FutureExample::service1, pool)
        .thenApply(FutureExample::service2);

    //执行service3
    CompletableFuture<String> cf3 = CompletableFuture
        .supplyAsync(FutureExample::service3, pool);

    //cf3和cf2一起执行完后,把结果都传给service4
    CompletableFuture<String> cf4 = cf3.thenCombine(cf2, FutureExample::service4);
    return cf4.join();
}

 

posted @ 2022-04-04 11:38  花好月圆梦  阅读(412)  评论(0)    收藏  举报