JDK8新特性

  参考菜鸟教程:https://www.runoob.com/java/java8-new-features.html

1. 接口默认方法

  Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法;而且允许定义static方法,使用的时候直接类名.方法名即可。

package com.zd.bx.test;

public interface Interface1 {

    /**
     * 抽象方法1
     * 
     * @param str
     */
    void method1(String str);

    /**
     * 静态方法
     * 
     * @param str
     */
    public static void method2(String str) {
        System.out.println(str);
    }

    default void log(String str) {
        System.out.println("I1 logging::" + str);
    }

}

2. Lambda 表达式

  Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。使用 Lambda 表达式可以使代码变的更加简洁紧凑。

语法如下:

(parameters) -> expression
或
(parameters) ->{ statements; }

1. 以下是lambda表达式的重要特征:

可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。

例如:

package com.zd.bx;

public class MyTest {

    public static void main(String args[]) {
        MyTest tester = new MyTest();

        // 类型声明
        MathOperation addition = (int a, int b) -> a + b;

        // 不用类型声明
        MathOperation subtraction = (a, b) -> a - b;

        // 大括号中的返回语句
        MathOperation multiplication = (int a, int b) -> {
            return a * b;
        };

        // 没有大括号及返回语句
        MathOperation division = (int a, int b) -> a / b;

        System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
        System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
        System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
        System.out.println("10 / 5 = " + tester.operate(10, 5, division));

        // 不用括号
        GreetingService greetService1 = message -> System.out.println("Hello " + message);

        // 用括号
        GreetingService greetService2 = (message) -> System.out.println("Hello " + message);

        greetService1.sayMessage("Runoob");
        greetService2.sayMessage("Google");
    }

    interface MathOperation {
        int operation(int a, int b);
    }

    interface GreetingService {
        void sayMessage(String message);
    }

    private int operate(int a, int b, MathOperation mathOperation) {
        return mathOperation.operation(a, b);
    }
}

结果:

10 + 5 = 15
10 - 5 = 5
10 x 5 = 50
10 / 5 = 2
Hello Runoob
Hello Google

2. 变量作用域:

  lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。

例如:

package com.zd.bx;

public class MyTest {

    public static void main(String args[]) {
        String salutation = "Hello! ";

        GreetingService greetService1 = message -> {
            System.out.println(salutation + message);
            // 不允许修改引用,salutation隐式的被声明为final
            // salutation = "";
        };
        greetService1.sayMessage("Runoob");
    }

    interface GreetingService {
        void sayMessage(String message);
    }
}

lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)

3.方法引用

方法引用通过方法的名字来指向一个方法。
方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
方法引用使用一对冒号 :: 。

 例如:

package com.zd.bx;

import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;

public class Car {

    // Supplier是jdk1.8的接口,这里和lamda一起使用了
    public static Car create(final Supplier<Car> supplier) {
        return supplier.get();
    }

    public static void collide(final Car car) {
        System.out.println("Collided " + car.toString());
    }

    public void follow(final Car another) {
        System.out.println("Following the " + another.toString());
    }

    public void repair() {
        System.out.println("Repaired " + this.toString());
    }

    public static void main(String[] args) {
        System.out.println("===1===");
        // 构造器引用:它的语法是Class::new,或者更一般的Class< T >::new
        final Car car = Car.create(Car::new);
        final List<Car> cars = Arrays.asList(car);

        System.out.println("===2===");
        // 静态方法引用:它的语法是Class::static_method
        cars.forEach(Car::collide);

        System.out.println("===3===");
        // 特定类的任意对象的方法引用:它的语法是Class::method
        cars.forEach(Car::repair);

        System.out.println("===4===");
        // 特定对象的方法引用:它的语法是instance::method
        final Car police = Car.create(Car::new);
        cars.forEach(police::follow);
    }
}

结果:

===1===
===2===
Collided com.zd.bx.Car@3e3abc88
===3===
Repaired com.zd.bx.Car@3e3abc88
===4===
Following the com.zd.bx.Car@3e3abc88

补充:方法引用可以理解为lambda表达式的快捷写法,它比lambda表达式更加的简洁,可读性更高.有更好的重用性.如果实现比较简单,一句话就可以实现,复用的地方又不多推荐使用lambda表达式,否则应该使用方法引用.  

(1)方法引用的分类如下:

 (2)举一个简单的例子,我们需要搜集list中所有用户的username: map 用于映射每个元素到对应的结果

第一版可能就是for循环往新的集合里面加

第二版可能就是:

        List<User> list = Lists.newArrayList(user, user2, user3, user4);
        List<Object> collect = list.stream().map(new Function<User, Object>() {
            @Override
            public Object apply(User user) {
                return user.getUsername();
            }
        }).collect(Collectors.toList());
        System.out.println(collect);

第三版:

        List<User> list = Lists.newArrayList(user, user2, user3, user4);
        List<Object> collect = list.stream().map((userTmp) -> {
            return userTmp.getUsername();
        }).collect(Collectors.toList());
        System.out.println(collect);

第四版:

        List<User> list = Lists.newArrayList(user, user2, user3, user4);
        List<Object> collect = list.stream().map(userTmp -> {
            return userTmp.getUsername();
        }).collect(Collectors.toList());
        System.out.println(collect);

第五版: 使用对象方法引用。这种方法的使用规则是:抽象方法的第一个参数类型刚好是实例方法的类型,抽象方法剩余的参数恰好可以当做实例方法的参数。

        List<User> list = Lists.newArrayList(user, user2, user3, user4);
        List<Object> collect = list.stream().map(User::getUsername).collect(Collectors.toList());
        System.out.println(collect);

  这个就精简多了,可以参照第四版lambda表达式进行理解。只有一个参数且第一个参数是实例方法的参数调用者,后面的参数(为空)是实例方法的参数,满足上面条件可以用对象方法的引用。

4.函数式接口

  函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。函数式接口可以被隐式转换为 lambda 表达式。Lambda 表达式和方法引用(实际上也可认为是Lambda表达式)上。
如定义了一个函数式接口如下:

package com.zd.bx;

@FunctionalInterface
public interface GreetingService {
    void sayMessage(String message);
    
    default int method2() {
        return 2;
    }
}

那么就可以使用Lambda表达式来表示该接口的一个实现(注:JAVA 8 之前一般是用匿名类实现的):

        GreetingService greetService1 = message -> System.out.println("Hello " + message);
        greetService1.sayMessage("234");

函数式接口可以对现有的函数友好地支持 lambda。

1.JDK8 之前已有的函数式接口

java.lang.Runnable、java.util.concurrent.Callable、java.util.Comparator等。如:

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

2.JDK8提供的函数式接口

  JDK8提供了好几个函数式接口,其中有用的是如下几个:

1.Consumer<T> 代表了接受一个输入参数并且无返回的操作

package java.util.function;
import java.util.Objects;

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

2. Supplier<T> 无参数,返回一个结果。 代表了接受一个输入参数并且无返回的操作

package java.util.function;

@FunctionalInterface
public interface Supplier<T> {

    T get();
}

3. Predicate<T> 接受一个输入参数,返回一个布尔值结果。

package java.util.function;
import java.util.Objects;

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

4. Function<T,R> 接受一个输入参数,返回一个结果。

package java.util.function;
import java.util.Objects;

@FunctionalInterface
public interface Function<T, R> {

    R apply(T t);

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

例如:

package com.zd.bx;

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

import lombok.Data;

@Data
public class Car {
    public static void main(String args[]) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);

        // Predicate<Integer> predicate = n -> true
        // n 是一个参数传递到 Predicate 接口的 test 方法
        // n 如果存在则 test 方法返回 true

        System.out.println("输出所有数据:");

        // 传递参数 n
        eval(list, n -> true);

        // Predicate<Integer> predicate1 = n -> n%2 == 0
        // n 是一个参数传递到 Predicate 接口的 test 方法
        // 如果 n%2 为 0 test 方法返回 true

        System.out.println("输出所有偶数:");
        eval(list, n -> n % 2 == 0);

        // Predicate<Integer> predicate2 = n -> n > 3
        // n 是一个参数传递到 Predicate 接口的 test 方法
        // 如果 n 大于 3 test 方法返回 true

        System.out.println("输出大于 3 的所有数字:");
        eval(list, n -> n > 3);
    }

    public static void eval(List<Integer> list, Predicate<Integer> predicate) {
        for (Integer n : list) {
            if (predicate.test(n)) {
                System.out.println(n + " ");
            }
        }
    }
}

结果:

输出所有数据:
1 
2 
3 
4 
5 
6 
7 
8 
9 
输出所有偶数:
2 
4 
6 
8 
输出大于 3 的所有数字:
4 
5 
6 
7 
8 
9 

5. Stream

  添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。

和以前的Collection操作不同, Stream操作还有两个基础的特征:

(1)Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。

(2)内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。

 

流分类:大体分为中间操作和终止操作。中间操作返回stream, 终止操作就是结束流处理。  stream 采用惰性求值,惰性求值就是终止没有调用的情况下,中间操作不会执行。

中间操作: 分为有状态和无状态。无状态代表当前操作和其他元素操作前后无关系; 有状态代表当前结果需要依赖其他元素。

 终止操作: 终止操作又分为短路操作和非短路操作。短路操作就是不需要等待所有结果计算完,我们只需要得到一个或者任何一个结果就可以了; 非短路操作就是需要等待所有结果执行完。

1.生成流

集合接口有两个方法来生成流:

stream() − 为集合创建串行流。

parallelStream() − 为集合创建并行流。

 例如:

        // 集合
        Stream<Integer> stream = Lists.newArrayList(1, 2).stream();
        Stream<Integer> stream2 = Lists.newArrayList(1, 2).parallelStream();

        // 数组
        IntStream stream3 = Arrays.stream(new int[]{1, 2, 3});

        // 数字流
        IntStream intStream = IntStream.of(1, 2, 3);
        IntStream intStream2 = IntStream.rangeClosed(1, 3); // 1, 2, 3 俊包含
        // 使用random 创建无限流 (随机选取十个)
        IntStream limit = new Random().ints().limit(10);
        Random random = new Random();
        Stream<Integer> limit1 = Stream.generate(new Supplier<Integer>() {
            @Override
            public Integer get() {
                return random.nextInt();
            }
        }).limit(10);

更全的生成流的方法

package org.example;

import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

@Slf4j
public class PlainTest {

    public static void main(String[] args) throws Exception {
        // 创建流以及求和
        Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5, 6, 7);
        Integer result = integerStream.reduce(0, Integer::sum);
        System.out.println(result);

        // 数组创建流以及求和
        IntStream stream = Arrays.stream(new int[]{1, 2, 3, 4, 5, 6, 7});
//        int sum = stream.sum();
//        System.out.println(sum);
        // IntStream 转换为 Stream
        Stream<Integer> integerStream1 = stream.mapToObj(Integer::valueOf);

        // Stream.iterate()生成无限流 (第一个参数可以理解为种子)
        List<Integer> collect = Stream.iterate(2, x -> x + 1).limit(7).collect(Collectors.toList());
        System.out.println(collect);

        // 使用Stream.generare()方法生成流
        // generate()和iterate()有些不一样,前者需要一个Supplier来创建一个无线流,按照你的需求来创建,而后者则根据你给的种子和迭代器来创建
        Stream<String> limit = Stream.generate(UUID.randomUUID()::toString).limit(2);
        limit.collect(Collectors.toList()).forEach(System.out::println);

        // 生成范围流, rangeClosed 表示闭区间, range 表示开区间
        IntStream intStream = IntStream.rangeClosed(1, 3);//生成1->3的int流
//        int sum = intStream.sum();
//        System.out.println(sum);
        // 转为Stream, boxed 等价于 mapToObj(Integer::valueOf);
        Stream<Integer> boxed = intStream.boxed();
        List<Integer> collect1 = boxed.collect(Collectors.toList());
        System.out.println(collect1);
    }

}

2. forEach / peek

Stream 提供了新的方法 'forEach' 来迭代流中的每个数据。只不过forEach 是终止操作,peek 是中间操作。

以下代码片段使用 forEach 输出了10个随机数:

        Random random = new Random();
        random.ints().limit(10).forEach(System.out::println);

peek 如下:

        Random random = new Random();
        random.ints().limit(10).peek(System.out::println).count();

补充:foreach中跳出本次循环与结束循环

  foreach中跳出本次循环使用return,类似于continue关键字;如果想结束循环,可以用try、catch + throw异常实现。

        List<String> strings = new ArrayList<>();
        strings.add("1");
        strings.add("2");
        strings.add("3");

        strings.forEach(str -> {
            if ("2".equals(str)) {
                System.out.println("这是2");
                return;
            }

            System.out.println(str);
        });

结果:

1
这是2
3

补充:foreach遍历map的方式

        Map<Long, Long[]> userIdDeptIds = new LinkedHashMap<>();
        userIdDeptIds.put(1L, new Long[] { 1L, 11L });
        userIdDeptIds.put(2L, new Long[] { 2L, 22L });

        userIdDeptIds.forEach((userId, departmentIds) -> {
            System.out.println("==============");
            System.out.println(userId);
            System.out.println(ToStringBuilder.reflectionToString(departmentIds, ToStringStyle.NO_CLASS_NAME_STYLE));
        });

结果:

==============
1
[{1,11}]
==============
2
[{2,22}]

补充:distinct() 用于元素去重

package estest;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class ESTest {

    public static void main(String[] args) {
        // 元素去重
        String[] a = {"1", "2", "3", "3", "4"};
        // 获取到stream对象
        Stream<String> stream = Arrays.stream(a);
        // 获取到去重后的元素保存到集合中
        List<String> collect = stream.distinct().collect(Collectors.toList());
        // 集合转数组
        String[] strings = collect.toArray(new String[collect.size()]);
        System.out.println(Arrays.toString(strings));
    }
}

结果:

[1, 2, 3, 4]

补充: count 用于计数,是个终结方法

        ArrayList<String> strings = Lists.newArrayList("1", "2", "3");
        long count = strings.stream().count();
        System.out.println(count);

3. map 

(1) 解释: 将流中的元素映射到另外一个流中 (把流中的每一个值,使用所提供的函数执行一遍,一一对应。得到元素个数相同的流。)

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

参数解释:(简单的说入参就是上一个流的元素,返回值就是你要转换为的新stream的类型)

返回由将给定函数应用于该流元素的结果组成的流。
这是一个中间操作。

参数:
    映射器-适用于每个元素的无干扰、无状态功能
类型参数:
    新流的元素类型
返参:新流

(2)例如: 用于映射每个元素到对应的结果,以下代码片段使用 map 输出了元素对应的平方数:

        List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
        // 获取对应的平方数
        List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());
        System.out.println(squaresList);

结果:

[9, 4, 49, 25]

(3) 补充:

另外提供了几个mapToInt, mapToLong 等api, 返回不同的stream

    IntStream mapToInt(ToIntFunction<? super T> mapper);

    LongStream mapToLong(ToLongFunction<? super T> mapper);

    DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);

补充: flatMap, 可以理解为将stream 扁平映射。

  流中的每个元素都走一遍Function, 且返回值是stream。

1. 源码

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

  参数解释:我的理解是假如你的集合流中包含子集合,那么使用flatMap可以返回该子集合的集合流, 然后进行合并或者其他操作

2. 测试

(1) 测试1

    private static void reactorTest() {
        User user = new User(Lists.newArrayList("语文书", "数学书"));
        User user2 = new User(Lists.newArrayList("英语书", "数学书"));
        List<User> users = Lists.newArrayList(user, user2);
        List<String> collect = users.stream().flatMap(tmp -> tmp.getBooks().stream()).collect(Collectors.toList());
        System.out.println(collect);
    }

    @AllArgsConstructor
    @Data
    private static class User {

        private List<String> books;
    }

结果:

[语文书, 数学书, 英语书, 数学书]

(2) 测试2

        List<List<String>> strs = Lists.newArrayList(Lists.newArrayList("1", "2"), Lists.newArrayList("3", "4"));
        List<String> collect = strs.stream().flatMap(Collection::stream).collect(Collectors.toList());
        System.out.println(collect.size());
        System.out.println(collect);

结果:

4
[1, 2, 3, 4]

3. 补充

stream 也提供了一些 flatMapToInt、 flatMapToLong、flatMapToDouble 方法

    IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper);

4. filter

filter 方法用于通过设置的条件过滤出元素。以下代码片段使用 filter 方法过滤出空字符串:

        List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
        // 获取空字符串的数量
        long count = strings.stream().filter(string -> string.isEmpty()).count();
        System.out.println(count);

结果:

2

5. limit、skip

limit 方法用于获取指定数量的流。 以下代码片段使用 limit 方法打印出 10 条数据:

        Random random = new Random();
        random.ints().limit(10).forEach(System.out::println);

skip 用于跳过指定数量的流:

        ArrayList<String> strings = Lists.newArrayList("1", "2", "3");
        long count = strings.stream().skip(2).count();
        System.out.println(count); // 1

6. sorted

sorted 方法用于对流进行排序。以下代码片段使用 sorted 方法对输出的 10 个随机数进行排序:

Random random = new Random();
random.ints().limit(10).sorted().forEach(System.out::println);

7.并行(parallel)流

parallelStream 是流并行处理程序的代替方法。

1. 默认使用的是使用ForkJoin 线程池处理(ForkJoinPool.commonPool)。

        IntStream.range(1, 20).parallel().forEach(num -> {
            System.out.println(Thread.currentThread().getName() + "\t" + num);
        });

结果:

ForkJoinPool.commonPool-worker-11    3
ForkJoinPool.commonPool-worker-11    4
ForkJoinPool.commonPool-worker-7    14
ForkJoinPool.commonPool-worker-7    10
ForkJoinPool.commonPool-worker-7    1
ForkJoinPool.commonPool-worker-7    5
ForkJoinPool.commonPool-worker-7    16
ForkJoinPool.commonPool-worker-7    15
ForkJoinPool.commonPool-worker-7    19
ForkJoinPool.commonPool-worker-7    18
ForkJoinPool.commonPool-worker-13    2
ForkJoinPool.commonPool-worker-9    11
ForkJoinPool.commonPool-worker-15    13
main    12
ForkJoinPool.commonPool-worker-3    6
ForkJoinPool.commonPool-worker-5    17
ForkJoinPool.commonPool-worker-11    8
ForkJoinPool.commonPool-worker-11    9
ForkJoinPool.commonPool-worker-11    7

2. 如果多次调用并行、串行以最后一次为准

        IntStream.range(1, 3).parallel().peek(num -> {
            System.out.println(Thread.currentThread().getName() + "\t" + num);
        }).sequential().forEach(num -> {
            System.out.println(Thread.currentThread().getName() + "\t" + num);
        });

结果:

main    1
main    1
main    2
main    2

3. 可以使用自定义的线程池: 可以看到使用的是自己的线程池进行的forkJoin

        // 使用自己的ForkJoinPool
        ForkJoinPool forkJoinPool = new ForkJoinPool(2);
        forkJoinPool.submit(() ->
                IntStream.range(1, 6).parallel().peek(num -> {
                            System.out.println(Thread.currentThread().getName() + "\t" + num);
                        }
                ).count());
        forkJoinPool.shutdown();
        synchronized (forkJoinPool) {
            forkJoinPool.wait();
        }

结果:

ForkJoinPool-1-worker-0    2
ForkJoinPool-1-worker-1    3
ForkJoinPool-1-worker-0    1
ForkJoinPool-1-worker-1    5
ForkJoinPool-1-worker-0    4

8. collector 收集器

Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串:

        List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
        List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
        System.out.println("筛选列表: " + filtered);
        String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
        System.out.println("合并字符串: " + mergedString);

结果:

筛选列表: [abc, bc, efg, abcd, jkl]

合并字符串: abc, bc, efg, abcd, jkl

其他常用操作如下:

    public static void main(String[] args) {
        ArrayList<User> users = Lists.newArrayList(new User(18, "男", 1),
                new User(18, "男", 1),
                new User(19, "女", 2),
                new User(18, "男", 2),
                new User(18, "男", 2)
        );
        // 获取所有人的年龄
        List<Integer> collect = users.stream().map(User::getAge).
                collect(Collectors.toList());
        System.out.println("年龄: " + collect);
        TreeSet<Integer> collect1 = users.stream().map(User::getAge).
                collect(Collectors.toCollection(TreeSet::new));
        System.out.println("年龄2: " + collect1);
        // 统计汇总信息
        IntSummaryStatistics collect2 = users.stream().collect(Collectors.summarizingInt(User::getAge));
        System.out.println("统计信息: " + collect2);

        // 分块 (一个特殊的分组信息)
        Map<Boolean, List<User>> groups = users.stream().collect(Collectors.partitioningBy(user -> user.getSex().equals("男")));
        MapUtils.verbosePrint(System.out, "分块信息", groups);
        // 分组
        Map<Integer, List<User>> collect3 = users.stream().collect(Collectors.groupingBy(User::getGroup));
        MapUtils.verbosePrint(System.out, "班级分组信息", collect3);
        Map<Integer, Long> collect4 = users.stream().collect(Collectors.groupingBy(User::getGroup, Collectors.counting()));
        MapUtils.verbosePrint(System.out, "班级分组信息汇总人数", collect4);
    }

    @Data
    @AllArgsConstructor
    public static class User {

        private int age;

        private String sex;

        private int group;
    }

结果:

年龄: [18, 18, 19, 18, 18]
年龄2: [18, 19]
统计信息: IntSummaryStatistics{count=5, sum=91, min=18, average=18.200000, max=19}
分块信息 = 
{
    false = [PlainTest2.User(age=19, sex=女, group=2)]
    true = [PlainTest2.User(age=18, sex=男, group=1), PlainTest2.User(age=18, sex=男, group=1), PlainTest2.User(age=18, sex=男, group=2), PlainTest2.User(age=18, sex=男, group=2)]
}
班级分组信息 = 
{
    1 = [PlainTest2.User(age=18, sex=男, group=1), PlainTest2.User(age=18, sex=男, group=1)]
    2 = [PlainTest2.User(age=19, sex=女, group=2), PlainTest2.User(age=18, sex=男, group=2), PlainTest2.User(age=18, sex=男, group=2)]
}
班级分组信息汇总人数 = 
{
    1 = 2
    2 = 3
}

9. 统计

一些产生统计结果的收集器也非常有用。它们主要用于int、double、long等基本类型上,它们可以用来产生类似如下的统计结果。

        List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
        IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics();
        System.out.println("列表中最大的数 : " + stats.getMax());
        System.out.println("列表中最小的数 : " + stats.getMin());
        System.out.println("所有数之和 : " + stats.getSum());
        System.out.println("平均数 : " + stats.getAverage());

10. 判断匹配的方法: 都是终结方法

allMatch()、anyMatch()、nonMatch()

        ArrayList<String> strings = Lists.newArrayList("1", "2", "3", "5", "1");
        boolean b = strings.stream().allMatch(str -> "1".equals(str));
        boolean c = strings.stream().anyMatch(str -> "1".equals(str));
        boolean d = strings.stream().noneMatch(str -> "1".equals(str));
        System.out.println(b); // f
        System.out.println(c); // t
        System.out.println(d); // f

11. max、min 返回最大最小, 是一个终结方法

        ArrayList<Integer> strings = Lists.newArrayList(1, 2, 3, 4);
        System.out.println(strings.stream().max(Comparator.comparing(Integer::valueOf)).get());

源码如下:

    Optional<T> max(Comparator<? super T> comparator);

12.  reduce 用于归纳元素

    Optional<T> reduce(BinaryOperator<T> accumulator);

    T reduce(T identity, BinaryOperator<T> accumulator);

identity 可以理解为默认值

测试:

        ArrayList<Integer> strings = Lists.newArrayList(1, 2, 3, 4);
        System.out.println(strings.stream().reduce((o1, o2) -> {
            return o1 + o2;
        }).get());

13. IntStream 用法

    private static void reactorTest() {
        IntSummaryStatistics intSummaryStatistics = IntStream.range(11, 16).summaryStatistics();
        System.out.println(intSummaryStatistics.getCount());
        System.out.println(intSummaryStatistics.getSum());
        int sum = IntStream.range(11, 16).sum();
        System.out.println(sum);
        List<String> collect = IntStream.range(11, 16).mapToObj(i -> {
            return i + "_val";
        }).collect(Collectors.toList());
        System.out.println(collect);
    }

结果:

5
65
65
[11_val, 12_val, 13_val, 14_val, 15_val]

补充: stream 原理如下 

1.所有的操作都是链式调用,一个元素只迭代一次。其原理是拿每一个元素依次调用中间操作。

2.每一个中间操作都返回一个新的流,流里面有一个属性: sourceStage, 指向同一个地方head(第一个stream)

head->nextStage->nextStage->...->null

3.有状态操作会截断无状态,单独处理。相当于有状态操作是一个分水岭,前后都是一串stream 按上面规则跑。

4.并行环境下,有状态操作的中间操作不一定能并行操作

5.parallel、sequetial也是中间操作,只不过他们不会创建流,只是修改head的并行标志属性 parallel

测试1:(验证上面1, 2 ,3)

        Stream<Integer> integerStream = Lists.newArrayList(1, 2).stream()
                // 第一个无状态操作
                .filter(key -> {
                    System.out.println("1" + "\t" + key);
                    return true;
                })
                // 第二个无状态操作
                .filter(key -> {
                    System.out.println("2" + "\t" + key);
                    return true;
                })
                // 第一个有状态操作
                .sorted((k1, k2) -> {
                    System.out.println("有状态排序:" + k1);
                    return k1 - k2;
                })
                // 第三个无状态操作
                .filter(key -> {
                    System.out.println("3" + "\t" + key);
                    return true;
                })
                // 第四个无状态操作
                .filter(key -> {
                    System.out.println("4" + "\t" + key);
                    return true;
                });
        long count = (long) integerStream.count();

结果:

1    1
2    1
1    2
2    2
有状态排序:2
3    1
4    1
3    2
4    2

debug 查看 integerStream  如下:

 测试2:(验证上面4, 5)

        Stream<Integer> integerStream = Lists.newArrayList(1, 2, 3).stream()
                // 第一个无状态操作
                .filter(key -> {
                    System.out.println(Thread.currentThread().getName() + "\t" + key);
                    return true;
                })
                // 第一个有状态操作
                .sorted((k1, k2) -> {
                    System.out.println(Thread.currentThread().getName() + " 有状态排序:" + k1);
                    return k1 - k2;
                })
                // 第四个无状态操作
                .filter(key -> {
                    System.out.println(Thread.currentThread().getName() + "\t" + key);
                    return true;
                }).parallel();
        long count = (long) integerStream.count();

结果:

main    2
ForkJoinPool.commonPool-worker-1    1
ForkJoinPool.commonPool-worker-2    3
main 有状态排序:2
main 有状态排序:3
main    2
ForkJoinPool.commonPool-worker-2    1
ForkJoinPool.commonPool-worker-1    3

debug 查看 integerStream:

补充: findFirst、findAny 返回Optional 对象 

    public static void main(String[] args) {
        // findFirst、findAny 返回 Optional
        /**
         * findFirst:此方法返回流中的第一个元素(按照流的遍历顺序)。在串行流中,它简单地返回遇到的第一个元素。在并行流中,它的行为也相对可预测,通常会返回处理结果中的第一个元素。
         * findAny:此方法返回流中的任意一个元素(按照流的遍历顺序)。在串行流中,它通常也返回遇到的第一个元素,与 findFirst 的行为相似。但在并行流中,findAny 的行为可能会有所不同,因为它可能会返回最先处理完的元素,这样可以提高性能,因为它允许多个线程同时搜索元素。
         */
        List<Integer> integers = Lists.newArrayList(1, 2, 3, 2, 1);
        Optional<Integer> first1 = integers.stream().findFirst();
        System.out.println(first1.get());
        System.out.println(integers.stream().filter(integer -> integer == 2).findFirst().orElse(null));
        // 找不到,返回的是 java.util.Optional.EMPTY, 空的Optional 对象
        System.out.println(integers.stream().filter(integer -> integer == 4).findFirst().orElse(null));

        // findAny
        System.out.println(integers.stream().filter(integer -> integer == 3).findAny().orElse(null));

        /**
         * 1
         * 2
         * null
         * 3
         */
    }

 

6.Optional 类

  Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。

  Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。

  Optional提供很多有用的方法,这样我们就不用显式进行空值检测。Optional 类的引入很好的解决空指针异常。

例如:

package com.zd.bx;

import java.util.Optional;

import lombok.Data;

@Data
public class Car {
    public static void main(String args[]) {
        Car java8Tester = new Car();
        Integer value1 = null;
        Integer value2 = new Integer(10);

        // Optional.ofNullable - 允许传递为 null 参数
        Optional<Integer> a = Optional.ofNullable(value1);

        // Optional.of - 如果传递的参数是 null,抛出异常 NullPointerException
        Optional<Integer> b = Optional.of(value2);
        System.out.println(java8Tester.sum(a, b));
    }

    public Integer sum(Optional<Integer> a, Optional<Integer> b) {

        // Optional.isPresent - 判断值是否存在

        System.out.println("第一个参数值存在: " + a.isPresent());
        System.out.println("第二个参数值存在: " + b.isPresent());

        // Optional.orElse - 如果值存在,返回它,否则返回默认值
        Integer value1 = a.orElse(new Integer(0));

        // Optional.get - 获取值,值需要存在
        Integer value2 = b.get();
        return value1 + value2;
    }
}

结果:

第一个参数值存在: false

第二个参数值存在: true

10

 

7. 日期时间 API

在之前的API中,日期时间API有下列问题:

(1)非线程安全:java.util.Date 是非线程安全的,所有的日期类都是可变的

(2)设计上:java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。

Java 8 在 java.time 包下提供了很多新的 API。以下为两个比较重要的 API:

(1)Local(本地) − 简化了日期时间的处理,没有时区的问题。

package com.zd.bx;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;

import lombok.Data;

@Data
public class Car {
    public static void main(String args[]) {
        Car java8tester = new Car();
        java8tester.testLocalDateTime();
    }

    public void testLocalDateTime() {
        // 获取当前的日期时间
        LocalDateTime currentTime = LocalDateTime.now();
        System.out.println("当前时间: " + currentTime);

        LocalDate date1 = currentTime.toLocalDate();
        System.out.println("date1: " + date1);

        Month month = currentTime.getMonth();
        int day = currentTime.getDayOfMonth();
        int seconds = currentTime.getSecond();

        System.out.println("月: " + month + ", 日: " + day + ", 秒: " + seconds);

        LocalDateTime date2 = currentTime.withDayOfMonth(10).withYear(2012);
        System.out.println("date2: " + date2);

        // 12 december 2014
        LocalDate date3 = LocalDate.of(2014, Month.DECEMBER, 12);
        System.out.println("date3: " + date3);

        // 22 小时 15 分钟
        LocalTime date4 = LocalTime.of(22, 15);
        System.out.println("date4: " + date4);

        // 解析字符串
        LocalTime date5 = LocalTime.parse("20:15:30");
        System.out.println("date5: " + date5);
    }
}

结果:

当前时间: 2020-07-16T22:58:21.163

date1: 2020-07-16

月: JULY, 日: 16, 秒: 21

date2: 2012-07-10T22:58:21.163

date3: 2014-12-12

date4: 22:15

date5: 20:15:30

(2)Zoned(时区) − 通过制定的时区处理日期时间。

package com.zd.bx;

import java.time.ZoneId;
import java.time.ZonedDateTime;

import lombok.Data;

@Data
public class Car {
    public static void main(String args[]) {
        Car java8tester = new Car();
        java8tester.testZonedDateTime();
    }

    public void testZonedDateTime() {

        // 获取当前时间日期
        ZonedDateTime date1 = ZonedDateTime.parse("2015-12-03T10:15:30+05:30[Asia/Shanghai]");
        System.out.println("date1: " + date1);

        ZoneId id = ZoneId.of("Europe/Paris");
        System.out.println("ZoneId: " + id);

        ZoneId currentZone = ZoneId.systemDefault();
        System.out.println("当期时区: " + currentZone);
    }
}

 

8.  Base64

  在Java 8中,Base64编码已经成为Java类库的标准。Java 8 内置了 Base64 编码的编码器和解码器。

例如:

package com.zd.bx;

import java.io.UnsupportedEncodingException;
import java.util.Base64;
import java.util.UUID;

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

            // 使用基本编码
            String base64encodedString = Base64.getEncoder().encodeToString("runoob?java8".getBytes("utf-8"));
            System.out.println("Base64 编码字符串 (基本) :" + base64encodedString);

            // 解码
            byte[] base64decodedBytes = Base64.getDecoder().decode(base64encodedString);

            System.out.println("原始字符串: " + new String(base64decodedBytes, "utf-8"));
            base64encodedString = Base64.getUrlEncoder().encodeToString("runoob?java8".getBytes("utf-8"));
            System.out.println("Base64 编码字符串 (URL) :" + base64encodedString);

            StringBuilder stringBuilder = new StringBuilder();

            for (int i = 0; i < 10; ++i) {
                stringBuilder.append(UUID.randomUUID().toString());
            }

            byte[] mimeBytes = stringBuilder.toString().getBytes("utf-8");
            String mimeEncodedString = Base64.getMimeEncoder().encodeToString(mimeBytes);
            System.out.println("Base64 编码字符串 (MIME) :" + mimeEncodedString);

        } catch (UnsupportedEncodingException e) {
            System.out.println("Error :" + e.getMessage());
        }
    }
}

补充:Java8根据某个属性分组的快速方法,转为map结构 

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        User user = new User("u1", 1);
        User user2 = new User("u2", 2);
        User user3 = new User("u3", 3);
        User user4 = new User("u4", 1);
        User user5 = new User("u5", 5);
        User user6 = new User("u6", 5);
        List<User> users = new ArrayList<>();
        users.add(user);
        users.add(user2);
        users.add(user3);
        users.add(user4);
        users.add(user5);
        users.add(user6);
        users.add(user6);
        // 根据年龄分组转为map
        Map<Integer, List<User>> collect = users.stream().collect(Collectors.groupingBy(User::getAge));
        System.out.println(collect);

        // 搜集数据的username 属性
        List<String> collect1 = users.stream().map(User::getUsername).collect(Collectors.toList());
        System.out.println(collect1);

        // 过滤,去重,然后收集username, 并且用空格拼接
        String collect2 = users.stream().filter((userTmp) -> userTmp.getAge() > 3).distinct().map(User::getUsername).collect(Collectors.joining("    "));
        System.out.println(collect2);
    }

结果:

{1=[User{username='u1', age=1}, User{username='u4', age=1}], 2=[User{username='u2', age=2}], 3=[User{username='u3', age=3}], 5=[User{username='u5', age=5}, User{username='u6', age=5}, User{username='u6', age=5}]}
[u1, u2, u3, u4, u5, u6, u6]
u5    u6

补充:Collectors.toMap 使用 

1. Collectors.toMap 如果key 值为null 会报NPE 异常, 如果key 值重复,需要自己指定保留的value 值, 如果不指定也会报错。

        List<User> users = Lists.newArrayList(new User("zs", "张三", "男"),
                new User("zs2", "张三", "男"),
                new User(null, null, "男"),
                new User("zs3", "张三3", "男"));
        // key 能为空,value 不允许为空。 如果重复不做处理会报错,所以Collectors.toMap 第三个参数指定key 重复之后保留的数据
        Map<String, String> collect = users.stream().filter((user) -> user.getUsername() != null).collect(Collectors.toMap(User::getFullname, User::getUsername, (v1, v2) -> {
            System.out.println("v1: " + v1);
            System.out.println("v2: " + v2);
            return v1;
        }));
        System.out.println(collect);

结果:

v1: zs

v2: zs2

{张三=zs, 张三3=zs3}

2. 比如有个用户集合,我们想将其按id 分组:

    public static void main(String[] args) throws NoSuchFieldException {
        User user = new User();
        user.setId(1);
        user.setUsername("username" + 1);
        User user2 = new User();
        user2.setId(2);
        user2.setUsername("username" + 2);
        ArrayList<User> users = Lists.newArrayList(user, user2);
        Map<Integer, User> collect = users.stream().collect(Collectors.toMap(User::getId, Function.identity()));
        System.out.println(collect);
    }

    @Data
    private static class User {

        private Integer id;

        private String username;
    }

结果:

{1=PlainTest.User(id=1, username=username1), 2=PlainTest.User(id=2, username=username2)}

补充: .collect 实现简单解读 

1. .collect 源码解读

    /**
     * Performs a <a href="package-summary.html#MutableReduction">mutable
     * reduction</a> operation on the elements of this stream using a
     * {@code Collector}.  A {@code Collector}
     * encapsulates the functions used as arguments to
     * {@link #collect(Supplier, BiConsumer, BiConsumer)}, allowing for reuse of
     * collection strategies and composition of collect operations such as
     * multiple-level grouping or partitioning.
     *
     * <p>If the stream is parallel, and the {@code Collector}
     * is {@link Collector.Characteristics#CONCURRENT concurrent}, and
     * either the stream is unordered or the collector is
     * {@link Collector.Characteristics#UNORDERED unordered},
     * then a concurrent reduction will be performed (see {@link Collector} for
     * details on concurrent reduction.)
     *
     * <p>This is a <a href="package-summary.html#StreamOps">terminal
     * operation</a>.
     *
     * <p>When executed in parallel, multiple intermediate results may be
     * instantiated, populated, and merged so as to maintain isolation of
     * mutable data structures.  Therefore, even when executed in parallel
     * with non-thread-safe data structures (such as {@code ArrayList}), no
     * additional synchronization is needed for a parallel reduction.
     *
     * @apiNote
     * The following will accumulate strings into an ArrayList:
     * <pre>{@code
     *     List<String> asList = stringStream.collect(Collectors.toList());
     * }</pre>
     *
     * <p>The following will classify {@code Person} objects by city:
     * <pre>{@code
     *     Map<String, List<Person>> peopleByCity
     *         = personStream.collect(Collectors.groupingBy(Person::getCity));
     * }</pre>
     *
     * <p>The following will classify {@code Person} objects by state and city,
     * cascading two {@code Collector}s together:
     * <pre>{@code
     *     Map<String, Map<String, List<Person>>> peopleByStateAndCity
     *         = personStream.collect(Collectors.groupingBy(Person::getState,
     *                                                      Collectors.groupingBy(Person::getCity)));
     * }</pre>
     *
     * @param <R> the type of the result
     * @param <A> the intermediate accumulation type of the {@code Collector}
     * @param collector the {@code Collector} describing the reduction
     * @return the result of the reduction
     * @see #collect(Supplier, BiConsumer, BiConsumer)
     * @see Collectors
     */
    <R, A> R collect(Collector<? super T, A, R> collector);

可以看到传入的是一个Collector,是一个泛型,泛型的T,A,R 分别表示:

T : 要处理的元素的类型

A : 累加器的类型

R : 返回结果的类型

2. java.util.stream.Collector 源码如下:

public interface Collector<T, A, R> {
    /**
     * A function that creates and returns a new mutable result container.
     *
     * @return a function which returns a new, mutable result container
     */
    Supplier<A> supplier();

    /**
     * A function that folds a value into a mutable result container.
     *
     * @return a function which folds a value into a mutable result container
     */
    BiConsumer<A, T> accumulator();

    /**
     * A function that accepts two partial results and merges them.  The
     * combiner function may fold state from one argument into the other and
     * return that, or may return a new result container.
     *
     * @return a function which combines two partial results into a combined
     * result
     */
    BinaryOperator<A> combiner();

    /**
     * Perform the final transformation from the intermediate accumulation type
     * {@code A} to the final result type {@code R}.
     *
     * <p>If the characteristic {@code IDENTITY_TRANSFORM} is
     * set, this function may be presumed to be an identity transform with an
     * unchecked cast from {@code A} to {@code R}.
     *
     * @return a function which transforms the intermediate result to the final
     * result
     */
    Function<A, R> finisher();

    /**
     * Returns a {@code Set} of {@code Collector.Characteristics} indicating
     * the characteristics of this Collector.  This set should be immutable.
     *
     * @return an immutable set of collector characteristics
     */
    Set<Characteristics> characteristics();

    /**
     * Returns a new {@code Collector} described by the given {@code supplier},
     * {@code accumulator}, and {@code combiner} functions.  The resulting
     * {@code Collector} has the {@code Collector.Characteristics.IDENTITY_FINISH}
     * characteristic.
     *
     * @param supplier The supplier function for the new collector
     * @param accumulator The accumulator function for the new collector
     * @param combiner The combiner function for the new collector
     * @param characteristics The collector characteristics for the new
     *                        collector
     * @param <T> The type of input elements for the new collector
     * @param <R> The type of intermediate accumulation result, and final result,
     *           for the new collector
     * @throws NullPointerException if any argument is null
     * @return the new {@code Collector}
     */
    public static<T, R> Collector<T, R, R> of(Supplier<R> supplier,
                                              BiConsumer<R, T> accumulator,
                                              BinaryOperator<R> combiner,
                                              Characteristics... characteristics) {
        Objects.requireNonNull(supplier);
        Objects.requireNonNull(accumulator);
        Objects.requireNonNull(combiner);
        Objects.requireNonNull(characteristics);
        Set<Characteristics> cs = (characteristics.length == 0)
                                  ? Collectors.CH_ID
                                  : Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH,
                                                                           characteristics));
        return new Collectors.CollectorImpl<>(supplier, accumulator, combiner, cs);
    }

    /**
     * Returns a new {@code Collector} described by the given {@code supplier},
     * {@code accumulator}, {@code combiner}, and {@code finisher} functions.
     *
     * @param supplier The supplier function for the new collector
     * @param accumulator The accumulator function for the new collector
     * @param combiner The combiner function for the new collector
     * @param finisher The finisher function for the new collector
     * @param characteristics The collector characteristics for the new
     *                        collector
     * @param <T> The type of input elements for the new collector
     * @param <A> The intermediate accumulation type of the new collector
     * @param <R> The final result type of the new collector
     * @throws NullPointerException if any argument is null
     * @return the new {@code Collector}
     */
    public static<T, A, R> Collector<T, A, R> of(Supplier<A> supplier,
                                                 BiConsumer<A, T> accumulator,
                                                 BinaryOperator<A> combiner,
                                                 Function<A, R> finisher,
                                                 Characteristics... characteristics) {
        Objects.requireNonNull(supplier);
        Objects.requireNonNull(accumulator);
        Objects.requireNonNull(combiner);
        Objects.requireNonNull(finisher);
        Objects.requireNonNull(characteristics);
        Set<Characteristics> cs = Collectors.CH_NOID;
        if (characteristics.length > 0) {
            cs = EnumSet.noneOf(Characteristics.class);
            Collections.addAll(cs, characteristics);
            cs = Collections.unmodifiableSet(cs);
        }
        return new Collectors.CollectorImpl<>(supplier, accumulator, combiner, finisher, cs);
    }

    /**
     * Characteristics indicating properties of a {@code Collector}, which can
     * be used to optimize reduction implementations.
     */
    enum Characteristics {
        /**
         * Indicates that this collector is <em>concurrent</em>, meaning that
         * the result container can support the accumulator function being
         * called concurrently with the same result container from multiple
         * threads.
         *
         * <p>If a {@code CONCURRENT} collector is not also {@code UNORDERED},
         * then it should only be evaluated concurrently if applied to an
         * unordered data source.
         */
        CONCURRENT,

        /**
         * Indicates that the collection operation does not commit to preserving
         * the encounter order of input elements.  (This might be true if the
         * result container has no intrinsic order, such as a {@link Set}.)
         */
        UNORDERED,

        /**
         * Indicates that the finisher function is the identity function and
         * can be elided.  If set, it must be the case that an unchecked cast
         * from A to R will succeed.
         */
        IDENTITY_FINISH
    }
}
View Code

可以看到,内部包含:

supplier:提供一个新的集合,用于存放元素

accumulator:返回一个BiConsumer累加器,也就是元素和我们的集合进行的操作。 需要做的就是元素放入集合。

combiner:返回一个BinaryOperator,用于并行流执行时合并两个累加器list到一个累加器list中

finisher: 返回一个Function,用于完成时,将累加器的结果A转换成想要的结果R,不需要转换时可以使用 t -> t;表达式表示;

characteristics:它会返回一个不可变的Characteristics集合,用于定义收集器的行为;

1、UNORDERED:归约结果不受流中项目的遍历和累积顺序的影响,即流归约结果是无序的,可以并行执行归约。

2、CONCURRENT:accumulator函数可以从多个线程同时调用,且该收集器可以并行归约流。如果收集器没有标为UNORDERED,那它仅在用于无序数据源时才可以并行归约。

3、IDENTITY_FINISH:这表明完成器方法返回的函数是一个恒等函数,可以跳过。这种情况下,累加器对象将会直接用作归约过程的最终结果。这也意味着,将累加器A不加检查地转换为结果R是安全的,即不需要定义finisher,或定义为 t -> t 。

2. 自己实现Collectors.toList

import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;

public class PlainTest {

    public static void main(String[] args) {
        List<User> userList = new ArrayList<>();
        userList.add(new User("zs", 2));
        userList.add(new User("zs", 4));
        userList.add(new User("ls", 2));
        userList.add(new User("ls", 23));

//        List<String> collect = userList.stream().map(User::getName).collect(Collectors.toList());
        List<String> collect1 = userList.stream().map(User::getName).collect(new Collector<String, List<String>, List<String>>() {

            @Override
            public Supplier<List<String>> supplier() {
                System.out.println(1);
                return ArrayList::new;
            }

            @Override
            public BiConsumer<List<String>, String> accumulator() {
                System.out.println(2);
                return new BiConsumer<List<String>, String>() {
                    @Override
                    public void accept(List<String> strings, String s) {
                        System.out.println(21);
                        strings.add(s);
                    }
                };
//                return List::add;
            }

            /**
             *
             * @return
             */
            @Override
            public BinaryOperator<List<String>> combiner() {
                System.out.println(3);
                return new BinaryOperator<List<String>>() {
                    @Override
                    public List<String> apply(List<String> left, List<String> right) {
                        System.out.println(4);
                        left.addAll(right);
                        return left;
                    }
                };
            }

            @Override
            public Function<List<String>, List<String>> finisher() {
                System.out.println(5);
                return new Function<List<String>, List<String>>() {
                    @Override
                    public List<String> apply(List<String> strings) {
                        System.out.println(6);
                        return strings;
                    }
                };
            }

            @Override
            public Set<Characteristics> characteristics() {
                System.out.println(7);
                return Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH));
            }
        });
        System.out.println(collect1);
    }


    @Data
    @AllArgsConstructor
    public static class User {

        private String name;

        private int age;
    }

}

结果:

1
2
3
7
21
21
21
21
7
[zs, zs, ls, ls]

换成parallelStream() 再次查看:

7
1
2
3
7
21
21
21
21
4
4
4
7
[zs, zs, ls, ls]

3. 精简下

List<String> collect1 = userList.parallelStream().map(User::getName).collect(new Collector<String, List<String>, List<String>>() {

            @Override
            public Supplier<List<String>> supplier() {
                return ArrayList::new;
            }

            @Override
            public BiConsumer<List<String>, String> accumulator() {
//                return (a, b) -> a.add(b);
                return List::add;
            }

            /**
             *
             * @return
             */
            @Override
            public BinaryOperator<List<String>> combiner() {
                return (left, right) -> {
                    left.addAll(right);
                    return left;
                };
            }

            @Override
            public Function<List<String>, List<String>> finisher() {
                return t -> t;
            }

            @Override
            public Set<Characteristics> characteristics() {
                return Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH));
            }
        });
        System.out.println(collect1);
    }

4. 方法二:

    <R> R collect(Supplier<R> supplier,
                  BiConsumer<R, ? super T> accumulator,
                  BiConsumer<R, R> combiner);

简单实现:

        List<User> userList = new ArrayList<>();
        userList.add(new User("zs", 2));
        userList.add(new User("zs", 4));
        userList.add(new User("ls", 2));
        userList.add(new User("ls", 23));

//        List<String> collect = userList.stream().map(User::getName).collect(Collectors.toList());
        List<String> collect1 = userList.parallelStream().map(User::getName)
                .collect(ArrayList::new,
                        List::add,
                        List::addAll
                );
        System.out.println(collect1);

结果:

[zs, zs, ls, ls]

5. 自己实现 Collectors.groupBying(User::getName)

        List<User> userList = new ArrayList<>();
        userList.add(new User("zs", 2));
        userList.add(new User("zs", 4));
        userList.add(new User("ls", 2));
        userList.add(new User("ls", 23));

        HashMap<String, List<User>> collect = userList.stream().
                collect(HashMap::new,
                        new BiConsumer<HashMap<String, List<User>>, User>() {
                            @Override
                            public void accept(HashMap<String, List<User>> objectObjectHashMap, User user) {
                                List<User> users = objectObjectHashMap.get(user.getName());
                                if (users == null) {
                                    users = new ArrayList<>();
                                    objectObjectHashMap.put(user.getName(), users);
                                }
                                users.add(user);
                            }
                        },
                        HashMap::putAll
                );
        System.out.println(collect);

结果:

{ls=[PlainTest.User(name=ls, age=2), PlainTest.User(name=ls, age=23)], zs=[PlainTest.User(name=zs, age=2), PlainTest.User(name=zs, age=4)]}

6. toList 和 groupBying 源码查看

    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);
    }

    public static <T, K, D, A, M extends Map<K, D>>
    Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,
                                  Supplier<M> mapFactory,
                                  Collector<? super T, A, D> downstream) {
        Supplier<A> downstreamSupplier = downstream.supplier();
        BiConsumer<A, ? super T> downstreamAccumulator = downstream.accumulator();
        BiConsumer<Map<K, A>, T> accumulator = (m, t) -> {
            K key = Objects.requireNonNull(classifier.apply(t), "element cannot be mapped to a null key");
            A container = m.computeIfAbsent(key, k -> downstreamSupplier.get());
            downstreamAccumulator.accept(container, t);
        };
        BinaryOperator<Map<K, A>> merger = Collectors.<K, A, Map<K, A>>mapMerger(downstream.combiner());
        @SuppressWarnings("unchecked")
        Supplier<Map<K, A>> mangledFactory = (Supplier<Map<K, A>>) mapFactory;

        if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) {
            return new CollectorImpl<>(mangledFactory, accumulator, merger, CH_ID);
        }
        else {
            @SuppressWarnings("unchecked")
            Function<A, A> downstreamFinisher = (Function<A, A>) downstream.finisher();
            Function<Map<K, A>, M> finisher = intermediate -> {
                intermediate.replaceAll((k, v) -> downstreamFinisher.apply(v));
                @SuppressWarnings("unchecked")
                M castResult = (M) intermediate;
                return castResult;
            };
            return new CollectorImpl<>(mangledFactory, accumulator, merger, finisher, CH_NOID);
        }
    }

    public static <T, K, U, M extends Map<K, U>>
    Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
                                Function<? super T, ? extends U> valueMapper,
                                BinaryOperator<U> mergeFunction,
                                Supplier<M> mapSupplier) {
        BiConsumer<M, T> accumulator
                = (map, element) -> map.merge(keyMapper.apply(element),
                                              valueMapper.apply(element), mergeFunction);
        return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID);
    }

另外groupingBy 还有一个参数、一个参数的重载。

    public static <T, K> Collector<T, ?, Map<K, List<T>>>
    groupingBy(Function<? super T, ? extends K> classifier) {
        return groupingBy(classifier, toList());
    }
    
    public static <T, K, A, D>
    Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,
                                          Collector<? super T, A, D> downstream) {
        return groupingBy(classifier, HashMap::new, downstream);
    }

比如实现自动转为TreeMap

Map<String, List<Map<String, Object>>> collect = datas.stream().collect(Collectors.groupingBy(tmp -> MapUtils.getString(tmp, DATE_TIME, ""), TreeMap::new, Collectors.toList()));

7. 自己实现 按某个字段分组然后计数存到map 中

package qz;

import com.beust.jcommander.internal.Lists;
import lombok.extern.slf4j.Slf4j;
import qz.bean.User;

import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;

/**
 * PlainTest
 *
 * @author qlq
 * @date 2024/4/18 10:48
 */
@Slf4j
public class PlainTest {

    public static void main(String[] args) {
        User user = new User("qlq", "123456", "1");
        User user2 = new User("qlq1", "123456", "2");
        User user3 = new User("qlq", "123456", "1");
        List<User> users = Lists.newArrayList(user, user2, user3);


        Map<String, List<User>> collect = users.stream().collect(Collectors.groupingBy(User::getClassNum));
        System.out.println(collect);

        // users 按 classNum 分组,统计每个组的个数。 结果收集到map 中
        Map<String, Long> collect1 = users.stream().collect(Collectors.groupingBy(User::getClassNum, Collectors.counting()));
        System.out.println(collect1);


        // 自己实现
        /**
         * 泛型对应的顺序如下
         *
         * T : 要处理的元素的类型
         * A : 累加器的类型
         * R : 返回结果的类型
         */
        Map<String, Integer> collect2 = users.parallelStream().collect(new Collector<User, Map<String, Long>, Map<String, Integer>>() {
            /**
             * 返回一个新的集合, 用于处理初期存储数据
             * @return
             */
            @Override
            public Supplier<Map<String, Long>> supplier() {
                PrintTool.printTimeAndThread("supplier", "1");
                return HashMap::new;
            }

            /**
             * biConsumer, 遍历操作, 也就是遍历集合做的操作
             * @return
             */
            @Override
            public BiConsumer<Map<String, Long>, User> accumulator() {
                PrintTool.printTimeAndThread("accumulator", "2");
                return new BiConsumer<Map<String, Long>, User>() {
                    @Override
                    public void accept(Map<String, Long> stringLongMap, User user) {
                        PrintTool.printTimeAndThread("accumulator", "21");
                        if (stringLongMap.containsKey(user.getClassNum())) {
                            stringLongMap.put(user.getClassNum(), stringLongMap.get(user.getClassNum()) + 1);
                        } else {
                            stringLongMap.put(user.getClassNum(), 1L);
                        }
                    }
                };
            }

            /**
             * 合并操作, parallelStream 操作下的操作
             * @return
             */
            @Override
            public BinaryOperator<Map<String, Long>> combiner() {
                PrintTool.printTimeAndThread("accumulator", "3");
                return new BinaryOperator<Map<String, Long>>() {
                    @Override
                    public Map<String, Long> apply(Map<String, Long> stringLongHashMap, Map<String, Long> stringLongHashMap2) {
                        PrintTool.printTimeAndThread("accumulator", "31");

                        // 创建一个新Map用于存放合并后的结果
                        Map<String, Long> mergedMap = new HashMap<>(stringLongHashMap);

                        // 遍历第二个Map的所有键值对
                        for (Map.Entry<String, Long> entry : stringLongHashMap2.entrySet()) {
                            String key = entry.getKey();
                            long value = entry.getValue();

                            // 如果第一个Map中存在相同的键,则将对应值累加到新Map中
                            if (mergedMap.containsKey(key)) {
                                mergedMap.put(key, mergedMap.get(key) + value);
                            } else {
                                // 否则直接将键值对添加到新Map中
                                mergedMap.put(key, value);
                            }
                        }
                        return mergedMap;
                    }
                };
            }

            /**
             * 转换结果处理器。 A -> R
             * @return
             */
            @Override
            public Function<Map<String, Long>, Map<String, Integer>> finisher() {
                PrintTool.printTimeAndThread("accumulator", "4");

                return new Function<Map<String, Long>, Map<String, Integer>>() {

                    @Override
                    public Map<String, Integer> apply(Map<String, Long> stringLongHashMap) {
                        PrintTool.printTimeAndThread("accumulator", "41");
                        HashMap<String, Integer> stringIntegerHashMap = new HashMap<>();
                        stringLongHashMap.forEach((key, value) -> {
                            stringIntegerHashMap.put(key, Integer.valueOf(value.toString()));
                        });
                        return stringIntegerHashMap;
                    }
                };
            }

            /**
             * 1、UNORDERED:归约结果不受流中项目的遍历和累积顺序的影响,即流归约结果是无序的,可以并行执行归约。
             *
             * 2、CONCURRENT:accumulator函数可以从多个线程同时调用,且该收集器可以并行归约流。如果收集器没有标为UNORDERED,那它仅在用于无序数据源时才可以并行归约。
             *
             * 3、IDENTITY_FINISH:这表明完成器方法返回的函数是一个恒等函数,可以跳过。这种情况下,累加器对象将会直接用作归约过程的最终结果。这也意味着,将累加器A不加检查地转换为结果R是安全的,即不需要定义finisher,或定义为 t -> t 。
             * @return
             */
            @Override
            public Set<Characteristics> characteristics() {
                PrintTool.printTimeAndThread("accumulator", "51");
                return Collections.unmodifiableSet(EnumSet.of(Characteristics.CONCURRENT));
            }
        });
        PrintTool.printTimeAndThread("collect2", collect2.toString());


        /**
         * 精简之后的
         *     <R> R collect(Supplier<R> supplier,
         *                   BiConsumer<R, ? super T> accumulator,
         *                   BiConsumer<R, R> combiner);
         */
        Map<Object, Long> collect3 = users.parallelStream().collect(HashMap::new, (map, tmpUser) -> {
            // 遍历map, 根据classNum 计数
            Long count = map.getOrDefault(tmpUser.getClassNum(), 0L);
            map.put(tmpUser.getClassNum(), count + 1);
        }, Map::putAll);
        PrintTool.printTimeAndThread("collect3", collect3.toString());
    }
}

 

 

 

补充: jdk8 map 提供的方法 

1. compute: 对 hashMap 中指定 key 的值进行重新计算。

package org.example;

import java.util.HashMap;
import java.util.function.BiFunction;

public class PlainTest {

    public static void main(String[] args) {
        // compute: 对 hashMap 中指定 key 的值进行重新计算。
        // 如果 key 对应的 value 不存在,则返回该 null,如果存在,则返回通过 remappingFunction 重新计算后的值
        //创建一个 HashMap
        HashMap<String, Integer> prices = new HashMap<>();
        // 往HashMap中添加映射项
        prices.put("Shoes", 200);
        prices.put("Bag", 300);
        prices.put("Pant", 150);
        PrintUtils.print("HashMap: " + prices);

        MyBiFunction myBiFunction = new MyBiFunction();

        // 重新计算鞋子打了10%折扣后的值
        Integer newPrice = prices.compute("Shoes", myBiFunction);
        PrintUtils.print("Discounted Price of Shoes: " + newPrice);
        // 输出更新后的HashMap
        PrintUtils.print("Updated HashMap: " + prices);

        Integer tshirtPrice = prices.compute("tshirt", myBiFunction);
        PrintUtils.print("Discounted Price of tshirtPrice: " + tshirtPrice);
        // 输出更新后的HashMap
        PrintUtils.print("Updated HashMap: " + prices);

        Integer tshirt2Price = prices.compute("tshirt2", myBiFunction);
        PrintUtils.print("Discounted Price of tshirt2Price: " + tshirt2Price);
        // 输出更新后的HashMap
        PrintUtils.print("Updated HashMap: " + prices);

        Integer bag = prices.compute("Bag", myBiFunction);
        PrintUtils.print("Discounted Price of bag: " + bag);
        // 输出更新后的HashMap
        PrintUtils.print("Updated HashMap: " + prices);
    }

    public static class MyBiFunction implements BiFunction<String, Integer, Integer> {
        @Override
        public Integer apply(String key, Integer value) {
            PrintUtils.print("key: %s, value: %s", key, value);
            if (value != null && !"Bag".equals(key)) {
               return value - value * 10 / 100;
            }
            if ("tshirt".equals(key)) {
                return 20;
            }
            return null;
        }
    }

}

结果:

threadName main, str: HashMap: {Pant=150, Bag=300, Shoes=200}
threadName main, str: key: Shoes, value: 200
threadName main, str: Discounted Price of Shoes: 180
threadName main, str: Updated HashMap: {Pant=150, Bag=300, Shoes=180}
threadName main, str: key: tshirt, value: null
threadName main, str: Discounted Price of tshirtPrice: 20
threadName main, str: Updated HashMap: {Pant=150, tshirt=20, Bag=300, Shoes=180}
threadName main, str: key: tshirt2, value: null
threadName main, str: Discounted Price of tshirt2Price: null
threadName main, str: Updated HashMap: {Pant=150, tshirt=20, Bag=300, Shoes=180}
threadName main, str: key: Bag, value: 300
threadName main, str: Discounted Price of bag: null
threadName main, str: Updated HashMap: {Pant=150, tshirt=20, Shoes=180}

注意:

1. 如果map 中不存在对应的key,传到java.util.function.BiFunction#apply 的是null
2. 如果java.util.function.BiFunction#apply 返回的是null,则原map 将删除对应的key; 否则是替换对应的值

2. computeIfAbsent

  如果不存在元素就计算。也就是不包含指定的key 才进入 Function#apply 方法进行计算:然后计算后的值和上面规则一样;不为null就返回并且更新map,否则就返回null,且不更新map。

package org.example;

import java.util.HashMap;
import java.util.function.Function;

public class PlainTest {

    public static void main(String[] args) {
        // compute: 对 hashMap 中指定 key 的值进行重新计算。
        // 如果 key 对应的 value 不存在,则返回该 null,如果存在,则返回通过 remappingFunction 重新计算后的值
        //创建一个 HashMap
        HashMap<String, Integer> prices = new HashMap<>();
        // 往HashMap中添加映射项
        prices.put("Shoes", 200);
        prices.put("Bag", 300);
        prices.put("Pant", 150);
        PrintUtils.print("HashMap: " + prices);

        MyFunction myFunction = new MyFunction();
        // 重新计算鞋子打了10%折扣后的值
        Integer shoes = prices.computeIfAbsent("Shoes", myFunction);
        PrintUtils.print("shoes: " + shoes);
        PrintUtils.print("prices: " + prices);
        Integer tshirt = prices.computeIfAbsent("tshirt", myFunction);
        PrintUtils.print("tshirt: " + tshirt);
        PrintUtils.print("prices: " + prices);
        Integer Shoes2 = prices.computeIfAbsent("Shoes2", myFunction);
        PrintUtils.print("Shoes2: " + Shoes2);
        PrintUtils.print("prices: " + prices);
    }

    public static class MyFunction implements Function<String, Integer> {
        @Override
        public Integer apply(String key) {
            PrintUtils.print("key: %s", key);
            if ("tshirt".equals(key)) {
                return null;
            }
            // 返回一个默认值
            return 299;
        }
    }

}

结果:

threadName main, str: HashMap: {Pant=150, Bag=300, Shoes=200}
threadName main, str: shoes: 200
threadName main, str: prices: {Pant=150, Bag=300, Shoes=200}
threadName main, str: key: tshirt
threadName main, str: tshirt: null
threadName main, str: prices: {Pant=150, Bag=300, Shoes=200}
threadName main, str: key: Shoes2
threadName main, str: Shoes2: 299
threadName main, str: prices: {Pant=150, Shoes2=299, Bag=300, Shoes=200}

常用场景:

# map 初始化一些元素,有则返回、无则创建

  Map<String, List<String>> result = new HashMap<>();
  result.computeIfAbsent(key, (k) -> new ArrayList<>()).add(str);

3. computeIfPresent

只有指定的key 才参与计算。

package org.example;

import java.util.HashMap;
import java.util.function.BiFunction;

public class PlainTest {

    public static void main(String[] args) {
        // compute: 对 hashMap 中指定 key 的值进行重新计算。
        // 如果 key 对应的 value 不存在,则返回该 null,如果存在,则返回通过 remappingFunction 重新计算后的值
        //创建一个 HashMap
        HashMap<String, Integer> prices = new HashMap<>();
        // 往HashMap中添加映射项
        prices.put("Shoes", 200);
        prices.put("Bag", null);
        prices.put("Pant", 150);
        PrintUtils.print("HashMap: " + prices);

        MyBiFunction myBiFunction = new MyBiFunction();

        // 重新计算鞋子打了10%折扣后的值
        Integer newPrice = prices.computeIfPresent("Shoes", myBiFunction);
        PrintUtils.print("Discounted Price of Shoes: " + newPrice);
        PrintUtils.print("Updated HashMap: " + prices);

        Integer tshirtPrice = prices.computeIfPresent("tshirt", myBiFunction);
        PrintUtils.print("Discounted Price of tshirtPrice: " + tshirtPrice);
        PrintUtils.print("Updated HashMap: " + prices);

        Integer tshirt2Price = prices.computeIfPresent("tshirt2", myBiFunction);
        PrintUtils.print("Discounted Price of tshirt2Price: " + tshirt2Price);
        PrintUtils.print("Updated HashMap: " + prices);

        Integer bag = prices.computeIfPresent("Bag", myBiFunction);
        PrintUtils.print("Discounted Price of bag: " + bag);
        // 输出更新后的HashMap
        PrintUtils.print("Updated HashMap: " + prices);
    }

    public static class MyBiFunction implements BiFunction<String, Integer, Integer> {
        @Override
        public Integer apply(String key, Integer value) {
            PrintUtils.print("key: %s, value: %s", key, value);
            if (value != null && !"Bag".equals(key)) {
                return value - value * 10 / 100;
            }
            if ("tshirt".equals(key)) {
                return 20;
            }
            return null;
        }
    }

}

结果:

threadName main, str: HashMap: {Pant=150, Bag=null, Shoes=200}
threadName main, str: key: Shoes, value: 200
threadName main, str: Discounted Price of Shoes: 180
threadName main, str: Updated HashMap: {Pant=150, Bag=null, Shoes=180}
threadName main, str: Discounted Price of tshirtPrice: null
threadName main, str: Updated HashMap: {Pant=150, Bag=null, Shoes=180}
threadName main, str: Discounted Price of tshirt2Price: null
threadName main, str: Updated HashMap: {Pant=150, Bag=null, Shoes=180}
threadName main, str: Discounted Price of bag: null
threadName main, str: Updated HashMap: {Pant=150, Bag=null, Shoes=180} 

补充:stream 直接转数组

String[] strings = Lists.newArrayList("1", "2", "3").stream().toArray(String[]::new);

 

补充:  关于collector null 问题

package qz;

import com.beust.jcommander.internal.Lists;
import lombok.extern.slf4j.Slf4j;
import qz.bean.User;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * PlainTest
 *
 * @author qlq
 */
@Slf4j
public class PlainTest {

    public static void main(String[] args) {
        List<User> users =
                Lists.newArrayList(new User("1", "1", null), new User("01", "1", "1"), new User("2", "2", null), new User("3", "3", "3"));
        // key 不允许为空。 也就是分组的key 不能为空
//        Map<String, List<User>> collect = users.stream().collect(Collectors.groupingBy(User::getClassNum));
//        System.out.println(collect);
        // key 允许为空, value 不允许为空
//        Map<String, String> collect2 = users.stream().collect(Collectors.toMap(User::getClassNum, User::getName));
//        System.out.println(collect2);
        //  允许为空
        Set<String> collect1 = users.stream().map(User::getClassNum).collect(Collectors.toSet());
        System.out.println(collect1);
        //  允许为空
        List<String> collect3 = users.stream().map(User::getClassNum).collect(Collectors.toList());
        System.out.println(collect3);
    }
}

 

posted @ 2020-07-16 23:10  QiaoZhi  阅读(441)  评论(0编辑  收藏  举报