Java的stream操作

发布时间 2023-05-01 22:42:12作者: sqw666

Java中的stream

只需告诉做什么,而不用管怎么做

1. 创建流

1.1 从数组创建流

1.1.1 Arrays提供

String[] names = {"nick", "jack", "michael", "jone", "jane"};
// Arrays提供的返回流的接口
Stream<String> stream = Arrays.stream(strs);
  • 查看Arrays中产生流的所有方法:
		
		public static <T> Stream<T> stream(T[] array) {
        return stream(array, 0, array.length);
    }
    public static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive) {
        return StreamSupport.stream(spliterator(array, startInclusive, endExclusive), false);
    }
    public static IntStream stream(int[] array) {
        return stream(array, 0, array.length);
    }
    public static IntStream stream(int[] array, int startInclusive, int endExclusive) {
        return StreamSupport.intStream(spliterator(array, startInclusive, endExclusive), false);
    }
    public static LongStream stream(long[] array) {
        return stream(array, 0, array.length);
    }
    public static LongStream stream(long[] array, int startInclusive, int endExclusive) {
        return StreamSupport.longStream(spliterator(array, startInclusive, endExclusive), false);
    }
    public static DoubleStream stream(double[] array) {
        return stream(array, 0, array.length);
    }
    public static DoubleStream stream(double[] array, int startInclusive, int endExclusive) {
        return StreamSupport.doubleStream(spliterator(array, startInclusive, endExclusive), false);
    }
类型 数组类型 具体方法 解释
引用类型数组 T[] Stream stream(T[] array) 传入一个引用类型的数组T[],会返回一个该引用类型的流Stream
Stream stream(T[] array, int startInclusive, int endExclusive) 返回数组中指定范围(左闭右开)的流,实际上上面的方法会调用该方法产生流
基本数据类型数组 int[] IntStream stream(int[] array) 传入一个int类型的数组int[],返回一个IntStream
IntStream stream(int[] array, int startInclusive, int endExclusive) 返回int数组中指定范围的流
long[] LongStream stream(long[] array) 传入一个long类型的数组long[],返回一个LongStream
LongStream stream(long[] array, int startInclusive, int endExclusive) 返回long数组中指定范围的流
double[] DoubleStream stream(double[] array) 传入一个double类型的数组double[],返回一个DoubleStream
DoubleStream stream(double[] array, int startInclusive, int endExclusive) 返回double数组中指定范围的流

1.1.2 Stream接口提供

    public static<T> Stream<T> of(T... values) {
        return Arrays.stream(values);
    }
  1. 传入一个引用类型数组
Stream<String> stream4 = Stream.of(new String[]{"aaa", "bbb"});
Stream<Integer> integerStream2 = Stream.of(new Integer[]{1, 2, 3});

查看.class文件:

Stream<String> stream4 = Stream.of("aaa", "bbb");
Stream<Integer> integerStream2 = Stream.of(1, 2, 3);

得出结论:当传入一个引用类型数组,编译器会自动将该数组编译成一个一个元素传入到of方法中。

  1. 传入一个基本数据类型数组

注意这里的类型是引用类型T,假如传入一个基本数据类型的数组会发生什么?

Stream<int[]> stream3 = Stream.of(new int[]{1, 2, 3});

可以看到如果传入的是一个基本数据类型数组,则会将该数组当成传入的一个T值,实际上调用的是:

		public static<T> Stream<T> of(T t) {
        return StreamSupport.stream(new Streams.StreamBuilderImpl<>(t), false);
    }

结论:当给Stream.of方法传入一个基本数据类型数组时,会调用Stream.of(T t)方法,将整个数组对象当成一个T传入,生成一个数组类型的流。

如何将Stream<int[]>流转换成一个正常的IntStream?

// 第一个map将 int[]流 转换成 IntStream,得到Stream<IntStream>,flatMapToInt将IntStream原地转换然后展开成一个IntStream
IntStream intStream = stream3.map(a->Arrays.stream(a)).flatMapToInt(a->a);
// 后面就可以正常使用这个流

1.2 从集合创建流

java1.8后在Collection接口中添加了两个default方法:

    // 意味着任何实现了Collection的子类都可以通过这两个方法生成一个流
		default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }
    default Stream<E> parallelStream() {
        return StreamSupport.stream(spliterator(), true);
    }
    default Spliterator<E> spliterator() {
        return Spliterators.spliterator(this, 0);
    }

使用例子:

// 利用Arrays的asList方法生成一个集合对象
List<String> list = Arrays.asList(new String[]{"aaa","bbb","ccc","ddd"});
Stream<String> stream1 = list.stream(); 
Stream<String> stream2 = list.parallelStream(); // 并行流

1.3 其他情况

1.3.1 空流

    public static<T> Stream<T> empty() {
        return StreamSupport.stream(Spliterators.<T>emptySpliterator(), false);
    }

使用例子:

Stream<String> empty = Stream.empty();
Stream<Object> empty1 = Stream.<Object>empty();

不清楚空流能有啥作用。。。(下面是chatgpt生成)

  1. 提供默认值:当我们需要返回一个Stream类型的结果时,如果某些条件不满足需要返回一个空的Stream,这时就可以使用Stream.empty()返回一个空流作为默认值。
  2. 消除空指针异常:在使用Stream进行操作时,如果我们需要在某个条件下过滤掉所有元素,那么可以使用filter()方法和Stream.empty()方法结合使用,这样可以消除空指针异常。

例如,下面的代码将会在a为null时返回一个空流:chatgpt生成,好像有错误

List<String> list = Optional.ofNullable(a)
                             .map(Arrays::stream)
                             .orElseGet(Stream::empty)
                             .collect(Collectors.toList());
  1. 在测试中使用:在编写测试用例时,我们需要模拟各种场景,包括空流的场景。Stream.empty()可以用来模拟空流的情况。

1.3.2 无限流

  1. iterator方法

    方法签名:static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)

    重载:public static<T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next)

    用途:可以根据一个seed生成无限流;重载版本的hasNext表示生成的元素需要满足该断言才能加入到流中。

    例子:

    // 生成一个包含a-z字符的流,seed='a',表示从'a'开始生成,同时要满足元素e<='z'
    // 注意:是首先判断元素满足hasNext,然后再加入到流,假如seed就不满足hasNext,则该流中不包含任何元素
    Stream<Character> iterate = Stream.iterate('a', a->a.compareTo('z') <= 0, a->(char)(a+1));
    
  2. generate

    方法签名:public static<T> Stream<T> generate(Supplier<? extends T> s)

    只需要提供一个返回元素值的lambda表达式即可

    Stream.generate(()->Math.random());
    

2. intermediate operation

2.1 filter、map、flatMap

2.1.1 filter

Stream<T> filter(Predicate<? super T> predicate);

返回一个满足predicate的新流

例子:

Stream<Student> stream = getData().stream(); // 生成一个Student类型的流
// 返回成绩大于等于60的所有学生
stream.filter(s->s.getScore() >= 60).forEach(s->System.out.println(s));

2.1.2 map

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

应用一个mapper到原流上,得到一个新流,注意新流的类型可能会发生变化

Stream<Integer> integerStream = Stream.of(60, 59, 89, 78, 99, 100, 87, 43);
Stream<String> stringStream = integerStream.map(score -> {
    if (score < 60) return "不及格";
    else if (score < 70) return "合格";
    else if (score < 80) return "良好";
    else return "优秀";
});
stringStream.forEach(des-> System.out.print(des+" "));
//output:合格 不及格 优秀 良好 优秀 优秀 优秀 不及格 

可以看到,通过使用map方法,对原流中的每个元素使用一个mapper进行转换,可以得到一个新流。


此外,map还有三个衍生方法,用于生成IntStream、LongStream、DoubleStream

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

    查看ToIntFunction函数式接口:

    @FunctionalInterface
    public interface ToIntFunction<T> {
    
        /**
         * Applies this function to the given argument.
         *
         * @param value the function argument
         * @return the function result
         */
        int applyAsInt(T value);
    }
    

    因此mapToInt方法传入的lambda表达式必须返回一个int类型的值。

    比如上面的案例修改一下:

    Stream<Integer> integerStream1 = Stream.of(60, 59, 89, 78, 99, 100, 87, 43);
    IntStream intStream = integerStream1.mapToInt(a -> a); // 值类型本来是Integer,由于返回类型必须是int,自动拆箱返回一个int类型
    
  2. LongStream mapToLong(ToLongFunction<? super T> mapper); 类似mapToInt

  3. DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper); 类似mapToInt

2.1.3 flatMap

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

查看Function函数式接口:

@FunctionalInterface
public interface Function<T, R> {

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);
}

传入一个T类型的元素,返回一个R类型的元素。在flatMap中就是传入一个T类型元素,返回一个Stream<R>

例子:

Student s1 = new Student("张三", '男', 26, 99.0);
Student s2 = new Student("李四", '男', 25, 98.0);
Student s3 = null;
List<Student> list = new ArrayList<>();
list.add(s1);
list.add(s2);
list.add(s3);
list.stream().flatMap(s->Stream.ofNullable(s)).forEach(s->{
    System.out.println(s);
});

结合上面的flatMap方法签名和该例子解释一下参数的意思:首先必须传入一个类型T的参数(这里是Student类型),然后返回类型R,这里类型R就是? extends Stream<? extends R>,因此返回类型必须是一个Stream<R>,如果flatMap函数的参数的lambda函数返回的不是一个Stream类型的值,则会报错。

flatMap中的flat体现在:在得到了一个包含流元素的流之后(Stream<Stream<Student>>),会将其流元素展开,变成Stream<Student>

上面例子中的Stream.ofNullable(T t)方法,当t不为null时,生成一个包含t的流,否则生成一个空流。


和map一样,flatMap也有三个衍生方法:

  1. flatMapToInt

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

    传入的lambda表达式必须有一个IntStream的返回值

  2. flatMapToLong

    LongStream flatMapToLong(Function<? super T, ? extends LongStream> mapper);
    
  3. flatMapToDouble

    DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream> mapper);
    

2.2 子流、组合流

假如我们只需要流中前n项,或者由于流的前k项是无用的流数据而需要跳过咋办?

假如已经存在一个有序流,需要将满足条件的流数据保留,而不满足的流数据则直接丢弃怎么办?

假如已经存在一个有序流,需要将满足条件的流数据删除,而不满足条件的流数据保留形成一个新流怎么办?

如果需要将两个流组合成一个流怎么办?

2.2.1 子流

  • 取出流的前n项

    Stream类中提供了limit(long maxSize)方法用来获取前maxSize元素

    String[] strs = {"","Hello", "World", "How", "are", "you", "I", "am", "fine", "thank", "you"};
    // 1、limit,取前n个元素
    System.out.println("---------limit---------");
    Arrays.stream(strs).limit(2).forEach(s-> System.out.println(s));
    // output:
    // Hello
    

    关于limit需要注意的点:

    虽然limit在顺序流管道上是一个代价很小的操作,但在有序的并行流管道上可能是代价比较大的操作,特别是maxSize很大时,因为limit(n)不仅仅是返回任意n个元素,而是要返回前n个元素。

    如果实际情况允许的话,使用无序流源(generator(Supplier))或者用unordered()移除排序约束,可能会使得limit()在并行管道中的速度大幅提升。

    否则最好切换到顺序流管道上( sequential())执行该语句。

    *上面的unordered()sequential()方法都是BaseStream中的,都是在不改变原流的情况下返回一个无序流和顺序流管道下的流。

  • 跳过流的前k项

    Stream<T> skip(long n);
    

    如果n小于流中元素个数,返回一个包含之后元素的新流,否则返回一个空流

    String[] strs = {"","Hello", "World", "How", "are", "you", "I", "am", "fine", "thank", "you"};
    Arrays.stream(strs).skip(1).limit(2).forEach(s-> System.out.println(s));
    

    注意点和limit相同

  • 有序流,取出满足条件的流

    default Stream<T> takeWhile(Predicate<? super T> predicate)
    

    java9加的默认方法

    满足predicate的最长前缀

    如果流无序,则该方法没啥用,因为可能不会获取到所有满足条件的元素

    Stream<Integer> integerStream1 = Stream.of(99,89,88,78,73,69,65,60,59,51,40);
    integerStream1.takeWhile(so->so>=60).forEach(so-> System.out.println(so));
    // 99,89,88,78,73,69,65,60
    
  • 有序流,删除满足条件的流

    default Stream<T> dropWhile(Predicate<? super T> predicate)
    

    java9加的默认方法

    去掉满足predicate的最长前缀之后剩下的元素

    Stream<Integer> integerStream1 = Stream.of(99,89,88,78,73,69,65,60,59,51,40);
    integerStream1.dropWhile(so->so>=60).forEach(so-> System.out.println(so));
    // 59,51,40
    

2.2.2 组合流

public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)

对两个流进行组合

如果a和b都是有序流,则新流也是一个有序流;

如果a或b是并行流,则新流也是一个并行流

例子:

Stream.concat(Stream.of("a","b"), Stream.of("c", "d")).forEach(s -> System.out.println(s));
// a b c d

2.3 distinct、sorted、peek

2.3.1 对元素去重

Stream<T> distinct();

怎么判断的两个元素相等:`Object.equals(Object)

2.3.2 排序

Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);

如果不传入一个comparator,则类型T必须是Comparable的,否则会抛出java.lang.ClassCastException

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
}

2.3.3 peek

Stream<T> peek(Consumer<? super T> action);

该方法返回一个和原流一样的流,但是可以对元素执行一个action操作,且该操作不会影响元素。

该方法也是一个惰性操作,可以用于调试,查看每一步流的结果是否符合预期。

@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);
}

查看了一下Consumer这个函数式接口,主要有一个返回类型为void的accept方法,所以不会对原流产生影响。

例子:

Stream<String> stream = Stream.of("sqw", "lw", "sqw", "lw", "hkk", "zjy");
stream.distinct().peek(System.out::println).sorted((a,b)->b.compareTo(a)).forEach(System.out::println);
// sqw
// lw
// hkk
// zjy
// 上面是去重的结果,并使用peek打印了这一步的输出
// zjy
// sqw
// lw
// hkk
// 上面是倒序排序的结果

3. terminal Operation

3.1 简单reduce

什么是reduce约简:终结操作,在流上执行指定的操作得到一个我们想要的值,简单地来说就是化简,把众多元素变成一个元素

一些简单的约简:

  • count

    计算流中元素数量

  • max和min

    流中最大值和最小值

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

    需要传入一个比较器对象

  • findFirst

    找到第一个匹配要求的元素

    Optional<T> findFirst();
    
  • findAny

    找到任意一个匹配的元素,一般用于并行流

    Optional<T> findAny();
    
  • anyMatch

    如果有一个匹配就返回true,否则false

    boolean anyMatch(Predicate<? super T> predicate);
    
  • allMatch

    如果全部匹配返回true,否则返回false

    boolean allMatch(Predicate<? super T> predicate);
    
  • noneMatch

    如果全部不匹配返回true,否则返回false

    boolean noneMatch(Predicate<? super T> predicate);
    

前四个方法返回一个类型为Optional的值,其中要么包装了答案,要么没有任何值。

例子:


import java.util.*;
import java.util.stream.Stream;

/**
 * 约简操作
 */
public class ReduceTest {
    public static void main(String[] args) {
        System.out.println("----------max-----------");
        testMax();
        System.out.println("----------findFirst-----------");
        testFindFirst();
        System.out.println("----------findAny-----------");
        testFindAny();
        System.out.println("----------anyMatch-----------");
        testAnyMatch();
        System.out.println("----------allMatch-----------");
        testAllMatch();
        System.out.println("----------noneMatch-----------");
        testNoneMatch();
    }

    /**
     * max约简操作,根据比较器的比较规则,取出最大的元素,返回的Optional类型
     */
    public static void testMax() {
        Stream<String> dataForMax = getDataForMax();

        Optional<String> max = dataForMax.max(String::compareToIgnoreCase);
        System.out.println(max.get());
    }

    /**
     * findFirst:返回 非空集合中的第一个元素
     */
    public static void testFindFirst() {
        Optional<String> first = getDataForMax().findFirst();
        System.out.println(first.get());
    }

    /**
     * findAny:如果任意一个元素都可以,就可以使用这个,并行流可以看出效果
     */
    public static void testFindAny() {
        Optional<String> any = getDataForFindAny().findAny();
        System.out.println(any.get());
    }

    /**
     * anyMatch方法:只要有一个匹配,就返回true
     */
    public static void testAnyMatch() {
        boolean sq = getDataForMax().anyMatch(s -> s.startsWith("sq"));
        if (sq) {
            System.out.println("存在以sq开头的元素");
        }
        else
            System.out.println("不存在以sq开头的元素");
    }

    /**
     * allMatch:如果全部满足该断言,则返回true
     */
    public static void testAllMatch() {
        boolean b = getDataForMax().allMatch(s -> s.length() > 0);
        if (b) {
            System.out.println("所有字符串长度都大于0");
        }else {
            System.out.println("存在长度为0的字符串");
        }
    }

    /**
     * noneMatch:任何元素都不满足断言时,返回true
     */
    public static void testNoneMatch() {
        boolean b = getDataForMax().noneMatch(s -> s.length() == 0);
        if (b) {
            System.out.println("所有字符串长度都不为0");
        }else {
            System.out.println("存在长度为0的字符串");
        }
    }
    public static Stream<String> getDataForMax() {
        Stream<String> stream = Stream.of("gdd", "edsd", "sdaf", "wdf", "hello", "zip");
        return stream;
    }
    public static Stream<String> getDataForFindAny() {

        String[] strs = {"gdd", "edsd", "sdaf", "wdf", "hello", "zip"};
        List<String> list = new ArrayList<>();
        for (String str : strs) {
            list.add(str);
        }
        Stream<String> stream = list.parallelStream();
        return stream;
    }
}

3.2 Optional类型

Optional中要么包装了结果,要么没有包装任何对象,具体实现如下:

public final class Optional<T> {
    /**
     * Common instance for {@code empty()}.
     */
    private static final Optional<?> EMPTY = new Optional<>();

    /**
     * If non-null, the value; if null, indicates no value is present
     */
    private final T value;

    /**
     * Constructs an empty instance.
     *
     * @implNote Generally only one empty instance, {@link Optional#EMPTY},
     * should exist per VM.
     */
    private Optional() {
        this.value = null;
    }

    /**
     * Returns an empty {@code Optional} instance.  No value is present for this
     * {@code Optional}.
     *
     * @apiNote
     * Though it may be tempting to do so, avoid testing if an object is empty
     * by comparing with {@code ==} against instances returned by
     * {@code Optional.empty()}.  There is no guarantee that it is a singleton.
     * Instead, use {@link #isPresent()}.
     *
     * @param <T> The type of the non-existent value
     * @return an empty {@code Optional}
     */
    public static<T> Optional<T> empty() {
        @SuppressWarnings("unchecked")
        Optional<T> t = (Optional<T>) EMPTY;
        return t;
    }

    /**
     * Constructs an instance with the described value.
     *
     * @param value the non-{@code null} value to describe
     * @throws NullPointerException if value is {@code null}
     */
    private Optional(T value) {
        this.value = Objects.requireNonNull(value);
    }
  
		public static <T> Optional<T> of(T value) {
        return new Optional<>(value);
    }

    /**
     * Returns an {@code Optional} describing the given value, if
     * non-{@code null}, otherwise returns an empty {@code Optional}.
     *
     * @param value the possibly-{@code null} value to describe
     * @param <T> the type of the value
     * @return an {@code Optional} with a present value if the specified value
     *         is non-{@code null}, otherwise an empty {@code Optional}
     */
    public static <T> Optional<T> ofNullable(T value) {
        return value == null ? empty() : of(value);
    }
    ....
}

几个注意点:

  • 构造器私有化,包括两个构造器:第一个构造器是无参构造器,创建一个空的Optional对象;第二个构造器是带参构造器,会将值包装进value中。
  • 在类加载时就创建了一个EMPTY的对象,该对象内没有包装任何值。
  • 提供了ofofNullable两个静态方法供外部调用,区别就是of方法的参数为null时抛出NullPointerException ,而ofNullable会返回一个EMPTY

为何需要Optional来对结果包装??

假如不使用Optional包装,则当结果为空时,会返回null,可能会因此产生NullPointerException,因此Optional是一种表示缺少返回值的更好的方式

3.2.1 获取Optional值

Optional提供多种获取值的方式:

  1. public T get()

    public T get() {
        if (value == null) {
            throw new NoSuchElementException("No value present");
        }
        return value;
    }
    

    这种方式在值为空时抛出 NoSuchElementException,否则返回值

  2. public T orElse(T other)

    public T orElse(T other) {
        return value != null ? value : other;
    }
    

    这种方式需要提供一个参数作为value为空的替代值。

  3. public T orElseGet(Supplier<? extends T> supplier)

    public T orElseGet(Supplier<? extends T> supplier) {
        return value != null ? value : supplier.get();
    }
    

    相比于orElse方法直接提供一个替代值,该方法提供了一个supplier参数,可以返回更加定制化的替代结果

  4. public T orElseThrow()

    public T orElseThrow() {
        if (value == null) {
            throw new NoSuchElementException("No value present");
        }
        return value;
    }
    

    get方法一样

  5. public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X

    public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
        if (value != null) {
            return value;
        } else {
            throw exceptionSupplier.get();
        }
    }
    

    提供了一个supplier,返回值必须是Throwable及其子类,用于定制化异常。

例子:


import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Optional;

public class OptionalTest {
    public static void main(String[] args) {
        testGetValue();
    }

    /**
     * Optional对象中获取值
     */
    public static void testGetValue() {
        // 创建一个Optional对象
        Optional<String> s = Optional.of("Hello,Optional");
        // 1、直接get
        System.out.println("the example of get:");
        String s1 = s.get();
        System.out.println("\t" + s1);
        // null情况
        // Optional.empty().get();

        // 2、orElse
        System.out.println("the example of orElse:");
        String other = s.orElse("other");
        String other1 = Optional.<String>empty().orElse("other");
        System.out.println("\tOptional有值的情况:" + other);
        System.out.println("\tOptional没有值的情况:" + other1);

        // 3、orElseGet
        System.out.println("the example of orElseGet:");
        Optional<String> s2 = Optional.of("当前时间:2022-9-11 00:00:00");
        System.out.println("\tOptional有值:");
        System.out.println("\t" + s2.orElseGet(()->
                new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())));
        System.out.println("\tOptional无值:");
        System.out.println("\t" + Optional.<String>empty().orElseGet(()->
                new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())));

        // 4、orElseThrow()
        // 由于orElseThrow不带参数和get效果一样,就不举例子了
        Optional.<String>empty().orElseThrow(()->new UnsupportedOperationException("测试"));
    }
}

3.2.2 消费Optional值

首先看一下Consumer<T>这个函数式接口

@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);

    ...
}

该接口提供的函数功能是:通过accept方法接收一个元素,然后执行函数体内的代码将该元素消费掉

Optional<T>中提供了两个方法来消费里面的值:

  1. public void ifPresent(Consumer<? super T> action)

    public void ifPresent(Consumer<? super T> action) {
        if (value != null) {
            action.accept(value);
        }
    }
    

    如果value为空,啥也不做;否则执行action中的accept方法

  2. public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)

    public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) {
        if (value != null) {
            action.accept(value);
        } else {
            emptyAction.run();
        }
    }
    

    如果value为空,则执行emptyAction的run方法;否则执行action的accept方法

例子:

Optional<String> opt = Optional.of("Hello Optional");
opt.ifPresent(s->{
    System.out.println(s+"中包含单词数量:" + s.split(" ").length);
});
Optional.<String>empty().ifPresentOrElse(s->{
    System.out.println(s+"中包含单词数量:" + s.split(" ").length);
},()-> System.out.println("not match"));

3.2.3 管道化Optional值

类似Stream的管道化操作,Optional也提供一系列的API用于管道化操作Optional。

  1. public <U> Optional<U> map(Function<? super T, ? extends U> mapper)

    如果value存在,则将mapper作用于value上得到一个结果,将结果包装成Optional返回;如果value不存在,则直接返回EMPTY

    源码:

    public <U> Optional<U> map(Function<? super T, ? extends U> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent()) {
            return empty();
        } else {
            return Optional.ofNullable(mapper.apply(value));
        }
    }
    
  2. public Optional<T> filter(Predicate<? super T> predicate)

    如果value存在,则判断value是否能使predicate返回true,如果可以,则返回this,否则返回EMPTY;如果value不存在,直接返回this

    源码:

    public Optional<T> filter(Predicate<? super T> predicate) {
        Objects.requireNonNull(predicate);
        if (!isPresent()) {
            return this;
        } else {
            return predicate.test(value) ? this : empty();
        }
    }
    
  3. public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier)

    提供了一个supplier,当value存在时,返回this;否则返回supplier的返回值,该返回值必须是Optional<? extends T>类型的。

    相当于给该Optional对象设置了一个value为空时的返回值。

    public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier) {
        Objects.requireNonNull(supplier);
        if (isPresent()) {	
            return this;
        } else {
            @SuppressWarnings("unchecked")
            Optional<T> r = (Optional<T>) supplier.get();
            return Objects.requireNonNull(r);
        }
    }
    

例子:

Optional<String> opt = Optional.of("Hell Optional");
Optional<String> hello = opt.map(s -> s.toLowerCase()).filter(s -> s.startsWith("hello")).
        or(() -> Optional.of("Hello, initialize"));
System.out.println(hello.get());
System.out.println(Optional.of("Hello Optional").map(s -> s.toLowerCase()).
        filter(s -> s.startsWith("hello")).or(() -> Optional.of("Hello, initialize")).get());
// Hello, initialize
// hello optional

3.2.4 创建Optional值

主要包含三个方法:

  1. public static <T> Optional<T> of(T value)

    public static <T> Optional<T> of(T value) {
        return new Optional<>(value);
    }
    
  2. public static <T> Optional<T> ofNullable(T value)

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

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

第1个方法在value为空时,抛出空指针异常;第二个方法在value为空时返回empty();第三个方法返回一个EMPTY对象

3.2.5 用flatMap构建Optional值的函数

该方法和Stream中的flatMap类似,主要是为了转换类型。

假如s存在一个方法f可以产生Optional<T>,而T又具有一个方法g可以产生Optional<U>,如何将其组合起来最终产生Optional<U>??

此时就可以使用flatMaps.f().flatMap(T::g)

源码:

public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent()) {
        return empty();
    } else {
        @SuppressWarnings("unchecked")
        Optional<U> r = (Optional<U>) mapper.apply(value);
        return Objects.requireNonNull(r);
    }
}

例子:

public static void testFlatMap() {
    // 计算倒数的平方根
    Optional<Double> aDouble = Optional.of(1.0).flatMap(OptionalTest::inverse).
            flatMap(OptionalTest::squareRoot);
    Double val = aDouble.orElseThrow(() -> new NoSuchElementException("该元素不存在倒数的平方根"));
    System.out.println("\t结果=" + val);
  	// 也可以使用map代替,map写法
    Optional<Double> aDouble1 = Optional.of(0.0).map(v -> {
        return v == 0 ? null : 1 / v;
    });
    System.out.println(aDouble1.isPresent());
    Optional<Double> aDouble2 = aDouble1.map(v -> {
        return v < 0 ? null : Math.sqrt(v);
    });
    System.out.println(aDouble2.isPresent());
}
// 倒数
private static Optional<Double> inverse(Double x) {
    return x==0 ? Optional.empty() : Optional.of(1/x);
}
// 平方根
private static Optional<Double> squareRoot(Double x) {
    return x < 0 ? Optional.empty() : Optional.of(Math.sqrt(x));
}
// 	结果=1.0
// false
// false

3.2.6 将Optional转换成流

stream方法的源码:

public Stream<T> stream() {
    if (!isPresent()) {
        return Stream.empty();
    } else {
        return Stream.of(value);
    }
}

Optional中的value为空时,返回一个空流;否则返回一个只有value 的流

例子:

    ...
		public static void testStream() {
        ClassRoom classRoom = new ClassRoom();
        Stream<String> ids = classRoom.getIds().stream();
      	// 通过Optional::stream将空的Optional给过滤掉
        Stream<Student> studentStream = ids.map(classRoom::lookup).flatMap(Optional::stream);
        studentStream.forEach(s-> System.out.println(s.getName()));
    }
		...
class ClassRoom {
    private List<String> ids;
    private List<Student> studentList;
    public ClassRoom() {
        ids = new ArrayList<>();
        studentList = new ArrayList<>();
        String[] names = {"aaa","bbb","ccc","ddd","eee","fff","ggg","hhh","iii","jjj"};
        for (int i = 0; i < 10; i++) {
            ids.add(Integer.toString(i));
            studentList.add(new Student(Integer.toString(i), names[i]));
        }
        ids.add("10");
    }

    ...getter,setter

    public Optional<Student> lookup(String id) {
        Student res = null;
        for (Student s : studentList) {
            if (s.getId().equals(id)) {
                res = s;
                break;
            }
        }
        return Optional.ofNullable(res);
    }

}
class Student {
    private String id;
    private String name;

    public Student(String id, String name) {
        this.id = id;
        this.name = name;
    }

    ...getter,setter
}

3.3 收集结果

3.3.1 查看结果

  1. iterator

    Stream<String> stream = Stream.of("aaa", "bbb", "ccc");
    Iterator<String> iterator = stream.iterator();
    while (iterator.hasNext()) {
        String next = iterator.next();
        System.out.println(next);
    }
    
  2. forEach、forEachOrdered

    Stream<String> stream = Stream.of("aaa", "bbb", "ccc");
    stream.forEach(s-> System.out.println(s));
    

    注意forEach在并行流管道中不能保证顺序,而forEachOrdered可以

3.3.2 收集到数组

Object[] toArray(); // 直接转换成Object类型的数组
 <A> A[] toArray(IntFunction<A[]> generator); // 可以创建需要类型的数组
Stream<String> stream = Stream.of("aaa", "bbb", "ccc");
String[] strings = stream.toArray(String[]::new);
for (int i = 0; i < strings.length; i++) {
    System.out.print(strings[i]+" ");
}

3.3.3 收集器

Stream中还提供了便捷的collect方法,用于将流中的元素收集到另一个目标中,这个方法会接受一个Collector接口的实例

<R, A> R collect(Collector<? super T, A, R> 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_FINISH} 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();
  	
  	...
}

可以看到Collector中至少需要实现四个方法:

  1. Supplier<A> supplier();

    该方法会返回一个新的可变结果的容器(也就是类似ArrayList、Set这些容器)

  2. BiConsumer<A, T> accumulator();

    用来将流中每个元素添加进supplier提供的新容器中

  3. BinaryOperator<A> combiner();

    接收两部分结果然后合并

  4. Function<A, R> finisher();

    用于将最后的转换:从A类型转换为R类型。举个例子,A为ArrayList类型,最终需要一个不可变的List,则需要使用finisher完成A到不可变List的转换。

Collectors类提供了大量用于生成常见收集器的工厂方法。

这里列出几个常见的:

  • 收集到集合中
  1. 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的容器;accumulator使用List中的add方法,用于将流中元素添加到容器中;combiner使用lambda函数,将结果合并返回。

  1. toCollection(Supplier<C> collectionFactory)

    public static <T, C extends Collection<T>>
    Collector<T, ?, C> toCollection(Supplier<C> collectionFactory) {
        return new CollectorImpl<>(collectionFactory, Collection<T>::add,
                                   (r1, r2) -> { r1.addAll(r2); return r1; },
                                   CH_ID);
    }
    

    toList()返回的收集器中的supplier返回的是固定的ArrayList类型,当我们想获得其他类型的collection对象时,就可以使用该方法。

    也就是,该方法允许我们定制化实现了Collection接口的任意类型。

  2. toSet()

    public static <T>
    Collector<T, ?, Set<T>> toSet() {
        return new CollectorImpl<>((Supplier<Set<T>>) HashSet::new, Set::add,
                                   (left, right) -> {
                                       if (left.size() < right.size()) {
                                           right.addAll(left); return right;
                                       } else {
                                           left.addAll(right); return left;
                                       }
                                   },
                                   CH_UNORDERED_ID);
    }
    

    supplier返回的是一个HashSet对象。

  • 收集到字符串中

    1. joining()

      public static Collector<CharSequence, ?, String> joining() {
          return new CollectorImpl<CharSequence, StringBuilder, String>(
                  StringBuilder::new, StringBuilder::append,
                  (r1, r2) -> { r1.append(r2); return r1; },
                  StringBuilder::toString, CH_NOID);
      }
      

      收集到字符串中。

    2. joining(CharSequence delimiter)

      public static Collector<CharSequence, ?, String> joining(CharSequence delimiter) {
          return joining(delimiter, "", "");
      }
      

      在每个元素之间插入一个delimiter,最终返回字符串

    3. joining(CharSequence delimiter,CharSequence prefix,CharSequence suffix)

      public static Collector<CharSequence, ?, String> joining(CharSequence delimiter,
                                                               CharSequence prefix,
                                                               CharSequence suffix) {
          return new CollectorImpl<>(
                  () -> new StringJoiner(delimiter, prefix, suffix),
                  StringJoiner::add, StringJoiner::merge,
                  StringJoiner::toString, CH_NOID);
      }
      

      不仅在元素之间插入delimiter,还插入前缀prefix,以及后缀suffix,然后返回字符串。

      注意使用这几个收集器时,要求流的类型是CharSequence,比较典型的是String,因为StringCharSequence的实现类

  • 约减成数值的收集器

    1. summarizingInt

      public static <T>
      Collector<T, ?, IntSummaryStatistics> summarizingInt(ToIntFunction<? super T> mapper) {
          return new CollectorImpl<T, IntSummaryStatistics, IntSummaryStatistics>(
                  IntSummaryStatistics::new,
                  (r, t) -> r.accept(mapper.applyAsInt(t)),
                  (l, r) -> { l.combine(r); return l; }, CH_ID);
      }
      

      提供一个将流中元素映射成int类型数据的mapper,最终收集成一个IntSummaryStatistics

      IntSummaryStatistics中比较典型的方法:

      public final long getCount(); // 返回记录value的个数
      public final long getSum(); // 返回所有value的和
      public final int getMin(); // 返回value中最小值
      public final int getMax(); // 返回value中最大值
      public final double getAverage(); // 返回平均值
      
    2. summarizingLongsummarizingInt类似

    3. summarizingDoublesummarizingInt类似

例子:

Stream<String> stringStream = Stream.of("aaa", "bbb", "ccc","ccc");
Stream<Character> charStream = Stream.of('a', 'b', 'c', 'd');
// 1、收集到ArrayList中
List<String> arrayListRes = Stream.of("aaa", "bbb", "ccc", "ccc").collect(Collectors.toList());
// 2、收集到LinkedList中
LinkedList<String> linkedListRes = Stream.of("aaa", "bbb", "ccc", "ccc").
        collect(Collectors.toCollection(LinkedList::new));
// 3、收集到HashSet中
Set<String> hashSetRes = Stream.of("aaa", "bbb", "ccc", "ccc").collect(Collectors.toSet());
// 查看结果
System.out.println("ArrayList结果:");
arrayListRes.forEach(e-> System.out.print(e+" "));
System.out.println();
System.out.println("LinkedList结果:");
linkedListRes.forEach(e-> System.out.print(e+" "));
System.out.println();
System.out.println("HashSet结果:");
hashSetRes.forEach(e-> System.out.print(e+" "));
System.out.println();

// 收集到字符串中
String stringRes = Stream.of("aaa", "bbb", "ccc", "ccc").collect(Collectors.joining());
String stringWithDelimiter = Stream.of("aaa", "bbb", "ccc", "ccc").collect(Collectors.joining(","));
String stringWithAll = Stream.of("aaa", "bbb", "ccc", "ccc").collect(Collectors.
        joining(",", "prefix:", ":suffix"));
System.out.println("收集到字符串中:");
System.out.println(stringRes);
System.out.println("收集到带delimiter的字符串中:");
System.out.println(stringWithDelimiter);
System.out.println("收集到带delimiter、prefix、suffix的字符串中");
System.out.println(stringWithAll);

String collect = Stream.of('a', 'b', 'c').map(s -> Character.toString(s)).collect(Collectors.joining());
System.out.println("将字符变成字符串然后收集:\n" + collect);

// 约简成数值
Stream<Double> doubleStream = Stream.of(98.0, 99.9, 100.0, 89.8, 69.8, 55.6, 60.3, 65.4);
DoubleSummaryStatistics collect1 = doubleStream.collect(Collectors.summarizingDouble(a -> a));
System.out.println("元素个数:" + collect1.getCount());
System.out.println("分数和:" + collect1.getSum());
System.out.println("最高分:" + collect1.getMax());
System.out.println("最低分:" + collect1.getMin());
System.out.println("平均分:" + collect1.getAverage());
// ArrayList结果:
// aaa bbb ccc ccc 
// LinkedList结果:
// aaa bbb ccc ccc 
// HashSet结果:
// aaa ccc bbb 
// 收集到字符串中:
// aaabbbcccccc
// 收集到带delimiter的字符串中:
// aaa,bbb,ccc,ccc
// 收集到带delimiter、prefix、suffix的字符串中
// prefix:aaa,bbb,ccc,ccc:suffix
// 将字符变成字符串然后收集:
// abc
// 元素个数:8
// 分数和:638.8
// 最高分:100.0
// 最低分:55.6
// 平均分:79.85

3.3.4 收集到映射表中

同样需要使用Collectors提供的接口

主要包括三个收集器:

public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                Function<? super T, ? extends U> valueMapper) {
    return new CollectorImpl<>(HashMap::new,
                               uniqKeysMapAccumulator(keyMapper, valueMapper),
                               uniqKeysMapMerger(),
                               CH_ID);
}

该方法提供keyMappervalueMapper,将其返回值分别作为HashMapkeyvalue如果碰到多个value和一个key配对,则会报错IllegalStateException

public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                Function<? super T, ? extends U> valueMapper,
                                BinaryOperator<U> mergeFunction) {
    return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
}

该方法相比于上面的方法提供了一个mergeFunction来告诉多个value对应一个key的情况下,如何处理该key对应的value

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> mapFactory) {
    BiConsumer<M, T> accumulator
            = (map, element) -> map.merge(keyMapper.apply(element),
                                          valueMapper.apply(element), mergeFunction);
    return new CollectorImpl<>(mapFactory, accumulator, mapMerger(mergeFunction), CH_ID);
}

前面两种方法得到的映射表是HashMap类型的,该方法则可以自定义映射表的实现类,比如想要得到TreeMap,则可以指定mapFactory

例子:

public static void testToMap() {
    Map<String, String> collect = getData().collect(Collectors.toMap(
            Student::getName,
            Student::getHobby,
            (oldValue, newValue)->oldValue+","+newValue));
    collect.forEach((k,v)->{
        System.out.println("姓名="+k+",爱好="+v);
    });
    getData().collect(Collectors.toMap(
            Student::getName,
            s->Set.of(s.getHobby()),
            (oldValue, newValue)->{
                HashSet<String> union = new HashSet<>(newValue);
                union.addAll(oldValue);
                return union;
            }
    )).forEach((k,v)->{
        System.out.print("姓名="+k+",爱好=");
        v.forEach(s-> System.out.print(s+" "));
        System.out.println();
    });

}
private static Stream<Student> getData() {
    return Stream.of(
            new Student("aaa","唱"),
            new Student("aaa", "跳"),
            new Student("aaa", "rap"),
            new Student("aaa", "篮球"),
            new Student("bbb", "羽毛球"),
            new Student("ccc", "乒乓球"),
            new Student("ccc", "羽毛球"));
}
static class Student {
    String name;
    String hobby;

    public Student(String name, String hobby) {
        this.name = name;
        this.hobby = hobby;
    }

    public String getName() {
        return name;
    }

    public String getHobby() {
        return hobby;
    }
}
//姓名=aaa,爱好=唱,跳,rap,篮球
//姓名=ccc,爱好=乒乓球,羽毛球
//姓名=bbb,爱好=羽毛球
//姓名=aaa,爱好=rap 唱 跳 篮球 
//姓名=ccc,爱好=羽毛球 乒乓球 
//姓名=bbb,爱好=羽毛球

3.3.5 群组和分区

  1. groupingBy

假如已经有了一个流Stream<T>,而我们需要根据流元素T中具有相同特性的值来对所有元素进行分组。

举个例子,假如学生有姓名和对应的爱好,为了把某一个姓名对应的所有爱好聚成一组,则可以使用groupingBy

源码:

// 只需提供一个classifier,classifier的输入是T,返回K,K作为分组的键
public static <T, K> Collector<T, ?, Map<K, List<T>>>
groupingBy(Function<? super T, ? extends K> classifier) {
    return groupingBy(classifier, toList());
}
// 多了一个downstream,用来指定收集器,及确定K对应的D的形式,叫做下游收集器
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);
}
// 多了一个mapFactory,指定Map的具体实现类
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);
    }
}
  1. partitionBy

    参数是一个断言,因此Mapkeytrue或者falsevalue为对应满足断言以及不满足断言的结果。

    // 提供一个断言,收集满足这个断言和不满足这个断言的结果
    public static <T>
    Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate) {
        return partitioningBy(predicate, toList());
    }
    // 指定下游收集器
    public static <T, D, A>
    Collector<T, ?, Map<Boolean, D>> partitioningBy(Predicate<? super T> predicate,
                                                    Collector<? super T, A, D> downstream) {
        BiConsumer<A, ? super T> downstreamAccumulator = downstream.accumulator();
        BiConsumer<Partition<A>, T> accumulator = (result, t) ->
                downstreamAccumulator.accept(predicate.test(t) ? result.forTrue : result.forFalse, t);
        BinaryOperator<A> op = downstream.combiner();
        BinaryOperator<Partition<A>> merger = (left, right) ->
                new Partition<>(op.apply(left.forTrue, right.forTrue),
                                op.apply(left.forFalse, right.forFalse));
        Supplier<Partition<A>> supplier = () ->
                new Partition<>(downstream.supplier().get(),
                                downstream.supplier().get());
        if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) {
            return new CollectorImpl<>(supplier, accumulator, merger, CH_ID);
        }
        else {
            Function<Partition<A>, Map<Boolean, D>> finisher = par ->
                    new Partition<>(downstream.finisher().apply(par.forTrue),
                                    downstream.finisher().apply(par.forFalse));
            return new CollectorImpl<>(supplier, accumulator, merger, finisher, CH_NOID);
        }
    }
    

    例子(数据参考上面代码):

Map<String, List<Student>> collect = getData().collect(Collectors.groupingBy(Student::getName));
System.out.print("aaa的爱好:");
collect.get("aaa").forEach(student -> System.out.print(student.getHobby()+" "));
System.out.println();
Map<Boolean, List<Student>> collect1 = getData().collect(Collectors.
        partitioningBy(student -> student.getHobby().equals("羽毛球")));
System.out.print("会羽毛球的学生:");
collect1.get(true).forEach(student -> System.out.print(student.getName()+" "));
//aaa的爱好:唱 跳 rap 篮球 
//会羽毛球的学生:bbb ccc 

3.3.6 下游收集器

对于groupingBypartitionBy收集器产生的映射表,其值都是一个列表,如果想处理这些列表,比如使用Set存储、求和、求最大/小值、求不同元素的个数、元素个数、转换成每个元素的长度进行存储等等这些,则需要指定下游收集器downstream

首先看看有哪些收集器:

// 指定该收集器为下游收集器,则会将列表中的元素存储到HashSet中
public static <T>
Collector<T, ?, Set<T>> toSet() {
    return new CollectorImpl<>((Supplier<Set<T>>) HashSet::new, Set::add,
                               (left, right) -> {
                                   if (left.size() < right.size()) {
                                       right.addAll(left); return right;
                                   } else {
                                       left.addAll(right); return left;
                                   }
                               },
                               CH_UNORDERED_ID);
}
// 指定该收集器为下游收集器,则会统计列表中元素个数
public static <T> Collector<T, ?, Long>
counting() {
    return summingLong(e -> 1L);
}
// 指定该收集器为下游收集器,需要提供一个比较器参数,返回一个Optional,包装最小值结果
public static <T> Collector<T, ?, Optional<T>>
minBy(Comparator<? super T> comparator) {
    return reducing(BinaryOperator.minBy(comparator));
}
// 指定该收集器为下游收集器,需要提供一个比较器参数,返回一个Optional,包装最大值结果
public static <T> Collector<T, ?, Optional<T>>
maxBy(Comparator<? super T> comparator) {
    return reducing(BinaryOperator.maxBy(comparator));
}
// 提供一个mapper,传入一个T,返回一个int类型值,直接返回所有int值的和;类似的还有summingLong、summingDouble
public static <T> Collector<T, ?, Integer>
summingInt(ToIntFunction<? super T> mapper) {
    return new CollectorImpl<>(
            () -> new int[1],
            (a, t) -> { a[0] += mapper.applyAsInt(t); },
            (a, b) -> { a[0] += b[0]; return a; },
            a -> a[0], CH_NOID);
}
// 指定一个downstream之后,再对收集的结果执行finisher
public static<T,A,R,RR> Collector<T,A,RR> collectingAndThen(Collector<T,A,R> downstream,
                                                            Function<R,RR> finisher) {
    Set<Collector.Characteristics> characteristics = downstream.characteristics();
    if (characteristics.contains(Collector.Characteristics.IDENTITY_FINISH)) {
        if (characteristics.size() == 1)
            characteristics = Collectors.CH_NOID;
        else {
            characteristics = EnumSet.copyOf(characteristics);
            characteristics.remove(Collector.Characteristics.IDENTITY_FINISH);
            characteristics = Collections.unmodifiableSet(characteristics);
        }
    }
    return new CollectorImpl<>(downstream.supplier(),
                               downstream.accumulator(),
                               downstream.combiner(),
                               downstream.finisher().andThen(finisher),
                               characteristics);
}
// 先将元素通过mapper转换,然后收集到downstream中
public static <T, U, A, R>
Collector<T, ?, R> mapping(Function<? super T, ? extends U> mapper,
                           Collector<? super U, A, R> downstream) {
    BiConsumer<A, ? super U> downstreamAccumulator = downstream.accumulator();
    return new CollectorImpl<>(downstream.supplier(),
                               (r, t) -> downstreamAccumulator.accept(r, mapper.apply(t)),
                               downstream.combiner(), downstream.finisher(),
                               downstream.characteristics());
}
// mapper会将T转换成Stream<U>,然后收集到downstream中时会将Stream<U>中的元素取出收集
public static <T, U, A, R>
Collector<T, ?, R> flatMapping(Function<? super T, ? extends Stream<? extends U>> mapper,
                               Collector<? super U, A, R> downstream) {
    BiConsumer<A, ? super U> downstreamAccumulator = downstream.accumulator();
    return new CollectorImpl<>(downstream.supplier(),
                        (r, t) -> {
                            try (Stream<? extends U> result = mapper.apply(t)) {
                                if (result != null)
                                    result.sequential().forEach(u -> downstreamAccumulator.accept(r, u));
                            }
                        },
                        downstream.combiner(), downstream.finisher(),
                        downstream.characteristics());
}
// 之前提到的summingInt/summingLong/summingDouble都可以作为下游收集器

// 只收集满足predicate的元素到downstrean中
Collector<T, ?, R> filtering(Predicate<? super T> predicate,
                             Collector<? super T, A, R> downstream) {
    BiConsumer<A, ? super T> downstreamAccumulator = downstream.accumulator();
    return new CollectorImpl<>(downstream.supplier(),
                               (r, t) -> {
                                   if (predicate.test(t)) {
                                       downstreamAccumulator.accept(r, t);
                                   }
                               },
                               downstream.combiner(), downstream.finisher(),
                               downstream.characteristics());
}

例子:

// 通过指定downstream为toSet(),将每个分组存放在Set中
Map<String, Set<Student>> collect = getData().collect(Collectors.groupingBy(Student::getName, Collectors.toSet()));
collect.forEach((k,v)->{
    System.out.println("姓名:" + k);
    System.out.print("爱好:");
    v.forEach(h-> System.out.print(h.getHobby()+" "));
    System.out.println();
});
// 姓名:aaa
// 爱好:跳 唱 rap 篮球 
// 姓名:ccc
// 爱好:乒乓球 羽毛球 
// 姓名:bbb
// 爱好:羽毛球 

// 通过指定downstream为counting(),将每个分组的元素数量存放在Long中
Map<String, Long> collect1 = getData().collect(Collectors.groupingBy(Student::getName, Collectors.counting()));
collect1.forEach((k,v)->{
    System.out.println("姓名:" + k);
    System.out.println("爱好数量:" + v);
});
// 姓名:aaa
// 爱好数量:4
// 姓名:ccc
// 爱好数量:2
// 姓名:bbb
// 爱好数量:1

// 通过mapping,将Student转换成对应的hobby,然后收集到Set中
Map<String, Set<String>> collect2 = getData().collect(Collectors.
        groupingBy(Student::getName,
                Collectors.mapping((s -> s.getHobby()), Collectors.toSet())));
collect2.forEach((k,v)->{
    System.out.println("姓名:" + k);
    System.out.print("爱好:");
    v.forEach(h-> System.out.print(h+" "));
    System.out.println();
});
// 姓名:aaa
// 爱好:rap 唱 跳 篮球 
// 姓名:ccc
// 爱好:羽毛球 乒乓球 
// 姓名:bbb
// 爱好:羽毛球 

// 结合了mapping和collectingAndThen,如果一个人爱好多于1个,则输出"爱好广泛",否则输出"爱好单一"
Map<String, String> collect3 = getData().collect(Collectors.groupingBy(
        Student::getName,
        Collectors.mapping(
                student -> student.getHobby(),
                Collectors.collectingAndThen(
                        Collectors.toSet(),
                        s -> {
                            return s.size() > 1 ? "爱好广泛" : "爱好单一";
                        }
                )
        )
));
collect3.forEach((k,v)->{
    System.out.println("姓名:" + k);
    System.out.println("爱好情况:" + v);
});
// 姓名:aaa
// 爱好情况:爱好广泛
// 姓名:ccc
// 爱好情况:爱好广泛
// 姓名:bbb
// 爱好情况:爱好单一

// 结合mapping和filtering,如果存在爱好rap,则将其过滤掉,也就是只返回rap以外的爱好
Map<String, Set<String>> collect4 = getData().collect(Collectors.groupingBy(
        Student::getName,
        Collectors.mapping(
                student -> student.getHobby(),
                Collectors.filtering(s -> !s.equals("rap"), Collectors.toSet())
        )
));
collect4.forEach((k,v)->{
    System.out.println("姓名:" + k);
    System.out.print("爱好:");
    v.forEach(h-> System.out.print(h+" "));
    System.out.println();
});
// 姓名:aaa
// 爱好:唱 跳 篮球 
// 姓名:ccc
// 爱好:羽毛球 乒乓球 
// 姓名:bbb
// 爱好:羽毛球 

3.3.7 约简操作

从流中计算某个值,具体的形式:从流中依次取出两个元素进行计算,最后返回一个结果

// 返回一个Optional类型,如果流为空,则Optional中的value为空
Optional<T> reduce(BinaryOperator<T> accumulator);
// 给定了一个初始值identity,直接返回结果,不需要Optional,因为一定会有结果identity
T reduce(T identity, BinaryOperator<T> accumulator);
// 流元素类型T,需要计算的类型U
<U> U reduce(U identity,
             BiFunction<U, ? super T, U> accumulator,
             BinaryOperator<U> combiner);

例子:

Stream<Character> stream = Stream.of('a', 'b', 'c', 'd');
StringBuilder sb = new StringBuilder();
Optional<String> collect = stream.map(character -> character.toString()).
        collect(Collectors.reducing(
        (a, b) -> a + b
));
System.out.println(collect.orElse("流为空"));
String collect1 = Stream.of('a', 'b', 'c', 'd').map(c -> c.toString()).collect(
        Collectors.reducing("", (a, b) -> a + b)
);
System.out.println(collect1);
Integer collect2 = Stream.of(1, 2, 3, 4, 5, 6).collect(Collectors.reducing(0, (total, val) -> {
    if (val % 2 == 0) return total + 1;
    else return total;
}));
System.out.println(collect2);
Integer reduce = Stream.of("Hello world", "How are you", "I am fine Thank you", "Where are you", "see you tomorrow", "good man")
        .reduce(0,
                (total, val) -> total + val.split(" ").length,
                (total1, total2) -> total1 + total2);
Optional<Integer> reduce1 = Stream.<Integer>empty().reduce((a, b) -> a + b);
System.out.println(reduce1.orElse(-1));
//abcd
//abcd
//3
//18
//-1