首页 > 编程语言 >Java的stream操作

Java的stream操作

时间:2023-05-01 22:44:20浏览次数:42  
标签:Java stream Stream System return 操作 Optional public out

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

标签:Java,stream,Stream,System,return,操作,Optional,public,out
From: https://www.cnblogs.com/sqwblog/p/17367133.html

相关文章

  • Java内置工具类
    Java内置工具类1.String类首先,String类的值不能被更改如果对String对象操作(增加长度等),会新开辟一块内存空间,再更改String的指向(如果有的话),而原来字符串不变(可能指向被更改或者不存在)。因此如果要大量更改String类型时不推荐用他,应该用StringBuffer或者StringBuliderStrin......
  • C++文件读写常用操作整理
    C++对于文件的操作需要包含<fstream>头文件文件类型分为两种:文本文件-文件以文件的ASCII码的形式存储在计算机中二进制文件-文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们操作文件的三大类:ofstream:写操作ifstream:读操作fstream:读写操作一、文......
  • 20 文件系统的格式化操作
    文件系统设备:使用4MB内存空间模拟真实的储存设备,rfsdevext_t结构表示,保存了内存空间的地址和大小;new_rfsdevext_mmblk函数分配了一个内存空间,初始化了一个rfsdevext_t结构实例化变量;该结构的地址放在了device_t结构的dev_extdata字段中;rfs_entry驱动函数放在驱动表中,文......
  • MySQL DDL表操作
    一、查询创建1、查询当前数据库所有表showtables;2、查看指定表结构desc表名;通过这条指令,我们可以查看到指定表的字段,字段的类型、是否可以为NULL,是否存在默认值等信息。3、查询指定表的建表语句showcreatetable表名;通过这条指令,主要是用来查看建表语句的,而有部分参数......
  • 考研408操作系统-5.2设备独立性软件
    23王道书第7题第9题选D第13题选D没有多道程序设计实现的操作系统并发性,那么其他技术无从谈起,因为其他技术都是以并发性为前提的。第16题选A内存中的用户进程将打印结果首先送到了磁盘第17题采用SPOOLing技术,不需要物理上的外围机第19题考点对应第16题,选B第22......
  • c语言实现链表的基本操作——初始化,求长度,添加节点,遍历输出
    #include<stdio.h>#include<stdlib.h>//创建结构体并命名typedefstructNode //typedef用于对struct的重命名{ inti; structNode*next;}LNode,*LinkList; //定义一个结构体指针//链表初始化boolInistList(LinkListL){ L=(LNode*)malloc(sizeof(LNo......
  • Java异常
    原文链接:Java里的异常(Exception)详解(一)什么是java里的异常由于java是c\c++发展而来的, 首先我们先看看c语言里的错误.1.c语言里的错误我们实现一个程序的过程包括, 代码编写,编译代码成为程序, 执行程序.其中大部分常见的语法错误都会被编译代码这样部过滤掉. 但......
  • 手机操作API
    目录手机操作API获取手机分辨率手机截图获取和设置手机网络发送键到设备操作手机通知栏总结手机操作API获取手机分辨率应用场景自动化测试可能会需要根据当前设备的屏幕分辨率来计算一些点击或者滑动的坐标核心代码#获取手机分辨率print(driver.get_window_size())执行结......
  • 前端进化笔记-JavaScript(二)
    因为作者学过其他类c语言,就不对大家都熟悉的内容进行赘述了。语法JavaScript区分大小写标识符:变量,函数,属性,函数参数的名称第一个字符必须是字母,下划线(_),美元符号($);关键字、保留字、true、false和null不能用作标识符作者在后续阅读的过程中,发现对各种名称不熟悉导致阅读不......
  • JavaWeb复习笔记
    MysqlsqlDDLDMLDQL约束设计多表查询内连接外连接子查询事务......