Java函数式编程 JavaSE

2021-05-14 约 5502 字 阅读时长11 分钟

Java函数式编程

基础使用

java
1String[] array = new String[] { "Apple", "Orange", "Banana", "Lemon" };
2Arrays.sort(array,(o1,o2)->o2.compareTo(o1));

传入方法

方法签名和接口一致时,在这里,方法签名只看参数类型返回类型,不看方法名称,也不看类的继承关系

java
1public static void main(String[] args) {
2    String[] array = new String[] { "Apple", "Orange", "Banana", "Lemon" };
3    Arrays.sort(array, Main::cmp);
4}
5
6// 
7static int cmp(String s1, String s2) {
8    return s1.compareTo(s2);
9}

构造方法引用

流式API

全新的流式API:Stream API,位于Java.util.stream

这个Stream不同于Java.ioInputStreamOutputStream,它代表的是任意Java对象的序列

这个StreamList也不一样,List存储的每个元素都是已经存储在内存中的某个Java对象,而Stream输出的元素可能并没有预先存储在内存中,而是实时计算出来的,惰性计算

java
1Stream<BigInteger> naturals = createNaturalStream(); // 不计算
2Stream<BigInteger> s2 = naturals.map(BigInteger::multiply); // 不计算
3Stream<BigInteger> s3 = s2.limit(100); // 不计算
4s3.forEach(System.out::println); // 计算

惰性计算的特点是:一个Stream转换为另一个Stream时,实际上只存储了转换规则,并没有任何计算发生

Stream API的基本用法就是:创建一个Stream,然后做若干次转换,最后调用一个求值方法获取真正计算的结果

java
1int result = createNaturalStream() // 创建Stream
2             .filter(n -> n % 2 == 0) // 任意个转换
3             .map(n -> n * n) // 任意个转换
4             .limit(100) // 任意个转换
5             .sum(); // 最终计算结果

创建Stream

  1. Stream.of:Stream<String> stream = Stream.of("A", "B", "C", "D")

    java
    1Stream.of("A","B","C").forEach(System.out::println);
  2. 基于数组或Collection:Arrays.stream()collection.stream()

    java
    1Stream<String> stream1 = Arrays.stream(new String[] { "A", "B", "C" });
    2Stream<String> stream2 = List.of("X", "Y", "Z").stream();
  3. 基于Supplier:Stream.generate()方法,它需要传入一个Supplier对象;基于Supplier创建的Stream会不断调用Supplier.get()方法来不断产生下一个元素,这种Stream保存的不是元素,而是算法,它可以用来表示无限序列

  4. 基本类型:IntStreamLongStreamDoubleStream这三种使用基本类型的Stream

Stream.map()

map()方法用于将一个Stream的每个元素映射成另一个元素并转换成一个新的Stream

可以将一种元素类型转换成另一种元素类型。

java
1//字符串转化为日期,并打印
2SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
3List.of("2021-12-12","2021-12-13","2021-12-14")
4    .stream()
5    .map(n->sdf.parse(n))
6    .forEach(System.out::println);

Stream.flatMap()

flatMap()对流扁平化处理

java
 1private static void streamTest4() {
 2    List<String> list = new ArrayList<>();
 3    list.add("123,234");
 4    list.add("3456");
 5    list.add("456,789");
 6
 7    Set<String> strings = list.stream().map(e -> e.split(","))
 8        .flatMap(Arrays::stream)
 9        .collect(Collectors.toSet());
10    
11   // 输出:[123, 234, 3456, 456, 789]
12    System.out.println(strings);
13}

解释:map 操作获得一个 String[] 流,然后通过 flatMap 将 String[] 生成的新流的新流

Stream.filter()

filter()操作,就是对一个Stream的所有元素一一进行测试,不满足条件的就被“滤掉”了,剩下的满足条件的元素就构成了一个新的Stream

filter()方法接收的对象是Predicate接口对象,它定义了一个test()方法,负责判断元素是否符合条件

java
1// 过滤成绩小于15的Person
2Person p1=new Person("张三",12);
3Person p2=new Person("李四",21);
4Person p3=new Person("王五",35);
5List.of(p1,p2,p3).stream()
6    .filter(n->n.score>15)
7    .forEach(System.out::println);

Stream.reduce()

reduce()Stream的一个聚合方法,它可以把一个Stream的所有元素按照聚合函数聚合成一个结果

reduce()方法传入的对象是BinaryOperator接口,它定义了一个apply()方法,负责把上次累加的结果和本次的元素 进行运算,并返回累加的结果

java
 1int sum = 0;
 2for (n : stream) {
 3    sum = (sum, n) -> sum + n;
 4}
 5
 6// 将配置文件的每一行配置通过map()和reduce()操作聚合成一个Map<String, String>
 7List<String> props = List.of("profile=native", "debug=true", "logging=warn", "interval=500");
 8Map<String,String> map=props.stream()
 9    .map((kv)->{
10        String[] ss = kv.split("\\=", 2);
11        return Map.of(ss[0],ss[1]);
12    })
13    .reduce(new HashMap<>(),(o,v)->{
14        o.putAll(v);
15        return o;
16    });
17map.forEach((k,v)->{
18    System.out.println(k+":"+v);
19});
20
21//分组后根据某个属性分组,然后每组再次聚合,自定义收集器,返回Map
22Map<String,BigDecimal> res=list.stream().collect(
23    Collectors.groupingBy(Material::getType,
24                          Collectors.reducing(BigDecimal.ZERO, Material::getMoney, BigDecimal::add)));
25    
26    
27//对 bigdecimal 求和
28BigDecimal bigDecimal = decimalList.stream().filter(Objects::nonNull).reduce(BigDecimal.ZERO, (res,v)->{
29    return res.add(v);
30});

输出集合

  1. 输出为List:调用collect()并传入Collectors.toList()对象,类似有Collectors.toSet()输出集合

    java
    1//输出list,并过滤掉无用元素
    2List<String> list=Stream.of("Apple", "", null, "Pear", "  ", "Orange")
    3    .filter(s -> s != null && !s.isBlank())
    4    .collect(Collectors.toList());
    5list.forEach((v)->{
    6    System.out.println(v);
    7});
  2. 输出为数组:toArray(String[]::new)

    java
    1List<String> list = List.of("Apple", "Banana", "Orange");
    2String[] array = list.stream().toArray(String[]::new);
  3. 输出为Map

    java
    1Stream<String> stream = Stream.of("APPL:Apple", "MSFT:Microsoft");
    2Map<String, String> map = stream
    3    .collect(Collectors.toMap(
    4        // 把元素s映射为key:
    5        s -> s.substring(0, s.indexOf(':')),
    6        // 把元素s映射为value:
    7        s -> s.substring(s.indexOf(':') + 1)));
  4. 分组输出:分组输出使用Collectors.groupingBy(),它需要提供两个函数:一个是分组的key,第二个是分组的value

    java
     1// 根据classID,分组,返回Map(Integer,List<Student>)
     2List<Student> studentList=new ArrayList<>();
     3for (int i=1;i<10;i++){
     4    Student s1=new Student(20%i,"张"+i);
     5    studentList.add(s1);
     6}
     7Map<Integer, List<Student>> groups=studentList.stream()
     8    .collect(Collectors.groupingBy(stu->stu.classId,Collectors.toList()));
     9
    10groups.forEach((k,v)->{
    11    System.out.print(k+":");
    12    v.forEach((lv)->{
    13        System.out.print(lv+",");
    14    });
    15    System.out.println();
    16});
  5. anyMatch表示,判断的条件里,任意一个元素成功,返回true

    allMatch表示,判断条件里的元素,所有的都是,返回true

    noneMatchallMatch相反,判断条件里的元素,所有的都不是,返回true

    java
    1List<String> strs = Arrays.asList("a", "a", "a", "a", "b");
    2        boolean aa = strs.stream().anyMatch(str -> str.equals("a"));
    3        boolean bb = strs.stream().allMatch(str -> str.equals("a"));
    4        boolean cc = strs.stream().noneMatch(str -> str.equals("a"));
    5        long count = strs.stream().filter(str -> str.equals("a")).count();
    6        System.out.println(aa);// TRUE
    7        System.out.println(bb);// FALSE
    8        System.out.println(cc);// FALSE
    9        System.out.println(count);// 4

collectingAndThen()

Collectors.collectingAndThen(Collector下游,函数完成器)先进行结果集的收集,然后将收集到的结果集进行下一步的处理

第一个参数是Collector接口的子类,所以还是对于Collector的处理,Collectors工具类里面的toList()、toSet()、joining()、mapping()、collectingAndThen()等几乎所有的方法都可以使用

第二个参数是一个Function函数,Function函数是这样的:R apply(T t),他调用的ArrayList的有参构造方法

java
 1// list去重
 2//将集合放到TreeSet中,然后再将TreeSet转为List, 其中TreeSet要传入一个根据哪个属性进行比较的比较器,然后使用public ArrayList(Collection<? extends E> c)将TreeSet放入构造器中生成List
 3collect = personList.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(()->
 4    new TreeSet<>(Comparator.comparing(e->e.name+e.score))
 5), ArrayList::new));
 6
 7//创建不变map
 8personList.stream().collect(Collectors.collectingAndThen(Collectors.toMap(e ->
 9     e.score+e.name, e -> e.score
10), HashMap::new));

Collector与Collectors

Collector是专门用来作为Stream的collect方法的参数的

Collectors是作为生产具体Collector的工具类

Collector

Collector主要包含五个参数

java
 1/*
 2* T 入参类型
 3* A 容器类型
 4* R 结果类型
 5*/
 6public interface Collector<T, A, R> {
 7    /*
 8     * supplier参数用于生成结果容器,容器类型为A
 9     * 返回一个结果为空的 Supplier,也就是一个无参数函数,在调用时它会创建一个空的累加器实例,供数据收集过程使用
10    */ 
11    Supplier<A> supplier();
12    
13    /*
14     * accumulator用于消费元素,也就是归纳元素,这里的T就是元素,它会将流中的元素一个一个与结果容器A发生操作
15     * 返回执行归约操作的函数,当遍历到流中第n个元素时,
16     * 这个函数执行时会有两个参数:保存归约结果的累加器(已收集了流中的前 n-1 个项目),还有第n个元素本身
17     * 该函数将返回 void ,因为累加器是原位更新,即函数的执行改变了它的内部状态
18    */ 
19    BiConsumer<A, T> accumulator();
20    
21    /* 
22     * combiner用于两个两个合并并行执行的线程的执行结果,将其合并为一个最终结果A
23     * combiner 方法会返回一个供归约操作使用的函数,
24     * 它定义了对流的各个子部分进行并行处理时,各个子部分归约所得的累加器要如何合并
25    */
26    BinaryOperator<A> combiner();
27    
28    /* finisher用于将之前整合完的结果R转换成为A
29     * 在遍历完流后, finisher 方法必须返回在累积过程的最后要调用的一个函数,
30     * 以便将累加器对象转换为整个集合操作的最终结果
31    */
32    Function<A, R> finisher();
33    
34    /*
35     * NORDERED —— 归约结果不受流中项目的遍历和累积顺序的影响
36     * CONCURRENT —— accumulator 函数可以从多个线程同时调用,且该收集器可以并行归约流。如果收集器没有标为 UNORDERED ,那它仅在用于无序数据源时才可以并行归约
37     * IDENTITY_FINISH —— 这表明完成器方法返回的函数是一个恒等函数,可以跳过。这种情况下,累加器对象将会直接用作归约过程的最终结果。这也意味着,将累加器 A 不加检查地转换为结果 R 是安全的
38     */
39    Set<Characteristics> characteristics();
40}

Collector拥有两个of方法用于生成Collector实例,其中一个拥有上面所有五个参数,另一个四个参数,不包括finisher

Collectors

Collectors是一个工具类,是JDK预实现Collector的工具类,它内部提供了多种Collector,我们可以直接拿来使用

通过Collector的of方法自定义Collector收集器

java
 1//自定义collector实现,作用将BigDecimal求和,对于null忽略
 2Collector<BigDecimal,BigDecimal[],BigDecimal> collector=Collector.of(
 3    ()-> new BigDecimal[1],
 4    (a, t) -> {
 5        //这里对结果容器赋初始值
 6        if (a[0] == null) {
 7            a[0] = BigDecimal.ZERO;
 8        }
 9        //这里忽略流中null元素
10        if (t!=null){
11             a[0] = a[0].add(t);
12        }
13    },
14    (a, b) -> {
15        a[0] = a[0].add(b[0]);
16        return a;
17    },
18    a -> a[0],
19    Collector.Characteristics.IDENTITY_FINISH);
20
21list.stream().collect(collector);

自定义实现类实现Collectors

java
 1// T 是流中要收集的项目的泛型
 2// A 是累加器的类型,累加器是在收集过程中用于累积部分结果的对象
 3// R 是收集操作得到的对象(通常但并不一定是集合)的类型
 4new Collector<BigDecimal, BigDecimal[], String>() {
 5    /**
 6     * 建立新的结果容器
 7     */
 8    @Override
 9    public Supplier<BigDecimal[]> supplier() {
10        return () -> new BigDecimal[1];
11    }
12
13    /**
14     * 将元素添加到结果容器
15     */
16    @Override
17    public BiConsumer<BigDecimal[], BigDecimal> accumulator() {
18        return (a,t)->{
19            if (a[0]==null){
20                a[0]=BigDecimal.ZERO;
21            }
22            if (t != null){
23                a[0]=a[0].add(t);
24            }
25        };
26    }
27
28    /**
29     * 合并两个结果容器
30     */
31    @Override
32    public BinaryOperator<BigDecimal[]> combiner() {
33        return (a1,a2)->{
34            a1[0]=a1[0].add(a2[0]);
35            return a1;
36        };
37    }
38
39    /**
40     * 对结果容器应用最终转换
41     */
42    @Override
43    public Function<BigDecimal[], String> finisher() {
44        return (a)-> a[0].toString();
45    }
46
47    /**
48     * 定义了收集器特性
49     */
50    @Override
51    public Set<Characteristics> characteristics() {
52        return Collections.unmodifiableSet(EnumSet.of(
53                Characteristics.CONCURRENT,
54                Collector.Characteristics.UNORDERED));
55    }
56}

其他

Stream提供的常用操作有:

转换操作:map()filter()sorted()distinct()

合并操作:concat()flatMap()

并行处理:parallel()

聚合操作:reduce()collect()count()max()min()sum()average()

其他操作:allMatch(), anyMatch(), forEach()

java提供的函数接口

Function函数

接口描述
Function<T,R>接收一个参数并返回结果的函数
BiFunction<T,U,R>接受两个参数并返回结果的函数
DoubleFunction<R>接收一个double类型的参数并返回结果的函数
DoubleToIntFunction接收一个double类型的参数并返回int结果的函数
DoubleToLongFunction接收一个double类型的参数并返回long结果的函数
IntFunction<R>接收一个int类型的参数并返回结果的函数
IntToDoubleFunction接收一个int类型的参数并返回double结果的函数
IntToLongFunction接收一个int类型的参数并返回long结果的函数
LongFunction<R>接收一个long类型的参数并返回结果的函数
LongToDoubleFunction接收一个long类型的参数并返回double结果的函数
LongToIntFunction接收一个long类型的参数并返回int结果的函数
ToDoubleBiFunction<T,U>接收两个参数并返回double结果的函数
ToDoubleFunction<T>接收一个参数并返回double结果的函数
ToIntBiFunction<T,U>接收两个参数并返回int结果的函数
ToIntFunction<T>接收一个参数并返回int结果的函数
ToLongBiFunction<T,U>接收两个参数并返回long结果的函数
ToLongFunction<T>接收一个参数并返回long结果的函数

Consumer消费者

接口描述
Consumer<T>提供一个T类型的输入参数,不返回执行结果
BiConsumer<T,U>提供两个自定义类型的输入参数,不返回执行结果
DoubleConsumer表示接受单个double值参数,但不返回结果的操作
IntConsumer表示接受单个int值参数,但不返回结果的操作
LongConsumer表示接受单个long值参数,但不返回结果的操作
ObjDoubleConsumer<T>表示接受object值和double值,但是不返回任何操作结果
ObjIntConsumer<T>表示接受object值和int值,但是不返回任何操作结果
ObjLongConsumer<T>表示接受object值和long值,但是不返回任何操作结果

Predicate谓语

接口描述
Predicate<T>对给定的输入参数执行操作,返回一个boolean类型的结果(布尔值函数)
BiPredicate<T,U>对给定的两个输入参数执行操作,返回一个boolean类型的结果(布尔值函数)
DoublePredicate对给定的double参数执行操作,返回一个boolean类型的结果(布尔值函数)
IntPredicate对给定的int输入参数执行操作,返回一个boolean类型的结果(布尔值函数)
LongPredicate对给定的long参数执行操作,返回一个boolean类型的结果(布尔值函数)

Supplier供应商

接口描述
Supplier<T>不提供输入参数,但是返回结果的函数
BooleanSupplier不提供输入参数,但是返回boolean结果的函数
DoubleSupplier不提供输入参数,但是返回double结果的函数
IntSupplier不提供输入参数,但是返回int结果的函数
LongSupplier不提供输入参数,但是返回long结果的函数

Operator操作员

接口描述
UnaryOperator<T>提供单个类型参数,并且返回一个与输入参数类型一致的结果
BinaryOperator<T>提供两个相同类型参数,并且返回结果与输入参数类型一致的结果
DoubleBinaryOperator提供两个double参数并且返回double结果
DoubleUnaryOperator提供单个double参数并且返回double结果
IntBinaryOperator提供两个int参数并且返回int结果
IntUnaryOperator提供单个int参数并且返回int结果
LongBinaryOperator提供两个long参数并且返回long结果
LongUnaryOperator提供单个long参数并且返回long结果

常用操作

求最大最小值

java
 1List<BigDecimal> decimalList = Arrays.asList(
 2    new BigDecimal("1"),
 3    new BigDecimal("2"),
 4    new BigDecimal("3"),
 5    new BigDecimal("-10"),
 6    null,
 7    new BigDecimal("0")
 8);
 9
10//过滤null,并取最大值
11Optional<BigDecimal> max = decimalList.stream().filter(Objects::nonNull).max(BigDecimal::compareTo);
12if (max.isPresent()) {
13    BigDecimal maxdecimal = max.get();
14}
15
16//过滤null,并取最小值
17Optional<BigDecimal> min = decimalList.stream().filter(Objects::nonNull).max(BigDecimal::compareTo);
18if (max.isPresent()) {
19    BigDecimal mindecimal = max.get();
20}

求decimal积

java
 1List<BigDecimal> decimalList = List.of(
 2    new BigDecimal("1"),
 3    new BigDecimal("2"),
 4    new BigDecimal("3"),
 5    null,
 6    new BigDecimal("-10"),
 7    new BigDecimal("0")
 8);
 9
10//求decimalList所有元素乘积,并忽略null元素
11String res = decimalList.stream().collect(Collector.of(
12    () -> new BigDecimal[1],
13    (a, t) -> {
14        if (a[0] == null) {
15            a[0] = BigDecimal.ONE;
16        }
17        if (t != null && t.compareTo(BigDecimal.ZERO) != 0) {
18            a[0] = a[0].multiply(t);
19        }
20    },
21    (a1, a2) -> {
22        a1[0] = a1[0].multiply(a2[0]);
23        return a1;
24    },
25    (a) -> a[0].toString()
26));

切分list

java
 1List<String> arr=new ArrayList<>();
 2for (int i = 0; i < 10950; i++) {
 3    arr.add(String.valueOf(i));
 4}
 5
 6int step = 100;
 7
 8List<List<String>> lists = Stream.iterate(0L, n -> n + 1L)   //返回一个无限有序流 0123456.......
 9    .limit((arr.size() + step - 1) / step)   //指定最后返回的 list大小,
10    .parallel()  //转换并行流
11    .map(a -> arr.stream().skip(a * step)
12         .limit(step).parallel()   //转换为并行流并进行归约
13         .collect(Collectors.toList()))
14    .collect(Collectors.toList());
使用滚轮缩放
按住拖动