行百里er 行百里er
首页
  • 分类
  • 标签
  • 归档
设计模式
  • JVM
  • Java基础
MySQL
Elastic Stack
Redis
  • Kafka
  • RocketMQ
分布式
Spring Cloud Alibaba
云原生
数据结构与算法
关于
GitHub (opens new window)

行百里er

Java程序员一枚
首页
  • 分类
  • 标签
  • 归档
设计模式
  • JVM
  • Java基础
MySQL
Elastic Stack
Redis
  • Kafka
  • RocketMQ
分布式
Spring Cloud Alibaba
云原生
数据结构与算法
关于
GitHub (opens new window)
  • JVM

  • Java基础

    • 【优雅的避坑】从验证码生成代码的优化到 JVM 栈和堆
    • 【优雅的避坑】HashMap正确的避免扩容带来的额外开销
    • 【优雅的避坑】不要轻易使用==比较两个Integer的值
    • 【优雅的避坑】为什么0.1+0.2不等于0.3了!?
    • 【优雅的避坑】不安全!别再共享SimpleDateFormat了
    • new Object在内存中占多少字节?
    • Java 8之Lambda表达式的写法套路
    • Java 8 Stream API可以怎么玩?
      • Stream API主要方法介绍
        • long count()
        • Stream distinct()
        • Stream predicate)
        • Optional findAny()
        • Optional findFirst()
        • void forEach(Consumer action)
        • static s)
        • static f)
        • Stream limit(long maxSize)
        • IntStream mapToInt(ToIntFunction mapper)
        • Optional comparator)
        • static of(T t)
        • Stream skip(long n)
        • Stream action)
        • Stream sorted()
        • Stream comparator)
        • Object[] toArray()
      • 玩一玩Stream API
        • Stream的创建
      • 还有这种操作?
        • 输出一个集合中所有的偶数
        • 计算一个集合中所有偶数的和
        • 输出集合中的最大值、最小值
        • 集合去重
        • 输出一个区域内的数字
      • 小结
    • Java最强大的技术之一:反射
    • 从一道面试题进入Java并发新机制---JUC
  • Java
  • Java基础
行百里er
2020-08-28
目录

Java 8 Stream API可以怎么玩?

作者:行百里er

博客:https://chendapeng.cn (opens new window)

提示

这里是 行百里er 的博客:行百里者半九十,凡事善始善终,吾将上下而求索!

Java 8新特性之一 Stream 的官方描述:

Classes in the new java.util.stream package provide a Stream API to support functional-style operations on streams of elements.

The Stream API is integrated into the Collections API, which enables bulk operations on collections, such as sequential or parallel map-reduce transformations.

Stream是一组用来处理数组、集合的API。

Java 8为什么引入函数式编程呢?简单说为了简洁,效率。

  • 函数式编程写出的代码简洁且意图明确,基本告别繁琐的for循环
  • 多核友好,函数式编程编写并行执行程序简单,调用一下parallel()方法就好

那么,使用Stream API怎么个简洁法呢?我们先来看一下Stream API给我们提供了哪些方法,然后实际操作一下,看看使用上有是不是真的很爽。

# Stream API主要方法介绍

# long count()

Returns the count of elements in this stream.

返回stream中的元素个数。

# Stream<T> distinct()

Returns a stream consisting of the distinct elements (according to Object.equals(Object)) of this stream.

返回由该流的不同元素(根据 Object.equals(Object) )组成的流。(也就是去重后组成的新的stream)

# Stream<T> filter(Predicate<? super T> predicate)

Returns a stream consisting of the elements of this stream that match the given predicate.

返回由与此给定谓词匹配的此流的元素组成的流。

# Optional<T> findAny()

Returns an Optional describing some element of the stream, or an empty Optional if the stream is empty.

返回描述流的一些元素的Optional如果流为空,则返回一个空的Optional 。

# Optional<T> findFirst()

Returns an Optional describing the first element of this stream, or an empty Optional if the stream is empty.

返回描述此流的第一个元素的可选项,如果流为空,则返回空的可选项。

# void forEach(Consumer<? super T> action)

Performs an action for each element of this stream.

对该流的每个元素执行操作。

# static <T> Stream<T> generate(Supplier<T> s)

Returns an infinite sequential unordered stream where each element is generated by the provided Supplier.

返回无限顺序无序流,其中每个元素由参数Supplier生成 。

生成的是一个无限多元素的流,所以需要其他方法来限制一下。

# static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)

Returns an infinite sequential ordered Stream produced by iterative application of a function f to an initial element seed, producing a Stream consisting of seed, f(seed), f(f(seed)), etc.

返回有序无限连续 Stream由函数的迭代应用产生 f至初始元素 seed ,产生 Stream包括 seed , f(seed) , f(f(seed)) ,等 。

# Stream<T> limit(long maxSize)

Returns a stream consisting of the elements of this stream, truncated to be no longer than maxSize in length.

返回由此流的元素组成的流,截短长度不能超过 maxSize 。

# IntStream mapToInt(ToIntFunction<? super T> mapper)

Returns an IntStream consisting of the results of applying the given function to the elements of this stream.

返回一个 IntStream ,其中包含将给定函数应用于此流的元素的结果。

类似的还有 map() ,mapToDouble() ,mapToLong() 。

# Optional<T> max(Comparator<? super T> comparator)

Returns the maximum element of this stream according to the provided Comparator.

根据提供的 Comparator返回此流的最大元素。

类似的还有 min() 。

# static <T> Stream<T> of(T t)

Returns a sequential Stream containing a single element.

返回包含单个元素的顺序 Stream 。

类似的还有 of(T... values) 。

# Stream<T> skip(long n)

Returns a stream consisting of the remaining elements of this stream after discarding the first n elements of the stream.

在丢弃流的第一个 n元素后,返回由该流的 n元素组成的流。

# Stream<T> peek(Consumer<? super T> action)

Returns a stream consisting of the elements of this stream, additionally performing the provided action on each element as elements are consumed from the resulting stream.

返回由该流的元素组成的流,另外在从生成的流中消耗元素时对每个元素执行提供的操作。

# Stream<T> sorted()

Returns a stream consisting of the elements of this stream, sorted according to natural order.

返回由此流的元素组成的流,根据自然顺序排序。

# Stream<T> sorted(Comparator<? super T> comparator)

Returns a stream consisting of the elements of this stream, sorted according to the provided Comparator.

返回由该流的元素组成的流,根据提供的 Comparator进行排序。

# Object[] toArray()

Returns an array containing the elements of this stream.

返回一个包含此流的元素的数组。

# 玩一玩Stream API

关于Stream API,一般情况下是结合 Lambda表达式 来使用的。我们来看一下具体怎么操作。

# Stream的创建

  1. 通过数组创建一个Stream

Arrays.stream(array)

/**
 * 通过数组创建Stream
 */
static void generateStreamByArray(){
    String[] str = {"行", "百", "里", "er"};
    Stream<String> arrStream = Arrays.stream(str);
    arrStream.forEach(System.out::print);
}
1
2
3
4
5
6
7
8

可以通过 stream.forEach(System.out::print) 这种 Lambda 表达式的方式打印 Stream 中的每个元素,运行结果:

行百里er
Process finished with exit code 0
1
2
  1. 通过集合创建一个Stream

list.stream()

static void generateStreamByList(){
    List<String> list = Arrays.asList("行", "百", "里", "er");
    list.stream().forEach(System.out::print);
    //list.forEach(System.out::print);
}
1
2
3
4
5
  1. 通过 generate 方法创建Stream

Stream.generate(Supplier<T> s)

generate方法的参数是一个 Supplier 接口,这个接口的描述文档是:

说明 Supplier 是一个函数式接口,只有一个方法 get() ,因此我们可以用 Lambda 表达式来搞:

//Stream.generate
static void withGenerate() {
    Supplier<String> s = () -> "行百里er";
    //用limit方法限制生成的个数
    Stream.generate(s).limit(3).forEach(System.out::println);

    System.out.println("-----------------华丽的分割线-------------");

    Stream<String> stream = Stream.generate(() -> "行百里er");
    //用limit方法限制生成的个数
    stream.limit(3).forEach(System.out::println);
}
1
2
3
4
5
6
7
8
9
10
11
12

运行结果:

行百里er
行百里er
行百里er
-----------------华丽的分割线-------------
行百里er
行百里er
行百里er

Process finished with exit code 0
1
2
3
4
5
6
7
8
9
  1. 通过iterate方法创建Stream

Stream.iterate(seed, UnaryOperator<T>)

通过调用 Stream 的 iterate 方法也能生成一个stream,该方法有2个参数,第一个参数是种子,专门为第二个参数 UnaryOperator 提供初始值,那么 UnaryOperator 又是个什么鬼呢?来看源码:

@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {

    /**
     * Returns a unary operator that always returns its input argument.
     *
     * @param <T> the type of the input and output of the operator
     * @return a unary operator that always returns its input argument
     */
    static <T> UnaryOperator<T> identity() {
        return t -> t;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

说明 UnaryOperator 也是函数式的,表示对单个操作数执行的操作,该操作产生与其操作数类型相同的结果。

这是专门针对 操作数和对操作结果类型相同 这一类的。

比如我要产生一个stream,给定种子元素为2,产生3个元素,每个元素是是前一个元素的2倍,可以这样来:

//Stream.iterate(seed, UnaryOperator)
static void withIterate() {
    UnaryOperator<Integer> u = i -> i * 2;
    Stream.iterate(2, u).limit(3).forEach(System.out::println);

    System.out.println("---------华丽的分割线-----------");
    //等价于下面这种写法
    Stream.iterate(2, i -> i * 2).limit(3).forEach(System.out::println);
}
1
2
3
4
5
6
7
8
9

执行结果:

2
4
8
---------华丽的分割线-----------
2
4
8

Process finished with exit code 0
1
2
3
4
5
6
7
8
9
  1. 其他方式创建Stream

字符串的 chars() 方法也能生成stream,不过是 IntStream :

static void others() {
    IntStream chars = "行百里er".chars();
    //打印的是int类型
    chars.forEach(System.out::println);
}
1
2
3
4
5

运行结果:

34892
30334
37324
101
114
1
2
3
4
5

要想按照字符输出,做一下强制转换就行了:

static void others() {
    IntStream chars = "行百里er".chars();
    //打印的是int类型
    //chars.forEach(System.out::println);
    //可以将int转成char输出
    chars.forEach((c) -> System.out.print((char) c));
}
1
2
3
4
5
6
7
行百里er
Process finished with exit code 0
1
2

# 还有这种操作?

# 输出一个集合中所有的偶数

一句话搞定:

Arrays.asList(11, 2, 3, 4, 5, 19, 8, 18, 23).stream().filter((x) -> x % 2 == 0).forEach(System.out::println);
1

filter 方法的参数 Predicate<T> 也是函数式接口, 代表一个谓词(boolean-valued函数)的一个参数 。

# 计算一个集合中所有偶数的和

int sum = Arrays.asList(11, 2, 3, 4, 5, 19, 8, 18, 23).stream().filter((x) -> x % 2 == 0).mapToInt(x -> x).sum();
1

这里要注意的是,filter 出偶数之后要转成 IntStream ,才有 sum() 方法,用 mapToInt(ToIntFunction<? super T> mapper) 转即可。

参数ToIntFunction:表示产生值为int的结果的函数。这是专门为int- production转换提供的方法。

# 输出集合中的最大值、最小值

用 max() ,min() 方法:

List<Integer> intList = Arrays.asList(11, 2, 3, 4, 5, 6);
//输出最大值
Optional<Integer> maxOptional = intList.stream().max(Comparator.comparingInt(x -> x));
Integer max = maxOptional.get();
System.out.println("max:" + max);

//输出最小值
System.out.println(intList.stream().min(Comparator.comparingInt(x -> x)).get());
1
2
3
4
5
6
7
8

不用 max,min 也能实现,我们可以先排好序:

//输出最小值,先排好序,sorted不传参默认升序排序,找到第一个就是最小的了
System.out.println(intList.stream().sorted().findFirst().get());

//按照降序排序,再找第一个就是最大的了
System.out.println(intList.stream().sorted((x, y) -> y - x).findFirst().get());
1
2
3
4
5

# 集合去重

使用 distinct 方法或转成 Set :

//去重
Stream<Integer> distinctStream = Arrays.asList(1, 1, 2, 3, 5, 5, 3).stream().distinct();
distinctStream.forEach(System.out::println);
//去重方法二:转成Set
Set<Integer> collect = Arrays.asList(1, 1, 2, 3, 5, 5, 3).stream().collect(Collectors.toSet());
collect.forEach(System.out::println);
1
2
3
4
5
6

# 输出一个区域内的数字

比如,构造一个集合,里面存放1 ~ 50数字,要求输出21 ~ 30这10个数。

可以使用 iterate 方法构造stream,用 limit 限制为50个,然后调用 skip 方法跳过前20个,这时会返回一个从21 ~ 50的stream,我们只需在limit一下就可以满足只有20 ~ 30的stream了:

//输出21~30
Stream<Integer> stream = Stream.iterate(1, x -> x + 1).limit(50).skip(20).limit(10);
stream.forEach(System.out::println);
1
2
3

# 小结

  • Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。

原始版本的 Iterator,只能显式地一个一个遍历元素并对其执行某些操作;

高级版本的 Stream,只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。

  • Stream不支持索引访问
  • Stream很容易生成数组或集合
  • Stream支持过滤,查找,转换,汇总,聚合等操作

首发公众号 行百里er ,欢迎老铁们关注阅读指正。

#Java
上次更新: 2022/10/04, 18:14:30
Java 8之Lambda表达式的写法套路
Java最强大的技术之一:反射

← Java 8之Lambda表达式的写法套路 Java最强大的技术之一:反射→

最近更新
01
重要数据不能丢!MySQL数据库定期备份保驾护航!
05-22
02
分布式事务解决方案之 Seata(二):Seata AT 模式
09-09
03
Seata 番外篇:使用 docker-compose 部署 Seata Server(TC)及 K8S 部署 Seata 高可用
09-05
更多文章>
Theme by Vdoing | Copyright © 2020-2023 行百里er | MIT License | 豫ICP备2022020385号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式