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<E> stream() {
        return StreamSupport.stream(spliterator(), false);
}

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

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

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

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

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

1.2 Array 生成流方法

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

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

1.3 使用 Stream 类的方法

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

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

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

示例:

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

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

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

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

//生成包含100个元素是“aaa”的流
Stream<String> 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.)
     *
     * <p>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 <T> the type of stream elements
     * @see Stream#builder()
     * @since 1.8
     */
    public interface Builder<T> extends Consumer<T> {

        /**
         * 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:
         * <pre>{@code
         *     accept(t)
         *     return this;
         * }</pre>
         *
         * @param t the element to add
         * @return {@code this} builder
         * @throws IllegalStateException if the builder has already transitioned to
         * the built state
         */
        default Builder<T> 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<T> build();

    }

使用示例:

// 使用 add() 方法链式添加流元素
Stream<Object> 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<String[]> stream = Stream.of(new String [] {"1", "2", "3"}, new String [] {"2", "4", "6"}) // Stream<String[]> 类型

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

// 示例2
stream.flatMap(Arrays::stream) // Stream<Integer> 类型
           .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<Integer> 类型,输出:[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<String[]> 类型
       .flatMap(Arrays::stream) // Stream<String> 类型
       .map(Integer::valueOf) // Stream<Integer> 类型
       .sorted() // Stream<Integer> 类型
       .collect(Collectors.toList()); // List<Integer> 类型,输出:[1, 2, 2, 3, 4, 6]

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

2.2 终止操作

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

查询匹配

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

示例:

Stream<String> 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>) T 指定一个初始值,根据 BinaryOperator 对初始值及流元素执行归约操作
reduce BinaryOperator<T> Optional<T> 根据 BinaryOperator 对流元素执行归约操作
reduce (U, BiFunction<U, ? super T, U>,BinaryOperator<U>) 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<Course> courses = new ArrayList<Course>(){
         {
            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类型数组的构造器,返回A类型数组
collect (Supplier<R>, BiConsumer<R, ? super T>, BiConsumer<R, R>) R 提供 Supplier 及 BiConsumer,对流元素进行聚合操作
collect Collector<? super T, A, R> 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<String> asList = stringStream.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);

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

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

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

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

// 示例 8,聚合成Map
Map<String, String> map = persons.stream().collect(Collectors.toMap(Person::getName, Person::getSex)); 
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270