Java8 Stream API介绍

简介: Stream API是Java8中处理集合的关键组件,提供了各种丰富的函数式操作。Stream的创建任何集合都可以转换为Stream: //数组 String[] strArr = new String[]{"aa","bb","cc"}; Stream<String> streamArr = Stream.of(st
Stream API是Java8中处理集合的关键组件,提供了各种丰富的函数式操作。

Stream的创建

任何集合都可以转换为Stream:
    //数组
    String[] strArr = new String[]{"aa","bb","cc"};
    Stream<String> streamArr = Stream.of(strArr);
    Stream<String> streamArr2 = Arrays.stream(strArr);
    //集合
    List<String> list = new ArrayList<>();
    Stream<String> streamList = list.stream();
    Stream<String> streamList2 = list.parallelStream();//并行执行
    ...

    //generator 生成无限长度的stream
    Stream.generate(Math::random);
    // iterate 也是生成无限长度的Stream,其元素的生成是重复对给定的种子值调用函数来生成的
    Stream.iterate(1, item -> item + 1)

Stream的简单使用

Stream的使用分为两种类型:

  1. Intermediate,一个Stream可以调用0到多个Intermediate类型操作,每次调用会对Stream做一定的处理,返回一个新的Stream,这类操作都是惰性化的(lazy),就是说,并没有真正开始流的遍历。
    常用操作:map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel

  2. Terminal,一个Stream只能执行一次terminal 操作,而且只能是最后一个操作,执行terminal操作之后,Stream就被消费掉了,并且产生一个结果。
    常用操作:forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny

使用示例:

/********** Intermediate **********/
//filter 过滤操作
streamArr.filter(str -> str.startsWith("a"));
//map 遍历和转换操作
streamArr.map(String::toLowerCase);
//flatMap 将流展开
List<String> list1 = new ArrayList<>();
list1.add("aa");list1.add("bb");
List<String> list2 = new ArrayList<>();
list2.add("cc");list2.add("dd");
Stream.of(list1,list2).flatMap(str -> str.stream()).collect(Collectors.toList());  
//limit 提取子流
streamArr.limit(1);
//skip 跳过
streamArr.skip(1);
//peek 产生相同的流,支持每个元素调用一个函数
streamArr.peek(str - > System.out.println("item:"+str));
//distinct 去重
Stream.of("aa","bb","aa").distinct();
//sorted 排序
Stream.of("aaa","bb","c").sorted(Comparator.comparing(String::length).reversed());
//parallel 转为并行流,谨慎使用
streamArr.parallel();

/********** Terminal **********/
//forEach
streamArr.forEach(System.out::println);
//forEachOrdered 如果希望顺序执行并行流,请使用该方法
streamArr.parallel().forEachOrdered(System.out::println);
//toArray 收集到数组中
streamArr.filter(str -> str.startsWith("a")).toArray(String[]::new);
//reduce 聚合操作
streamArr.reduce((str1,str2) -> str1+str2);
//collect 收集到ListstreamArr.collect(Collectors.toList());
//collect 收集到SetstreamArr.collect(Collectors.toSet());
//min 取最小值?
IntStream.of(1,2,3,4).min();
Stream.of(arr).min(String::compareTo);
//max 取最大值?
IntStream.of(1,2,3,4).max();
Stream.of(arr).max(String::compareTo);
//count 计算总量?
streamArr.count();
//anyMatch 判断流中是否含有匹配元素
boolean hasMatch = streamArr.anyMatch(str -> str.startsWith("a"));
//allMatch 判断流中是否全部匹配
boolean hasMatch = streamArr.allMatch(str -> str.startsWith("a"));
//noneMatch 判断流中是否全部不匹配
boolean hasMatch = streamArr.noneMatch(str -> str.startsWith("a"));
//findFirst 找到第一个就返回
streamArr.filter(str -> str.startsWith("a")).findFirst();
//findAny 找到任意一个就返回
streamArr.filter(str -> str.startsWith("a")).findAny();

收集结果

collect操作主要用于将stream中的元素收集到一个集合容器中,collect函数的定义如下:
    <R> R collect(Supplier<R> supplier,
                  BiConsumer<R, ? super T> accumulator,
                  BiConsumer<R, R> combiner);

第一个参数Supplier用于生成一个目标集合容器类型的实例;
函数BiConsumer

Set<String> result = Stream.of("aa", "bb", "cc", "aa").collect(
                () -> new HashSet<String>(), 
                (set, item) -> set.add(item),
                (set, subSet) -> set.addAll(subSet));

以上写法可以使用操作符“::”简化,语法如下:

  • 对象::实例方法
  • 类::静态方法
  • 类::实例方法
Set<String> result = Stream.of("aa", "bb", "cc", "aa").collect(
                HashSet::new,
                HashSet::add,
                HashSet::addAll);

java.util.stream.Collectors类中已经预定义好了toList,toSet,toMap,toCollection等方便使用的方法,所以以上代码还可以简化如下:

Set<String> result2 = Stream.of("aa", "bb", "cc", "aa").collect(Collectors.toSet());

将结果收集到Map中,Collectors.toMap方法的两个重载定义如下:

  • keyMapper函数用于从实例T中得到一个K类型的Map key;
  • valueMapper函数用于从实例T中得到一个U类型的Map value;
  • mergeFunction函数用于处理key冲突的情况,默认为throwingMerger(),抛出IllegalStateException异常;
  • mapSupplier函数用于生成一个Map实例;
public static <T, K, U>
    Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                    Function<? super T, ? extends U> valueMapper) {
        return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
    }


public static <T, K, U, M extends Map<K, U>>
    Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
                                Function<? super T, ? extends U> valueMapper,
                                BinaryOperator<U> mergeFunction,
                                Supplier<M> mapSupplier) {
        BiConsumer<M, T> accumulator
                = (map, element) -> map.merge(keyMapper.apply(element),
                                              valueMapper.apply(element), mergeFunction);
        return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID);
    }

假设有一个User实体类,有方法getId(),getName(),getAge()等方法,现在想要将User类型的流收集到一个Map中,示例如下:

Stream<User> userStream = Stream.of(new User(0, "张三", 18), new User(1, "张四", 19), new User(2, "张五", 19), new User(3, "老张", 50));

Map<Integer, User> userMap = userSteam.collect(Collectors.toMap(User::getId, item -> item));
假设要得到按年龄分组的Map<Integer,List<User>>,可以按这样写:
Map<Integer, List<User>> ageMap = userStream.collect(Collectors.toMap(User::getAge, Collections::singletonList, (a, b) -> {
            List<User> resultList = new ArrayList<>(a);
            resultList.addAll(b);
            return resultList;
        }));

这种写法虽然可以实现分组功能,但是太过繁琐,好在Collectors中提供了groupingBy方法,可以用来实现该功能,简化后写法如下:

Map<Integer, List<User>> ageMap2 = userStream.collect(Collectors.groupingBy(User::getAge));

类似的,Collectors中还提供了partitioningBy方法,接受一个Predicate函数,该函数返回boolean值,用于将内容分为两组。假设User实体中包含性别信息getSex(),可以按如下写法将userStream按性别分组:

Map<Boolean, List<User>> sexMap = userStream.collect(Collectors.partitioningBy(item -> item.getSex() > 0));

Collectors中还提供了一些对分组后的元素进行downStream处理的方法:

  • counting方法返回所收集元素的总数;
  • summing方法会对元素求和;
  • maxBy和minBy会接受一个比较器,求最大值,最小值;
  • mapping函数会应用到downstream结果上,并需要和其他函数配合使用;
 Map<Integer, Long> sexCount = userStream.collect(Collectors.groupingBy(User::getSex,Collectors.counting()));

 Map<Integer, Integer> ageCount = userStream.collect(Collectors.groupingBy(User::getSex,Collectors.summingInt(User::getAge)));

Map<Integer, Optional<User>> ageMax =  userStream.collect(Collectors.groupingBy(User::getSex,Collectors.maxBy(Comparator.comparing(User::getAge))));

Map<Integer, List<String>> nameMap =  userStream.collect(Collectors.groupingBy(User::getSex,Collectors.mapping(User::getName,Collectors.toList())));

以上为各种collectors操作的使用案例。

Optional类型

 Optional<T> 是对T类型对象的封装,它不会返回null,因而使用起来更加安全。

ifPresent方法接受一个函数作为形参,如果存在当前Optinal存在值则使用当前值调用函数,否则不做任何操作,示例如下:
    Optional<T> optional = ...
    optional.ifPresent(v -> results.add(v));
orElse方法,orElseGet方法,当值不存在时产生一个替代值,示例如下:
    String result = optional.orElse("defaultValue");
    String result = optional.orElseGet(() -> getDefalutValue());
可以使用Optional.of()方法和Optional.empty()方法来创建一个Optional类型对象,示例如下:
    a - b > 0 ? Optional.of(a - b) : Optional.empty();

函数式接口

Steam.filter方法接受一个Predicate函数作为入参,该函数返回一个boolean类型,下图为Stream和COllectors方法参数的函数式接口:

函数式接口

总结

  • Stream的处理总会在最后的Terminal操作才会真正执行;
  • 没有内部存储,也不能改变使用到的数据源,每次操作都会生成一个新的流;
  • 并行流使用fork/join 池来实现,对于非CPU密集型任务,需要谨慎使用;
  • 相对于循环遍历操作代码可读性更高;
目录
相关文章
|
4月前
|
安全 Java API
告别繁琐编码,拥抱Java 8新特性:Stream API与Optional类助你高效编程,成就卓越开发者!
【8月更文挑战第29天】Java 8为开发者引入了多项新特性,其中Stream API和Optional类尤其值得关注。Stream API对集合操作进行了高级抽象,支持声明式的数据处理,避免了显式循环代码的编写;而Optional类则作为非空值的容器,有效减少了空指针异常的风险。通过几个实战示例,我们展示了如何利用Stream API进行过滤与转换操作,以及如何借助Optional类安全地处理可能为null的数据,从而使代码更加简洁和健壮。
119 0
|
1月前
|
Java API 数据处理
探索Java中的Lambda表达式与Stream API
【10月更文挑战第22天】 在Java编程中,Lambda表达式和Stream API是两个强大的功能,它们极大地简化了代码的编写和提高了开发效率。本文将深入探讨这两个概念的基本用法、优势以及在实际项目中的应用案例,帮助读者更好地理解和运用这些现代Java特性。
|
2月前
|
Java 流计算
Flink-03 Flink Java 3分钟上手 Stream 给 Flink-02 DataStreamSource Socket写一个测试的工具!
Flink-03 Flink Java 3分钟上手 Stream 给 Flink-02 DataStreamSource Socket写一个测试的工具!
43 1
Flink-03 Flink Java 3分钟上手 Stream 给 Flink-02 DataStreamSource Socket写一个测试的工具!
|
2月前
|
Java Shell 流计算
Flink-02 Flink Java 3分钟上手 Stream SingleOutputStreamOpe ExecutionEnvironment DataSet FlatMapFunction
Flink-02 Flink Java 3分钟上手 Stream SingleOutputStreamOpe ExecutionEnvironment DataSet FlatMapFunction
25 1
Flink-02 Flink Java 3分钟上手 Stream SingleOutputStreamOpe ExecutionEnvironment DataSet FlatMapFunction
|
3月前
|
存储 Java API
Java——Stream流详解
Stream流是JDK 8引入的概念,用于高效处理集合或数组数据。其API支持声明式编程,操作分为中间操作和终端操作。中间操作包括过滤、映射、排序等,可链式调用;终端操作则完成数据处理,如遍历、收集等。Stream流简化了集合与数组的操作,提升了代码的简洁性
109 11
Java——Stream流详解
|
2月前
|
存储 Java 数据处理
Flink-01 介绍Flink Java 3分钟上手 HelloWorld 和 Stream ExecutionEnvironment DataSet FlatMapFunction
Flink-01 介绍Flink Java 3分钟上手 HelloWorld 和 Stream ExecutionEnvironment DataSet FlatMapFunction
36 1
|
3月前
|
Java API C++
Java 8 Stream Api 中的 peek 操作
本文介绍了Java中`Stream`的`peek`操作,该操作通过`Consumer&lt;T&gt;`函数消费流中的每个元素,但不改变元素类型。文章详细解释了`Consumer&lt;T&gt;`接口及其使用场景,并通过示例代码展示了`peek`操作的应用。此外,还对比了`peek`与`map`的区别,帮助读者更好地理解这两种操作的不同用途。作者为码农小胖哥,原文发布于稀土掘金。
123 9
Java 8 Stream Api 中的 peek 操作
|
3月前
|
Java C# Swift
Java Stream中peek和map不为人知的秘密
本文通过一个Java Stream中的示例,探讨了`peek`方法在流式处理中的应用及其潜在问题。首先介绍了`peek`的基本定义与使用,并通过代码展示了其如何在流中对每个元素进行操作而不返回结果。接着讨论了`peek`作为中间操作的懒执行特性,强调了如果没有终端操作则不会执行的问题。文章指出,在某些情况下使用`peek`可能比`map`更简洁,但也需注意其懒执行带来的影响。
151 2
Java Stream中peek和map不为人知的秘密
|
3月前
|
Java 大数据 API
Java 流(Stream)、文件(File)和IO的区别
Java中的流(Stream)、文件(File)和输入/输出(I/O)是处理数据的关键概念。`File`类用于基本文件操作,如创建、删除和检查文件;流则提供了数据读写的抽象机制,适用于文件、内存和网络等多种数据源;I/O涵盖更广泛的输入输出操作,包括文件I/O、网络通信等,并支持异常处理和缓冲等功能。实际开发中,这三者常结合使用,以实现高效的数据处理。例如,`File`用于管理文件路径,`Stream`用于读写数据,I/O则处理复杂的输入输出需求。
|
3月前
|
Java 程序员 API
Java 8新特性之Lambda表达式与Stream API的探索
【9月更文挑战第24天】本文将深入浅出地介绍Java 8中的重要新特性——Lambda表达式和Stream API,通过实例解析其语法、用法及背后的设计哲学。我们将一探究竟,看看这些新特性如何让Java代码变得更加简洁、易读且富有表现力,同时提升程序的性能和开发效率。