前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java Stream 优雅编程

Java Stream 优雅编程

作者头像
测试蔡坨坨
发布2024-06-18 14:47:16
1050
发布2024-06-18 14:47:16
举报

“阅读本文大概需要10分钟。

你好,我是测试蔡坨坨。

Stream 流式编程的出现,显著推进了Java对函数式编程的支持,它允许开发者可以用声明式的方式处理数据集合(比如列表、数组等),还能有效利用多核处理器进行并行操作,提升应用程序的性能,同时保持代码简洁易读。

本篇,我们将深入探讨Stream API。

Stream流初体验

为了体验这玩意到底有多爽,我们先来举个简单的栗子。

需求:

  1. 创建一个集合,存储多个字符串元素
  2. 把所有以“范”开头的元素存储到新的集合中
  3. 把所有以“范”开头,长度为3的元素存储到新的集合中
  4. 遍历打印最终结果

传统实现:

代码语言:javascript
复制
package top.caituotuo.intermediate.streamDemo;

import java.util.ArrayList;

/**
 * author: 测试蔡坨坨 caituotuo.top
 * datetime: 2024/6/9 14:48
 * function: 不使用Stream流
 */
public class StreamDemo1 {
    // 创建一个集合,存储多个字符串
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("范闲");
        list.add("范建");
        list.add("范思哲");
        list.add("范若若");
        list.add("林婉儿");

        // 把所有以“范”开头的元素存储到新的集合中
        ArrayList<String> newList = new ArrayList<>();

        for (String name : list) {
            if (name.startsWith("范")) {
                newList.add(name);
            }
        }

        System.out.println(newList);

        // 把所有以“范”开头,长度为3的元素存储到新的集合中
        ArrayList<String> newList2 = new ArrayList<>();

        for (String name : newList) {
            if (name.startsWith("范") && name.length() == 3) {
                newList2.add(name);
            }
        }

        System.out.println(newList2);

        // 遍历打印最终结果
        for (String name : newList2) {
            System.out.println(name);
        }
    }
}

使用Stream流的方式实现:

代码语言:javascript
复制
package top.caituotuo.intermediate.streamDemo;

import java.util.ArrayList;

/**
 * author: 测试蔡坨坨 caituotuo.top
 * datetime: 2024/6/9 14:48
 * function: Stream流初体验
 */
public class StreamDemo2 {
    // 创建一个集合,存储多个字符串
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("范闲");
        list.add("范建");
        list.add("范思哲");
        list.add("范若若");
        list.add("林婉儿");

        // 使用Stream流的方式实现(只需要一句代码)
        list.stream().filter(name -> name.startsWith("范")).filter(name -> name.length() == 3).forEach(System.out::println);
    }
}

Stream流的思想

经过以上简单的栗子,我们不难发现,使用 Stream流 可以让我们的代码变得非常简洁。

(:人生苦短,就不要浪费时间在重复造轮子这件事情上了。

下面,我们就开始正式地学习它。

首先,我们先来解释一下什么是

这个我们可以将它理解为工厂的流水线,假设有一条制作饮料的流水线,它将会是这样的:

检查瓶子 --> 消毒 --> 灌装 --> 密封 --> 包装

Java中的Stream流跟它类似,我们会把要操作的数据都放到流水线上,进行多轮过滤,最终输出剩余的数据:

创建流并将元素放到流中 --> 过滤操作(留下以范开头的) --> 过滤操作(留下长度为3的) --> 输出操作

Stream流的使用步骤

  1. 先得到一条Stream流(流水线),并把数据放上去
  2. 使用中间方法(intermediate operation)对流水线上的数据进行操作(比如:过滤、转换等,方法调用完毕之后,还可以调用其他的方法)
  3. 使用终端方法/终结方法(terminal operation)对流水线上的数据进行操作(比如:统计、打印等,终端方法是Stream流的最后一步,调用完毕之后,不能再调用其他方法)

同时,Stream流在使用的时候会结合Lambda表达式,简化集合、数组的操作。

获取Stream流

如何获取一条流水线,并把数据放上去?

流可以通过多种方式生产,每种方法适用于不同的场景。

从固定元素或数据结构中创建

获取方法

方法名

说明

单列集合(ArrayList、LinkedList、HashSet、TreeSet等)

default Streamstream()

Collection中的默认方法,对于任何实现了Collection接口的集合都可以使用stream方法创建流

双列集合

无法直接使用Stream流,需要通过keySet()或者entrySet()先转成单列集合,再获取Stream流

数组

public staticStreamstream(T[] array)

Arrays工具类中的静态方法Arrays.stream()

一堆零散的数据(没有放到集合或数组中的数据)

public staticStreamof(T... values)

Stream接口中的静态方法Stream.of(),但是这些数据需要是同种数据类型的

合并两个流

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

合并两个流

单列集合
代码语言:javascript
复制
package top.caituotuo.intermediate.streamDemo.createStreamDemo;

import java.util.ArrayList;
import java.util.Collections;
import java.util.stream.Stream;

/**
 * author: 测试蔡坨坨 caituotuo.top
 * datetime: 2024/6/10 16:58
 * function: 单列集合获取Stream流
 */
public class createStreamByCollection {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "范闲", "范建", "范思哲", "范若若", "林婉儿");

        // 获取到一条流水线,并把集合中的数据放到流水线上
        // 由于ArrayList是Collection接口的实现类,所以可以直接使用Collection接口的stream()方法获取Stream流
        list.stream().forEach(s -> System.out.println(s));
        System.out.println("--------------------");
        list.stream().forEach(System.out::println);
    }
}
双列集合

keySet():

代码语言:javascript
复制
package top.caituotuo.intermediate.streamDemo.createStreamDemo;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.stream.Stream;

/**
 * author: 测试蔡坨坨 caituotuo.top
 * datetime: 2024/6/10 16:58
 * function: 双列集合获取Stream流
 */
public class createStreamByMap {
    public static void main(String[] args) {
        // 创建双列集合
        HashMap<String, Integer> hm = new HashMap<>();

        // 添加元素
        hm.put("范闲", 24);
        hm.put("范建", 40);
        hm.put("范思哲", 20);
        hm.put("范若若", 23);
        hm.put("林婉儿", 23);

        // 获取Stream流
        hm.keySet().stream().forEach(System.out::println);
        hm.keySet().stream().forEach(s -> System.out.println(hm.get(s)));
    }
}

entrySet():

代码语言:javascript
复制
package top.caituotuo.intermediate.streamDemo.createStreamDemo;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.stream.Stream;

/**
 * author: 测试蔡坨坨 caituotuo.top
 * datetime: 2024/6/10 16:58
 * function: 双列集合获取Stream流
 */
public class createStreamByMap {
    public static void main(String[] args) {
        // 创建双列集合
        HashMap<String, Integer> hm = new HashMap<>();

        // 添加元素
        hm.put("范闲", 24);
        hm.put("范建", 40);
        hm.put("范思哲", 20);
        hm.put("范若若", 23);
        hm.put("林婉儿", 23);
        
        // entrySet 把所有的键值对对象放到Stream流中
        hm.entrySet().stream().forEach(System.out::print); // 范建=40林婉儿=23范闲=24范思哲=20范若若=23 无序 HashMap的底层是哈希表 不能保证存取的顺序
    }
}
数组
代码语言:javascript
复制
package top.caituotuo.intermediate.streamDemo.createStreamDemo;

import java.util.Arrays;
import java.util.HashMap;

/**
 * author: 测试蔡坨坨 caituotuo.top
 * datetime: 2024/6/10 16:58
 * function: 数组获取Stream流
 */
public class createStreamByArrays {
    public static void main(String[] args) {
        // 创建数组
        // 基本数据类型 "范闲-男-24", "范建-男-40", "范思哲-男-20", "范若若-女-23", "林婉儿-女-23"
        int[] array1 = {24, 40, 20, 23, 23};
        // 引用数据类型
        String[] array2 = {"范闲", "范建", "范思哲", "范若若", "林婉儿"};

        // 获取Stream流
        Arrays.stream(array1).forEach(System.out::println);
        Arrays.stream(array2).forEach(System.out::println);
    }
}
一堆零散的数据
代码语言:javascript
复制
package top.caituotuo.intermediate.streamDemo.createStreamDemo;

import java.util.stream.Stream;

/**
 * author: 测试蔡坨坨 caituotuo.top
 * datetime: 2024/6/10 10:27
 * function: 一堆零散的数据(没有放到集合或数组中),使用of()方法创建Stream流
 */
public class CreateStreamByOf {
    public static void main(String[] args) {
        // 创建一个整数流
        Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).forEach(System.out::println);

        // 创建一个字符串流
        Stream.of("范闲", "范建", "范思哲", "范若若", "林婉儿").forEach(System.out::println);
    }
}

注意点:

Stream.of方法的形参是一个可变参数,可变参数的底层是数组,所以可以传递一堆零散的数据,也可以传递数组:

代码语言:javascript
复制
String[] arr = {"范闲", "范建", "范思哲", "范若若", "林婉儿"};
Stream.of(arr).forEach(System.out::println);

但是,数组必须是引用数据类型,如果传递的是基本数据类型,是会把整个数组当做一个元素,放到Stream当中:

代码语言:javascript
复制
int[] arr = {24, 40, 20, 23, 23};
Stream.of(arr).forEach(System.out::println); // [I@15aeb7ab 打印出来是地址
合并两个流

两个流的数据类型尽量保持一致,如果不一致,合并后流的数据类型就是它两共同的父类,相当于对数据类型进行了提升,就会导致无法使用子类里面的方法。

代码语言:javascript
复制
package top.caituotuo.intermediate.streamDemo.createStreamDemo;

import java.util.ArrayList;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
 * author: 测试蔡坨坨
 * datetime: 2024/6/9 14:48
 * function: concat合并两个流
 */
public class ConcatStream {
    public static void main(String[] args) {
        Stream<String> stream1 = Stream.of("a", "b", "c");
        Stream<String> stream2 = Stream.of("d", "e", "f");
        // 合并两个流
        Stream.concat(stream1, stream2).forEach(System.out::println);
    }
}
动态构建

以上栗子都是从固定元素或数据结构中创建的,元素数量和内容在创建时就已经确定,如果我们想动态构建一个流,比如根据特定条件动态的决定是否将元素加入流中,我们可以使用StreamBuilder流构建器来添加元素和构建流。

代码语言:javascript
复制
package top.caituotuo.intermediate.streamDemo.createStreamDemo;

import java.util.stream.Stream;

/**
 * author: 测试蔡坨坨 caituotuo.top
 * datetime: 2024/6/10 20:09
 * function: StreamBuilder动态构建流
 */
public class StreamBuilderDemo {
    public static void main(String[] args) {
        // 创建一个StreamBuilder对象
        Stream.Builder<String> streamBuilder = Stream.builder();

        // 通过add方法添加元素到流中
        streamBuilder.add("测试");
        streamBuilder.add("蔡坨坨");

        // 根据条件动态添加,表示这里有50%的概率
        if (Math.random() > 0.5) {
            streamBuilder.add("小趴蔡");
        }

        // 通过builder方法来生成Stream对象
        // 一旦调用了build方法,StreamBuilder对象就不能再添加元素
        Stream<String> stream = streamBuilder.build();
        // streamBuilder.add("caituotuo.top"); // IllegalStateException
        stream.forEach(System.out::println);
    }
}
从文件创建流

从文件创建流也是一个非常实用的功能,特别适合于文本分析,日志文件处理等场景。

我们可以通过Java的Files类的lines方法来实现:

caituotuo.txt

代码语言:javascript
复制
范闲
范思哲
范建
陈萍萍
林婉儿
叶轻眉
代码语言:javascript
复制
package top.caituotuo.intermediate.streamDemo.createStreamDemo;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;

/**
 * author: 测试蔡坨坨 caituotuo.top
 * datetime: 2024/6/10 20:18
 * function: 通过文件创建流
 */
public class CreateStreamByFile {
    public static void main(String[] args) {
        // 获取文件路径
        Path path = Paths.get("src/main/java/top/caituotuo/intermediate/streamDemo/createStreamDemo/caituotuo.txt");

        // 通过Files.lines打开指定路径的文件
        // 它会逐行读取文件内容,每行文本都会被仿作一行字符串处理,然后将其作为元素放入流中
        try (Stream<String> lines = Files.lines(path)) {
            lines.forEach(System.out::println);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

运行结果:

基本类型创建流

此外,对于基本类型的处理,Stream API 专门提供了IntStream、LongStream、DoubleStream来分别处理int、long和double类型,通过使用range和rangeClosed等方法,可以方便地创建这些基本数据类型的流。

代码语言:javascript
复制
package top.caituotuo.intermediate.streamDemo.createStreamDemo;

import java.util.Random;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.Stream;

/**
 * author: 测试蔡坨坨 caituotuo.top
 * datetime: 2024/6/10 20:35
 * function: 创建基本数据类型的流
 */
public class CreateStreamByPrimitiveData {
    public static void main(String[] args) {
        IntStream intStream = IntStream.of(1, 2, 3, 4, 5);
        intStream.forEach(System.out::println);

        // 使用range方法创建一个指定范围的int流
        IntStream intStream2 = IntStream.range(1, 6); // 包含1到5 不包含6
        intStream2.forEach(System.out::println);

        // 使用rangeClosed方法创建一个包含两个指定值的int流
        IntStream intStream3 = IntStream.rangeClosed(1, 6); // 包含1到6
        intStream3.forEach(System.out::println);

        // 使用Random类生成生成一个包含随机整数的流
        new Random().ints(5).forEach(System.out::println);

        // 把基本类型的流转化成对象流
        // 通过在基本类型上调用boxed
        IntStream intStream4 = IntStream.of(1, 2, 3, 4, 5);
        Stream<Integer> stream = intStream4.boxed();
        stream.forEach(System.out::println);
    }
}
无限流

无限流,顾名思义就是没有固定大小的流,它可以无限的生成元素,我们可以通过generate和iterate这两个方法来创建。处理无限流时需要谨慎,防止无限循环的发生。因此,通常会结合limit等操作来限制流的元素数量。

代码语言:javascript
复制
package top.caituotuo.intermediate.streamDemo.createStreamDemo;

import java.util.stream.Stream;

/**
 * author: 测试蔡坨坨 caituotuo.top
 * datetime: 2024/6/10 21:07
 * function: 创建无限流
 */
public class CreateStreamByGenerateAndIterate {
    public static void main(String[] args) {
        // 使用generate方法创建一个无限流
        // 它主要用于生成重复的值或者生成随机数据
        // 生成重复的值
        Stream.generate(() -> "caituotuo")
                .limit(5) // 限制流的大小为5
                .forEach(System.out::println); // 打印流中的元素
        // 生成随机数据
        Stream.generate(Math::random)
                .limit(5) // 限制流的大小为5
                .forEach(System.out::println); // 打印流中的元素

        // 使用iterate方法创建一个无限流
        // 它主要用于生成数学的序列或实现迭代算法
        // 创建一个无限流,从0开始,每次增加1(等差数列)
        Stream.iterate(0, n -> n + 1)
                .limit(10) // 限制流的大小为10
                .forEach(System.out::println); // 打印流中的元素
    }
}
中间方法intermediate operations

中间操作用于对流中的元素进行处理,比如筛选、映射、排序等。

方法名

说明

Streamfilter(Predicate<? super T> predicate)

过滤

Streamlimit(long maxSize)

获取前几个元素

Streamskip(long n)

跳过前几个元素

Streamdistinct()

元素去重,依赖hashCode和equals方法(如果流的元素是自定义对象时,需要重写hashCode和equals方法)

Streammap(Function<? super T, ? extends R> mapper)

转换流中的数据类型

排序

注意:

中间方法,返回新的Stream流,原来的Stream流只能使用一次,建议使用链式编程

代码语言:javascript
复制
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "范闲", "范建", "范思哲", "范若若", "林婉儿");

// 假设不使用链式编程
Stream<String> stream1 = list.stream().filter(s -> s.startsWith("范"));
Stream<String> stream2 = stream1.filter(s -> s.length() == 3);
stream2.forEach(System.out::println);

// 再次对stream1进行操作,会报错 stream has already been operated upon or closed
// Stream<String> stream3 = stream1.filter(s -> s.length() == 3);

修改Stream流中的数据,不会影响原来集合或数组中的数据

代码语言:javascript
复制
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "范闲", "范建", "范思哲", "范若若", "林婉儿");

// 对流中的数据进行两次过滤
Stream<String> stream1 = list.stream().filter(s -> s.startsWith("范"));
Stream<String> stream2 = stream1.filter(s -> s.length() == 3);
stream2.forEach(System.out::println);
// 在打印原集合,原集合还是原来的数据
System.out.println("原集合:" + list); // 原集合:[范闲, 范建, 范思哲, 范若若, 林婉儿]

根据操作性质,中间操作可以分为以下几个功能类别:

  • 筛选和切片(Filtering and Slicing):用于过滤或缩减流中的元素数量,包括filter、distinct、limit、skip
  • 映射(Mapping):用于转换流中的元素或提取元素的特定属性,包括map、flatMap、mapToInt等
  • 排序(Sorting):用于对流中的元素进行排序,包括sorted
筛选和切片
filter()

filter()方法的形参Predicate是一个FunctionalInterface函数式接口,所以可以使用匿名内部类或Lambda表达式实现:

代码语言:javascript
复制
package top.caituotuo.intermediate.streamDemo.intermediate;

import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Predicate;

/**
 * author: 测试蔡坨坨 caituotuo.top
 * datetime: 2024/6/10 21:42
 * function: filter过滤
 */
public class intermediateDemo1 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "范闲", "范建", "范思哲", "范若若", "林婉儿");

        // 过滤 把“范”开头的留下,其余的不要
        // 匿名内部类实现
        list.stream().filter(new Predicate<String>() {
            @Override
            public boolean test(String s) {
                // 如果返回值为true,表示当前数据留下
                // 如果返回值为false,表示当前数据不要
                return s.startsWith("范");
            }
        }).forEach(s -> System.out.println(s));

        // 改成Lambda表达式
        // () -> {}
        // 只有一个参数,参数类型可以省略 s
        // 方法体为 s.startsWith("范")
        // s -> s.startsWith("范")
        list.stream().filter(s -> s.startsWith("范")).forEach(System.out::println);
    }
}
limit()
代码语言:javascript
复制
package top.caituotuo.intermediate.streamDemo.intermediate;

import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Predicate;
import java.util.stream.Stream;

/**
 * author: 测试蔡坨坨 caituotuo.top
 * datetime: 2024/6/10 21:42
 * function: limit(N)获取前N个元素
 */
public class intermediateDemo2 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "范闲", "范建", "范思哲", "范若若", "林婉儿");

        list.stream().limit(3).forEach(System.out::println);
        /*
        运行结果:打印前3个
                范闲
                范建
                范思哲
         */
    }
}
skip()
代码语言:javascript
复制
package top.caituotuo.intermediate.streamDemo.intermediate;

import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Predicate;
import java.util.stream.Stream;

/**
 * author: 测试蔡坨坨 caituotuo.top
 * datetime: 2024/6/10 21:42
 * function: skip(N)跳过前N个元素
 */
public class intermediateDemo2 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "范闲", "范建", "范思哲", "范若若", "林婉儿");

        list.stream().skip(3).forEach(System.out::println);
        /*
        运行结果:
            范若若
            林婉儿
         */
    }
}
distinct()
代码语言:javascript
复制
package top.caituotuo.intermediate.streamDemo.intermediate;

import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Predicate;
import java.util.stream.Stream;

/**
 * author: 测试蔡坨坨 caituotuo.top
 * datetime: 2024/6/10 21:42
 * function: distinct去重
 */
public class intermediateDemo2 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "范闲", "范建", "范思哲", "范若若", "林婉儿", "范闲");

        // 去除重复的元素
        list.stream().distinct().forEach(System.out::println);
    }
}

distinct的源码非常的复杂,可以简单的看一下重点部分,先是调用了makeRef方法,makeRef方法里面可以看到调用了HashSet,所以distinct的底层其实是调用了HashSet进行去重的,而HashSet在存储自定义元素的时候会重写hashCode和equals方法:

映射
map()

映射本质上是一个数据转换的过程,map方法可以通过提供的函数,将流中的每个元素转换成新的元素,最后生成一个新元素构成的流。

map的形参是Function,也是一个函数式接口:

代码语言:javascript
复制
package top.caituotuo.intermediate.streamDemo.intermediate;

import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Function;

/**
 * author: 测试蔡坨坨 caituotuo.top
 * datetime: 2024/6/10 21:42
 * function: map类型转换
 */
public class intermediateDemo3 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "范闲-24", "范建-40", "范思哲-20", "范若若-23", "林婉儿-23");

        // 只获取集合里面的年龄 String --> int
        // Function第一个泛型表示原本的数据类型,第二个泛型表示转换后的数据类型
        // apply方法的形参s:依次表示流里面的每个数据
        // 返回值:表示转换后的数据
        list.stream()
                .map(new Function<String, Integer>() {
                    @Override
                    public Integer apply(String s) {
                        return Integer.parseInt(s.split("-")[1]);
                    }
                })
                .forEach(s -> System.out.println(s));

        System.out.println("--------------------------------");

        list.stream()
                .map(s -> Integer.parseInt(s.split("-")[1]))
                .forEach(System.out::println);
    }
}
flatMap()

map方法适用于单层结构的流,进行元素一对一的转换,例如更改数据类型或提取信息,但对于嵌套的数组或其他多层结构的数据处理不够灵活,在这些情况下,flatMap成为更合适的选择,它不仅能够执行map的转换功能,还能够扁平化多层数据结构,将它们转换合并成一个单层流。

处理嵌套列表:

代码语言:javascript
复制
package top.caituotuo.intermediate.streamDemo.intermediate;

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

/**
 * author: 测试蔡坨坨 caituotuo.top
 * datetime: 2024/6/10 22:59
 * function: 处理嵌套列表
 */
public class FlatMapExample {
    public static void main(String[] args) {
        List<List<String>> nestedList = Arrays.asList(
                Arrays.asList("a", "b", "c"),
                Arrays.asList("d", "e"),
                Arrays.asList("f", "g", "h")
        );

        List<String> flatList = nestedList.stream()
                .flatMap(List::stream)
                .collect(Collectors.toList());

        System.out.println(flatList); // 输出: [a, b, c, d, e, f, g, h]
    }
}

处理嵌套数组:

代码语言:javascript
复制
package top.caituotuo.intermediate.streamDemo.intermediate;

import java.util.Arrays;

/**
 * author: 测试蔡坨坨 caituotuo.top
 * datetime: 2024/6/10 23:04
 * function: 处理嵌套数组
 */
public class FlatMapArrayExample {
    public static void main(String[] args) {
        String[][] nestedArray = {
                {"a", "b", "c"},
                {"d", "e"},
                {"f", "g", "h"}
        };

        String[] flatArray = Arrays.stream(nestedArray)
                .flatMap(Arrays::stream)
                .toArray(String[]::new);

        System.out.println(Arrays.toString(flatArray)); // 输出: [a, b, c, d, e, f, g, h]
    }
}

处理对象流:

代码语言:javascript
复制
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

class Person {
    private String name;
    private List<String> hobbies;

    public Person(String name, List<String> hobbies) {
        this.name = name;
        this.hobbies = hobbies;
    }

    public List<String> getHobbies() {
        return hobbies;
    }
}

public class FlatMapObjectExample {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
            new Person("Joker", Arrays.asList("Reading", "Hiking")),
            new Person("Jane", Arrays.asList("Cooking", "Running")),
            new Person("Jack", Arrays.asList("Swimming", "Cycling"))
        );

        List<String> hobbies = people.stream()
                                     .flatMap(person -> person.getHobbies().stream())
                                     .collect(Collectors.toList());

        System.out.println(hobbies); // 输出: [Reading, Hiking, Cooking, Running, Swimming, Cycling]
    }
}

简单来说,map方法用于元素一对一的转换,而flatMap不仅可以用于转换,还用于将多层的流合并为一个单层流,另外我们也可以通过mapToInt、mapToLong、mapToDouble等方法将流转换位对应的数值流。

排序

排序分为自然排序和自定义排序,当流中的元素类型实现了Comparable接口时,比如字符串或包装类型的数字,如Integer、Long等,可以直接调用无参数的sorted方法,按照自然顺序进行排序。

代码语言:javascript
复制
package top.caituotuo.intermediate.streamDemo.intermediate;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

/**
 * author: 测试蔡坨坨 caituotuo.top
 * datetime: 2024/6/10 21:42
 * function:
 */
public class intermediateDemo4 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "cherry", "apple", "blueberry", "pear");

        // 排序
        list.stream().sorted().forEach(System.out::println);

        System.out.println("--------------------");
        // 自定义排序规则 长度从短到长排序
        list.stream().sorted((s1, s2) -> s1.length() - s2.length()).forEach(System.out::println);

        System.out.println("--------------------");

        // Comparator接口的实现类 长度从短到长排序
        list.stream()
                .sorted(Comparator.comparingInt(String::length))
                .forEach(System.out::println);

        System.out.println("--------------------");

        // Comparator接口的实现类 长度从长到短排序
        list.stream()
                .sorted(Comparator.comparingInt(String::length).reversed())
                .forEach(System.out::println);
    }
}
终端方法terminal operation

常见的终端方法有以下四个:

名称

说明

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

遍历(返回值是void,所以forEach结束后不能再调用流里面的其他方法)

long count();

统计(返回值是long类型的整数,所以也不能在调用流里面的其他方法)

Object[] toArray();

收集流中的数据,放到数组中

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

收集流中的数据,放到集合中

根据操作性质,终端操作可以分为以下几个功能类别:

  • 查找与匹配(Search and Match):这类操作也属于短路操作,当这些操作在找到符合条件的元素后,会立即结束处理返回结果,而不需要处理整个流,有效短路了流的遍历,提高了处理效率,特别适用在快速筛选或验证数据的场景中,包括anyMatch、noneMatch、allMatch、findFirst、findAny
  • 聚合操作(Aggregation):count、max、min、average、sum
  • 规约操作(Reduction):reduce
  • 收集操作(Collection):collect
  • 迭代操作(Iteration):forEach
forEach()

可以看到forEach的形参是一个Consumer<? super T> action

Consumer是一个函数式接口,所以可以使用匿名内部类或Lambda表达式实现:

使用匿名内部类实现:

代码语言:javascript
复制
package top.caituotuo.intermediate.streamDemo;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.function.Consumer;

/**
 * author: 测试蔡坨坨
 * datetime: 2024/6/9 14:48
 * function: 终端方法forEach 匿名内部类
 */
public class StreamDemo9 {
    public static void main(String[] args) {
        // 创建一个集合,存储多个字符串
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "范闲", "范建", "范思哲", "范若若", "林婉儿");

        // 遍历获取流里面的每一个元素并打印
        // 匿名内部类实现
        // Consumer的泛型:表示流中数据的类型,这里是String类型
        // accept方法的形参:表示对流中的每一个元素,跟泛型里面的保持一致,也是String类型
        // accept方法体:对每一个数据的处理操作
        list.stream().forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });
    }
}

运行结果:

使用Lambda表达式实现:

代码语言:javascript
复制
package top.caituotuo.intermediate.streamDemo;

import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Consumer;

/**
 * author: 测试蔡坨坨
 * datetime: 2024/6/9 14:48
 * function: 终端方法forEach Lambda表达式
 */
public class StreamDemo10 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "范闲", "范建", "范思哲", "范若若", "林婉儿");

        // 遍历获取流里面的每一个元素并打印
        list.stream().forEach(name -> System.out.println(name));
    }
}

运行结果:

count()
代码语言:javascript
复制
package top.caituotuo.intermediate.streamDemo;

import java.util.ArrayList;
import java.util.Collections;

/**
 * author: 测试蔡坨坨
 * datetime: 2024/6/9 14:48
 * function: 终端方法 count
 */
public class StreamDemo11 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "范闲", "范建", "范思哲", "范若若", "林婉儿");

        // 当然这里可以直接用list.size()来获取集合的长度,使用count就显得有点蠢了
        long count = list.stream().count();
        System.out.println(count);

        // count主要用于统计筛选后的结果有多少个
        long count2 = list.stream().filter(name -> name.startsWith("范")).count();
        System.out.println(count2);
    }
}

运行结果:

toArray()

toArray有两个方法,一个是返回Object对象,另一个是返回具体的类型:

空参,返回Object对象:

代码语言:javascript
复制
package top.caituotuo.intermediate.streamDemo;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.function.IntFunction;

/**
 * author: 测试蔡坨坨
 * datetime: 2024/6/9 14:48
 * function: 终端方法 toArray 空参 返回Object数组
 */
public class StreamDemo12 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "范闲", "范建", "范思哲", "范若若", "林婉儿");

        // 空参 返回Object数组
        Object[] array = list.stream().toArray();
        System.out.println(Arrays.toString(array)); // [范闲, 范建, 范思哲, 范若若, 林婉儿]
    }
}

传参,指定返回的数组类型:形参是IntFunction

IntFunction也是一个函数式接口

匿名内部类实现:

代码语言:javascript
复制
package top.caituotuo.intermediate.streamDemo;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.function.IntFunction;

/**
 * author: 测试蔡坨坨
 * datetime: 2024/6/9 14:48
 * function: 终端方法 toArray 指定数组类型 匿名内部类实现
 */
public class StreamDemo12 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "范闲", "范建", "范思哲", "范若若", "林婉儿");

        // 非空参 返回指定类型数组
        // 匿名内部类实现
        // IntFunction的泛型:具体类型的数组,这里就是String类型
        // apply的形参:表示流中数据的个数,要跟数组的长度保持一致
        // apply的返回值:表示数组的类型,要跟泛型一致
        // apply的方法体:创建数组

        // toArray方法的参数的作用:创建一个指定类型的数组
        // toArray方法的底层:会依次得到流里面的每一个数据,并把数据放到数组中
        // toArray方法的返回值:装着流里面所有数据的数组
        String[] array2 = list.stream().toArray(new IntFunction<String[]>() {
            @Override
            public String[] apply(int value) {
                return new String[value];
            }
        });
        System.out.println(Arrays.toString(array2)); // [范闲, 范建, 范思哲, 范若若, 林婉儿]
    }
}

Lambda表达式实现:

代码语言:javascript
复制
package top.caituotuo.intermediate.streamDemo;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.function.IntFunction;

/**
 * author: 测试蔡坨坨
 * datetime: 2024/6/9 14:48
 * function: 终端方法 toArray lambda表达式实现
 */
public class StreamDemo13 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "范闲", "范建", "范思哲", "范若若", "林婉儿");

        // 非空参 返回指定类型数组
        // lambda表达式实现
        // lambda表达式格式:() -> {}
        // 形参只有一个,数据类型可以省略,再根据指定的个数value创建数组 value -> new String[value]
        String[] array = list.stream().toArray(value -> new String[value]);
        System.out.println(Arrays.toString(array)); // [范闲, 范建, 范思哲, 范若若, 林婉儿]);
    }
}
collect()

收集流中的数据,放到集合中(List、Set、Map)

代码语言:javascript
复制
package top.caituotuo.intermediate.streamDemo;

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

/**
 * author: 测试蔡坨坨
 * datetime: 2024/6/9 14:48
 * function: 终端方法 collect (toList、toSet)
 */
public class StreamDemo14 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "范闲-男-24", "范建-男-40", "范思哲-男-20", "范若若-女-23", "林婉儿-女-23", "范闲-男-24");

        // 收集到List集合中 ArrayList
        // 收集所有的男性
        List<String> newList = list.stream()
                .filter(s -> "男".equals(s.split("-")[1]))
                .collect(Collectors.toList());
        System.out.println(newList); // [范闲-男-24, 范建-男-40, 范思哲-男-20, 范闲-男-24]

        // 收集到Set集合中 HashSet
        Set<String> newSet = list.stream()
                .filter(s -> "男".equals(s.split("-")[1]))
                .collect(Collectors.toSet());
        System.out.println(newSet); // [范建-男-40, 范闲-男-24, 范思哲-男-20]
    }
}

收集到LIst和收集到Set的区别:

  • ArrayList是有序的,且不会去重
  • HashSet是无序的,且会进行去重
  • Set的查询效率是O(1),LIst的查询效率是O(N)

匿名内部类实现收集到Map集合中:

代码语言:javascript
复制
package top.caituotuo.intermediate.streamDemo;

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

/**
 * author: 测试蔡坨坨
 * datetime: 2024/6/9 14:48
 * function: 终端方法 collect Map
 */
public class StreamDemo15 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "范闲-男-24", "范建-男-40", "范思哲-男-20", "范若若-女-23", "林婉儿-女-23");

        // 收集到Map集合中
        // 键值对
        // 查找所有男性 -> 收集到Map集合中 键:姓名 值:年龄
        /*
        toMap:
            第一个参数:键的生成规则
            第二个参数:值的生成规则
        参数1:
            Function泛型1:表示流中每个数据的类型
                    泛型2:表示Map集合中键的数据类型
            apply方法的形参:依次表示流里面的每一个数据
                 方法体:生成键的逻辑
                 返回值:已经生成的键

        参数2:
            Function泛型1:表示流中每个数据的类型
                    泛型2:表示Map集合中值的数据类型
            apply方法的形参:依次表示流里面的每一个数据
                 方法体:生成值的逻辑
                 返回值:已经生成的值
                 
        注意:收集到Map集合中,键不能重复,否则会报错
         */
        Map<String, Integer> newMap = list.stream()
                .filter(s -> "男".equals(s.split("-")[1]))
                .collect(Collectors.toMap(
                        new Function<String, String>() {
                            @Override
                            public String apply(String s) {
                                return s.split("-")[0];
                            }
                        },
                        new Function<String, Integer>() {
                            @Override
                            public Integer apply(String s) {
                                return Integer.parseInt(s.split("-")[2]);
                            }
                        }));

        System.out.println(newMap);
    }
}
reduce()

对流中的元素进行聚合操作。

代码语言:javascript
复制
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class ReduceExample1 {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        Optional<Integer> sum = numbers.stream()
                                       .reduce((a, b) -> a + b);

        sum.ifPresent(System.out::println); // 输出: 15
    }
}
代码语言:javascript
复制
import java.util.Arrays;
import java.util.List;

public class ReduceExample2 {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        int sum = numbers.stream()
                         .reduce(0, (a, b) -> a + b);

        System.out.println(sum); // 输出: 15
    }
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-06-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 测试蔡坨坨 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Stream流初体验
  • Stream流的思想
  • Stream流的使用步骤
    • 获取Stream流
      • 从固定元素或数据结构中创建
      • 动态构建
      • 从文件创建流
      • 基本类型创建流
      • 无限流
    • 中间方法intermediate operations
      • 筛选和切片
      • 映射
      • 排序
    • 终端方法terminal operation
      • forEach()
      • count()
      • toArray()
      • collect()
      • reduce()
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档