开发中常用的Java Stream流

引言

本文旨在介绍 Java Stream API 的基本概念及其强大之处,同时探讨如何利用这一特性来编写更加简洁、高效的代码。我们将从 Stream API 的基本原理讲起,逐步深入到实际应用中的高级技巧,包括如何创建 Stream、中间操作与终结操作的区别,以及并行流的使用等。本文包含了个人实际开发中所常用的api,无论你是 Java 初学者还是有经验的开发者,都将在本文中找到有价值的内容,帮助你在日常开发中更好地利用 Stream API。本人尽量采用上下文低耦合的例子,部分参数已按照命名规范定义,方便各位读者阅读。

废话不多说,咱们进入主题吧!

一. 流创建

1. Stream创建

Stream创建
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();  //串行流
Stream<String> parallelStream = list.parallelStream(); //并行流
Stream<Integer> stream1 = Stream.of(1,2,3,4,5);

2、Collection集合创建(应用中最常用的一种)

List<Integer> integerList = new ArrayList<>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
        integerList.add(4);
        integerList.add(5);
Stream<Integer> listStream = integerList.stream();

3、Array数组创建

int[] intArr = {1, 2, 3, 4, 5};
IntStream arrayStream = Arrays.stream(intArr);

二. 操作符

流的操作类型主要分为两种:中间操作符、终端操作符

中间操作符

通常对于Stream的中间操作,可以视为是源的查询,并且是懒惰式的设计,对于源数据进行的计算只有在需要时才会被执行,与数据库中视图的原理相似;

Stream流的强大之处便是在于提供了丰富的中间操作,相比集合或数组这类容器,极大的简化源数据的计算复杂度

一个流可以跟随零个或多个中间操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用

这类操作都是惰性化的,仅仅调用到这类方法,并没有真正开始流的遍历,真正的遍历需等到终端操作时,常见的中间操作有下面即将介绍的 filter、map 等

筛选(filter)

filter 是 Java Stream API 中的一个方法,用于对流中的元素进行过滤。它接受一个谓词(Predicate),即一个返回布尔值的函数,用于判断元素是否应该保留在流中

举例:

  1. // 过滤出大于7的数字并打印
    List<Integer> list = Arrays.asList(6, 7, 3, 8, 1, 2, 9);
    list.stream.filter(x -> x > 7).forEach(System.out::println);
    
  2. // 过滤出元素非空的集合
    List<String> nonBlankPaths = chunkPaths.stream().filter(StringUtils::isNotBlank).collect(Collectors.toList());
    

    解释:.filter(StringUtils::isNotBlank)isNotBlank 方法作为谓词传递给 filter 方法。这意味着流中的每个元素都会被传递给 isNotBlank 方法,如果 isNotBlank 返回 true,则该元素会被保留在流中;否则,该元素会被过滤掉

映射(map/flatMap)

举例:

  1. // 将所有用户对象根据id映射组成set集合(去重)
    Set<Integer> userIds = userMapper.selectList(null).stream().map(User::getId).collect(Collectors.toSet());
    
  2. // 英文字符串数组的元素全部改为大写
    String[] strArr = { "abcd", "bcdd", "defde", "fTr" };
    		List<String> strList = Arrays.stream(strArr).map(String::toUpperCase).collect(Collectors.toList());
    

去重(distinct)

返回一个元素各异(根据流所生成元素的hashCode和equals方法实现)的流

举例:

List<String> distinctUsers =  userList.stream()
    .map(User::getCity)
    .distinct()
    .collect(Collectors.toList());
distinctUsers.forEach(System.out::println);

排序(sorted)

sorted()方法可以用来对流中的元素进行排序。sorted()方法接受一个Comparator对象作为参数,该对象定义了元素之间的比较逻辑。如果没有提供Comparator对象,则默认使用自然顺序进行排序(如果元素实现了Comparable接口)

举例:

  1. List<String> strings = new ArrayList<String>() {{
    	add("222");add("666");add("444");add("111");add("333");add("555");
    }};
    // sorted()默认升序排序
    List<String> sortedStrings = strings.stream().sorted().collect(Collectors.toList());
    
  2. // 根据用户年龄升序排序
    List<User> sortedUsers = users.stream()
        .sorted(Comparator.comparing(e -> e.getAge()))
        .collect(Collectors.toList());
    // 等效写法如下
    List<User> sortedUsers = users.stream()
        .sorted(Comparator.comparing(User::getAge))
        .collect(Collectors.toList());
    // 根据用户年龄降序排序(先升序,再逆序)
    List<User> collect = users.stream()
        .sorted(Comparator.comparing(User::getAge).reversed())
        .collect(Collectors.toList());
    // 根据用户年龄降序排序(直接降序)
    List<User> collect2 = users.stream()
        .sorted((x, y) -> y.getAge().compareTo(x.getAge()))
        .collect(Collectors.toList());
    

限流(limit)

会返回一个不超过给定长度的流,说白了就是自定义返回个数

userList.stream().limit(5).collect(Collectors.toList()).forEach(System.out::println);

peek

它的主要功能是对流中的每个元素执行一个操作(可以是获取、修改或打印等),而不影响流的整体处理流程。

// 对集合非空的元素进行打印信息
addrList.stream()
    .filter(Objects::nonNull)
    .peek(info -> {
        System.out.println("Processing Element: " + info);
    }).collect(Collectors.toList());

进阶:

// 查询聊天记录后将集合中的所有readFlag已读标识变为true
chatMapper.selectFriendChats(userFriendIds)
                .stream().peek(chat -> chat.setReadFlag(true)).collect(Collectors.toList())

注意:peek()方法是 Strean 接口中的一个中间操作,它允许你在流的每 个元素上执行一个操作,但是这个操作是在最终的终端操作(如forEach,collect,limit 等)执行 前进行的。 然而,如果 peek()是流中唯一的操作,那么它实际上不会执行。这是因为
peek ()本身并不是一个 终端操作,它不会触发流的执行。在 jav 8 及以后的版本中,流的执行是情性的,这意味着流操作
不会立即执行,而是在遇到终端操作时才会实际执行

终端操作符

tream流执行完终端操作之后,无法再执行其他动作,否则会报状态异常,提示该流已经被执行操作或者被关闭,想要再次执行操作必须重新创建Stream流

一个流有且只能有一个终端操作,当这个操作执行后,流就被关闭了,无法再被操作,因此一个流只能被遍历一次,若想在遍历需要通过源数据在生成流。

终端操作的执行,才会真正开始流的遍历。如 count、collect 等

遍历/匹配(foreach/find/match)

// 遍历输出符合条件的元素
list.stream().filter(x -> x > 6).forEach(System.out::println);

收集(collect)

收集器,将流转换为其他形式

Set set = userList.stream().collect(Collectors.toSet());
List list = userList.stream().collect(Collectors.toList());
        list.forEach(System.out::println);

聚合(max/min/count)

举例:

  1. // 过滤出大于6的数字数量
    List<Integer> list = Arrays.asList(7, 6, 4, 8, 2, 11, 9); 		
    long count = list.stream().filter(x -> x > 6).count();
    
  2. // 获取String集合中最长的元素
    List<String> list = Arrays.asList("adnm", "admmt", "pot", "xbangd", "weoujgsd");
    Optional<String> max = list.stream().max(Comparator.comparing(String::length));
    

归约(reduce)

可以将流中元素反复结合起来,得到一个值

List strings = Arrays.asList(“cv”, “abd”, “aba”, “efg”, “abcd”,“jkl”, “jkl”);
Optional reduce = strings.stream().reduce((acc,item) -> {
    return acc+item;
});
if(reduce.isPresent())
    out.println(reduce.get());

Collectors

Collector:结果收集策略的核心接口,具备将指定元素累加存放到结果容器中的能力;并在Collectors工具中提供了Collector接口的实现类

归集(toList/toSet/toMap)

举例:

  1. // key: 用户ID  value: 礼物名称
    Map<Integer, String> productNameMap =
           orders.stream().collect(Collectors.toMap(Order::getUserId, 
    order -> order.getProduct().getProductName()));  
    
  2. // Set集合去重
    Set<String> userIdSet = users.stream().map(User::getUserId).collect(Collectors.toSet());  
    

Collectors.joining

以指定分隔符链接成字符串

举例:

List<String> list1 = new ArrayList<>();
        list1.add("1111");
        list1.add("222");
        list1.add("1113331");
        list1.add("444");
        list1.add("555");
List<String> list2 = new ArrayList<>();
        list2.add("1111");
        list2.add("222");
        list2.add("1113331");
        list2.add("444");
        list2.add("666");
        
        String collect1 = list1.stream().collect(Collectors.joining());
        String collect2 = list2.stream().collect(Collectors.joining(","));
        System.out.println(collect1);
        System.out.println(collect2);
//控制台打印结果
11112221113331444555
1111,222,1113331,444,666

Collectors.groupingBy

按条件分组,根据字段对集合进行分组;

Map<String,List<User>> groupCity = userList.stream().collect(Collectors.groupingBy(User::getCity));

番外

整理了一些大家常见的疑问,为大家倾情献上!希望能对各位读者有所帮助!

1. list.stream().forEach()和list.forEach()的区别

list.stream().forEach() 和 list.forEach() 在 Java 中都是用于遍历集合元素的方法,但它们在使用场景和功能上有所不同:

list.forEach():

是从 Java 8 开始引入到 java.util.List 接口的标准方法。
直接对列表进行迭代,它采用内部迭代的方式,不需要显式创建迭代器。
使用 Lambda 表达式或方法引用来处理集合中的每个元素。
这个操作是同步的,并且在执行过程中不会创建 Stream 流对象,因此没有额外的流管道开销。

list.stream().forEach():

首先将列表转换为一个 Stream 对象,这是 Java 8 引入的 Stream API 的一部分。
转换为 Stream 后可以利用 Stream API 提供的一系列丰富操作,如 filter、map、sorted 等,然后再调用 forEach 方法。
尽管最终也是对集合元素进行迭代,但它允许你在一个惰性计算(lazy computation)的上下文中工作,也就是说,Stream 操作可以在需要时才进行计算,这有助于优化性能,特别是在链式操作中。
同样接受 Lambda 表达式来处理每个元素,但因为涉及到了 Stream,所以提供了更多的函数式编程能力,比如并行处理(parallel processing),通过调用 .parallel().forEach() 可以在多核环境下并行执行操作。

简单来说就是:

list.forEach()节省了创建流的开销,如果简单对集合进行遍历则可以使用。而list.stream().forEach()当你想要利用 Stream API 的特性(如函数式编程风格、延迟计算、并行处理等)处理复杂集合逻辑,则应该使用list.stream().forEach()。

2. Java17的stream流里toList和.collect(Collectors.toList())有什么不同

.collect(Collectors.toList());

源码:

public static <T>
    Collector<T, ?, List<T>> toList() {
        return new CollectorImpl<>(ArrayList::new, List::add,
           (left, right) -> { left.addAll(right); return left; },
           CH_ID);
    }

解释:显而易见,Collectors.toList()返回的是new一个ArrayList集合

.tolist()

default List<T> toList() {
  return (List<T>) Collections.unmodifiableList(
      new ArrayList<>(Arrays.asList(this.toArray())));
}
@SuppressWarnings("unchecked")
public static <T> List<T> unmodifiableList(List<? extends T> list) {
    if (list.getClass() == UnmodifiableList.class || list.getClass() == 					   UnmodifiableRandomAccessList.class) {
       return (List<T>) list;
    }
    return (list instanceof RandomAccess ?
        new UnmodifiableRandomAccessList<>(list) :
        new UnmodifiableList<>(list));
}

解释:但是tolist收集出来的list实际类是UnmodifiableList这个类

根据某神秘男子的解释:UnmodifiableList 是 Java 中的一个类,用于创建不可修改的列表。它实际上是通过 Collections.unmodifiableList() 方法创建的。一旦列表被创建为不可修改的,就无法对其进行添加、删除或修改元素的操作

总结:

1.如果你想在之后的代码中对集合进行更改操作,则需要使用**.collect(Collectors.toList())**

2.如果你的集合已经不想被更改,则使用toList()集合

个人建议

想要熟练使用StreamAPI,不妨学习csdn收藏量靠前blog的写法,在开发的过程中处理集合尽量使用相关语句。等到熟悉基础语法后,结合Lambda表达式可以飞速提升开发效率,并且让别人觉得你是个掌握Java语言特性的高手。包括本人在开发中遇到繁杂的业务逻辑,也会参考各大开发社区的学习文档,所以技术没有上限,尽量提升自己吧!

参考文档

java stream使用指南-------sorted使用及进阶
这么简单,还不会使用java8 stream流的map()方法吗?
Java–Stream流详解
Java8中Collectors详解
list.stream().forEach()和list.forEach()的区别
Java17的stream流里toList和.collect(Collectors.toList())有什么不同

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值