Skip to content

Stream流与Guava工具类使用

1、Stream API

1、了解 Stream

Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则是 Stream API(java.util.stream.*)。Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。

2、什么是 Stream

  • 流(Stream) 到底是什么呢?

    是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。“集合讲的是数据讲的是计算!”

注意

  • Stream 自己不会存储元素
  • Stream不会改变源对象。相反,他们会返回一个持有结果的新Stream
  • Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

方法链式调用: Stream 操作可以通过方法链式调用的方式构建操作序列。在这个序列中,每个中间操作都会返回一个新的 Stream 对象,但实际的操作并没有立即发生。只有在终端操作(如 forEachcollect)被调用时,整个操作序列才会开始执行。

java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> stream = numbers.stream()
    .filter(n -> n % 2 == 0)   // 中间操作
    .map(n -> n * n)           // 中间操作
    .limit(3);                 // 中间操作

stream.forEach(System.out::println);  // 终端操作,触发计算

惰性求值: Stream 操作在进行中间操作时,并不会立即遍历整个集合元素。它只会记录操作步骤,等到终端操作需要结果时,才会实际遍历并计算。

短路操作: 由于 Stream 操作是延迟执行的,某些操作可以在满足条件时立即停止,不再继续遍历后续元素。比如,findFirstfindAny 操作会在找到满足条件的第一个元素后立即停止遍历。

java
Optional<Integer> firstEvenSquare = numbers.stream()
    .filter(n -> n % 2 == 0)
    .map(n -> n * n)
    .findFirst();  // 找到第一个满足条件的元素后立即停止

3、Stream 的操作三个步骤

  • 创建 Stream

    一个数据源(如:集合、数组),获取一个流

  • 中间操作

    一个中间操作链,对数据源的数据进行处理

  • 终止操作(终端操作)

    一个终止操作,执行中间操作链,并产生结果

image-20230814152158443

4、创建 Stream

Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:

  • default Stream<E> stream() : 返回一个顺序流
  • default Stream<E> parallelStream() : 返回一个并行流
java
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream(); //获取一个顺序流
Stream<String> parallelStream = list.parallelStream(); //获取一个并行流
1、由数组创建流

Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:

  • static <T> Stream<T> stream(T[] array): 返回一个流

重载形式,能够处理对应基本类型的数组:

  • public static IntStream stream(int[] array)
  • public static LongStream stream(long[] array)
  • public static DoubleStream stream(double[] array)
java
Integer[] nums = new Integer[10];
Stream<Integer> stream = Arrays.stream(nums);
2、由值创建流

可以使用静态方法 Stream.of(), 通过显示值创建一个流。它可以接收任意数量的参数。

  • public static<T> Stream<T> of(T... values) : 返回一个流
java
Stream<Integer> stream2 = Stream.of(1,2,3,4,5,6);
3、由函数创建流:创建无限流

可以使用静态方法 Stream.iterate() 和Stream.generate(), 创建无限流。

  • 迭代 public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)

    java
    Stream<Integer> stream3 = Stream.iterate(0, (x) -> x + 2).limit(10);
    stream3.forEach(System.out::println);
  • 生成 public static<T> Stream<T> generate(Supplier<T> s)

    java
    Stream<Double> stream4 = Stream.generate(Math::random).limit(2);
    stream4.forEach(System.out::println);

5、测试Stream操作准备

1、UserInfo.class

为后续测试准备的用户实体类

java
package com.xx.stream;

import lombok.Data;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;

/**
 * @Author: xueqimiao
 * @Date: 2023/8/14 21:17
 */
@Data
public class UserInfo implements Serializable {

    /**
     * 用户id
     */
    private Integer id;

    /**
     * 用户名
     */
    private String userName;

    /**
     * 年龄
     */
    private Integer age;

    /**
     * 生日
     */
    private Date birthday;

    /**
     * 薪资
     */
    private Double salary;
    
    /**
     * BigDecimal类型的 薪资
     */
    private BigDecimal salaryDecimal;

    public UserInfo() {
    }

    public UserInfo(Integer id, String userName, Integer age, Date birthday, Double salary,BigDecimal salaryDecimal) {
        this.id = id;
        this.userName = userName;
        this.age = age;
        this.birthday = birthday;
        this.salary = salary;
        this.salaryDecimal = salaryDecimal;
    }
}
2、添加单元测试依赖
java
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
</dependency>
3、初始化数据
java
package com.xx.stream;

import com.xx.utils.DateTimeUtil;
import org.junit.Before;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author: xueqimiao
 * @Date: 2023/8/14 21:22
 */
public class StreamAPITest {

    List<UserInfo> userList01 = new ArrayList<>();
    List<UserInfo> userList02 = new ArrayList<>();

    @Before
    public void init(){
        // 初始化 userList01 数据
        userList01.add(new UserInfo(1, "张雨涵", 18, DateTimeUtil.parseToDate("2005-09-16"), 8500.0, new BigDecimal("8500.0")));
        userList01.add(new UserInfo(2, "王子涵", 19, DateTimeUtil.parseToDate("2004-07-18"), 8500.0, new BigDecimal("8500.0")));
        userList01.add(new UserInfo(3, "李宇航", 18, DateTimeUtil.parseToDate("2005-06-17"), 9000.0, new BigDecimal("9000.0")));
        userList01.add(new UserInfo(4, "赵心怡", 17, DateTimeUtil.parseToDate("2006-06-16"), 14000.0, new BigDecimal("14000.0")));
        userList01.add(new UserInfo(5, "杨一帆", 16, DateTimeUtil.parseToDate("2007-10-21"), 23000.0, new BigDecimal("23000.0")));
        userList01.add(new UserInfo(6, "刘梦洁", 19, DateTimeUtil.parseToDate("2004-09-14"), 25000.0, new BigDecimal("25000.0")));

        // 初始化 userList02 数据
        userList02.add(new UserInfo(2, "王子涵", 19, DateTimeUtil.parseToDate("2004-07-18"), 8500.0, new BigDecimal("8500.0")));
        userList02.add(new UserInfo(6, "刘梦洁", 19, DateTimeUtil.parseToDate("2004-09-14"), 25000.0, new BigDecimal("25000.0")));
        userList02.add(new UserInfo(7, "谢雨欣", 17, DateTimeUtil.parseToDate("2006-05-18"), 6000.0, new BigDecimal("6000.0")));
        userList02.add(new UserInfo(8, "许子轩", 19, DateTimeUtil.parseToDate("2004-06-13"), 5000.0, new BigDecimal("5000.0")));
    }

}

6、Stream 的中间操作

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”

1、筛选与切片
方 法描 述
filter(Predicate p)接收 Lambda , 从流中排除某些元素。
distinct()筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
limit(long maxSize)截断流,使其元素不超过给定数量。
skip(long n)跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素
不足 n 个,则返回一个空流。与 limit(n) 互补
java
@Test
public void test01(){
    // 过滤出年龄大于18岁的用户
    // 所有的中间操作不会做任何的处理
    Stream<UserInfo> userInfoStream = userList01.stream().filter((e) -> e.getAge() > 18);
    // 只有当做终止操作时,所有的中间操作会一次性的全部执行,称为“惰性求值”
    userInfoStream.forEach(System.out::println);
}

@Test
public void test02(){
    // 根据年龄去重 
    // 注意要重写 UserInfo 的 equals 和 hashCode 方法
    userList01.stream().distinct().forEach(System.out::println);
}

@Test
public void test03(){
    // 获取 userList01 前2个数据
    userList01.stream().limit(2).forEach(System.out::println);
}

@Test
public void test04(){
    // 获取 userList01 第3个和第4个数据
    userList01.stream().skip(2).limit(2).forEach(System.out::println);
}
java
@Override
public boolean equals(Object o) {
    if (this == o) {
        return true;
    }
    if (o == null || getClass() != o.getClass()) {
        return false;
    }
    UserInfo userInfo = (UserInfo) o;
    return age.equals(userInfo.age);
}

@Override
public int hashCode() {
    return Objects.hash(age);
}
2、映射
方 法描 述
map(Function f)接收一个函数作为参数,该函数会被应用到每个元
素上,并将其映射成一个新的元素。
mapToDouble(ToDoubleFunction f)接收一个函数作为参数,该函数会被应用到每个元
素上,产生一个新的 DoubleStream。
mapToInt(ToIntFunction f)接收一个函数作为参数,该函数会被应用到每个元
素上,产生一个新的 IntStream。
mapToLong(ToLongFunction f)接收一个函数作为参数,该函数会被应用到每个元
素上,产生一个新的 LongStream。
flatMap(Function f)接收一个函数作为参数,将流中的每个值都换成另
一个流,然后把所有流连接成一个流
java
@Test
public void test05() {
    // 获取 userList01 中的所有id
    userList01.stream().map(UserInfo::getId).forEach(System.out::println);
    // 把 userList01 每个人的年龄都加1岁
    userList01.stream().map((e) -> e.getAge() + 1).forEach(System.out::println);
}

@Test
public void test06() {
    userList01.stream().mapToDouble(UserInfo::getSalary).forEach(System.out::println);
    userList01.stream().mapToInt(UserInfo::getId).forEach(System.out::println);
    userList01.stream().mapToLong(UserInfo::getId).forEach(System.out::println);
}

@Test
public void test07() {
    // 使用 flatMap 方法将字符串流映射为字符流
    List<String> strList = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
    List<Character> charList = strList.stream()
        .flatMap(str -> str.chars().mapToObj(c -> (char) c))
        .collect(Collectors.toList());
    charList.forEach(System.out::println);
}
3、排序
方 法描 述
sorted()产生一个新流,其中按自然顺序排序
sorted(Comparator comp)产生一个新流,其中按比较器顺序排序
java
@Test
public void test08() {

    // 取出所有年龄 并排序 打印年龄
    userList01.stream().map(UserInfo::getAge).sorted().forEach(System.out::println);

    // 将 userList01 中 年龄排序
    userList01.stream().sorted((e1, e2) -> Integer.compare(e1.getAge(), e2.getAge())).forEach(System.out::println);

    // 将 userList01 中 年龄排序 年龄一样 按工资高到底排序
    userList01.stream()
        .sorted((x, y) -> {
            if (x.getAge().equals(y.getAge())) {
                return BigDecimal.valueOf(y.getSalary()).compareTo(BigDecimal.valueOf(x.getSalary()));
            } else {
                return Integer.compare(x.getAge(), y.getAge());
            }
        }).forEach(System.out::println);
}

7、Stream 的终止操作

终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:ListInteger,甚至是 void

1、查找与匹配
方 法描 述
allMatch(Predicate p)检查是否匹配所有元素
anyMatch(Predicate p)检查是否至少匹配一个元素
noneMatch(Predicate p)检查是否没有匹配所有元素
findFirst()返回第一个元素
findAny()返回当前流中的任意元素
count()返回流中元素总数
max(Comparator c)返回流中最大值
min(Comparator c)返回流中最小值
forEach(Consumer c)内部迭代(使用 Collection 接口需要用户去做迭
代,称为外部迭代。相反,Stream API 使用内部
迭代——它帮你把迭代做了)
java
@Test
public void test09() {
    // 判断 userList01 中的所有年龄是不是全部大于16岁
    boolean b = userList01.stream().allMatch(o -> o.getAge() > 16);
    System.out.println(b);

    // 判断 userList01 中的年龄 有没有大于 16 岁的
    boolean b1 = userList01.stream().anyMatch(o -> o.getAge() > 16);
    System.out.println(b1);

    // 判断 userList01 中的年龄 是不是没有 大于 16 岁的
    boolean b2 = userList01.stream().noneMatch(o -> o.getAge() > 16);
    System.out.println(b2);
}

@Test
public void test10() {
    // 获取 userList01 中 年龄最大的第一个用户

    Optional<UserInfo> optional = userList01.stream().sorted((e1, e2) -> Integer.compare(e2.getAge(), e1.getAge())).findFirst();
    System.out.println(optional.get());

    userList01.stream().sorted((e1, e2) -> Integer.compare(e2.getAge(), e1.getAge())).limit(1).forEach(System.out::println);



    // 返回 userList01 中年龄大于17的任意一个用户
    Optional<UserInfo> optional1 = userList01.stream().filter((e) -> e.getAge() > 17).findAny();
    System.out.println(optional1.get());

    // 返回 userList01 用户总数
    long count = userList01.stream().count();
    System.out.println(count);
    System.out.println(userList01.size());

    // 获取 userList01 中 年龄最大的第一个用户
    userList01.stream().max((e1, e2) -> Integer.compare(e1.getAge(), e2.getAge())).ifPresent(System.out::println);

    // 获取 userList01 中 年龄最小的第一个用户
    userList01.stream().min((e1, e2) -> Integer.compare(e1.getAge(), e2.getAge())).ifPresent(System.out::println);
}
2、归约
方 法描 述
reduce(T iden, BinaryOperator b)可以将流中元素反复结合起来,得到一个值。
返回 T
reduce(BinaryOperator b)可以将流中元素反复结合起来,得到一个值。
返回 Optional<T>
java
@Test
public void test11() {
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    Integer sum = list.stream()
        .reduce(0, (x, y) -> x + y);
    System.out.println(sum);

    // 累加 userList01 中所有用户的年龄
    Integer sum1 = userList01.stream()
        .map(UserInfo::getAge)
        .reduce(0, (x, y) -> x + y);
    System.out.println(sum1);

    Integer sum2 = userList01.stream()
        .map(UserInfo::getAge)
        .reduce(Integer::sum).get();
    System.out.println(sum2);
}

@Test
public void test12(){

    // 统计 userList01 中 名字中 洁 出现的次数
    OptionalInt reduce = userList01.stream()
        .map(UserInfo::getUserName)
        .flatMapToInt(CharSequence::chars)
        .map((ch) -> {
            if (ch == '洁') {
                return 1;
            } else {
                return 0;
            }
        }).reduce(Integer::sum);

    System.out.println(reduce.getAsInt());


    long count2 = userList01.stream()
        .map(UserInfo::getUserName)
        .flatMapToInt(CharSequence::chars)
        .filter((e) -> e == '洁')
        .count();

    System.out.println(count2);
}
3、收集
方 法描 述
collect(Collector c)将流转换为其他形式。接收一个 Collector接口的
实现,用于给Stream中元素做汇总的方法

​ Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到 List、Set、Map)。但是 Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:

方法返回类型作用代码
toListList<T>把流中元素收集到ListList<UserInfo> collect = userList01.stream().collect(Collectors.toList());
toSetSet<T>把流中元素收集到SetSet<UserInfo> collect1 = userList01.stream().collect(Collectors.toSet());
toCollectionCollection<T>把流中元素收集到创建的集合Collection<UserInfo> collect2 = userList01.stream().collect(Collectors.toCollection(ArrayList::new));
countingLong计算流中元素的个数Long collect3 = userList01.stream().collect(Collectors.counting());
summingIntInteger对流中元素的整数属性求和Integer collect4 = userList01.stream().collect(Collectors.summingInt(UserInfo::getAge));
averagingIntDouble计算流中元素Integer属性的平均值Double collect5 = userList01.stream().collect(Collectors.averagingInt(UserInfo::getAge));
summarizingIntIntSummaryStatistics收集流中Integer属性的和IntSummaryStatistics collect6 = userList01.stream().collect(Collectors.summarizingInt(UserInfo::getAge));
joiningString连接流中每个字符串String collect7 = userList01.stream().map(UserInfo::getUserName).collect(Collectors.joining());
maxByOptional<T>根据比较器选择最大值Optional<UserInfo> collect9 = userList01.stream().collect(Collectors.maxBy(Comparator.comparingInt(UserInfo::getAge)));
minByOptional<T>根据比较器选择最小值Optional<UserInfo> collect10 = userList01.stream().collect(Collectors.minBy(Comparator.comparingInt(UserInfo::getAge)));
reducing归约产生的类型从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值int total = userList01.stream().collect(Collectors.reducing(0, UserInfo::getAge, Integer::sum));
collectingAndThen转换函数返回的类型包裹另一个收集器,对其结果转换函数Integer collect11 = userList01.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
groupingByMap<K, List<T>>根据某属性值对流分组,属性为K,结果为VMap<Integer, List<UserInfo>> collect12 = userList01.stream().collect(Collectors.groupingBy(UserInfo::getAge));
partitioningByMap<Boolean, List<T>>根据true或false进行分区Map<Boolean, List<UserInfo>> collect13 = userList01.stream().collect(Collectors.partitioningBy((e) -> e.getAge() > 16));
java
@Test
public void test13() {

    // 把流中元素收集到List
    List<UserInfo> collect = userList01.stream().collect(Collectors.toList());

    // 把流中元素收集到Set
    Set<UserInfo> collect1 = userList01.stream().collect(Collectors.toSet());

    // 把流中元素收集到创建的集合
    Collection<UserInfo> collect2 = userList01.stream().collect(Collectors.toCollection(ArrayList::new));

    // 计算流中元素的个数
    Long collect3 = userList01.stream().collect(Collectors.counting());

    // 对流中元素的整数属性求和
    Integer collect4 = userList01.stream().collect(Collectors.summingInt(UserInfo::getAge));

    // 计算流中元素Integer属性的平均值
    Double collect5 = userList01.stream().collect(Collectors.averagingInt(UserInfo::getAge));

    // 收集流中Integer属性的和
    IntSummaryStatistics collect6 = userList01.stream().collect(Collectors.summarizingInt(UserInfo::getAge));

    // 连接流中每个字符串
    String collect7 = userList01.stream().map(UserInfo::getUserName).collect(Collectors.joining());
    String collect8 = userList01.stream().map(UserInfo::getUserName).collect(Collectors.joining(","));

    // 根据比较器选择最大值
    Optional<UserInfo> collect9 = userList01.stream().collect(Collectors.maxBy(Comparator.comparingInt(UserInfo::getAge)));

    // 根据比较器选择最小值
    Optional<UserInfo> collect10 = userList01.stream().collect(Collectors.minBy(Comparator.comparingInt(UserInfo::getAge)));

    // 从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值
    int total = userList01.stream().collect(Collectors.reducing(0, UserInfo::getAge, Integer::sum));

    // 包裹另一个收集器,对其结果转换函数
    Integer collect11 = userList01.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));


    // 根据某属性值对流分组,属性为K,结果为V
    Map<Integer, List<UserInfo>> collect12 = userList01.stream().collect(Collectors.groupingBy(UserInfo::getAge));

    // 根据true或false进行分区
    Map<Boolean, List<UserInfo>> collect13 = userList01.stream().collect(Collectors.partitioningBy((e) -> e.getAge() > 16));
}
java
@Test
public void test14() {
    // 大于 18 岁的成年人、大于 17 岁的年轻人和小于等于 17 岁的小孩
    Map<String, List<UserInfo>> groupedMap = userList01.stream()
        .collect(Collectors.groupingBy(user -> {
            if (user.getAge() >= 18) {
                return "成年人";
            } else if (user.getAge() >= 17) {
                return "年轻人";
            } else {
                return "小孩";
            }
        }));
    System.out.println(groupedMap);
    groupedMap.forEach((k, v) -> {
        System.out.println(k);
        System.out.println(v);
    });
}

@Test
public void test15(){
    // 分区 薪资大于2万
    Map<Boolean, List<UserInfo>> collect = userList01.stream().collect(Collectors.partitioningBy((e) -> e.getSalary() > 20000));
    collect.forEach((k, v) -> {
        System.out.println(k);
        System.out.println(v);
    });
}

8、并行流与串行流

并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。串行流是顺序处理每个元素,而并行流会将数据分成多个部分并在多个线程上同时处理。

需要注意的是,并行流可能会在多核处理器上提高性能,但不是所有情况下都会比串行流更快,因为并行处理也会涉及线程管理等开销。在实际应用中,选择使用并行流还是串行流要根据具体情况进行权衡和测试。

Java 8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API 可以声明性地通过 parallel()sequential() 在并行流与顺序流之间进行切换。

java
@Test
public void test16() {
    // 使用串行流计算所有用户的年龄总和
    int totalAgeSerial = userList01.stream()
        .mapToInt(UserInfo::getAge)
        .sum();

    System.out.println(totalAgeSerial);

    // 使用并行流计算所有用户的年龄总和
    int totalAgeParallel = userList01.parallelStream()
        .mapToInt(UserInfo::getAge)
        .sum();

    System.out.println(totalAgeParallel);
    
    int sum = userList01.parallelStream()
        .mapToInt(UserInfo::getAge)
        .sequential()
        .sum();

    System.out.println(sum);
}

9、map和peek的区别

在Java的Stream API中,mappeek都是用于对流中的元素进行处理的中间操作,但它们有不同的用途和行为。

假设我们想要创建一个新的用户列表,其中每个用户的姓名都加一个标识,并且在处理每个用户时,在控制台打印出用户的姓名

java
@Test
public void test18() {
    List<UserInfo> updatedListMap = userList01.stream()
        .map(user -> {
            return new UserInfo(user.getId(), user.getUserName() + "Map", user.getAge() + 1, user.getBirthday(), user.getSalary(), user.getSalaryDecimal());
        })
        .toList();

    for (UserInfo userInfo : updatedListMap) {
        System.out.println(userInfo.getUserName());
    }

    List<UserInfo> updatedListPeek = userList01.stream()
        .peek(user -> {
            user = new UserInfo(user.getId(), user.getUserName() + "Peek", user.getAge() + 1, user.getBirthday(), user.getSalary(), user.getSalaryDecimal());
            System.out.println(user.getUserName());
        }).toList();

    for (UserInfo userInfo : updatedListPeek) {
        System.out.println(userInfo.getUserName());
    }
}
  • 使用 map 操作: 我们使用map来创建一个新的用户对象,同时在map操作内部打印用户姓名。map操作将流中的每个元素映射为一个新的值,并将这些新值收集到一个新的列表中。
  • 使用 peek 操作: 我们使用peek来进行中间处理,并在peek操作内部打印用户姓名。peek操作不会改变流中的元素,而是允许你在处理元素时执行某些操作,然后将元素继续传递给后续的操作。

总结来说,map操作是用于映射流中的每个元素到一个新值,而peek操作是用于在流的处理过程中执行一些附加的操作,不会改变流中的元素。

10、stream 流 和 foreach 处理数据效率比较

Stream API的优势:

  • 并行处理: 使用并行流(通过.parallelStream())可以将任务分发给多个线程并在多核处理器上同时处理,从而加速数据处理。这对于大规模数据集特别有用。
  • 内部优化: Stream API内部可以进行一些优化,例如延迟执行和短路操作,以减少不必要的计算。

forEach的优势:

  • 简洁性: forEach方法提供了一种简单的方式来对集合中的每个元素执行操作,特别是在只需要顺序处理每个元素时,它可能更加直观和简洁。
  • 无需创建新流: 使用forEach方法可以避免创建新的流,从而减少一些内存开销。

尽管Stream API在大多数情况下具有更好的性能和扩展性,但在某些情况下,forEach可能会更适合。例如,当你只需要对每个元素执行一个简单的操作,而不需要进行其他的流操作时,使用forEach可能更直观和简单。

如果你需要对数据进行多个操作、筛选、映射等,或者希望利用并行处理优势,那么Stream API可能更适合。如果只是需要对每个元素执行简单的操作,forEach可能更合适。最好的方法是在具体情况下进行测试和评估,以确定哪种方法最适合你的需求

脱离业务的吹捧技术都是耍流氓!!!

java
@Test
public void test19(){

    System.out.println("开始添加数据");
    for (int i = 0; i < 10000; i++) {
        userList01.add(new UserInfo(6, "刘梦洁", 19, DateTimeUtil.parseToDate("2004-09-14"), 25000.0, new BigDecimal("25000.0")));
    }
    System.out.println("结束添加数据");
    // 使用 stream 计算工资总和

    long start = System.currentTimeMillis();
    BigDecimal bigDecimal = userList01.stream().map(UserInfo::getSalaryDecimal).reduce(BigDecimal.ZERO, BigDecimal::add);
    long end = System.currentTimeMillis();
    System.out.println("stream 耗时:" + (end - start) + "ms");
    System.out.println(bigDecimal);

    // 使用 parallelStream 计算工资总和
    //        long start = System.currentTimeMillis();
    //        BigDecimal bigDecimal = userList01.parallelStream().map(UserInfo::getSalaryDecimal).reduce(BigDecimal.ZERO, BigDecimal::add);
    //        long end = System.currentTimeMillis();
    //        System.out.println("parallelStream 耗时:" + (end - start) + "ms");
    //        System.out.println(bigDecimal);

    // 使用 foreach 计算工资总和
    //        long start = System.currentTimeMillis();
    //        BigDecimal bigDecimal = BigDecimal.ZERO;
    //        for (UserInfo userInfo : userList01) {
    //            bigDecimal = bigDecimal.add(userInfo.getSalaryDecimal());
    //        }
    //        long end = System.currentTimeMillis();
    //        System.out.println("foreach 耗时:" + (end - start) + "ms");
    //        System.out.println(bigDecimal);
}

以下数据仅作参考,具体数据与电脑配置不一样会有所差异

数据、方式streamparallelStreamforeach
1W4ms5ms3ms
10W6ms15ms4ms
100W23ms49ms17ms
1000W447ms317ms491ms
  • 单纯循环用foreach
  • 对数据进行多个操作、筛选、映射stream(串行)

11、FunctionUtils.class

java
package com.xx.util;

import com.google.common.collect.Lists;
import com.xx.utils.BigDecimalUtil;
import com.xx.utils.ValidationUtil;
import org.springframework.lang.Nullable;

import java.math.BigDecimal;
import java.util.*;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @ClassName: FunctionUtils
 * @Author: xueqimiao
 * @Date: 2021/11/19 10:50
 */
@SuppressWarnings("all")
public interface FunctionUtils {
    
    /**
     * 当重复时,选择第一个。
     * @return  函数,它总是返回第一个值。
     * @param <T>
     */
    static <T> BinaryOperator<T> selectFirstOne() {
        return (existing, replacement) -> existing;
    }



    /**
     * 比较器:这个元素到第一个
     *
     * @param element element
     * @param <T>     the type of the input and output objects to the function
     * @return 函数,该函数总是将元素排序到第一个
     */
    static <T> Comparator<T> goToFirst(T element) {
        return (s1, s2) -> Objects.equals(s1, element) ? -1 : (Objects.equals(s2, element) ? 1 : 0);
    }


    /**
     *
     * 返回集合流,null为空流,用于lambda使用
     *
     * @param collection 收集
     * @param <T>        type
     * @return null as empty stream
     */
    static <T> Stream<T> nullAsEmptyStream(@Nullable Collection<T> collection) {
        if (ValidationUtil.isEmpty(collection)) {
            return Stream.empty();
        }
        return collection.stream();
    }

    /**
     * 返回集合的parallelStream, null为空strparallelstream,用于lambda使用
     * @param collection
     * @return
     * @param <T>
     */
    static <T> Stream<T> nullAsEmptyParallelStream(@Nullable Collection<T> collection) {
        if (ValidationUtil.isEmpty(collection)) {
            return Stream.empty();
        }
        return collection.parallelStream();
    }

    /**
     * 批处理和并行执行功能。如有必要,请区分参数。
     * 参数:
     * originParams - origin参数 parameterConverter -参数转换 execFunction - exec函数 batchSize -批量大小
     * 返回:
     * 批处理和并行结果
     * @param originParams
     * @param parameterConverter 参数转换
     * @param execFunction exec函数
     * @param batchSize 批量大小
     * @return
     */
    static <T, R, U> List<U> batchAndParallel(@Nullable List<T> originParams,
                                              Function<List<T>, R> parameterConverter,
                                              Function<R, U> execFunction,
                                              Integer batchSize) {
        if (ValidationUtil.isEmpty(originParams)) {
            return Collections.emptyList();
        }
        //Keep in mind that the partitions are sublist views of the original collection
        List<List<T>> partition = Lists.partition(originParams, batchSize);
        return partition.parallelStream()
                .map(parameterConverter)
                .map(execFunction)
                .collect(Collectors.toList());
    }

    /**
     * 封装最常用的map函数
     *
     * @param list     集合
     * @param function 你想要进行的操作,需要带返回值,lambda写法例如:{@code i-> i.getId()或者 User::getId }
     * @param <O>      原来的集合中元素类型
     * @param <R>      map之后的集合中元素类型
     * @return 执行了map后的集合
     */
    static <O, R> List<R> map(List<O> list, Function<O, R> function) {
        List<R> collect = FunctionUtils.nullAsEmptyStream(list).map(function).collect(Collectors.toList());
        if (!ValidationUtil.isEmpty(collect)) {
            collect.removeIf(o -> ValidationUtil.isEmpty(o));
        }
        return collect;
    }

    static <O, R> Set<R> mapToSet(List<O> list, Function<O, R> function) {
        return FunctionUtils.nullAsEmptyStream(list).map(function).collect(Collectors.toSet());
    }

    /**
     * 封装了最常用的map函数的变种peek函数
     *
     * @param list     集合
     * @param function 你想要进行的操作
     * @param <E>      集合中元素类型
     * @return 执行了peek后的集合
     */
    static <E> List<E> peek(List<E> list, Consumer<E> function) {
        return FunctionUtils.nullAsEmptyStream(list).peek(function).collect(Collectors.toList());
    }

    /**
     * 封装了使用Stream过滤集合的方法
     *
     * @param list      集合
     * @param predicate 过滤的条件,lambda写法例如:{@code i-> i.getId() != null }
     * @param <E>       集合中元素类型
     * @return 过滤后的集合
     */
    static <E> List<E> filter(List<E> list, Predicate<E> predicate) {
        return FunctionUtils.nullAsEmptyStream(list).filter(predicate).collect(Collectors.toList());
    }

    /**
     * list转map
     * {@code List<实体>} 转为 {@code Map<实体属性,实体>}
     *
     * @param list        集合
     * @param keyFunction 作为key的lambda
     * @param <E>         集合类型
     * @param <K>         key的类型
     * @return {@code Map<实体属性,实体>}
     */
    static <E, K> Map<K, E> toMap(List<E> list, Function<E, K> keyFunction) {
        return FunctionUtils.nullAsEmptyStream(list).collect(HashMap::new, (m, v) -> m.put(Optional.ofNullable(v).map(keyFunction).orElse(null), v), HashMap::putAll);
    }

    static <E, K, U> Map<K, U> toMap(List<E> list, Function<E, K> keyFunction, Function<E, U> valueMapper) {
        return FunctionUtils.nullAsEmptyStream(list).collect(HashMap::new, (m, v) -> m.put(Optional.ofNullable(v).map(keyFunction).orElse(null), Optional.ofNullable(v).map(valueMapper).orElse(null)), HashMap::putAll);
    }

    /**
     * 通过实体属性分组
     * {@code List<实体>} 转为 {@code Map<实体属性,List<实体>>}
     *
     * @param list        集合
     * @param keyFunction 作为key的lambda
     * @param <E>         集合类型
     * @param <K>         key的类型
     * @return {@code Map<实体属性,List<实体>>}
     */
    static <E, K> Map<K, List<E>> group(List<E> list, Function<E, K> keyFunction) {
        return groupThen(list, keyFunction, Collectors.toList());
    }

    /**
     * List 转 Map
     *
     * @param list        集合
     * @param keyFunction 作为key的lambda
     * @param <E>         集合类型
     * @param <K>         key的类型
     * @return 一对一
     */
    static <E, K> Map<K, E> listToMap(List<E> list, Function<E, K> keyFunction) {
        return FunctionUtils.nullAsEmptyStream(list).collect(HashMap::new, (m, v) -> m.put(Optional.ofNullable(v).map(keyFunction).orElse(null), v), HashMap::putAll);
    }


    /**
     * 通过实体属性分组,还能继续后续操作
     * {@code List<实体>} 转为 {@code Map<实体属性,?>}
     *
     * @param list        集合
     * @param keyFunction 作为key的lambda
     * @param downstream  可能你还想进行别的操作
     * @param <E>         集合类型
     * @param <K>         key的类型
     * @param <R>         你想进行的后续操作类型
     * @return {@code Map<实体属性,?>}
     */
    static <E, K, R> Map<K, R> groupThen(List<E> list, Function<E, K> keyFunction, Collector<E, ?, R> downstream) {
        return FunctionUtils.nullAsEmptyStream(list).collect(Collectors.groupingBy(keyFunction, downstream));
    }

    /**
     * 对集合进行排序 默认升序
     *
     * @param list         需要排序的集合
     * @param keyExtractor 指定排序字段的函数
     * @param
     * @param <T>          集合元素类型
     * @param <R>          排序字段类型
     * @return 排序后的集合
     */
    static <T, R extends Comparable<? super R>> List<T> sort(List<T> list, Function<T, R> keyExtractor) {
        return sort(list, keyExtractor, true);
    }

    /**
     * 对集合进行排序
     *
     * @param list         需要排序的集合
     * @param keyExtractor 指定排序字段的函数
     * @param isAscending  是否为升序排序
     * @param <T>          集合元素类型
     * @param <R>          排序字段类型
     * @return 排序后的集合
     */
    static <T, R extends Comparable<? super R>> List<T> sort(List<T> list, Function<T, R> keyExtractor, boolean isAscending) {
        Comparator<T> comparator = Comparator.comparing(keyExtractor);
        if (!isAscending) {
            comparator = comparator.reversed();
        }
        return FunctionUtils.nullAsEmptyStream(list)
                .sorted(comparator)
                .collect(Collectors.toList());
    }

    /**
     * 根据指定 字段 取交集
     *
     * @param list1
     * @param list2
     * @param keyExtractor
     * @param <T>
     * @param <K>
     * @return
     */
    static <T, K> List<T> intersection(List<T> list1, List<T> list2, Function<T, K> keyExtractor) {
        Set<K> set = nullAsEmptyStream(list2).map(keyExtractor).collect(Collectors.toSet());
        return nullAsEmptyStream(list1).filter(e -> set.contains(keyExtractor.apply(e))).collect(Collectors.toList());
    }

    /**
     * 两个集合取并集
     *
     * @param list1
     * @param list2
     * @param <T>
     * @return
     */
    static <T> List<T> union(List<T> list1, List<T> list2) {
        Set<T> set = new HashSet<>(list1);
        set.addAll(list2);
        return new ArrayList<>(set);
    }

    /**
     * 两个集合取差集
     *
     * @param list1
     * @param list2
     * @param <T>
     * @return
     */
    static <T> List<T> difference(List<T> list1, List<T> list2) {
        Set<T> set = new HashSet<>(list2);
        return nullAsEmptyStream(list1).filter(e -> !set.contains(e)).collect(Collectors.toList());
    }

    /**
     * 两个集合取差集 根据指定字段
     *
     * @param list1
     * @param list2
     * @param keyExtractor
     * @param <T>
     * @param <K>
     * @return
     */
    static <T, K> List<T> difference(List<T> list1, List<T> list2, Function<T, K> keyExtractor) {
        Set<K> set = nullAsEmptyStream(list2).map(keyExtractor).collect(Collectors.toSet());
        return nullAsEmptyStream(list1).filter(e -> !set.contains(keyExtractor.apply(e))).collect(Collectors.toList());
    }

    /**
     * 两个几个取补集
     *
     * @param list1
     * @param list2
     * @param <T>
     * @return
     */
    static <T> List<T> complement(List<T> list1, List<T> list2) {
        List<T> difference1 = difference(list1, list2);
        List<T> difference2 = difference(list2, list1);
        List<T> union = union(difference1, difference2);
        return union;
    }

    /**
     * 累加指定的值
     *
     * @param list
     * @param fieldExtractor
     * @param operator
     * @param initialValue   初始值
     * @param <T>
     * @param <K>
     * @return reduce(list, User : : getMoney, BigDecimalUtil : : add, new BigDecimal ( " 0 "));
     */
    static <T, K> K reduce(List<T> list, Function<T, K> fieldExtractor, BinaryOperator<K> operator, K initialValue) {
        return FunctionUtils.nullAsEmptyStream(list)
                .map(fieldExtractor)
                .reduce(initialValue, operator);
    }

    static <T> BigDecimal bigDecimalAdd(List<T> list, Function<T, BigDecimal> fieldExtractor) {
        return FunctionUtils.nullAsEmptyStream(list)
                .map(fieldExtractor)
                .reduce(BigDecimalUtil::add)
                .orElse(BigDecimal.ZERO);
    }

}

12、交集、差集、并集、补集

java
@Test
public void test20(){
    // 根据 id 取出 userList01 和 userList02 中相同的用户 交集
    List<UserInfo> sameUserList = userList01.stream()
        .filter(user -> userList02.stream().anyMatch(user2 -> user.getId().equals(user2.getId())))
        .toList();
    for (UserInfo userInfo : sameUserList) {
        System.out.println(userInfo);
    }
    System.out.println("---------");
    FunctionUtils.intersection(userList01, userList02,UserInfo::getId).forEach(System.out::println);
}

@Test
public void test21(){
    // 根据 id 取出 userList01 在 userList02 中没有的用户 差集
    List<UserInfo> diffUserList = userList01.stream()
        .filter(user -> userList02.stream().noneMatch(user2 -> user.getId().equals(user2.getId())))
        .toList();
    for (UserInfo userInfo : diffUserList) {
        System.out.println(userInfo);
    }
    System.out.println("---------");
    FunctionUtils.difference(userList01, userList02,UserInfo::getId).forEach(System.out::println);
}

@Test
public void test22(){
    // 合并 userList01 userList02 中的用户 并集
    List<UserInfo> unionUserList = Stream.of(userList01, userList02)
        .flatMap(Collection::stream)
        .distinct()
        .toList();
    for (UserInfo userInfo : unionUserList) {
        System.out.println(userInfo);
    }
    System.out.println("---------");
    FunctionUtils.union(userList01, userList02).forEach(System.out::println);
}

@Test
public void test23(){
    //  userList01 userList02 补集
    List<UserInfo> complementUserList = Stream.of(userList01, userList02)
        .flatMap(Collection::stream)
        .distinct()
        .filter(user -> userList01.stream().noneMatch(user2 -> user.getId().equals(user2.getId()))
                || userList02.stream().noneMatch(user2 -> user.getId().equals(user2.getId())))
        .toList();
    for (UserInfo userInfo : complementUserList) {
        System.out.println(userInfo);
    }
    System.out.println("---------");
    FunctionUtils.complement(userList01, userList02).forEach(System.out::println);
}

2、Guava工具类

1、Maven依赖

xml
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>32.1.1-jre</version>
</dependency>

2、 ImmutableCollections 不可变集合

  • 线程安全性: 不可变集合是线程安全的,因为它们的内容在创建后不能被修改。这意味着多个线程可以在没有额外同步机制的情况下同时访问这些集合,从而减少了并发编程的复杂性。
  • **数据安全性:**在某些情况下,防止数据被修改是至关重要的。通过使用不可变集合,可以防止代码的其他部分无意间或恶意地修改集合中的数据。
  • API 设计: 在暴露给其他代码的 API 中使用不可变集合可以防止调用者无意间修改数据,从而更好地控制数据的使用和保护
java
@Test
public void test24() {
    // 创建不可变List
    ImmutableList<UserInfo> userList = ImmutableList.of(
        new UserInfo(1, "张雨涵", 18, DateTimeUtil.parseToDate("2005-09-16"), 8500.0, new BigDecimal("8500.0")),
        new UserInfo(2, "王子涵", 19, DateTimeUtil.parseToDate("2004-07-18"), 8500.0, new BigDecimal("8500.0"))
    );
    for (UserInfo userInfo : userList) {
        System.out.println(userInfo);
    }
}

3、将List按指定大小分割

java
@Test
public void test25() {
    // 将List按指定大小分割
    List<List<UserInfo>> partitionedLists = Lists.partition(userList01, 3);
    for (List<UserInfo> partitionedList : partitionedLists) {
        for (UserInfo userInfo : partitionedList) {
            System.out.println(userInfo);
        }
    }
}

4、根据年龄进行排序

java
@Test
public void test26() {
    // 根据年龄进行排序
    Ordering<UserInfo> byAgeOrdering = Ordering.natural().onResultOf(UserInfo::getAge);
    List<UserInfo> sortedByAge = byAgeOrdering.sortedCopy(userList01);
    for (UserInfo userInfo : sortedByAge) {
        System.out.println(userInfo);
    }
}

5、检查年龄是否合法

java
@Test
public void test27() {
    // 检查年龄是否合法
    for (UserInfo user : userList01) {
        Preconditions.checkArgument(user.getAge() >= 0 && user.getAge() <= 120, "年龄不合法");
    }
}

6、将用户姓名拼接成逗号分隔的字符串

java
@Test
public void test28() {
    // 将用户姓名拼接成逗号分隔的字符串
    List<String> userNames = userList01.stream().map(UserInfo::getUserName).collect(Collectors.toList());
    String joinedNames = Joiner.on(", ").join(userNames);
    System.out.println(joinedNames);
}

7、将逗号分隔的字符串拆分为姓名列表

java
@Test
public void test29(){
    // 将逗号分隔的字符串拆分为姓名列表
    String namesString = "张雨涵, 王子涵, 李宇航";
    List<String> namesList = Splitter.on(',').trimResults().splitToList(namesString);
    System.out.println(namesList);
}

8、范围Map

java
package com.xx.test;

import com.google.common.collect.Range;
import com.google.common.collect.RangeMap;
import com.google.common.collect.TreeRangeMap;

/**
 * @Author: xueqimiao
 * @Date: 2024/3/27 10:27
 */
public class RangeMapTest {

    public static void main(String[] args) {
        RangeMap<Integer, String> rangeMap = TreeRangeMap.create();
        rangeMap.put(Range.closedOpen(-20, 5), "低温"); // [-20,5)
        rangeMap.put(Range.closed(6, 29), "高温");// [6,29]
        rangeMap.put(Range.openClosed(29, 39), "热死人"); // (29,39]

        System.out.println(rangeMap.get(-20));
        System.out.println(rangeMap.get(5));
        System.out.println(rangeMap.get(6));
        System.out.println(rangeMap.get(29));
    }

}