为了账号安全,请及时绑定邮箱和手机立即绑定

05-Stream API 第一篇

标签:
Java

若想了解java8其它新特性,请到 00-java8常用新特性文章索引 阅读。
都说Java8最重要的两个新特性是lambda表达式和Stream API,lambda表达式在第一篇文章中,我已经介绍过,现在让我们一起继续探索Stream API的神秘面纱。

抛砖引玉

public class Person {

    //姓名
    private String name;
    //年龄
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

}

public class StreamAPITest {
    List<Person> persons = null;

    @Before
    public void before() {
        persons = Arrays.asList(
                new Person("刘一", 25),
                new Person("陈二", 12),
                new Person("张三", 24),
                new Person("李四", 29),
                new Person("王五", 22),
                new Person("赵六", 15),
                new Person("孙七", 16),
                new Person("周八", 18)
        );
    }

    /**
     * 求所有年龄大于20的人,并且按年龄升序排序
     * 不用 Stream API 的做法
     */
    @Test
    public void test00() {
        // 过滤年龄大于20后的列表
        List<Person> list = new ArrayList<>();
        // 求所有年龄大于20的人,并且按年龄升序排序
        for (Person person : persons) {
            if (person.getAge() > 20)
                list.add(person);
        }

        // 排序
//        Collections.sort(list, Comparator.comparingInt(Person::getAge));//方法引用方式
        Collections.sort(list, (p1, p2) -> p1.getAge() - p2.getAge());// lambda表达式方式
        // 打印排序后的列表
        list.forEach(item -> System.out.println(item));
    }

    /**
     * 求所有年龄大于20的人,并且按年龄升序排序
     * Stream API 的做法
     */
    @Test
    public void test01() {
        // 求所有年龄大于20的人,并且按年龄升序排序
        List<Person> list = persons.stream()
                .filter(person -> person.getAge() > 20)//保留年龄大于20后的元素
                .sorted((p1, p2) -> p1.getAge() - p2.getAge())//按年龄升序排序
                .collect(Collectors.toList());//终止操作,将流转为List
        // 打印排序后的列表
        list.forEach(item -> System.out.println(item));
    }
}

test00、test01 运行结果都是:

Person{name='王五', age=22}
Person{name='张三', age=24}
Person{name='刘一', age=25}
Person{name='李四', age=29}

test00中的做法相信很多朋友在工作中都有用过,如果对于列表需要处理的中间操作很多(比如取最大值,计数…),使用test00的方式就会使得代码很混乱,比较难以维护。
相对而言,test01使用了 Stream API+lambda表达式,对于集合的操作使用链式方式相继调用,代码逻辑清晰简洁。

通过上面的测试用例,我们初步感受到了Stream API 的便利。像之前的文章一样,我们从 是什么怎么用 两个方面来学习Stream。
篇幅问题,这篇文章介绍 Stream是什么 : Stream 的定义,创建还有各种操作的API。
下一篇文章再介绍 Stream怎么用 : Stream创建、相关API的使用。

1 Stream 是什么?

Stream不是数据结构也不存放任何数据,而是对由数据源所生成的元素序列进行一系列运算,如:查找、过滤、筛选等。
数据源:可能是一个数组,一个集合,一个生成器函数

有点抽象,看看下面的流程图:

注意Stream的特点:

  • Stream不存放任何数据;
  • 不会改变源对象,相反,他们会返回一个持有结果的新Stream。peek操作除外,只是对流中的元素进行操作,没有返回新流;
  • 惰性求值:中间操作,并不会立即执行,只有触发终止操作时,才会进行实际的运算;
  • 中间操作可以是0个或者多个

下面介绍Stream的创建中间操作终止操作相关的API。

2 如何创建Stream

接口或类 方法 描述
Collection default Stream stream()
default Stream parallelStream()
将此集合作为数据源,返回一个顺序流
将此集合作为数据源,返回一个并行流
Arrays static Stream stream(T[] array) 将指定的数组作为数据源,返回一个顺序流
Stream static Stream of(T… values)
static Stream iterate(final T seed, final UnaryOperator f)
static Stream generate(Supplier s)
底层调用的是Arrays.stream(T[] array),将参数数组作为数据源,返回一个顺序流
创建无限流,入参是初始元素和UnaryOperator函数式接口,返回一个有规律的无限顺序流
创建无限流,入参是Supplier,返回一个无规律的无限顺序流,其中每个元素由提供的Supplier生成,适用于生成恒定流、随机元素流

中间操作和终止操作,涉及到一些java内置的函数式接口,很多朋友可能不是很熟悉,这里把常用的列出来。

Java常用的内置函数式接口:

函数式接口 参数类型 返回类型 用途
Consumer 消费型接口 T void 对给定的 T 类型的参数,进行操作,
核心方法:void accept(T t);
Supplier 供给型接口 空参 T 获取一个 T 类型的对象
核心方法:T get();
Function<T, R> 函数型接口 T R 对 T 类型的参数进行操作,返回 R 类型的结果
核心方法:R apply(T t);
Predicate 断定型接口 T boolean 判断 T 类型的参数,是否满足某约束,返回 boolean 值
核心方法:boolean test(T t);
BiFunction<T, U, R> T, U R 对类型为 T, U 参数应用操作,
返回 R 类型的结果。
核心方法为 R apply(T t, U u);
UnaryOperator
(Function子接口)
T T 对类型为T的对象进行一元运算,
并返回T类型的结果。
核心方法为T apply(T t);
BinaryOperator
(BiFunction 子接口)
T, T T 对类型为T的对象进行二元运算,
并返回T类型的结果。
核心方法为T apply(T t1, T t2);
BiConsumer<T, U> T, U void 对类型为T, U 参数应用操作。
核心方法为void accept(T t, U u)
ToIntFunction
ToLongFunction
ToDoubleFunction
T int/long/double T 类型的参数,
返回int\long\double 类型的结果,
核心方法为int\long\double applyAsInt(T value);
IntFunction
LongFunction
DoubleFunction
int/long/double R 参数分别为int、long、double 类型的函数,
返回 R 类型的结果,
核心方法: R apply(int\long\double value);

3 Stream 中间操作

多个中间操作可以连接起来形成一个流水线,中间操作并不会立即执行,只有触发终止操作时,才会进行实际的运算。

Stream 接口中提供的中间操作API:

方法 描述
Stream distinct() 返回由不同元素组成的流,根据元素的equals()方法去重
Stream filter(Predicate<? super T> predicate); 过滤,排除掉没有匹配 predicate函数式接口的元素
Stream flatMap(Function<? super T, ? extends Stream<? extends R>> mapper) 通过入参Function函数式接口将流中的每个元素都换成单独一个流,然后把转换后的所有流连接成一个流
DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream> mapper) 通过入参Function函数式接口将流中的每个元素都换成单独一个DoubleStream(流中元素的类型是Double),然后把转换后的所有DoubleStream连接成一个DoubleStream
IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper) 通过入参Function函数式接口将流中的每个元素都换成单独一个IntStream(流中元素的类型是Integer),然后把转换后的所有IntStream连接成一个IntStream
LongStream flatMapToLong(Function<? super T, ? extends LongStream> mapper) 通过入参Function函数式接口将流中的每个元素都换成单独一个LongStream(流中元素的类型是Long),然后把转换后的所有LongStream连接成一个LongStream
Stream limit(long maxSize) 限制流的元素数量,截断流,使其元素不超过给定数量
Stream map(Function<? super T, ? extends R> mapper) 通过入参Function函数式接口将流中的每个T类型元素映射成一个R类型的新元素
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper) 通过入参ToDoubleFunction函数式接口将流中的每个T类型元素映射成一个Double类型的新元素
IntStream mapToInt(ToIntFunction<? super T> mapper) 通过入参ToIntFunction函数式接口将流中的每个T类型元素映射成一个Integer类型的新元素
LongStream mapToLong(ToLongFunction<? super T> mapper) 通过入参ToLongFunction函数式接口将流中的每个T类型元素映射成一个Long类型的新元素
Stream peek(Consumer<? super T> action) 通过入参Consumer函数式接口将流中的每个T类型元素进行操作,如给元素赋值,每个元素引用没变
Stream skip(long n) 跳过元素,返回一个扔掉了前 n 个元素的流。如果此流包含的元素少于n,则返回空流
Stream sorted() 自然排序后返回一个新的流,如果元素不是Comparable类型,则抛出ClassCastException
Stream sorted(Comparator<? super T> comparator) 根据给定 Comparator 排序后,返回一个新流

4 Stream 终止操作

终止操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:boolean、long、void… 。

Stream 接口中提供的终止操作API:

方法 描述
boolean allMatch(Predicate<? super T> predicate) 如果流中所有元素都匹配 predicate 或者是空流,返回true,否则返回false
boolean anyMatch(Predicate<? super T> predicate) 如果流中任意一个元素匹配 predicate返回true,否则返回false,空流返回false
<R, A> R collect(Collector<? super T, A, R> collector) 接收一个Collector实例,将流中元素收集成另外一个数据结构
long count() 返回流中元素总数
Optional findAny() 返回任意一个元素,用Optional描述,如果是空流,返回空的Optional(Optional.empty),值为null
Optional findFirst() 返回流中第一个元素,用Optional描述,如果是空流,返回空的Optional(Optional.empty),值为null
void forEach(Consumer<? super T> action) 对此流的每个元素执行 Consumer 操作
Optional max(Comparator<? super T> comparator) 根据给定的Comparator,返回流中最大的元素,用Optional描述,如果是空流,返回空的Optional(Optional.empty),值为null
Optional min(Comparator<? super T> comparator) 根据给定的Comparator,返回流中最小的元素,用Optional描述,如果是空流,返回空的Optional(Optional.empty),值为null
boolean noneMatch(Predicate<? super T> predicate) 如果流中没有元素匹配 predicate 或者是空流,返回true,否则返回false
Optional reduce(BinaryOperator accumulator) 第一次执行时,accumulator函数的第一个参数为流中的第一个元素,第二个参数为流中元素的第二个元素;第二次执行时,第一个参数为第一次函数执行的结果,第二个参数为流中的第三个元素;依次类推。最终得到用Optional描述的T类型的结果。
T reduce(T identity, BinaryOperator accumulator) 第一次执行时,accumulator函数的第一个参数identity,第二个参数为流中元素的第一个元素;第二次执行时,第一个参数为第一次函数执行的结果,第二个参数为流中的第二个元素;依次类推。最终得到T类型的结果。
Object[] toArray() 返回包含此流元素的数组

5 Stream API 分类

中间操作:
无状态:指元素的处理不受其他元素的影响;
有状态:指该操作只有拿到所有元素之后才能继续下去。

终止操作:
非短路操作:指必须处理所有元素才能得到最终结果;
短路操作:指遇到某些符合条件的元素就可以得到最终结果,如 A || B,只要A为true,则无需判断B的结果。

分类 操作
中间操作 无状态(不受其他元素的影响) filter()
flatMap()
flatMapToDouble()
flatMapToInt()
flatMapToLong()
map()
mapToDouble()
mapToInt()
mapToLong()
peek()
中间操作 有状态(该操作只有拿到所有元素之后才能继续下去) distinct()
limit()
skip()
sorted()
sorted(comparator)
终止操作 非短路操作(必须处理所有元素才能得到最终结果) collect()
count()
forEach()
max()
min()
reduce()
toArray()
终止操作 短路操作(遇到某些符合条件的元素就可以得到最终结果) allMatch()
anyMatch()
findAny()
findFirst()
noneMatch()

Stream的创建中间操作终止操作相关的API就介绍到这,下篇文章,一起看看这些API使用的demo。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
JAVA开发工程师
手记
粉丝
10
获赞与收藏
8

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消