菜鸟为了巩固所写
一、理解Stream
Stream API中的“ Stream (流)”,可以看成是一组数据的集合 ,我们可以对它施加一系列的数据操作,这些操作可以是顺序进行的,也可以是并行执行的。
Stream看上去是集合,但实际上不是集合,为什么呢?
二、Stream不是集合
- 普通的集合,关注的是数据的存储方式,而Stream ,关注的是施加于这些集合元素的处理任务(称为“ Operation (操作 )”)。
- 集合讲的是数据,流讲的是计算。
- Stream通常从普通的集合中“抽取”数据,但也可以从其它数据源(比如文件和数据库)中提取数据。
- Stream只“抽取”数据,它从不会修改底层的数据源。简言之:Stream Operation 是只读操作!
- 只要底层数据源允许,流中包容的元素数目是不受限制的。
- Stream API采用一种“ 即抽取、即使用、即丢弃 ”的方式处理数据,不需要“ 把所有数据都加载到内存中(而普通集合就是这样的)”才能工作,所以,能处理很大的数据集。
Stream API与"Stream"不同
Stream API中的“ Stream ”,与 Java IO 中的“ Stream ”含义是不一样的, Stream API 中的 Stream ,可以看成一种“特殊的集合”,我们可以向这个集合施加一系列的“操作( Operation)”而 Java IO 中的 Stream ,实际上是一个“数据流 ”,它是由 一连串有序的数据(字节或字符)所构成 的一个“数据序列” ”,是被加工的“原材料”。
三、Stream API使用
综合示例1
import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class ReUseStream {
public static void main(String[] args) {
StreamCannotBeReuse();
// StreamMustBeRecreated();
// StreamRecreatedViaSupplier();
}
//试图重用一个Stream,引发异常
private static void StreamCannotBeReuse() {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> stream = numbers.stream();
System.out.println("numbers集合中有元素" + stream.count() + "个");
//以下这句将引发java.lang.IllegalStateException
System.out.println("平均值为:" +
stream.mapToInt(num -> num.intValue()).average());
}
//只有被重建之后,才能再次执行操作
private static void StreamMustBeRecreated() {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> stream = numbers.stream();
System.out.println("numbers集合中有元素" + stream.count() + "个");
//必须重建一个流对象,否则,尝试重用流,会抛出IllegalStateException异常
stream = numbers.stream();
System.out.println("平均值为:"
+ stream.mapToInt(Integer::intValue)
.average().getAsDouble());
}
//通过构建一个Supplier对象,重复创建特定的流
private static void StreamRecreatedViaSupplier() {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Supplier<IntStream> streamSupplier =
() -> numbers.stream().mapToInt(Integer::intValue);
//使用Supplier接口所定义的get()方法,获取流对象的新实例
System.out.println("numbers集合中有元素"
+ streamSupplier.get().count() + "个");
System.out.println("平均值为:"
+ streamSupplier.get().average());
}
}
1、流对象只能使用一次
//试图重用一个Stream,引发异常
private static void StreamCannotBeReuse() {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> stream = numbers.stream();
System.out.println("numbers集合中有元素" + stream.count() + "个");
//以下这句将引发java.lang.IllegalStateException
System.out.println("平均值为:" +
stream.mapToInt(num -> num.intValue()).average());
}
该代码将会导致报错,出错原因在于红色字体
System.out.println("平均值为:" +
stream.mapToInt(num -> num.intValue()).average());
因为它重用了流对象stream。
2、处理“重用流”异常
//只有被重建之后,才能再次执行操作
private static void StreamMustBeRecreated() {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> stream = numbers.stream();
System.out.println("numbers集合中有元素" + stream.count() + "个");
//必须重建一个流对象,否则,尝试重用流,会抛出IllegalStateException异常
stream = numbers.stream();
System.out.println("平均值为:"
+ stream.mapToInt(Integer::intValue)
.average().getAsDouble());
}
只有重建,才可以再次引用。
3、推荐的“重用流”的编程方式
//通过构建一个Supplier对象,重复创建特定的流
private static void StreamRecreatedViaSupplier() {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Supplier<IntStream> streamSupplier =
() -> numbers.stream().mapToInt(Integer::intValue);
//使用Supplier接口所定义的get()方法,获取流对象的新实例
System.out.println("numbers集合中有元素"
+ streamSupplier.get().count() + "个");
System.out.println("平均值为:"
+ streamSupplier.get().average());
}
当需要反复执行特定流的不同操作系列时,可以构建一个Supplier对象,通过它获取新的流实例。
java.util.function包中提供了许多函数式的接口,Supplier就是其中的一个。
综合示例2
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class SumOfOddInteger {
public static void main(String[] args) {
//seperateForm();
compoundForm();
}
//以分步的方式展示Stream API如何完成复杂的数据处理任务
private static void seperateForm() {
// 生成一个包容[1,5]区间所有整数的集合
List<Integer> numbersList = Arrays.asList(1, 2, 3, 4, 5);
// 将集合转变为流
Stream<Integer> numbersStream = numbersList.stream();
// 过滤提取出奇数
Stream<Integer> oddNumbersStream= numbersStream.filter(n -> n % 2 == 1);
// 求奇数的平方值(映射/转换)
Stream<Integer> squaredNumbersStream = oddNumbersStream.map(n -> n * n);
// 累加求和(归约)
int sum = squaredNumbersStream.reduce(0, (n1, n2) -> n1 + n2);
// 使用方法引用特性,可以改写为:
//int sum = squaredNumbersStream.reduce(0, Integer::sum);
// 输出处理结果
System.out.println(sum);
}
//以级联的方式编程
private static void compoundForm(){
// 生成一个包容5个整数的集合
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 计算集合中所有奇数平方值的总和
int sum = numbers.stream()
.peek(num-> System.out.println("\nfilter之前,处理"+num))
.filter(n -> n %2 ==1)
.peek(num-> System.out.println("filter之后,map之前,过滤出"+num))
.map(n -> n * n)
.peek(num-> System.out.println("map之后,reduce之前,得到平方值:"+num))
.reduce(0, Integer::sum);
System.out.println("\n数据处理结束,其结果为:"+sum);
}
}
1、求所有奇数的平方和
//以分步的方式展示Stream API如何完成复杂的数据处理任务
private static void seperateForm() {
// 生成一个包容[1,5]区间所有整数的集合
List<Integer> numbersList = Arrays.asList(1, 2, 3, 4, 5);
// 将集合转变为流
Stream<Integer> numbersStream = numbersList.stream();
// 过滤提取出奇数
Stream<Integer> oddNumbersStream= numbersStream.filter(n -> n % 2 == 1);
// 求奇数的平方值(映射/转换)
Stream<Integer> squaredNumbersStream = oddNumbersStream.map(n -> n * n);
// 累加求和(归约)
int sum = squaredNumbersStream.reduce(0, (n1, n2) -> n1 + n2);
// 使用方法引用特性,可以改写为:
//int sum = squaredNumbersStream.reduce(0, Integer::sum);
// 输出处理结果
System.out.println(sum);
}
这里首先建立了一个存储Integer类型的List。
然后将集合numberList转为流numberStream。 numberStream=[1,2,3,4,5]
然后通过中间操作filter将流中的奇数留下来。 numberStream=[1,3,5]
然后通过中间操作map将流中的奇数平方。 numberStream=[1,9,25]
最后利用终端操作reduce,将流中的元素求和。
reduce(identity, BinaryOperator)
是一个终端操作,用于将流中的元素归约为一个值。identity
:初始值,这里是0
。(n1, n2) -> n1 + n2
:一个 Lambda 表达式,表示将两个元素相加。
上述代码也就构成了一条流水线,如图
这条数据处理管线,可以使用一种“级联调用”的代码编写方式,更清晰明了地表现出来 。
2、使用“级联”调用方式简写代码
//以级联的方式编程
private static void compoundForm(){
// 生成一个包容5个整数的集合
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 计算集合中所有奇数平方值的总和
int sum = numbers.stream()
.peek(num-> System.out.println("\nfilter之前,处理"+num))
.filter(n -> n %2 ==1)
.peek(num-> System.out.println("filter之后,map之前,过滤出"+num))
.map(n -> n * n)
.peek(num-> System.out.println("map之后,reduce之前,得到平方值:"+num))
.reduce(0, Integer::sum);
System.out.println("\n数据处理结束,其结果为:"+sum);
}
四、构成流处理管线的两种操作类型
流处理管线由“中间操作 ”和 终结(终端、终止)操作 ”所构成。数据流入“中间操作”,中间操作可以对这些数据进行特定的加工和处理,再将其转发给下一个操作,就象工厂中的流水线一样。
终止操作完成以下任务:
1.启动执行整个流操作系列
2.向调用者返回最终结果
3.操作结束后,销毁流对象