java8 Stream

2021/09/08 JAVA8 共 36829 字,约 106 分钟
闷骚的程序员

1. java8 Stream api

java8中除了改造原有的集合框架,往里面新增了一些default方法,以供外部方便调用之外,最值得关注的是,它还新增了一系列新的方法包,其中stream就是亮点之一。

Stream是java8新增的java.util.stream包下的独立的一个接口(不是函数式接口),并且jdk为Stream接口增加了很多内置实现,目的就是将数据转换成流操作。
常用于java中各种集合操作。

2. Stream API

Stream API的操作步骤:
(1)、创建Stream对象:从一个数据源,比如集合或者数组中获取Stream对象(java8对指定的接口比如Collection扩展了直接获取Stream对象的默认方法);
(2)、中间操作:一个操作的中间链,对数据源的数据进行操作(起始于Stream对象,调用Stream接口被实现的各种方法,这些方法往往返回的也是一个Stream对象,从而实现链式编程)。
(3)、终止操作:开始执行中间链操作,并产生结果。
我们可以看下Stream的源码:

package java.util.stream;

import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Objects;
import java.util.Optional;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.ToDoubleFunction;
import java.util.function.ToIntFunction;
import java.util.function.ToLongFunction;
import java.util.function.UnaryOperator;

public interface Stream<T> extends BaseStream<T, Stream<T>> {

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

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

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

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

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

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

    Stream<T> distinct();

    Stream<T> sorted();

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

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

    Stream<T> limit(long maxSize);

    Stream<T> skip(long n);

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

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

    Object[] toArray();

    <A> A[] toArray(IntFunction<A[]> generator);

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

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

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

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

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

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

    long count();

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

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

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

    Optional<T> findFirst();

    Optional<T> findAny();

    // Static factories

    public static<T> Builder<T> builder() {
        return new Streams.StreamBuilderImpl<>();
    }

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

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

    @SafeVarargs
    @SuppressWarnings("varargs") // Creating a stream from an array is safe
    public static<T> Stream<T> of(T... values) {
        return Arrays.stream(values);
    }

    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<T> Stream<T> generate(Supplier<T> s) {
        Objects.requireNonNull(s);
        return StreamSupport.stream(
                new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false);
    }

    public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) {
        Objects.requireNonNull(a);
        Objects.requireNonNull(b);

        @SuppressWarnings("unchecked")
        Spliterator<T> split = new Streams.ConcatSpliterator.OfRef<>(
                (Spliterator<T>) a.spliterator(), (Spliterator<T>) b.spliterator());
        Stream<T> stream = StreamSupport.stream(split, a.isParallel() || b.isParallel());
        return stream.onClose(Streams.composedClose(a, b));
    }

    public interface Builder<T> extends Consumer<T> {

        @Override
        void accept(T t);

        default Builder<T> add(T t) {
            accept(t);
            return this;
        }

        Stream<T> build();

    }
}

Stream API常见方法(大多数内置方法都用来接收一个回调对象从而执行回调逻辑,优秀的是java8中实例化一个函数式接口对象可以采用Lambda表达式方便的进行创建):

boolean allMatch(Predicate<? super T> predicate); //Stream所有元素都匹配指定的Predicate规则。   
boolean anyMatch(Predicate<? super T> predicate); //Stream部分元素匹配指定的Predicate规则。   
public static<T> Builder<T> builder();   
void close();   
<R, A> R collect(Collector<? super T, A, R> collector);//将Stream数据流按照指定的收集器规则聚合成目标类型的数据。   
<R> R collect(Supplier<R> supplier,   
BiConsumer<R, ? super T> accumulator,   
BiConsumer<R, R> combiner);   
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b);   
long count();   
Stream<T> distinct();   
public static<T> Stream<T> empty();   
Stream<T> filter(Predicate<? super T> predicate);   
Optional<T> findAny();   
Optional<T> findFirst();   
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);   
DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream> mapper);   
IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper);   
LongStream flatMapToLong(Function<? super T, ? extends LongStream> mapper);   
void forEach(Consumer<? super T> action);   
void forEachOrdered(Consumer<? super T> action);   
public static<T> Stream<T> generate(Supplier<T> s);   
boolean isParallel();   
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) ;   
Iterator<T> iterator();   
Stream<T> limit(long maxSize);   
<R> Stream<R> map(Function<? super T, ? extends R> mapper);//将集合元素转换为其他对象。   
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);   
IntStream mapToInt(ToIntFunction<? super T> mapper);   
LongStream mapToLong(ToLongFunction<? super T> mapper);   
Optional<T> max(Comparator<? super T> comparator);   
Optional<T> min(Comparator<? super T> comparator);   
boolean noneMatch(Predicate<? super T> predicate);   
public static<T> Stream<T> of(T t);   
public static<T> Stream<T> of(T... values);   
S onClose(Runnable closeHandler);   
S parallel();   
Stream<T> peek(Consumer<? super T> action);   
Optional<T> reduce(BinaryOperator<T> accumulator);   
T reduce(T identity, BinaryOperator<T> accumulator);//identity表示初始值;参数binaryOperator是一个函数接口,表示二元操作,可用于数学运算。   
<U> U reduce(U identity,   
BiFunction<U, ? super T, U> accumulator,   
BinaryOperator<U> combiner);   
S sequential();   
Stream<T> skip(long n);//跳过前几个元素   
Stream<T> sorted(); //按照默认比较规则对Stream进行排序。   
Stream<T> sorted(Comparator<? super T> comparator);//按照指定比较规则对Stream进行排序。   
Spliterator<T> spliterator();   
Object[] toArray();   
<A> A[] toArray(IntFunction<A[]> generator);   
S unordered();   

3. 实操演练

下来我们来看下操作Stream的案例。
先定义一个实体类,用于充当集合中的元素:

package zeh.myjavase.code42java8.demo06;

class ModeDemo06 {
    private String name;
    private Integer age;
    private String country;
    private char sex;

    public ModeDemo06(String name, Integer age, String country, char sex) {
        this.name = name;
        this.age = age;
        this.country = country;
        this.sex = sex;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    public char getSex() {
        return sex;
    }

    public void setSex(char sex) {
        this.sex = sex;
    }

    public String toString() {
        return "{name=" + this.getName() + ",age=" + this.getAge() + ",country=" + this.getCountry() + ",sex=" + this.getSex() + "}";
    }
}

接下来通过Stream API来操作集合。
创建一个运行类,先组装一个List出来:

package zeh.myjavase.code42java8.demo06;

import org.junit.Before;
import org.junit.Test;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamRun {
    private List<ModeDemo06> personList = new ArrayList<>();

    @Before
    public void packageList() {
        personList.add(new ModeDemo06("欧阳雪", 18, "中国", 'F'));
        personList.add(new ModeDemo06("Tom", 24, "美国", 'M'));
        personList.add(new ModeDemo06("Harley", 22, "英国", 'F'));
        personList.add(new ModeDemo06("向天笑", 20, "中国", 'M'));
        personList.add(new ModeDemo06("李康", 22, "中国", 'M'));
        personList.add(new ModeDemo06("小梅", 20, "中国", 'F'));
        personList.add(new ModeDemo06("何雪", 21, "中国", 'F'));
        personList.add(new ModeDemo06("李康", 22, "中国", 'M'));
        
        // 如果年龄为Null值的话,在进行获取后可能报空指针。
//        personList.add(new ModeDemo06("李康", null, "中国", 'M'));
//        personList.add(new ModeDemo06("李康", null, "中国", 'M'));
    }

}

3.1 Stream的filter方法

Stream的filter()方法用于对流中的元素进行过滤操作。
(1)Stream的filter方法接受一个Predicate对象,使用Lambda表达式实现Predicate接口的抽象方法。
(2)返回一个Stream对象,Predicate对象指定规则,返回的Stream是符合规则的过滤后的数据流。
(3)filter()方法返回一个新的Stream,其和源Stream完全独立。
(4)Collection集合在Java8中也增加了一个stream()默认方法,该方法返回Stream类型对象,用于将List转换为Stream。

    @Test
    public void testStream_filter() {
        // 1)找到年龄大于18岁的人并输出;
        // filter()是Stream接口中的方法,接收一个Predicate对象;
        // Predicate接口是函数式接口,所以使用Lambda表达式实现其对象并传递进去;
        // forEach()也是Stream接口的方法,接收默认的Consumer对象,同样Consumer接口是函数式接口,通过方法引用实现简写的Lambda表达式;
        // Predicate接口最开始是apache官方的第三方包,java8借鉴后自己也搞了一个。
        // 创建流:personList.stream();中间操作:filter();终止操作:foreach()。
        personList.stream().filter((p) -> p.getAge() > 18).forEach(System.out::println);

        // filter()方法是返回一个新的Stream,不会改变源数据。
        System.out.println("-------------------------------------------" + personList);

        // 2)找出所有中国人的数量
        //创建流:personList.stream();中间操作:filter();终止操作:count()。
        long chinaPersonNum = personList.stream().filter((p) -> p.getCountry().equals("中国")).count();
        System.out.println("中国人有:" + chinaPersonNum + "个");
    }

运行:

{name=Tom,age=24,country=美国,sex=M}
{name=Harley,age=22,country=英国,sex=F}
{name=向天笑,age=20,country=中国,sex=M}
{name=李康,age=22,country=中国,sex=M}
{name=小梅,age=20,country=中国,sex=F}
{name=何雪,age=21,country=中国,sex=F}
{name=李康,age=22,country=中国,sex=M}
-------------------------------------------[{name=欧阳雪,age=18,country=中国,sex=F}, {name=Tom,age=24,country=美国,sex=M}, {name=Harley,age=22,country=英国,sex=F}, {name=向天笑,age=20,country=中国,sex=M}, {name=李康,age=22,country=中国,sex=M}, {name=小梅,age=20,country=中国,sex=F}, {name=何雪,age=21,country=中国,sex=F}, {name=李康,age=22,country=中国,sex=M}]
中国人有:6个

3.2 Stream的map方法

Stream接口的map()方法用于对流中的元素进行转换,它接收流中的每一个元素,返回转换后的值,通过它转换后,源流中的所有元素都将被映射为它转换后的值了。
(1)map()方法接收一个Function对象,使用Lambda表达式实现Function接口的抽象方法,所谓map,从字面理解就是映射,这里指的是对象关系的映射,将上游Stream的每一个元素按照逻辑进行元素转换。
(2)该方法返回一个Stream对象。Function对象指定上游数据中每一个元素的转换规则,返回的Stream是转换后的数据流。
(3)查看map()方法的实现:map()默认处理的是上流Stream中的每一个元素对象,将每一个元素按照实现的Function接口的抽象方法实现规则(使用Lambda)进行转换后返回到一个新的Stream中。
(4)总结:map()就是按照指定的实现逻辑对传入的每一个元素对象进行转换后返回到新的Stream中。

@Test
public void testStream_map() {
    personList.stream().map((element) -> {
        //将每一个元素的age加上20后返回每一个元素到Stream中,最后使用Stream的foreach()方法进行遍历。
        element.setAge(element.getAge() + 20);
        return element;
    }).forEach(System.out::println);
}

运行:

{name=欧阳雪,age=38,country=中国,sex=F}
{name=Tom,age=44,country=美国,sex=M}
{name=Harley,age=42,country=英国,sex=F}
{name=向天笑,age=40,country=中国,sex=M}
{name=李康,age=42,country=中国,sex=M}
{name=小梅,age=40,country=中国,sex=F}
{name=何雪,age=41,country=中国,sex=F}
{name=李康,age=42,country=中国,sex=M}

3.3 Stream的reduce方法

reduce()方法用于对上游Stream中每一个元素进行聚合运算。可以求和、求平均数、求个数等。
(1)SQL中类似 sum()、avg() 或者 count() 的聚集函数,实际上就是 reduce 操作,因为它们接收多个值并返回一个值。
(2)它接收一个 BinaryOperator 对象,这个对象也是一个函数式接口,它继承BiFunction接口,因此,它接收两个参数,返回一个值。
(3)返回值是Optional,使用get()方法获取对应的统计结果。
(4)注意:上游Stream传入的数据中,每一个元素的数据类型必须要符合要求,如果不符合,应该先使用map()进行转换后再对整个Steam进行统计。

    @Test
    public void testStream_reduce() {
        //因为上游Stream传入的一堆Person,而reduce()方法接受的类型默认就是传入的类型,而默认传入的类型就是上游stream中的每一个元素类型,即Person。
        //很明显Person没法进行聚合,所以需要先使用map()将一堆Person类型先进行转换。
//        Long total = personList.stream().reduce((sum, element) -> {
//            return sum.getAge() + element.getAge();
//        }).get();
        Integer totalAge = personList.stream().map((element) -> element.getAge()).reduce((sum, element) -> sum + element).get();
        System.out.println("统计所有人的年龄之和:" + totalAge);
    }

运行:

统计所有人的年龄之和:169

3.4 Stream的collect方法

collect()方法接受一个Collector收集器对象,将上游stream数据流转换成自己Collectors收集器指定的数据。
目标就是将指定的上游stream流数据聚合成对应的集合对象。

    @Test
    public void testStream_collect() {
        List<String> resultList = personList.stream().map((element) -> element.getName()).collect(Collectors.toList());
        System.out.println("resultList:" + resultList);
    }

运行:

resultList:[欧阳雪, Tom, Harley, 向天笑, 李康, 小梅, 何雪, 李康]

3.5 filter结合collect

使用filter()方法指定回调过滤接口规则;使用collect()将数据按照指定收集器规则进行收集。
再强调一遍:filter()方法会产生一个符合过滤规则的新列表,而不会更改源列表。

    @Test
    public void testStream_filter_collect() {
        List<ModeDemo06> resultList = personList.stream().filter(e -> e.getAge() > 18).collect(Collectors.toList());
        System.out.println("resultList:" + resultList);
    }

运行:

resultList:[{name=Tom,age=24,country=美国,sex=M}, {name=Harley,age=22,country=英国,sex=F}, {name=向天笑,age=20,country=中国,sex=M}, {name=李康,age=22,country=中国,sex=M}, {name=小梅,age=20,country=中国,sex=F}, {name=何雪,age=21,country=中国,sex=F}, {name=李康,age=22,country=中国,sex=M}]

3.6 Collectors的joining方法

(1)对列表中每一个元素使用函数等,Collectors.joining():使用上游Stream中的各个元素拼接目标字符串使之成为一个新的字符串。
(2)最后一个元素将不会在末尾拼接。

    @Test
    public void testStream_function() {
        String resultStr = personList.stream().map(x -> x.getName().toUpperCase()).collect(Collectors.joining("---"));
        System.out.println("resultStr:" + resultStr);
    }

运行:

resultStr:欧阳雪---TOM---HARLEY---向天笑---李康---小梅---何雪---李康

进一步理解joining方法:

在 Java 8 中,你可以使用 Stream API 和 Collectors.joining() 来将 List 中的元素拼接成一个字符串,并且可以控制分隔符(如逗号 ,),而且最后一个元素不需要分隔符。

以下是实现示例:

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

public class Main {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("apple", "banana", "orange", "grape");

        // 使用 Collectors.joining(",") 进行拼接
        String result = list.stream()
                            .collect(Collectors.joining(","));
        
        System.out.println(result);
    }
}

代码说明:

  1. list.stream():将 List 转换为流。
  2. Collectors.joining(","):将流中的元素用 , 拼接起来,默认最后一个元素不会加上额外的 ,

输出结果:

apple,banana,orange,grape

其他可选拼接方式:

  • 你还可以自定义分隔符、前缀和后缀:
    String result = list.stream()
                      .collect(Collectors.joining(",", "[", "]"));
    System.out.println(result);
    

输出:

[apple,banana,orange,grape]

这种方式可以优雅地将列表中的元素拼接起来,同时确保最后一个元素不会多余加上分隔符。

3.7 Stream的distinct方法

对目标Stream进行结果去重复。
(1)distinct方法返回一个新的Stream,不会影响源Stream。
(2)distinct()对Stream数据流进行去重复,默认是根据元素对应的引用地址去判断是否是重复元素的(和hashSet一样,如果要真正去重复必须实现equals()方法和hashCode()方法)。
(3)注意,即便元素为null值,它也同样参与去重复比较。

    @Test
    public void testStream_distinct() {
        //对stream数据流进行去重复,stream中保存的是一堆对象
        List<ModeDemo06> persons = personList.stream().distinct().collect(Collectors.toList());
        System.out.println("去重复后的:" + persons);

        //转换成每一个年龄的集合,不去除重复
        List<Integer> resultList = personList.stream().map(e -> e.getAge()).collect(Collectors.toList());
        System.out.println("resultList:" + resultList);

        //转换成每一个年龄的集合,去除重复
        List<Integer> resultList1 = personList.stream().map(e -> e.getAge()).distinct().collect(Collectors.toList());
        System.out.println("resultList1:" + resultList1);

        Long nullCount = personList.stream().map(e -> e.getAge()).filter(e -> e == null).count();
        System.out.println("nullCount:" + nullCount);

        Long count = personList.stream().map(e -> e.getAge()).distinct().count();
        System.out.println("去重复后的age属性的count:" + count);
        System.out.println("去重复前的age属性的count:" + persons.size());
    }

运行:

去重复后的:[{name=欧阳雪,age=18,country=中国,sex=F}, {name=Tom,age=24,country=美国,sex=M}, {name=Harley,age=22,country=英国,sex=F}, {name=向天笑,age=20,country=中国,sex=M}, {name=李康,age=22,country=中国,sex=M}, {name=小梅,age=20,country=中国,sex=F}, {name=何雪,age=21,country=中国,sex=F}, {name=李康,age=22,country=中国,sex=M}]
resultList:[18, 24, 22, 20, 22, 20, 21, 22]
resultList1:[18, 24, 22, 20, 21]
nullCount:0
去重复后的age属性的count:5
去重复前的age属性的count:8

3.8 Stream的mapToInt方法

mapToInt():直接将上游元素每一个转换成int类型的。它的返回值是IntStream。
IntStream、LongStream 和 DoubleStream 等流的类中,有个非常有用的方法叫做 summaryStatistics() 。
可以返回 IntSummaryStatistics、LongSummaryStatistics 或者 DoubleSummaryStatistic s,描述流中元素的各种摘要数据。
在本例中,我们用这个方法来计算列表的最大值和最小值。它也有 getSum() 和 getAverage() 方法来获得列表的所有元素的总和及平均值。

    @Test
    public void testStream_IntStream() {
        IntSummaryStatistics summaryStatistics = personList.stream().map(e -> e.getAge()).mapToInt(x -> x).summaryStatistics();
        System.out.println("最大值:" + summaryStatistics.getMax());
        System.out.println("最小值:" + summaryStatistics.getMin());
        System.out.println("求个数:" + summaryStatistics.getCount());
        System.out.println("求和:" + summaryStatistics.getSum());
        System.out.println("求平均值:" + summaryStatistics.getAverage());
    }

运行:

最大值:24
最小值:18
求个数:8
求和:169
求平均值:21.125

mapToInt方法实际操作:Map中有多个key-value,其中每一个value是一个List,现在想通过java8立即得到整个map中所有value的size,注意是将所有value聚合后的list的总size。该怎么操作呢?

在 Java 8 中,可以使用 Stream API 来实现这个需求。你可以将 Map 中所有 List 类型的 value 聚合起来,计算出这些 List 的元素总数。

具体步骤如下:

  1. 获取 Map 中所有 value,即 List 的集合。
  2. 使用 Stream 展开每个 List 并计算其大小。
  3. 最终将这些 List 的大小累加,得到总数。

以下是实现代码示例:

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        // 创建一个包含 List 作为 value 的 Map
        Map<String, List<Integer>> map = new HashMap<>();
        map.put("list1", Arrays.asList(1, 2, 3));
        map.put("list2", Arrays.asList(4, 5));
        map.put("list3", Arrays.asList(6, 7, 8, 9));

        // 使用 Stream API 计算所有 List 的总大小
        int totalSize = map.values().stream()
                .mapToInt(List::size) // 计算每个 List 的大小
                .sum(); // 汇总所有大小

        System.out.println("Total size of all lists: " + totalSize);
    }
}

代码说明:

  1. map.values().stream():获取 Map 中所有 value(即 List)并将其转换为流。
  2. mapToInt(List::size):将每个 List 的大小映射为整数。
  3. sum():将所有 List 的大小累加,得到总的元素数量。

输出结果:

Total size of all lists: 9

优点:

  • 代码简洁,利用流式 API 实现复杂操作。
  • 可以轻松处理 Map 中多个 List 的聚合需求。

这种写法不仅简洁,还能很好地扩展到更多的聚合操作。

3.9 Stream的limit方法

limit():Stream的方法,对上游数据流进行截取。
同样,它返回一个新的Stream。

    // 指定过滤规则后,选取前面2个。
    @Test
    public void testStream_limit() {
        personList.stream().filter(e -> e.getAge() > 18).limit(2).forEach(e -> System.out.println(e));
    }

运行:

{name=Tom,age=24,country=美国,sex=M}
{name=Harley,age=22,country=英国,sex=F}

3.10 Stream的of方法

of():Stream的静态方法,创建一个指定数据集合的Stream流,该方法通常用于我们快速的创建一个Stream对象。
它接收一个T类型的可变参数,实际上本质就是一个T类型的数组。
因此,我们可以传递任意类型的一个数组对象进去,将它转换为Stream。

    @Test
    public void testStream_of() {
        Stream<String> stringStream = Stream.of("Eric", "Daisy", "Poppy", "Sam");
        stringStream.forEach(e -> System.out.println(e));
    }

运行:

Eric
Daisy
Poppy
Sam

3.11 Stream的max和min方法

max():从上游Stream中获取到指定比较元素对应的最大值的元素,注意返回的是对应规则的最大元素,即返回的是Person。
min():从上游Stream中获取到指定比较元素对一个的最小值的元素。
它接收一个Comparator比较器对象,该对象也被改造为函数式接口了,其中只有一个抽象方法 compare(),用于指定两个对象的比较规则。
max和min返回一个Optional。

    @Test
    public void testStream_max_min() {
        ModeDemo06 maxPerson = personList.stream().max(Comparator.comparing(a -> a.getAge())).get();
        System.out.println("年龄最大的人是:" + maxPerson);
        ModeDemo06 minPerson = personList.stream().min(Comparator.comparing(a -> a.getAge())).get();
        System.out.println("年龄最小的人是:" + minPerson);
    }

运行:

年龄最大的人是:{name=Tom,age=24,country=美国,sex=M}
年龄最小的人是:{name=欧阳雪,age=18,country=中国,sex=F}

3.12 Stream的allMatch

allMatch():上游Stream各个元素是否全部匹配指定的规则。
(1)接收一个Predicate,表示一个条件表达式。
(2)返回值是boolean,判断所有的元素是否都符合该条件表达式,如果符合则为true,如果有一个不符合,则为false。

    @Test
    public void testStream_allMatch() {
        Boolean result = personList.stream().allMatch(e -> e.getName().startsWith("T"));
        System.out.println("是否匹配:" + result);
    }

运行:

是否匹配:false

3.13 Stream的anyMatch

和allMatch刚好相反,只要上游Stream中有一个元素符合条件规则,则返回true,否则返回false。
anyMatch():上游Stream各个元素是否任意匹配指定的规则。

    @Test
    public void testStream_anyMatch() {
        Boolean result = personList.stream().anyMatch(e -> e.getName().startsWith("T"));
        System.out.println("是否匹配:" + result);
    }

运行:

是否匹配:true

3.14 IntStream 代替传统的for循环

在 Java 8 中,IntStream 提供了一种优雅的方式来替代传统的 for 循环。它可以通过流式 API 执行固定次数的操作。使用 IntStream.range()IntStream.rangeClosed(),我们可以轻松地执行指定次数的循环操作,并结合 Stream API 来编写更加简洁的代码。

以下是一个使用 IntStream 替代传统 for 循环的示例:

传统的 for 循环:

for (int i = 0; i < 5; i++) {
    System.out.println("Iteration: " + i);
}

使用 IntStream 替代:

import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        IntStream.range(0, 5).forEach(i -> System.out.println("Iteration: " + i));
    }
}

代码说明:

  1. IntStream.range(0, 5):生成一个从 0(含)到 5(不含)的整型流,相当于 for (int i = 0; i < 5; i++)
  2. forEach():对每个流中的元素执行指定的操作,类似于 for 循环体内的代码。

rangerangeClosed 的区别:

  • IntStream.range(start, end):生成的流范围是 [start, end),即不包含 end
  • IntStream.rangeClosed(start, end):生成的流范围是 [start, end],即包含 end

复杂操作示例:

除了简单的迭代输出外,还可以进行更复杂的操作,比如累加求和或多线程并行执行:

累加求和:

int sum = IntStream.range(1, 11).sum(); // 计算1到10的和
System.out.println("Sum: " + sum); // 输出: Sum: 55

并行执行任务:

IntStream.range(0, 5).parallel().forEach(i -> {
    System.out.println("Parallel Task: " + i + " - " + Thread.currentThread().getName());
});

这里使用 parallel() 可以将循环任务并行化,充分利用多核 CPU。

优点:

  1. 简洁优雅:相比传统的 for 循环,流式 API 更加简洁可读。
  2. 并行化:通过 parallel() 轻松实现多线程并行执行,提高性能。
  3. 函数式编程风格:使用 forEach() 等函数式接口,使代码更具表达性。

这类写法不仅使代码简洁清晰,还能帮助开发者更好地利用 Java 8 的函数式编程能力。

3.15 综合案例

    @Test
    public void testStream_zonghe() {
        List<ModeDemo06> allList = personList.stream().filter(e -> e.getName().startsWith("T")).sorted(Comparator.comparing(s -> s.getName())).collect(Collectors.toList());
        System.out.println("allList:" + allList);
    }

运行:

allList:[{name=Tom,age=24,country=美国,sex=M}]

4. Stream的collect()方法和Collectors收集器

collect是一个将管道流的结果集到一个list中的结束操作。
collect是一个将数据流缩减为一个值的归约操作。这个值可以是集合、映射,或者一个值对象。
你可以使用collect达到以下目的:
(1)、将数据流缩减为一个单一值:一个流执行后的结果能够被缩减为一个单一的值。单一的值可以是一个Collection,或者像int、double等的数值,再或者是一个用户自定义的值对象。
(2)、将一个数据流中的元素进行分组:根据任务类型将流中所有的任务进行分组。这将产生一个Map<TaskType, List>的结果,其中每个实体包含一个任务类型以及与它相关的任务。
你也可以使用除了列表以外的任何其他的集合。
如果你不需要与一任务类型相关的所有的任务,你可以选择产生一个Map<TaskType, Task>。这是一个能够根据任务类型对任务进行分类并获取每类任务中第一个任务的例子。
(3)、分割一个流中的元素:你可以将一个流分割为两组——比如将任务分割为要做和已经做完的任务。

下面看案例:
先搞一个实体作为流中的元素:

package zeh.myjavase.code42java8.demo09;

import java.math.BigDecimal;

// 测试类 Apple
class AppleDemo09 {
    private Integer id;
    private String name;
    private BigDecimal money;
    private Integer num;

    public AppleDemo09(Integer id, String name, BigDecimal money, Integer num) {
        this.id = id;
        this.name = name;
        this.money = money;
        this.num = num;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public BigDecimal getMoney() {
        return money;
    }

    public void setMoney(BigDecimal money) {
        this.money = money;
    }

    public Integer getNum() {
        return num;
    }

    public void setNum(Integer num) {
        this.num = num;
    }

    public String toString() {
        return "[id = " + this.getId() + ",name = " + this.getName() + ",money = " + this.getMoney() + ",num = " + this.getNum() + "]";
    }
}

测试Stream:

package zeh.myjavase.code42java8.demo09;

import org.junit.Before;
import org.junit.Test;

import java.math.BigDecimal;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

public class CollectorsRun {
    List<AppleDemo09> appleList = new ArrayList<>();//存放apple对象集合

    @Before
    public void beforeAppleList() {
        AppleDemo09 apple1 = new AppleDemo09(1, "苹果1", new BigDecimal("3.25"), 10);
        AppleDemo09 apple12 = new AppleDemo09(1, "苹果2", new BigDecimal("1.35"), 20);
        AppleDemo09 apple2 = new AppleDemo09(2, "香蕉", new BigDecimal("2.89"), 30);
        AppleDemo09 apple3 = new AppleDemo09(3, "荔枝", new BigDecimal("9.99"), 40);
        appleList.add(apple1);
        appleList.add(apple12);
        appleList.add(apple2);
        appleList.add(apple3);
    }
}

4.1 Collectors.toList()


    // 将数据收集进一个list列表。
    // toList收集器使用了ArrayList作为列表的实现。
    @Test
    public void testCollectors_list() {
        List<String> nameList = appleList.stream().map(AppleDemo09::getName).collect(Collectors.toList());
        nameList.forEach(System.out::println);
    }

4.2 Collectors.toSet()

    // 将数据收集进一个set集合。
    // toSet方法使用了HashSet作为集合的实现来存储结果集。
    // 注意:Set集合不能重复,并且hashSet集合是散列表顺序,如果需要确保收集的东西是唯一且不在乎顺序,可以使用toSet收集器。
    @Test
    public void testCollectors_set() {
        Set<AppleDemo09> appleSet = appleList.stream().collect(Collectors.toSet());
        appleSet.forEach(System.out::println);
    }

4.3 Collectors.toMap

4.3.1 最简单的toMap


    // 将Stream数据流收集进一个map映射,即将list集合转换成map.
    // toMap():有两个参数和三个参数的。
    // 第一个参数:目标map的key;第二个参数:目标map的value;第三个参数:目标map当key重复时,取值哪个key。
    // 需要注意的是:
    // toMap 如果集合对象有重复的key,会报错Duplicate key ....
    // apple1,apple12的id都为1。
    // 可以用 (k1,k2)->k1 来设置,如果有重复的key,则保留key1,舍弃key2
    // @Test
    public void testCollectors_toMap1() {
        //按照name作為key收集到一个map映射中
        Map<String, AppleDemo09> appleMap = appleList.stream().collect(Collectors.toMap(AppleDemo09::getName, e -> e));
        System.out.println("appleMap:" + appleMap);
    }

4.3.2 使用 Function.identity() 改进

    // 通过使用Function接口中的默认方法identity来改进上面展示的代码,如下所示,这样可以让代码更加简洁,并更好地传达开发者的意图。
    @Test
    public void testCollectors_toMap2() {
        //按照name作為key收集到一个map映射中
        Map<String, AppleDemo09> appleMap = appleList.stream().collect(Collectors.toMap(AppleDemo09::getName, Function.identity()));
        System.out.println("appleMap:" + appleMap);
    }

4.3.3 list转Map如果遇到重复key怎么办?

    // 如果收集的map中指定的key存在重复,将报错。
    // 通过使用toMap方法的另一个变体来处理重复问题,它允许我们指定一个合并方法。
    // 这个合并方法允许用户他们指定想如何处理多个值关联到同一个键的冲突。
    // 在下面展示的代码中,我们只是使用了新的值,当然你也可以编写一个智能的算法来处理冲突。
    @Test
    public void testCollectors_toMap3() {
        //按照id作为key收集到一个map映射中。id存在重复,所以toMap()方法中指定合并方法确定选择的key。
        Map<Integer, AppleDemo09> appleMap = appleList.stream().collect(Collectors.toMap(AppleDemo09::getId, a -> a, (key1, key2) -> key2));
        System.out.println("appleMap:" + appleMap);
    }

4.3.4 转换为map时可以指定单独的map存储容器

    // toMap()将Stream数据流收集到map中时,还可以指定特定的map实例进行存储。
    // 比如指定LinkedHashMap。
    @Test
    public void testCollectors_toMap4() {
        Map<Integer, AppleDemo09> appleMap = appleList.stream().collect(Collectors.toMap(AppleDemo09::getId, Function.identity(), (k1, k2) -> k2, LinkedHashMap::new));
        System.out.println("appleMap:" + appleMap);
    }

4.3.5 toConcurrentMap

    // 类似于toMap收集器,也有toConcurrentMap收集器,它产生一个ConcurrentMap而不是HashMap。
    @Test
    public void testCollectors_toMap5() {
        Map<Integer, AppleDemo09> appleMap = appleList.stream().collect(Collectors.toConcurrentMap(AppleDemo09::getId, Function.identity(), (k1, k2) -> k2));
        System.out.println("appleMap:" + appleMap);
    }

4.4 Collectors.toCollection

    // 将stream数据流收集进其他的收集容器中。
    // 像toList和toSet这类特定的收集器不允许你指定内部的列表或者集合实现。
    // 当你想要将结果收集到其它类型的集合中时,你可以像下面这样使用toCollection收集器。
    @Test
    public void testCollectors_toOthers() {
        //将数据流收集到LinkedHashSet中。
        Set<AppleDemo09> appleSet = appleList.stream().collect(Collectors.toCollection(LinkedHashSet::new));
        System.out.println("appleSet:" + appleSet);
    }

4.5 Collectors.groupingBy

    // 对list集合按照某个属性进行分组,分组后是一个Map,其中map的key为进行分组的id,value为源list进行分组后的多个list。
    @Test
    public void testCollectors_groupBy() {
        Map<Integer, List<AppleDemo09>> groupByMap = appleList.stream().collect(Collectors.groupingBy(AppleDemo09::getId));
        System.out.println("groupByMap:" + groupByMap);
    }

4.6 Collectors.partitioningBy

    // partitioningBy(分割) 和 groupingBy 的作用相似。
    // 不同的是他的key值为true/false。
    @Test
    public void testCollectors_partitioningBy() {
        Map<Boolean, List<AppleDemo09>> partioningByMap = appleList.stream().collect(Collectors.partitioningBy(e -> e.getId() > 1));
        System.out.println("partioningByMap:" + partioningByMap);
    }

4.7 Collectors.collectingAndThen方法详解

Collectors.collectingAndThen 是 Java 8 中 Collectors 类的一个静态方法,它用于对收集器的结果进行进一步处理或转换。具体来说,它可以在使用一个收集器收集数据后,执行一个额外的转换操作。

方法签名:

public static <T, A, R, RR> Collector<T, A, RR> collectingAndThen(
    Collector<T, A, R> downstream, 
    Function<R, RR> finisher
)

参数说明:

  1. downstream:一个收集器,用于收集数据,通常是已有的内置收集器,比如 Collectors.toList()Collectors.toSet() 等。
  2. finisher:一个 Function,用于对收集器生成的结果进行最终处理或转换。

返回值:

返回一个新的 Collector,该收集器先执行 downstream 收集数据,然后对结果应用 finisher 函数来生成最终的输出。

场景和用法:

collectingAndThen 常用于当你想对已有的 Collector 进行进一步的处理时。例如,将一个 List 通过 Collectors.toList() 收集起来后,再将其转换为不可修改的 List

示例 1:将 List 转换为不可修改的 List

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

public class Main {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("apple", "banana", "orange", "grape");

        // 使用 Collectors.collectingAndThen 收集 List 并转换为不可修改的 List
        List<String> unmodifiableList = list.stream()
                .collect(Collectors.collectingAndThen(
                        Collectors.toList(),          // 下游收集器:收集到 List
                        Collections::unmodifiableList // 转换函数:将 List 转换为不可修改的 List
                ));

        System.out.println(unmodifiableList);

        // 尝试修改不可修改的 List 会抛出异常
        // unmodifiableList.add("new fruit"); // 会抛出 UnsupportedOperationException
    }
}

代码解释:

  1. Collectors.toList():这是一个下游收集器,将流中的元素收集到一个 List 中。
  2. Collections::unmodifiableList:这是一个转换函数(finisher),将 List 转换为不可修改的 List
  3. Collectors.collectingAndThen():它将上述两个步骤组合在一起,先收集元素到 List,然后将 List 转换为不可修改的 List

示例 2:收集元素并计算结果(例如统计 List 中元素的个数)

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

public class Main {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("apple", "banana", "orange", "grape");

        // 使用 Collectors.collectingAndThen 收集 List 并计算 List 的大小
        int size = list.stream()
                .collect(Collectors.collectingAndThen(
                        Collectors.toList(),  // 下游收集器:收集到 List
                        List::size           // 转换函数:对 List 计算大小
                ));

        System.out.println("List size: " + size);
    }
}

代码解释:

  1. Collectors.toList():将流中的元素收集到 List 中。
  2. List::size:将收集到的 List 进一步处理,获取它的大小。

collectingAndThen 的使用场景:

  • 转换收集后的结果:在数据收集完成后,对结果进行一些不可变的操作或转换。例如将 Set 转换为不可修改的 Set,或者将收集到的 List 转换为某种特定类型的集合。
  • 计算最终的统计数据:你可以先收集数据,然后对收集到的结果进行计算或统计(如求总和、求大小等)。
  • 不可变集合:常用于确保收集后的集合是不可修改的。

小结:

Collectors.collectingAndThen 提供了一种灵活的方式,可以在使用收集器收集数据后立即对结果进行进一步的处理或转换。它结合了函数式编程的思想,将数据收集和后续处理分离,提高了代码的可读性和可维护性。

4.8 Collectors.counting()

    // 计算Stream数据流中元素的个数。
    @Test
    public void testCollectors_count() {
        Long count = appleList.stream().collect(Collectors.counting());
        System.out.println("Stream数据流中元素的个数:" + count);
    }

4.9 reduce统计总数

    // 统计stream数据流中的金额总数:本案例借助的是BigDecimal类中的初始值和自增统计方法add。
    @Test
    public void testCollectors_sum() {
        BigDecimal totalMoney = appleList.stream().map(AppleDemo09::getMoney).reduce(BigDecimal.ZERO, BigDecimal::add);
        System.out.println("总花费:" + totalMoney);
    }

4.10 Comparator.comparing

    // 使用收集器获取数据流中的最大值对应的元素对象,返回结果是一个Optional对象。
    @Test
    public void testCollectors_max_min() {
        Optional<AppleDemo09> maxResult = appleList.stream().collect(Collectors.maxBy(Comparator.comparing(AppleDemo09::getId)));
        maxResult.ifPresent(System.out::println);
        Optional<AppleDemo09> minResult = appleList.stream().collect(Collectors.minBy(Comparator.comparing(AppleDemo09::getId)));
        minResult.ifPresent(System.out::println);
    }

5. 使用Stream求两个集合的交集差集并集

直接上案例:

package zeh.myjavase.code42java8.demo10;

import org.junit.Test;

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

// java8计算交集、并集、差集
public class JiaoBingChaJiRun {
    @Test
    public void testJiaoJi() {
        List<Integer> integerList1 = Arrays.asList(1, 2, 3);
        List<Integer> integerList2 = Arrays.asList(3, 4, 5, 6);
        System.out.println("求交集:");
        List<Integer> jiaojiList = integerList1.stream().filter(e -> integerList2.contains(e)).collect(Collectors.toList());
        jiaojiList.forEach(System.out::println);
    }

    @Test
    public void testChaJi() {
        List<Integer> integerList1 = Arrays.asList(1, 2, 3);
        List<Integer> integerList2 = Arrays.asList(1, 2, 3, 4, 5, 6);
        System.out.println("求差集(list1-list2):");
        List<Integer> chajiList = integerList1.stream().filter(e -> !integerList2.contains(e)).collect(Collectors.toList());
        System.out.println("chajiList:" + chajiList.size());
        chajiList.forEach(System.out::println);
    }

    @Test
    public void testChaJi2() {
        List<Integer> integerList1 = Arrays.asList(1, 2, 3);
        List<Integer> integerList2 = Arrays.asList(3, 4, 5, 6);
        System.out.println("求差集(list2-list1):");
        List<Integer> chajiList = integerList2.stream().filter(e -> !integerList1.contains(e)).collect(Collectors.toList());
        chajiList.forEach(System.out::println);
    }

    @Test
    public void testBingJi() {
        List<Integer> list = new ArrayList<>();
        List<Integer> integerList1 = Arrays.asList(1, 2, 3);
        List<Integer> integerList2 = Arrays.asList(3, 4, 5, 6);
        list.addAll(integerList1);
        list.addAll(integerList2);
        System.out.println("求并集(不去重):");
        list.forEach(System.out::println);
    }


    @Test
    public void testBingJi2() {
        List<Integer> list = new ArrayList<>();
        List<Integer> integerList1 = Arrays.asList(1, 2, 3);
        List<Integer> integerList2 = Arrays.asList(3, 4, 5, 6);
        list.addAll(integerList1);
        list.addAll(integerList2);
        System.out.println("求并集(去重):");
        list.stream().distinct().collect(Collectors.toList()).forEach(System.out::println);
    }
}

6.Stream的skip和limit进行集合的行列去除

Java 8 Stream中的两个方法:skip()和limit()。这两个方法是Stream很常用的,不仅各自会被高频使用,还可以组合出现,并能实现一些小功能,如subList和分页等。

6.1 skip()方法

见名知义,skip()方法用于跳过前面n个元素,然后再返回新的流,如图所示:
来看看代码:

    List<Integer> result = Stream.of(1, 2, 3, 4, 5, 6).skip(4).collect(Collectors.toList());
    List<Integer> expected = asList(5, 6);
    assertEquals(expected, result);

方法skip()的参数n的四种情况:
(1)当n<0时,抛IllegalArgumentException异常;
(2)当n=0时,相当没有跳过任何元素,原封不动、完璧归赵;
(3)当0<n<length时,跳过n个元素后,返回含有剩下的元素的流;
(4)当n>=length时,跳过所有元素,返回空流。

6.2 limit()方法

对于limit()方法,它是用于限制流中元素的个数,即取前n个元素,返回新的流,如图所示:
代码如下:

    List<Integer> result = Stream.of(1, 2, 3, 4, 5, 6).limit(4).collect(Collectors.toList());
    List<Integer> expected = asList(1, 2, 3, 4);
    assertEquals(expected, result);

方法limit()的参数n的四种情况:
(1)当n<0时,抛IllegalArgumentException异常;
(2)当n=0时,不取元素,返回空流;
(3)当0<n<length时,取前n个元素,返回新的流;
(4)当n>=length时,取所有元素,原封不动、完璧归赵。

6.3 对无限流的操作

流Stream分为有限流和无限流,前面的例子我们都是使用的有限流,与Java集合类不同,流是可以无限的。对于无限流,skip()和limit()表现出了极大的差异,先上代码:

Stream.iterate(1, i -> i + 1).filter(num -> (num & (num - 1)) == 0).limit(10).forEach(System.out::println);
System.out.println("----------------");
Stream.iterate(1, i -> i + 1).filter(num -> (num & (num - 1)) == 0).skip(10).forEach(System.out::println);

执行后发现,limit()是可以将无限流转化为有限流的,所以我们也可以认为它是一个短路操作。而skip()则不行,不管你跳过了前面多少个元素,总还是会有源源不断的元素过来,无法收敛。
上述代码的结果是:
通过limit()输出了前十个2的n次方值:
1, 2, 4, 8, 16, 32, 64, 128, 256, 512
而skip()跳过了前10个,继续输出,但会不断执行下去(会有int的溢出现象):
1024, 2048, 4096, 8192, 16384, 32768…

6.4 组合应用

除了两者各自有各自的功能外,我们通过组合使用,可以实现其它功能。

6.4.1 与subList的替换

集合类如List是有subList()这个方法的,可以截取List中的某一部分,这个功能还可以通过组合skip()和limit()使用得到,如下面代码:

    List<Integer> list = asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
    List<Integer> expected = list.subList(3, 7);
    
    List<Integer> result = list.stream()
      .skip(3)
      .limit(7 - 3)
      .collect(Collectors.toList());
    assertEquals(expected, result);

6.4.2 分页

以通过组合使用skip()和limit()进行分页,如下面代码:

    int pageSize = 10;
    int pageIndex = 7;
    
    List<Integer> expected = asList(61, 62, 63, 64, 65, 66, 67, 68, 69, 70);
    List<Integer> result = Stream.iterate(1, i -> i + 1)
      .skip((pageIndex - 1) * pageSize)
      .limit(pageSize)
      .collect(Collectors.toList());
    
    assertEquals(expected, result);

上面代码例子是获取了第七页数据,每页大小为10。

6.5 总结

介绍了Java 8的Stream接口中两个常用的方法:skip()和limit(),比较简单易懂,也介绍了怎么组合使用。需要注意的是,如果Stream过大或是无限流,小心skip()会有性能问题。

7. 综合案例

实体类:

package zeh.myjavase.code42java8.demo11;

// 论文详情表
public class RequirePaperDetailVO {

    // 论文详情ID(主键)
    private Integer paperDetailId;
    
    // 论文详情中文标题
    private String detailTitleZH;
    
    // 论文详情中文内容
    private String detailContentZH;
    
    // 论文详情英文标题
    private String detailTitleEN;
    
    // 论文详情英文内容
    private String detailContentEN;
    
    // 论文选题信息ID
    private Integer paperId;
    
    // 章节ID
    private Integer chapterId;
    
    // 章节标题类别
    private String chapterType;

	public Integer getPaperDetailId() {
		return paperDetailId;
	}

	public void setPaperDetailId(Integer paperDetailId) {
		this.paperDetailId = paperDetailId;
	}

	public String getDetailTitleZH() {
		return detailTitleZH;
	}

	public void setDetailTitleZH(String detailTitleZH) {
		this.detailTitleZH = detailTitleZH;
	}

	public String getDetailContentZH() {
		return detailContentZH;
	}

	public void setDetailContentZH(String detailContentZH) {
		this.detailContentZH = detailContentZH;
	}

	public String getDetailTitleEN() {
		return detailTitleEN;
	}

	public void setDetailTitleEN(String detailTitleEN) {
		this.detailTitleEN = detailTitleEN;
	}

	public String getDetailContentEN() {
		return detailContentEN;
	}

	public void setDetailContentEN(String detailContentEN) {
		this.detailContentEN = detailContentEN;
	}

	public Integer getPaperId() {
		return paperId;
	}

	public void setPaperId(Integer paperId) {
		this.paperId = paperId;
	}

	public String getChapterType() {
		return chapterType;
	}

	public void setChapterType(String chapterType) {
		this.chapterType = chapterType;
	}

	public Integer getChapterId() {
		return chapterId;
	}

	public void setChapterId(Integer chapterId) {
		this.chapterId = chapterId;
	}
	
}

测试Stream:

package zeh.myjavase.code42java8.demo11;

import com.alibaba.fastjson.JSON;
import org.apache.commons.collections.CollectionUtils;
import org.junit.Test;

import java.util.*;
import java.util.stream.Collectors;

public class Java8Run {

    @Test
    public void test1() {
        List<Map<String, Object>> chapterMapList = new ArrayList<>();
        List<RequirePaperDetailVO> requirePaperDetailVOList2 = new ArrayList<>();
        Map map1 = new HashMap();
        Map map2 = new HashMap();
        Map map3 = new HashMap();
        map1.put("key1", "value1");
        map2.put("key1", "value1");
        map3.put("chapterId", 1234);

        chapterMapList.add(map1);
        chapterMapList.add(map2);
        chapterMapList.add(map3);

        RequirePaperDetailVO vo1 = new RequirePaperDetailVO();
        vo1.setChapterId(1234);
        vo1.setChapterType("zhao");
        vo1.setDetailContentEN("中文");
        vo1.setDetailContentEN("yingwen");
        requirePaperDetailVOList2.add(vo1);

        RequirePaperDetailVO vo2 = new RequirePaperDetailVO();
        vo2.setChapterId(1234);
        vo2.setChapterType("zhao");
        vo2.setDetailContentEN("中文");
        vo2.setDetailContentEN("yingwen");
        requirePaperDetailVOList2.add(vo2);

        List<Map<String, Object>> chapterMapList1 = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(chapterMapList) && CollectionUtils.isNotEmpty(requirePaperDetailVOList2)) {
            chapterMapList.stream().map(chapter -> requirePaperDetailVOList2.stream()
                    .filter(paperDetail -> Objects.equals(chapter.get("chapterId"), paperDetail.getChapterId()))
                    .findFirst().map(paperDetail -> {
                        chapter.put("paperDetailId", paperDetail.getPaperDetailId());
                        chapter.put("chapterNameZH", paperDetail.getDetailTitleZH());
                        chapter.put("chapterContent", paperDetail.getDetailContentZH());
                        chapter.put("chapterNameEN", paperDetail.getDetailTitleEN());
                        chapter.put("chapterContentEN", paperDetail.getDetailContentEN());
                        chapter.put("chapterType", "3");
//      chapterMapList1.add(chapter);
                        System.out.println(JSON.toJSONString(chapter));
                        chapterMapList1.add(chapter);
                        return chapter;
                    }).orElse(null)).filter(Objects::nonNull).collect(Collectors.toList());
            System.out.println(JSON.toJSONString(chapterMapList1));
        }

    }
}

文档信息

Search

    Table of Contents