一、什么是Stream API?
Java Stream API是Java 8中引入的一个重要功能,它允许开发者以声明性方式处理数据集合,使代码更加简洁、可读性更好,同时还提供了并行操作的能力,从而能够更有效地利用多核处理器。
Stream API的核心概念是Stream,它是处理数据的一种抽象方式。Stream可以看作是数据流,它可以从数据源获取数据,然后通过一系列的处理步骤,将数据转换或过滤成所需的结果。这个处理过程是声明性的,意味着开发者只需要关注数据的处理逻辑,而不必关心具体的实现细节。
Stream API的优点主要表现在以下几个方面:
- 简洁的代码:使用Stream API可以让代码更加简洁,因为许多复杂的操作都可以通过一条简单的Stream表达式来实现。例如,要过滤出一个列表中的偶数并求平方,使用传统的Java代码可能需要多行代码来实现,而使用Stream API只需要一条简单的链式调用即可。
- 易于阅读和理解:Stream API的代码结构清晰,易于阅读和理解。通过链式调用,可以很清楚地看到数据的处理过程和结果。
- 并行操作:Stream API支持并行操作,这意味着可以同时处理多个数据项,从而充分利用多核处理器的能力。这有助于提高程序的执行效率。
- 函数式编程风格:Stream API引入了函数式编程的思想,允许开发者使用高阶函数和lambda表达式来处理数据。这使得代码更加灵活和可重用。
Stream到底是什么呢?为什么要使用Stream API?
- 是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。 “集合讲的是存储数据,Stream讲的是计算数据!”
- 实际开发中,项目中多数数据源都来自于Mysql,Oracle等。但现在数据源可以更多了,有MongDB,Radis等,而这些NoSQL的数据就需要Java层面去处理。Stream能帮我们更高效率处理数据计算。
并行流
- 并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。相比较串行的流,并行的流可以很大程度上提高程序的执行效率。
- Java 8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API 可以声明性地通过
parallel()
与sequential()
在并行流与顺序流之间进行切换。
注意:
- Stream 自己不会存储元素。
- Stream 不会改变源对象。相反,它的方法都会返回一个持有结果的新Stream。
- Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
Java IO 流和 Stream API的关系?
- Java IO 流主要用于文件的读写操作,它以字节流和字符流为基础,分别用于处理字节数据和字符数据。IO 流提供了一种顺序访问数据的方式,可以从输入流中读取数据,或者将数据写入输出流。它以一种同步的方式进行操作,适用于处理大规模的数据。
- Stream API是一种用于处理集合数据的高级抽象概念,它提供了一种流式处理的方式,能够对集合中的元素进行过滤、映射、排序、归约等操作。Stream API的目的是为了提供一种更加简洁、灵活和并行的集合操作方式。与传统的集合操作方式相比,Stream API的优势在于可以通过链式调用方法,以一种声明性的方式对集合进行处理。
- 因此,Java IO 流和 Stream API是两种不同的数据处理方式,它们没有直接的关系。但是,它们可以相互配合使用,例如使用IO 流读取文件数据,然后使用Stream API对数据进行处理和分析
二、Stream 操作的三个步骤
-
创建Stream
一个数据源(如:集合、数组),获取一个流 -
中间操作
一个中间操作链,对数据源的数据进行处理。 -
终止操作(终端操作)
一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用说明Stream 操作是延迟执行的。一次性的。(执行终止操作时才执行中间操作)
图示:
1. 创建Stream
1.1 方式一:通过集合
Java8 中的 Collection 接口被扩展,提供了两个获取流的方法
-
default Stream<E> stream()
: 返回一个顺序流顺序流,即操作时会按集合的顺序处理数据。
-
default Stream<E> parallelStream()
: 返回一个并行流并行流,即操作时将集合分成多个块,同时处理多个块的数据。
1.2 方式二:通过数组
Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:
static <T> Stream<T> stream(T[] array)
: 返回一个流
重载形式,能够处理对应基本类型的数组:
public static IntStream stream(int[] array)
public static LongStream stream(long[] array)
public static DoubleStream stream(double[] array)
1.3 方式三:通过Stream的of()
可以调用Stream类静态方法 of(), 通过显示值创建一个流。它可以接收任意数量的参数。
public static<T> Stream<T> of(T... values)
: 返回一个流
1.4 方式四:创建无限流
可以使用静态方法 Stream.iterate()
和 Stream.generate()
, 创建无限流。
- 迭代、
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
- 生成
public static<T> Stream<T> generate(Supplier<T> s)
// 方式四:创建无限流
@Test
public void test4() {
// 迭代
// public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
//seed:初始值。
//遍历前10个偶数
Stream<Integer> stream = Stream.iterate(0, x -> x + 2);
stream.limit(10).forEach(System.out::println);
// 生成
// public static<T> Stream<T> generate(Supplier<T> s)
Stream<Double> stream1 = Stream.generate(Math::random);
stream1.limit(10).forEach(System.out::println);
}
2. 中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值” 。
通过Stream的方法进行的:
2.1 筛选与切片
方法 | 描述 |
---|---|
filter(Predicate p) | 接收 Lambda , 从流中排除某些元素 |
distinct() | 筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素 |
limit(long maxSize) | 截断流,使其元素不超过给定数量 |
skip(long n) | 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补 |
代码演示:
List<String> list = new ArrayList<>();
list.add("tom");
list.add("john");
list.add("jame");
list.add("mike");
list.add("tom");
Stream<String> stream = list.stream();
//筛选
stream.filter(e -> e.contains("m"));
//去重
stream.distinct();
...
//因为每个方法都返回一个新的Stream,所以可以形成一个中间操作链
stream.filter(e -> e.contains("m")).distinct();
2.2 映射(Map)
方法 | 描述 |
---|---|
map(Function f) | 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。 |
mapToDouble(ToDoubleFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。 |
mapToInt(ToIntFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。 |
mapToLong(ToLongFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。 |
flatMap(Function f) | 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流 |
代码演示:
List<String> list = Arrays.asList("aa", "bb", "cc", "dd");
list.stream().map(str -> str.toUpperCase());//将Stream中每个元素映射成新的元素,结果返回新的Stream。
import java.util.*;
import java.util.stream.Stream;
/**
* 比较 map(Function f)和flatMap(Function f)
*/
public class Test {
public static void main(String[] args) {
List<String> list = Arrays.asList("aa", "bb", "cc", "dd");
//map(Function f)
Stream<Stream<Character>> streamStream = list.stream().map(Test::fromStringToStream);//返回的是一个由Stream元素构成的Stream。类似于List集合的add()方法add一个List元素。
//输出所有元素,遍历流,还要遍历流中的流
streamStream.forEach(s -> {
s.forEach(System.out::println);
});
//flatMap(Function f)
Stream<Character> characterStream = list.stream().flatMap(Test::fromStringToStream);//Stream中的Stream元素被平铺开了。类似于List中的addAll()方法。
//输出所有元素
characterStream.forEach(System.out::println);
}
//将字符串中的多个字符构成的集合转换为对应的Stream的实例
public static Stream<Character> fromStringToStream(String str) {
ArrayList<Character> list = new ArrayList<>();
for (Character c : list) {
list.add(c);
}
return list.stream();
}
}
2.3 排序
方法 | 描述 |
---|---|
sorted() | 产生一个新流,其中按自然顺序排序(元素要实现Comparable接口) |
sorted(Comparator com) | 产生一个新流,其中按比较器顺序排序 |
3. 终止操作
终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。
流进行了终止操作后,不能再次使用。
也是通过Stream的方法实现:
3.1 匹配与查找
方法 | 描述 |
---|---|
boolean allMatch(Predicate p) | 检查是否匹配所有元素 |
boolean anyMatch(Predicate p) | 检查是否至少匹配一个元素 |
boolean noneMatch(Predicate p) | 检查是否没有匹配所有元素 |
Optional findFirst() | 返回第一个元素 |
Optional findAny() | 返回当前流中的任意元素 |
long count() | 返回流中元素总数 |
Optional max(Comparator c) | 返回流中最大值 |
Optional min(Comparator c) | 返回流中最小值 |
void forEach(Consumer c) | 内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。 相反,Stream API 使用内部迭代——它帮你把迭代做了) |
代码演示:
class Employee {...}
class EmployeeData {...}
List<Employee> employees = EmployeeData.getEmployees();
//allMatch(Predicate p)检查是否匹配所有元素
//练习:是否所有的员工的年龄都大于18
boolean allMatch = employees.stream().allMatch(e -> e.getAge() > 18);
//anyMatch(Predicate p)检查是否至少匹配一个元素
//练习:是否存在员工的工资大于 10000
boolean anyMatch = employees.stream.anyMatch(e -> e.getSalary() > 10000);
//noneMatch(Predicate p)检查是否没有匹配所有元素
//练习:是否存在员工姓“雷”
boolean noneMatch = employees.stream().noneMatch(e -> e.getName().startsWith("雷");
//findFirst()返回第一个元素
Optional<Employee> employee = employees.stream().findFirst();
//findAny()返回当前流中的任意元素
//count()返回流中元素总数
//max(Comparator c)返回流中最大值
//练习:返回最高的工资
Optional<Double> maxSalary = employees.stream.map(e -> e.getSalary()).max(Double::compare);
//min(Comparator c)返回流中最小值
//练习:返回最低工资的员工
Optional<Employee> employee = emloyees.stream().min(Double::compare);
//forEach 内部迭代
employees.stream().forEach(System.out::println);
3.2 归约(reduce)
方法 | 描述 |
---|---|
reduce(T iden, BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。返回 T |
reduce(BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。返回 Optional |
备注:map 和 reduce 的连接通常称为 map-reduce 模式,因 Google 用它来进行网络搜索而出名。
代码演示:
//reduce(T iden, BinaryOperator b)可以将流中元素反复结合起来,得到一个值。返回 T
//练习:计算1~10的自然数的和
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer sum = list.stream().reduce(0, Integer::sum);
//reduce(BinaryOperator b)可以将流中元素反复结合起来,得到一个值。返回 Optional<T>
//练习:计算公司所有员工工资的总和
List<Employee> employees = EmployeeData.getEmployees();
Double sum = employees.stream.map(Employee::getSalary).reduce(Double::sum);
3.3 收集
方法 | 描述 |
---|---|
collect(Collector c) | 将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法 |
Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到 List、Set、Map)。
代码演示:
//练习:查找工资大于6000的员工,结果返回一个List或Set
List<Employee> employees = EmployeeData.getEmployees();
List<Employee> list = employees.stream().filter(e -> e.getSalary()>6000).collect(Collectors.toList());
list.forEach(System.out::println);//Iterable接口有forEach方法
另外, Collectors 工具类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:
方法名 | 返回类型 | 作用 |
---|---|---|
toList | List | 把流中元素收集到List |
toSet | Set | 把流中元素收集到Set |
toCollection | Collection | 把流中元素收集到创建的集合 |
counting | Long | 计算流中元素的个数 |
summingInt | Integer | 对流中元素的整数属性求和 |
averagingInt | Double | 计算流中元素Integer属性的平均值 |
summarizingInt | IntSummaryStatistics | 收集流中Integer属性的统计值。如:平均值 |
joining | String | 连接流中每个字符串 |
maxBy | Optional | 根据比较器选择最大值 |
minBy | Optional | 根据比较器选择最小值 |
reducing | 归约产生的类型 | 从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值 |
collectingAndThen | 转换函数返回的类型 | 包裹另一个收集器,对其结果转换函数 |
groupingBy | Map<K, List> | 根据某属性值对流分组,属性为K,结果为V |
partitioningBy | Map<Boolean,List> | 根据true或false进行分区 |