Java8特性-Stream用法总结

Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。 Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 java7 引入的 fork/join 并行方式来拆分任务和加速处理过程。通常编写并行代码很难而且容易出错, 但使用 Stream API 无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。

在深入了解流之前,我们先了解 Stream 中几个常用的函数式接口:

函数名 入参类型 返回类型 接口描述
Predicate T boolean 判断入参是否符合要求,用于流元素的匹配和过滤
Function T R 将T对象转换成R对象返回,用于流元素类型转换
Comparator (T, T) int 比较传入的两个对象,用于流元素排序,求极值
Consumer T void 消费流元素,用于流元素的聚合和遍历操作
Supplier None T 生产流元素,根据提供的Supplier生成无限流
BinaryOperator (T, T) T 传入两个同类型对象返回一个同类型对象,用于reduce操作
UnaryOperator T T 传入一个对象返回一个同类型对象,用于iterate操作

1. 如何创建 Stream

所有流的基类是 BaseStream,主要子类有 Stream(最常用的流),IntStream(Int型流),DoubleStream(Double型流),LongStream(Long型流),创建流有很多种方式,总结如下:

1.1 collection 生成流方法

常用的 Collection 子类有 ListSetQueue(单向队列)、Deque(双向队列),Collection 类有两个生成流的方法:
1)生成串行流方法:stream()

default Stream stream() {
        return StreamSupport.stream(spliterator(), false);
}

2)生成并行流方法:parallelStream()

default Stream parallelStream() {
        return StreamSupport.stream(spliterator(), true);
}

Collection 的子类可以直接调用这两个方法来生成适合的流,一般如果是基本类型的流类型,建议直接使用串行流来进行数据操作。
示例

// 获取字符串集合的串行流
List strList = new ArrayList<>();
Stream stream = strList.stream();

// 获取实体类集合的并行流
List persons = new ArrayList<>();
Stream stream2 = persons.parallelStream();
Stream stream = strList.stream().parallel();

1.2 Array 生成流方法

使用 Arrays 的静态方法生成流:

String [] strArray = {"aa", "bb"};
Stream stream = Arrays.stream(strArray);

1.3 使用 Stream 类的方法

Stream 类可以直接使用原生方法来生成各种流,也可以通过构建器来进行构建,用法如下:
1)of() 方法:
通过传入一个实体或者实体数组来生成对应的流:

// 传入一个实体
public static Stream of(T t) {
    return StreamSupport.stream(new Streams.StreamBuilderImpl<>(t), false);
}

// 传入一个实体数组
@SafeVarargs
@SuppressWarnings("varargs") // Creating a stream from an array is safe
public static Stream of(T... values) {
    return Arrays.stream(values);
}

示例:

List strs = new ArrayList(){
            {
                add("aaa");
                add("bbb");
                add("ccc");
            }
};
Stream> strs1 = Stream.of(strs);

// 注意上边返回的并不是Stream类型的流,下边的构造方式更为常用
Stream aaa = Stream.of("aaa", "bbb", "ccc");

2)通过 generate()iterate() 生成无限流
generate() 方法通过通过一个 supplier 来生成无限流,supplier 定义一个生成规则,比方说生成随机数:Math.random() ,或者直接返回一个字符串:() -> "aa",由于生成的流是无限流,所以经常跟limit()方法合用,返回前n个数据,示例:

// 生成随机数流,并返回前100条数据
Stream generate1 = Stream.generate(() -> Math.random()).limit(100);

//生成包含100个元素是“aaa”的流
Stream generate = Stream.generate(() -> "aaa").limit(100);

3)使用builder()构建器生成流
Stream类含有一个builder(),该方法返回一个构造器对象,该对象是 Stream类的一个内部类,结构如下:

    /**
     * A mutable builder for a {@code Stream}.  This allows the creation of a
     * {@code Stream} by generating elements individually and adding them to the
     * {@code Builder} (without the copying overhead that comes from using
     * an {@code ArrayList} as a temporary buffer.)
     *
     * 

A stream builder has a lifecycle, which starts in a building * phase, during which elements can be added, and then transitions to a built * phase, after which elements may not be added. The built phase begins * when the {@link #build()} method is called, which creates an ordered * {@code Stream} whose elements are the elements that were added to the stream * builder, in the order they were added. * * @param the type of stream elements * @see Stream#builder() * @since 1.8 */ public interface Builder extends Consumer { /** * Adds an element to the stream being built. * * @throws IllegalStateException if the builder has already transitioned to * the built state */ @Override void accept(T t); /** * Adds an element to the stream being built. * * @implSpec * The default implementation behaves as if: *

{@code
         *     accept(t)
         *     return this;
         * }
* * @param t the element to add * @return {@code this} builder * @throws IllegalStateException if the builder has already transitioned to * the built state */ default Builder add(T t) { accept(t); return this; } /** * Builds the stream, transitioning this builder to the built state. * An {@code IllegalStateException} is thrown if there are further attempts * to operate on the builder after it has entered the built state. * * @return the built stream * @throws IllegalStateException if the builder has already transitioned to * the built state */ Stream build(); }

使用示例:

// 使用 add() 方法链式添加流元素
Stream build = Stream.builder().add("aaa").add("bbb").build();
 
 

4)IntStreamLongStreamrange()方法

// 返回范围为 1-10 的整数流,含头不含尾
IntStream range = IntStream.range(1, 10);
// 返回范围为 1-10 的整数流,含头且含尾
IntStream range2 = IntStream.rangeClosed(1, 10);

2. Stream 的基本使用

对于流的操作基本可以分为两类,一类是中间操作,一类是终止操作。中间操作输出的还是流对象,例如过滤排序映射转换操作,中间操作是惰性操作的,并不会立即执行,当终止操作出发后才会执行之前的所有中间操作,类似于sprak处理数据流;终止操作执行后流不可再使用,输出一个对象或对元素进行一系列操作,例如归约聚合遍历操作,下边详细介绍各个方法。

2.1 中间操作

中间操作执行后,输出的是一个新流,可以在新流的基础上再次执行中间操作,或者使用终止操作来结束流。

筛选切片

方法 入参类型 描述
filter Predicate 根据过滤条件筛选符合要求的数据流
distinct None 去重操作
limit int 根据输入值,返回前 n 条数据
skip int 根据输入值,忽略前 n 条数据

示例:

Stream.of("aa", "bbb", "cc", "abb", "ac", "bcc", "abc", "aa")
                .distinct() // 输出:{"aa", "bbb", "cc", "abb", "ac", "bcc", "abc",}
                .filter(str -> str.length() > 2)  // 输出:{"bbb",  "abb", "bcc", "abc"}
                .limit(3) // 输出:{"bbb",  "abb", "bcc"}
                .skip(2) // 输出:{"bcc"}
                .collect(Collectors.toList());

映射转换

方法 入参类型 描述
map Function 作用于流中的每个元素,按照函数接口将 A 对象转换成 B 对象
mapToInt ToIntFunction 作用于流中的每个元素,按照函数接口返回 IntStream
mapToLong ToLongFunction 作用于流中的每个元素,按照函数接口返回 LongStream
mapToDouble ToDoubleFunction 作用于流中的每个元素,按照函数接口返回 DoubleStream
flatMap Function 作用于流中的每个元素,将每个元素转换成另一个流,然后合并所有流返回
flatMapToInt Function 作用于流中的每个元素,将每个元素转换成一个 IntStream 流,然后合并所有流返回
flatMapToLong Function 作用于流中的每个元素,将每个元素转换成一个 LongStream 流,然后合并所有流返回
flatMapToDouble Function 作用于流中的每个元素,将每个元素转换成一个 DoubleStream 流,然后合并所有流返回

对于 flatMap 的理解:
flatMap 可以理解为,将原有流元素中的元素再次流化,然后将生成的所有流再次合并成一个新流,经常用于第一次流化时,流元素并不是我们想要的基本类型元素,比方说第一次流化后的流元素是数组或者集合类型,我们可以使用 flatMap 来扁平化元素类型。
示例:

Stream stream = Stream.of(new String [] {"1", "2", "3"}, new String [] {"2", "4", "6"}) // Stream 类型

//示例1
List list = stream.flatMap(Arrays::stream) // Stream 类型
                           .map(Integer::valueOf) // Stream 类型
                           .collect(Collectors.toList()); // List 类型,输出:[1, 2, 3, 2, 4, 6]

// 示例2
stream.flatMap(Arrays::stream) // Stream 类型
           .mapToInt(Integer::parseInt) // IntStream 类型
           .max() // OptionalInt 类型,获取最大值
           .getAsInt(); // int 类型

// 示例3
Stream.of("1,2,3,4", "5,6,7,8")
      .flatMap(e -> Arrays.stream(e.split(","))) // 将 ["1,2,3,4", "5,6,7,8"] -> ["1", "2", "3", "4", "5", "6", "7", "8"]
      .map(Integer::valueOf)
      .collect(Collectors.toList()); // List 类型,输出:[1, 2, 3, 4, 5, 6, 7, 8]

排序

方法 入参类型 描述
sorted None 按照流元素默认比较器进行排序,返回新流
sorted Comparator 按照传入比较器对流元素排序,返回新流

示例:

// 示例 1
Stream.of(new String [] {"1", "2", "3"}, new String [] {"2", "4", "6"}) // Stream 类型
       .flatMap(Arrays::stream) // Stream 类型
       .map(Integer::valueOf) // Stream 类型
       .sorted() // Stream 类型
       .collect(Collectors.toList()); // List 类型,输出:[1, 2, 2, 3, 4, 6]

// 示例 2,自定义比较器
Stream.of(new String[]{"1", "2", "3"}, new String[]{"2", "4", "6"})
      .flatMap(Arrays::stream)  // Stream 类型
      .map(Integer::valueOf) // Stream 类型
      .sorted((a, b) -> a < b ? 1 : a == b ? 0 :-1) // Stream 类型
      .collect(Collectors.toList()); // List 类型,输出:[6, 4, 3, 2, 2, 1]

// 示例 3,传入 Comparator,Comparator内置了常见的比较器
Stream.of(new String[]{"1", "2", "3"}, new String[]{"2", "4", "6"})
      .flatMap(Arrays::stream)  // Stream 类型
      .map(Integer::valueOf) // Stream 类型
      .sorted(Comparator.reverseOrder()) // Stream 类型
      .collect(Collectors.toList()); // List 类型,输出:[6, 4, 3, 2, 2, 1]

其他

方法 入参类型 描述
peek Consumer 作用于每个元素,对每个元素进行操作,然后返回原流,常用于 debug 打印日志
empty None 返回一个空的流对象

示例:

// 示例 1
Stream.of("aa", "bbb", "cc", "abb", "ac", "bcc", "abc", "aa")
      .filter(str -> str.length() > 2)
      .peek(e -> System.out.println("first filter: " + e))
      .distinct()
      .peek(e -> System.out.println("second filter: " + e))
      .limit(3)
      .peek(e -> System.out.println("third filter: " + e))
      .skip(2)
      .peek(e -> System.out.println("fourth filter: " + e))
      .collect(Collectors.toList());
// 输出:
  first filter: bbb
  second filter: bbb
  third filter: bbb
  first filter: abb
  second filter: abb
  third filter: abb
  first filter: bcc
  second filter: bcc
  third filter: bcc
  fourth filter: bcc

// 示例 2
Stream empty = Stream.empty();
 
 

2.2 终止操作

终止操作执行后,流结束,只有当终止操作触发,中间操作才会开始执行,也叫懒惰执行,常见的终止操作有三类:查询匹配、归约、遍历和聚合,这里涉及到一个新类 Optional,详细细节之后我会详细分析。

查询匹配

方法 入参类型 返回类型 描述
min Comparator Optional 根据比较器,返回流中最小的元素,可看作是一种特殊的 reduce 操作
min Comparator Optional 根据比较器,返回流中最大的元素,可看作是一种特殊的 reduce 操作
count None long 统计流中元素总数,可看作是一种特殊的 reduce 操作
anyMatch Predicate boolean 流中有任何元素符合条件返回true,否则返回false
allMatch Predicate boolean 流中所有元素符合条件返回true,否则返回false
noneMatch Predicate boolean 流中没有元素符合条件返回true,否则返回false
findFirst None Optional 返回流中第一个元素
findAny None Optional 返回流中任意一个元素

示例:

Stream s = Stream.of("aa", "bbb", "cc", "abb", "ac", "bcc", "abc", "aa");

// 根据字符串长度求最小值
String min = s.min(Comparator.comparingInt(String::length)).get();

// 根据字符串长度求最大值
String min = s.max(Comparator.comparingInt(String::length)).get();

// 统计总数
long min = s.count();

// 若有任何元素包含 a 则返回 true
boolean result1 = s..anyMatch(e -> e.contains("a"));

// 若所有元素包含 a 则返回 true
boolean result1 = s.allMatch(e -> e.contains("a"));

// 若没有有元素包含 a 则返回 true
boolean result1 = s.noneMatch(e -> e.contains("a"));

// 返回第一个元素
String first = s.findFirst().get();

// 返回任意元素
String any = s.findAny().get();

归约

方法 入参类型 返回类型 描述
reduce (T, BinaryOperator) T 指定一个初始值,根据 BinaryOperator 对初始值及流元素执行归约操作
reduce BinaryOperator Optional 根据 BinaryOperator 对流元素执行归约操作
reduce (U, BiFunction,BinaryOperator) U 指定一个初始值,根据 BiFunction 将 T类型的流数据元素转换成 U 类型,并根据根据 BinaryOperator 对新的流元素执行归约操作

示例:

// 示例 1
Stream.of(new String[]{"1", "2", "3"}, new String[]{"2", "4", "6"})
      .flatMap(Arrays::stream)
      .map(Integer::valueOf)
      .sorted(Comparator.reverseOrder())
      .reduce(2, Integer::sum);  // 输出:20(2+1+2+3+2+4+6)

// 示例 2
Stream.of(new String[]{"1", "2", "3"}, new String[]{"2", "4", "6"})
      .flatMap(Arrays::stream)
      .map(Integer::valueOf)
      .sorted(Comparator.reverseOrder())
      .reduce(Integer::sum);  // 输出:18(1+2+3+2+4+6)

// 示例 3:求科目总分数
@Data
@NoArgsConstructor
@AllArgsConstructor
class Course{
     private String name;
     private String course;
     private int score;
}

@Data
@NoArgsConstructor
class Score{
     private String course;
     private int total;
}

@Test
public void testReduce(){
     List courses = new ArrayList(){
         {
            add(new Course("LiHua", "YuWen", 90));
            add(new Course("XiaoMing", "YuWen", 98));
            add(new Course("XiaoGang", "YuWen", 85));
         }
     };

     courses.stream().reduce(new Score(), (s, c) -> {
         s.setCourse(c.getCourse());
         s.setTotal(c.getScore());
         return s;
     }, (a, b) -> {a.setTotal(a.getTotal() + b.getTotal()); return a;}); 
     // 输出:
      {
          "course":"YunWen",
          "total": 273
      }
}

遍历

方法 入参类型 返回类型 描述
forEach Consumer void 遍历流元素,并对流元素执行 Consumer 操作
forEachOrdered Consumer void 遍历流元素,如果流具有定义的遇到顺序,则以流的遇到顺序对流元素执行 Consumer 操作

示例:

// 打印流元素
Stream.of("aa", "bbb", "cc", "abb", "ac", "bcc", "abc", "aa").foreach(System.out::println);

聚合

方法 入参类型 返回类型 描述
toArray None Object[] 将流转化成数组形式返回
toArray A[] IntFunction 通过一个A类型数组的构造器,返回A类型数组
collect (Supplier, BiConsumer, BiConsumer) R 提供 Supplier 及 BiConsumer,对流元素进行聚合操作
collect Collector R 提供一个 Collector 对流元素进行聚合操作

关于Collectors
Collectorsjava1.8 加入的一个配合 stream 使用的工具类,功能十分强大便利,主要用来生成各种 Collector,可以帮助我们把流转换成几乎任何我们想要的数据格式。
示例:

// 示例 1
Object [] obj = Stream.of(new String[]{"1", "2", "3"}, new String[]{"2", "4", "6"})
                      .flatMap(Arrays::stream)
                      .map(Integer::valueOf)
                      .sorted(Comparator.reverseOrder())
                      .toArray();

// 示例 2
Person[] men = people.stream()
                     .filter(p -> p.getGender() == MALE)
                     .toArray(Person[]::new);

// 示例 3
List asList = stringStream.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);

// 示例 4
String concat = stringStream.collect(StringBuilder::new, StringBuilder::append, StringBuilder::append).toString();

// 示例 5,聚合成List
List asList = stringStream.collect(Collectors.toList());

// 示例 6,单层分组
Map> peopleByCity  = personStream.collect(Collectors.groupingBy(Person::getCity));

// 示例 7,多层分组
Map>> peopleByStateAndCity = personStream
                           .collect(Collectors.groupingBy(Person::getState, Collectors.groupingBy(Person::getCity)));

// 示例 8,聚合成Map
Map map = persons.stream().collect(Collectors.toMap(Person::getName, Person::getSex)); 

你可能感兴趣的:(Java8特性-Stream用法总结)