详解Java8特性之Stream API

前言

Java8中有两大最为重要的改变。第一个是Lambda表达式,还不了解的可以去这看看详解Java8特性之Lambda表达式

另外一个则是Stream API,在java.util.stream

是什么

Stream呢,很多地方说到它是处理集合的关键抽象概念,这样子说实在是太抽象了。这个Stream并不是我们以前认识的IO流,而是一个数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。集合专注的是数据,流专注的是计算

有什么用

它可以让你对集合执行非常复杂的查找、过滤和映射数据等操作。通过使用Stream API对集合数据进行操作,就类似于使用 SQL 执行的数据库查询,也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式

说这么多还是直接来个例子体会一下吧

@Test
public void test() {
    List<Integer> list = Arrays.asList(2, 3, 5, 4, 1, 8, 10, 9, 7, 6);

    // 传统方式
    for (Integer num : list) {
        if (num > 5)
            System.out.println(num);
    }

    System.out.println("-------------");

    // Stream方式
    list.stream()//
            .filter((e) -> e > 5)//
            .forEach(System.out::println);
}

输出结果

8
10
9
7
6
-------------
8
10
9
7
6

在这个例子中,我把1-10个数作为集合的元素,然后分别通过传统迭代方式和Stream方式把大于5的数字打印出来。

先不用知道Stream方式为什么是这么做的,后面会讲到,只是简单先示范一下Stream操作集合。

还有就是在用Stream方式进行操作时我们可以使用链式编程,何谓链式,就是多个操作连着来,不用中间声明变量。像我们最熟悉的StringBuffer

StringBuffer buffer = new StringBuffer();
buffer.append("aaa").append("bbb").append("ccc");

Stream的中间操作都是返回一个Stream,所以可以使用链式编程来使用Stream,至于中间操作后面会讲到。

还有就是在eclipse中,可以在一行代码后面加上//来防止格式化代码时链式编程被格式化成一行,如果不加这个的话,像上面的链式编程可能就会被格式化成下面这样子,不便于阅读。

list.stream().filter((e) -> e > 5).forEach(System.out::println);

怎么用

使用Stream操作有三个步骤

  • 创建 Stream:从一个数据源(如集合、数组)中获取一个流
  • 中间操作:一个或多个中间操作,对数据源的数据进行处理
  • 终止操作:执行中间操作链,并产生结果

创建 Stream

由集合创建 Stream

Java8 中的 Collection 接口被扩展了,提供了两个获取流的方法:

  • default Stream stream() : 返回一个顺序流
  • default Stream parallelStream() : 返回一个并行流

这两个方法都是接口默认方法,不了解的可以去这看看详解Java8特性之接口默认方法

可以像下面这样子直接调用stream()方法获取流

List<String> list = new ArrayList<>();
Stream<String> stream = list.stream(); //获取一个顺序流
Stream<String> parallelStream = list.parallelStream(); //获取一个并行流

由数组创建流

Java8 中的 Arrays 的静态方法 stream() 可以从数组获取流:

  • static Stream stream(T[] array): 返回一个流

还有重载形式,能够处理对应基本类型的数组:

  • public static IntStream stream(int[] array)
  • public static LongStream stream(long[] array)
  • public static DoubleStream stream(double[] array)

例子:

Integer[] nums = new Integer[8];
Stream<Integer> stream = Arrays.stream(nums);

由值创建流

可以使用静态方法 Stream.of(), 通过显示值创建一个流。它可以接收任意数量的参数。

  • public static Stream of(T… values) : 返回一个流

例子:

Stream<Integer> stream = Stream.of(1,2,3,4,5);

由函数创建流

可以使用静态方法 Stream.iterate() 和Stream.generate(), 创建无限流

  • public static Stream iterate(final T seed, final UnaryOperator f)
  • public static Stream generate(Supplier s) :

所谓无限流,顾名思义就是无限的.

iterate()方法举例,第一个参数seed其实就是一个起始值,而第二个参数是UnaryOperator类型实例,而这个UnaryOperator是一个函数式接口,继承了函数式接口Function,所以我们可以直接把他当做Function。关于函数式接口,可以去这里看看详解Java8特性之Lambda表达式

下面是iterate()方法的具体使用例子

@Test
public void test2() {
    Stream.iterate(1, (x) -> x + 1).forEach(System.out::println); }

这段代码的意思是这样的,第一个参数我指定的是1,意思是1作为起始值,第二参数是Lambda表达式创建一个UnaryOperator其实也可以说是Function实例,将它的唯一抽象方法实现为接受一个参数返回该参数+1的值。至于forEach()则是终止操作,在后面会讲到。

运行这段代码,你会发现,控制台一直在输出数字,不会停(这就是无限的体现),而且数字总是等差递增,差值为1。

再来看看generate()方法的例子,该方法接受一个Supplier实例的参数

@Test
public void test2() {
    Stream.generate(() -> Math.random()).forEach(System.out::println); }

generate()方法参数中我用Lambda表达式创建了一个Supplier实例,该实例的方法实现功能为返回一个随机数。

运行这段代码,你会发现,控制台一直在输出大于等于0小于1的随机数。

中间操作

在创建出流以后,我们就可以进行中间操作了。多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值” ,这个在后面我会演示一下。

中间操作又分四类

  • 筛选
  • 切割
  • 映射
  • 排序

筛选

筛选操作有两个方法

filter(Predicate p), 接收Predicate实例,根据该实例的test方法进行筛选,如

@Test
public void test() {
    List<Integer> list = Arrays.asList(2, 3, 5, 4, 1, 8, 10, 9, 7, 6);
    list.stream()//
            .filter((e) -> e > 5)//
            .forEach(System.out::println);
}

输出结果

8
10
9
7
6

用Lambda表达式创建Predicate实例,其中(e) -> e > 5便是test方法的实现,具体为接收一个Integer类型参数e,该参数如果大于5则test方法返回true,否则false。

distinct(),这个方法大家应该能看出来就是去重复的,根据流所生成元素的 hashCode() 和 equals() 去除重复元素。如

@Test
public void test() {
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 4, 4, 5, 6);
    list.stream()//
            .distinct()//
            .forEach(System.out::println);
}

输出结果

1
2
3
4
5
6

切割

切割也有两个方法

limit(long maxSize),截断流,使其元素不超过给定数量,跟SQL语句很像。如

@Test
public void test() {
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);
    list.stream()//
            .limit(3)//
            .forEach(System.out::println);
}

输出结果

1
2
3

skip(long n) ,返回一个去掉了前 n 个元素的流。若流中元素
不足 n 个,则返回一个空流。如

@Test
public void test() {
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);
    list.stream()//
            .skip(3)//
            .forEach(System.out::println);
}

输出结果

4
5
6

可以看到这个方法刚好跟limit方法互补了,limit去尾,skip去头。

映射

map(Function f) ,接收一个Function实例,Function的抽象方法apply是接收一个参数然后返回一个值,这个返回值就是映射的结果。如

@Test
public void test() {
    List<Integer> list = Arrays.asList(1, 2, 3);
    list.stream()//
            .map(x -> x*x)
            .forEach(System.out::println);
}

输出结果

1
4
9

可以发现流中的每个元素映射出来的新值是该元素的平方。

mapToDouble(ToDoubleFunction f),这个函数跟map很像,只不过映射的结果一定是要Double类型。如

@Test
public void test() {
    List<Integer> list = Arrays.asList(1, 2, 3);
    list.stream()//
            .mapToDouble(x -> x+0.1)
            .forEach(System.out::println);
}

输出结果

1.1
2.1
3.1

可以发现Integer类型的元素被映射成Double类型了

类似的还有mapToInt(ToIntFunction f)mapToLong(ToLongFunction f)方法,这里就不去演示了。

flatMap(Function f),将流中的每个元素映射成一个流。如

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

import org.junit.Test;

public class TestStreamAPI {

    @Test
    public void test() {
        List<String> list = Arrays.asList("abc", "efg", "xyz");
        list.stream()//
                .flatMap(TestStreamAPI::string2Stream)//
                .forEach(System.out::println);
    }

    /** * 接收一个字符串将其所以的字符添加到list中然后返回stream * @param str * @return */
    public static Stream<Character> string2Stream(String str) {
        List<Character> list = new ArrayList<>();
        char[] charArray = str.toCharArray();
        for (char c : charArray) {
            list.add(c);
        }
        return list.stream();
    }
}

输出结果

a
b
c
e
f
g
x
y
z

其实这也不难理解,就像上面mapToDouble方法把流中的每个元素映射成一个Double类型的值一样,现在只是映射成一个Stream。

排序

sorted(),可以将流中元素按自然顺序排序。如

@Test
public void test() {
    List<String> list = Arrays.asList("d", "a", "c");
    list.stream()//
            .sorted()//
            .forEach(System.out::println);
}

输出结果

a
c
d

sorted(Comparator comp),可以将流中元素按比较器顺序排序。如

@Test
public void test() {
    List<String> list = Arrays.asList("d", "a", "c");
    list.stream()//
            .sorted((x,y) -> -x.compareTo(y))//
            .forEach(System.out::println);
}

输出结果

d
c
a

终止操作

终止操作会从流的中间操作流水线生成结果。其结果可以是任何不是流的值,例如: ListInteger,甚至是 void

其中终止操作又有三类

  • 查找与匹配
  • 归约
  • 收集

查找与匹配

查找与匹配有下面这么多方法,我只演示几个典型的。

  • allMatch(Predicate p) 检查是否匹配所有元素
  • anyMatch(Predicate p) 检查是否至少匹配一个元素
  • noneMatch(Predicate p) 检查是否没有匹配所有元素
  • findFirst() 返回第一个元素
  • findAny() 返回当前流中的任意元素
  • count() 返回流中元素总数
  • max(Comparator c) 返回流中最大值
  • min(Comparator c) 返回流中最小值
  • forEach(Consumer c) 内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。相反, Stream API 使用内部迭代)

allMatch(Predicate p)例子

@Test
public void test() {
    List<Integer> list = Arrays.asList(10, 5, 7, 3);
    boolean allMatch = list.stream()//
            .allMatch(x -> x > 2);//是否全部元素都大于2
    System.out.println(allMatch);
}

输出结果

true

findFirst()例子

@Test
public void test() {
    List<Integer> list = Arrays.asList(10, 5, 7, 3);
    Optional<Integer> first = list.stream()//
            .findFirst();
    Integer val = first.get();
    System.out.println(val);//输出10
}

其中Optional是一个容器类,具体可要看看这里详解Java8特性之Optional类

max(Comparator c)例子

@Test
public void test() {
    List<Integer> list = Arrays.asList(10, 5, 7, 3);
    Optional<Integer> first = list.stream()//
            .min(Integer::compareTo);
    Integer val = first.get();
    System.out.println(val);//输出3
}

forEach(Consumer c)例子上面用过很多了,这里就不再演示了。

归约

归约操作有下面两个方法

  • reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 T
  • reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 Optional

reduce(T iden, BinaryOperator b)例子

@Test
public void test() {
    List<Integer> list = Arrays.asList(10, 5, 7, 3);
    Integer result = list.stream()//
        .reduce(2, Integer::sum);
    System.out.println(result);//输出27,其实相当于2+10+5+7+3,就是一个累加
}

reduce(BinaryOperator b)例子

@Test
public void test() {
    List<Integer> list = Arrays.asList(10, 5, 7, 3);
    Optional<Integer> optional = list.stream()//
        .reduce(Integer::sum);
    Integer result = optional.get();
    System.out.println(result);//输出25,其实相当于10+5+7+3,就是一个累加
}

收集

collect(Collector c) 将流转换为其他形式。接收一个Collector接口的
实现,用于给流中元素做汇总的方法。

下面是具体例子

@Test
public void test() {
    List<Integer> list = Arrays.asList(10, 5, 7, 3);
    // 将流中元素收集到List中
    List<Integer> resultList = list.stream()//
            .collect(Collectors.toList());

    // 将流中元素收集到Set中
    Set<Integer> resultSet = list.stream()//
            .collect(Collectors.toSet());

    System.out.println(resultList);// 输出[10, 5, 7, 3]
    System.out.println(resultSet);// 输出[3, 5, 7, 10]
}

上面的代码分别将流中的元素收集到ListSet中,其实还可以收集到其类型中,如MapOptional甚至是Integer等。在收集过程中还可以做点其它操作,比如下面例子,至于其它更多的操作大家还是去看API吧

@Test
public void test() {
    List<Integer> list = Arrays.asList(10, 5, 7, 3);

    //计算出流中的元素个数
    Long count = list.stream()//
            .collect(Collectors.counting());
    System.out.println(count);//输出4

    //计算流中Integer类型数据的总和
    Integer sum = list.stream()//
            .collect(Collectors.summingInt(x -> x));
    System.out.println(sum);//输出25
}

Stream延迟执行

上面“中间操作”那节我说到了“惰性求值”这个东西,即Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

来个例子就知道了。

先准备一个实体类Person,注意了,在getAge()方法中我输出了一句话


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() {
        System.out.println("getAge()执行");
        return age;
    }

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

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

然后是测试方法

@Test
public void test() {
    List<Person> list = Arrays.asList(//
            new Person("Jason", 18), //
            new Person("Hank", 46), //
            new Person("Alice", 23));
    // 过滤操作,找出age大于20的Person对象,但是没有终止操作
    list.stream()//
            .filter(x -> x.getAge() > 20);
}

运行这个测试方法,你会发现没有输出任何东西,因为它并没有终止操作,所以中间操作并未执行。下面加多一个终止操作

@Test
public void test() {
    List<Person> list = Arrays.asList(//
            new Person("Jason", 18), //
            new Person("Hank", 46), //
            new Person("Alice", 23));
    // 过滤操作,找出age大于20的Person对象,但是没有终止操作
    list.stream()//
            .filter(x -> x.getAge() > 20)//
            .forEach(System.out::println);
}

输出结果

getAge()执行
getAge()执行
Person [name=Hank, age=46]
getAge()执行
Person [name=Alice, age=23]

可以发现getAge()方法执行了,而且查找出两个age大于20的Person对象并输出

    原文作者:腹黑大壁花
    原文地址: https://blog.csdn.net/TimHeath/article/details/71275179
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞