Stream

Stream

1. 概念

  • util包下的Stream是Java8的新特性,与io包下的 InputStream 和 OutputStream 是完全不同的概念。Java8的Stream是对集合(Collection)对象功能的增强,专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。

  • Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。

  • 而和迭代器又不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个 item 读完后再读下一个 item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。Stream 的并行操作依赖于 Java7 中引入的 Fork/Join 框架(JSR166y)来拆分任务和加速处理过程。

2. 构成

当我们使用一个流的时候,通常包括三个基本步骤:

  • 获取一个数据源(source)
  • 数据转换
  • 执行操作获取想要的结果

每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。

①数据源

有多种方式生成数据源

  • 从集合、数组
    • Collection.stream()
    • Collection.parallelStream()
    • Arrays.stream()
    • Stream.of(T values):T可以为集合或者数组
  • BufferedReader
    • java.io.BufferedReader.lines()
  • 静态工厂
    • java.util.stream.IntStream.range()
    • java.nio.file.Files.walk()

需要注意的是,对于基本数值型,目前有三种对应的包装类型 Stream:

IntStream、LongStream、DoubleStream。当然我们也可以用 Stream<Integer>、Stream<Long>、Stream<Double>,但是 boxing 和 unboxing 会很耗时,所以特别为这三种基本数值型提供了对应的Stream。
Java 8 中还没有提供其它数值型 Stream,因为这将导致扩增的内容较多。而常规的数值型聚合运算可以通过上面三种 Stream 进行。

②操作类型

  • Intermediate:一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
  • Terminal:一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。

在对于一个 Stream 进行多次转换操作 (Intermediate 操作),每次都对 Stream 的每个元素进行转换,而且是执行多次,这样时间复杂度就是 N(转换次数)个 for 循环里把所有操作都做掉的总和吗?其实不是这样的,转换操作都是 lazy 的,多个转换操作只会在 Terminal 操作的时候融合起来,一次循环完成。我们可以这样简单的理解,Stream 里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在 Terminal 操作的时候循环 Stream 对应的集合,然后对每个元素执行所有的函数。

还有一种操作被称为 short-circuiting。用以指:

  • 对于一个intermediate 操作,如果它接受的是一个无限大(infinite/unbounded)的Stream,但返回一个有限的新Stream。
  • 对于一个 terminal 操作,如果它接受的是一个无限大的 Stream,但能在有限的时间计算出结果。

当操作一个无限大的 Stream,而又希望在有限时间内完成操作,则在管道内拥有一个 short-circuiting 操作是必要非充分条件。

③示例

int sum = widgets.stream()
.filter(w -> w.getColor() == RED)
 .mapToInt(w -> w.getWeight())
 .sum();
  • widgets.stream()为获取数据源

  • filter(w -> w.getColor() == RED)mapToInt(w -> w.getWeight())为intermediate 操作

  • sum()为Terminal操作

④总结

简单说,对 Stream 的使用就是实现一个 filter-map-reduce 过程,产生一个最终结果,或者导致一个副作用(side effect)。

⑤插件

Java Stream Debugger

插件地址:https://plugins.jetbrains.com/plugin/9696-java-stream-debugger

可以查看stream链中间操作的数据状态。

3.Intermediate

常见的Intermediate操作如下:

  • Intermediate:filter、map (mapToInt, flatMap 等)、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered
  • Short-circuiting:anyMatch、 allMatch、 noneMatch、findFirst、 findAny、 limit

①filter

过滤,可以通过 filter 方法将一个流转换成另一个子集流。

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

该接口接收一个 Predicate 函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件。

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

该方法将会产生一个boolean值结果,代表指定的条件是否满足。如果结果为true,那么Stream流的 filter 方法将会留用元素;如果结果为false,那么 filter 方法将会舍弃元素。

示例:

设置元素的长度必须大于5

    public static void main(String[] args) {
        Stream<String> original = Stream.of("Java", "C", "Python", "Hadoop", "Spark");
        Stream<String> result = original.filter(s -> s.length() >= 5);
        result.forEach(System.out::println);
    }
Python
Hadoop
Spark

②map

映射,如果需要将流中的元素映射到另一个流中,可以使用map方法。该接口需要传入一个Function函数式接口,可以将当前流中的T类型数据转换为另一种R类型的流。

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

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

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

Function接口只有一个方法,就是将传入的T类型转换成R类型。

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

示例:

将字符串流转换成整数流

    public static void main(String[] args) {
        String[] stringArray = {"1", "2","3","4"};
        Stream<String> stringStream = Arrays.stream(stringArray);
        Stream<Object> objectStream = stringStream.map(new Function<String, Object>() 	     {
            @Override
            public Object apply(String s) {
                return Integer.valueOf(s);
            }
        });
    }
1
2
3
4

lambda

    public static void main(String[] args) {
        String[] stringArray = {"1", "2","3","4"};
        Stream<String> stringStream = Arrays.stream(stringArray);
        Stream<Integer> integerStream = stringStream.map(Integer::valueOf);
        integerStream.forEach(System.out::println);
    }

③flatMap

扁平的映射,可以将一个2维的集合映射成一个一维,相当于他映射的深度比map深了一层 。

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

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

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

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

示例:

["Hello","World"]操作,返回["H","e","l","o","W","r","d"]

public static void main(String[] args) {
    String[] words = new String[]{"Hello","World"};
    Stream<String[]> doubleStream = Arrays.stream(words).map(word -> word.split(""));
    Stream<String> singleStream = doubleStream.flatMap(Arrays::stream);
    singleStream.forEach(System.out::print);
}
helloword

总结:使用flatMap方法的效果是,各个数组并不是分别映射一个流,而是映射成流的内容,所有使用map(Array::stream)时生成的单个流被合并起来,即扁平化为一个流。

④distinct

按照 Object.equals(Object)进行去重

Stream<T> distinct();

⑤sorted

通过元素的compareTo()方法进行排序,元素所在的类必须要实现Comparable接口

Stream<T> sorted();

如果不想让元素所在类实现Comparable接口,可以传入一个比较器

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

⑥peek

有点像迭代的forEach,传入每个元素,做一些操作,没有返回值。

与map的区别要注意,map是有返回值的,可以将传入类型转成其它类型。

与forEach的区别要注意,forEach是Terminal中的操作,结束该stream链,而peek是intermediate操作,返回的还是Stream对象。

Stream<T> peek(Consumer<? super T> action);
@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

⑦limit

取前maxSize个

对集合进行截取,参数maxSizelimit返回的流中的最大元素数。

Stream<T> limit(long maxSize);

⑧skip

跳过前N个

此方法以一个长(N)作为参数,并在删除前N个元素后返回流。

如果N的值很大,则在有序并行管道上skip()可能会非常昂贵,因为skip(N)被约束为跳过遇到顺序中的前N个元素,而不仅仅是任何n个元素。

Stream<T> skip(long n);

⑨parallel

将该stream设置为并行流,返回一个并行流。

Returns whether this stream, if a terminal operation were to be executed, would execute in parallel. Calling this method after invoking an terminal stream operation method may yield unpredictable results.
Returns:
true if this stream would execute in parallel if executed

S parallel();

⑩sequential

Returns an equivalent stream that is sequential. May return itself, either because the stream was already sequential, or because the underlying stream state was modified to be sequential.
    
S sequential();

⑪unordered

Returns an equivalent stream that is unordered. May return itself, either because the stream was already unordered, or because the underlying stream state was modified to be unordered.

S unordered();

4.Terminal

常见的Terminal操作如下:

  • Terminal:forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、noneMatch、 findFirst、 findAny、 iterator

①forEach

  • 该方法接收一个Consumer接口函数,会将每一个流元素交给函数进行处理。

  • Consumer接口是一个消费型的函数式接口,可以传递Lambda表达式,消费数据。

  • 如果将forEach()方法与并行流一起使用,则不会维护遇到顺序。 该操作将在每个元素上执行,但是它们的顺序不会固定。

void forEach(Consumer<? super T> action);

②forEachOrdered

  • 如果要按遇到顺序对流元素执行某些操作,则应使用forEachOrdered()方法。
void forEachOrdered(Consumer<? super T> action);

③toArray

将流转换成数组

Object[] toArray();
<A> A[] toArray(IntFunction<A[]> generator);
@FunctionalInterface
public interface IntFunction<R> {
    R apply(int value);
}

示例:

    List<String> strs = Arrays.asList("a", "b", "c");
    String[] dd = strs.stream().toArray(str -> new String[strs.size()]);
    String[] dd1 = strs.stream().toArray(String[]::new);
    Object[] obj = strs.stream().toArray();

④reduce

减少,缩小。根据指定的计算模型将Stream中的值计算得到一个最终结果

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

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

<U> U reduce(U identity,
             BiFunction<U, ? super T, U> accumulator,
             BinaryOperator<U> combiner);

接收两个参数T和U,返回一个R

@FunctionalInterface
public interface BiFunction<T, U, R> {
    R apply(T t, U u);
}

示例一:

Optional<T> reduce(BinaryOperator<T> accumulator)实现累加器。

该函数式接口需要两个参数,返回一个结果(reduce中返回的结果会作为下次累加器计算的第一个参数),也就是累加器。

@Test
public void reduceTest() {
    Optional accResult = Stream.of(1, 2, 3, 4).reduce((acc, item) -> {
        System.out.println("acc : " + acc);
        acc += item;
        System.out.println("item: " + item);
        System.out.println("acc+ : " + acc);
        System.out.println("--------");
        return acc;
    });
    System.out.println(accResult);
}

示例二:

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

  • 提供一个跟Stream中数据同类型的初始值identity,通过累加器accumulator迭代计算Stream中的数据,得到一个跟Stream中数据相同类型的最终结果
public class ReduceDemo {
    @Test
    public void reduceTest() {
        int accResult = Stream.of(1, 2, 3, 4)
                .reduce(100, (acc, item) -> {
                    System.out.println("acc : " + acc);
                    acc += item;
                    System.out.println("item: " + item);
                    System.out.println("acc+ : " + acc);
                    System.out.println("--------");
                    return acc;
                });
        System.out.println(accResult);
    }
}

⑤collect

可以收集流中的数据到集合或者数组中去。

最为核心,需要单独开一章节。

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

⑥min-max

需要传入一个comparator

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

示例:

Optional min = stream.min(Integer::compareTo);

将Integer的compareTo方法传入compare方法中,并调用compareTo方法

返回值为Optional类

Optional 类主要解决的问题是臭名昭著的空指针异常(NullPointerException) —— 每个 Java 程序员都非常了解的异常。

这个类型的对象可能包含值,也可能为空。

⑦count

返回此流的元素总数

long count();

⑧anyMatch-allMatch-noneMatch

  • anyMatch,断条件里的元素,任意一个元素匹配,返回true
  • allMatch,判断条件里的元素,所有的匹配,返回true
  • noneMatch,判断条件里的元素,所有的都不匹配,返回true
boolean anyMatch(Predicate<? super T> predicate);
boolean allMatch(Predicate<? super T> predicate);
boolean noneMatch(Predicate<? super T> predicate);

Predicate中放入一些条件

@FunctionalInterface
public interface Predicate<T> {
    /**
     * Evaluates this predicate on the given argument.
     */
    boolean test(T t);

⑨findFirst-findAny

  • findFirst,返回第一个元素
  • findAny,随机的返回一个元素
Optional<T> findFirst();
Optional<T> findAny();

5.静态方法

①concat

如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat

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

示例:

    public static void main(String[] args) {
        Stream<String> original1 = Stream.of("Java", "C", "Python");
        Stream<String> original2 = Stream.of("Hadoop", "Spark");
        Stream<String> result = Stream.concat(original1, original2);
        result.forEach(System.out::println);
    }
Java
C
Python
Hadoop
Spark

②generate

传递生成流元素的供应商(Supplier),返回一个新的无限顺序无序的流(Stream)。

由于长度是无限的,通常和limit搭配使用。

    public static<T> Stream<T> generate(Supplier<T> s) {
        Objects.requireNonNull(s);
        return StreamSupport.stream(
                new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false);
    }

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

示例:

Stream<Integer> stream = Stream.generate(() -> new Random().nextInt(10));
stream.forEach(e -> System.out.println(e)); 
2
5
1
.
.
.
public class LimitGenerateDemo {
  public static void main(String[] args) {
	Stream.generate(() -> new Random().nextInt(10)).limit(3)
	   .forEach(e -> System.out.println(e));
	
	Stream.generate(() -> new Random().nextBoolean()).limit(3)
	   .forEach(e -> System.out.println(e));
	
	Stream.generate(() -> "Hello World!").limit(3)
	   .forEach(e -> System.out.println(e));
  }
} 

③iterate

指定一个常量seed,生成从seed到常量f(由UnaryOperator返回的值得到)的流。

由于长度是无限的,通常和limit搭配使用。

    public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {
        Objects.requireNonNull(f);
        final Iterator<T> iterator = new Iterator<T>() {
            @SuppressWarnings("unchecked")
            T t = (T) Streams.NONE;

            @Override
            public boolean hasNext() {
                return true;
            }

            @Override
            public T next() {
                return t = (t == Streams.NONE) ? seed : f.apply(t);
            }
        };
        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(
                iterator,
                Spliterator.ORDERED | Spliterator.IMMUTABLE), false);
    }

示例:

    public static void main(String[] args) {
        Stream.iterate(0, n -> n + 1).limit(5).forEach(a -> {
            System.out.println(a);
        });
    }
0
1
2
3
4
5

6.collect详解

Java-Collectors常用的20个方法 - niocoder - 博客园 (cnblogs.com)

可以收集流中的数据到集合或者数组中去。需要传入一个Collector接口。

<R, A> R collect(Collector<? super T, A, R> collector);

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

Collectors类中有很多静态方法,可以提高各式各样的Collector对象

①转换

  • toList:用于将元素累积到List集合中。它将创建一个新List集合(不会更改当前集合)。
List<Integer> integers = Arrays.asList(1,2,3,4,5,6,6);
integers.stream().map(x -> x*x).collect(Collectors.toList());
// output: [1,4,9,16,25,36,36]
  • toSet:用于将元素累积到Set集合中。它会删除重复元素。
List<Integer> integers = Arrays.asList(1,2,3,4,5,6,6);
integers.stream().map(x -> x*x).collect(Collectors.toSet());
// output: [1,4,9,16,25,36]
  • toMap:根据集合的值创建Map

创建了一个Map,其中集合值作为key,在集合中的出现次数作为值。

List<String> strings = Arrays.asList("alpha","beta","gamma");
Map<String,Integer> map = strings
       .stream()
       .collect(Collectors
          .toMap(Function.identity(),String::length));
// output: {alpha=5, beta=4, gamma=5}
  • toCollection:可以将元素累积到指定的集合中。
List<Integer> integers = Arrays.asList(1,2,3,4,5,6,6);
integers
    .stream()
    .filter(x -> x >2)
    .collect(Collectors.toCollection(LinkedList::new));
// output: [3,4,5,6,6]
  • toUnmodifiableList:用于创建只读List集合。任何试图对此不可修改List集合进行更改的尝试都将导致UnsupportedOperationException
List<String> strings = Arrays.asList("alpha","beta","gamma");
List<String> collect2 = strings
       .stream()
       .collect(Collectors.toUnmodifiableList());
// output: ["alpha","beta","gamma"]
  • toUnmodifiableSet:返回不可修改的Set集合。
List<String> strings = Arrays.asList("alpha","beta","gamma","alpha");
Set<String> readOnlySet = strings
       .stream()
       .sorted()
       .collect(Collectors.toUnmodifiableSet());
// output: ["alpha","beta","gamma"]

②数学计算

  • Counting:用于返回计算集合中存在的元素个数。
List<Integer> integers = Arrays.asList(1,2,3,4,5,6,6);
Long collect = integers
                   .stream()
                   .filter(x -> x <4)
                   .collect(Collectors.counting());
// output: 3
  • minBy:用于返回列表中存在的最小值。
List<Integer> integers = Arrays.asList(1,2,3,4,5,6,6);
List<String> strings = Arrays.asList("alpha","beta","gamma");
integers
    .stream()
    .collect(Collectors.minBy(Comparator.naturalOrder()))
    .get();
// output: 1
strings
   .stream()
   .collect(Collectors.minBy(Comparator.naturalOrder()))
   .get();
// output: alpha

按照整数排序返回1,按照字符串排序返回alpha

可以使用reverseOrder()方法反转顺序。

List<Integer> integers = Arrays.asList(1,2,3,4,5,6,6);
List<String> strings = Arrays.asList("alpha","beta","gamma");
integers
    .stream()
    .collect(Collectors.minBy(Comparator.reverseOrder()))
    .get();
// output: 6
strings
   .stream()
   .collect(Collectors.minBy(Comparator.reverseOrder()))
   .get();
// output: gamma
  • maxBy:和最小值方法类似,使用maxBy()方法来获得最大值。
List<String> strings = Arrays.asList("alpha","beta","gamma");
strings
   .stream()
   .collect(Collectors.maxBy(Comparator.naturalOrder()))
   .get();
// output: gamma
  • averagingLong:查找Long类型集合的平均值。注意:返回的是Double类型而不是 Long类型
List<Long> longValues = Arrays.asList(100L,200L,300L);
Double d1 = longValues
    .stream()
    .collect(Collectors.averagingLong(x -> x * 2));
// output: 400.0
  • averagingInt

  • averagingDouble

  • averagingLong:

  • summingInt :

查找集合中所有整数的和。它并不总是初始集合的和,就像我们在下面的例子中使用的我们使用的是字符串列表,首先我们把每个字符串转换成一个等于它的长度的整数,然后把所有的长度相加。

 List<String> strings = Arrays.asList("alpha","beta","gamma");
 Integer collect4 = strings
       .stream()
       .collect(Collectors.summingInt(String::length));
 // output: 18
 List<Integer> integers = Arrays.asList(1,2,3,4,5,6,6);
 Integer sum = integers
     .stream()
     .collect(Collectors.summingInt(x -> x));
 // output: 27
  • summingDouble
  • **summingLong **
  • summarizingInt :它给出集合中出现的值的所有主要算术运算值,如所有值的平均值、最小值、最大值、所有值的计数和总和。
List<Integer> integers = Arrays.asList(1,2,3,4,5,6,6);
IntSummaryStatistics stats = integers
          .stream()
          .collect(Collectors.summarizingInt(x -> x ));
//output: IntSummaryStatistics{count=7, sum=27, min=1, average=3.857143, max=6}

可以使用get方法提取不同的值:

stats.getAverage();   // 3.857143
stats.getMax();       // 6
stats.getMin();       // 1
stats.getCount();     // 7
stats.getSum();       // 27

③链接

  • Joining:用指定的字符串链接集合内的元素。
    public static Collector<CharSequence, ?, String> joining(CharSequence delimiter) {
        return joining(delimiter, "", "");
    }
    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);
    }

示例:

List<String> strings = Arrays.asList("alpha","beta","gamma");
String collect3 = strings
     .stream()
     .distinct()
     .collect(Collectors.joining(","));
// output: alpha,beta,gamma
String collect4 = strings
     .stream()
     .map(s -> s.toString())
     .collect(Collectors.joining(",","[","]"));
// output: [alpha,beta,gamma]

④分组

  • GroupingBy:是一种高级方法,用于从任何其他集合创建Map

示例:

将字符串长度作为key,并将该长度的字符串列表作为value

List<String> strings = Arrays.asList("alpha","beta","gamma");
Map<Integer, List<String>> collect = strings
          .stream()
          .collect(Collectors.groupingBy(String::length));
// output: {4=[beta, beta], 5=[alpha, gamma]}

还可以指定Map中需要的列表类型(Libkedlist)。

List<String> strings = Arrays.asList("alpha","beta","gamma");
Map<Integer, LinkedList<String>> collect1 = strings
            .stream()
            .collect(Collectors.groupingBy(String::length, 
                Collectors.toCollection(LinkedList::new)));
// output: {4=[beta, beta], 5=[alpha, gamma]}
  • partitioningBy:返回的map的key是booelan类型的,也就是说会把流分成两类

示例:

map 中有两个key,一个是大于3的true,一个是小于3的false

    public static void main(String[] args) 
    { 
        Stream<Integer> 
            s = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 
        Map<Boolean, Long> 
            map = s.collect( 
                Collectors.partitioningBy( 
                    num -> (num > 3), Collectors.counting())); 
    } 
}

7.去重详解

list根据元素的某个字段或者多个字段去重

①toMap

利用Collectors.toMap去重

        List<Person> personList = new ArrayList<>();
        personList.add(new Person("one", 10));
        personList.add(new Person("two", 20));
        personList.add(new Person("three", 30));
        //这两的name值重复
        personList.add(new Person("four", 40));
        personList.add(new Person("four", 45));
 
        System.out.println("利用Collectors.toMap去重:");
        //利用Collectors.toMap去重
        personList.stream()
                .collect(Collectors.toMap(Person::getName, Function.identity(), (oldValue, newValue) -> oldValue))
            	.values()
                .stream()
                .forEach(System.out::println); //打印

Person

@Data
@AllArgsConstructor
class Person {
    private String name;
    private int age;
}

Collector

//参数1:keyMapper函数
//参数2:valueMapper函数
//参数3:去重的关键,BinaryOperator函数接口

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

这个BinaryOperator函数接收两个参数,如上面代码,一个oldValue,一个newValue,字面意思,第一个旧值,第二个是新值。当stream构造Map时,会先调用Map的get方法获取该key对应节点的旧值,如果该值为null,则不会调用BinaryOperator函数,如果不为null,则会把获取的旧值与新值作为参数传给函数执行,然后把函数的返回值作为新值put到Map中。

②toCollection & TreeSet

利用TreeSet原理去重(TreeSet内部使用的是TreeMap,使用指定Comparator比较元素,如果元素相同,则新元素代替旧元素)

 List<Person> personList = new ArrayList<>();
        personList.add(new Person("one", 10));
        personList.add(new Person("two", 20));
        personList.add(new Person("three", 30));
        //这两的name值重复
        personList.add(new Person("four", 40));
        personList.add(new Person("four", 42));
 
        System.out.println("利用Collectors.toCollection和TreeSet去重:");
        //利用TreeSet原理去重(TreeSet内部使用的是TreeMap,使用指定Comparator比较元素,如果元素相同,则新元素代替旧元素)
        personList.stream()
                .collect(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Person::getName))))
                .stream()
                .forEach(System.out::println); //打印

如果想要不想要返回TreeSet类型,那也可以使用Collectors.collectingAndThen转换成ArrayList,也可以用new ArrayList(set),原理一样

List<Person> distinctList = personList.stream()
                .collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Person::getName))), ArrayList::new));
posted @ 2021-08-31 00:07  FpengtoJava  阅读(31)  评论(0)    收藏  举报