Stream 流
说到 Stream 便容易想到 I/O Stream,而实际上,谁规定“流”就一定是“IO 流”呢?
在 Java 8 中,得益于Lambda 所带来的函数式编程,
引入了一个全新的 Stream 概念,用于解决已有集合类库既有的弊端。
Stream 流引入案例
案例需求:• 有如下集合List<String> list = new ArrayList<>();list.add("张无忌");list.add("周芷若");list.add("赵敏"); list.add("张强");list.add("张三丰");• 按要求执行以下三个操作1) 首先筛选所有姓张的人。2) 然后筛选名字有三个字的人。3) 最后进行对结果进行打印输出。
JDK1.8 之前的实现方式
实现步骤:1) 创建原始的集合添加上面的数据2) 创建一个新的集合用于存储姓张的姓名,判断字符串是否以"张"开头,如果符合要求添加到集合中。3) 再创建一个集合,遍历上面的集合,判断每个元素的长度,如果是 3 个就添加到集合中。4) 循环输出最后过滤的结果 实例代码:
public class Demo {
public static void main(String[] args){
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
// 创建集合:存储姓张的
List<String> oneList = new ArrayList<>();
// 首先筛选所有姓张的人
for(String name:list) {
if (name.startsWith("张")){
oneList.add(name);
}
}
// 创建集合:存储名字三个字且姓张的
List<String> twoList = new ArrayList<>();
// 然后筛选名字有三个字的人
for(String name:oneList) {
if (name.length() == 3){
twoList.add(name);
}
}
// 最后进行对结果进行打印输出。
for (String name: twoList) {
System.out.println(name);
}
}
}
//每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环。
//这是理所当然的么?
//不是。循环是做事情的方式,而不是目的。
//另一方面,使用线性循环就意味着只能遍历一次。
//如果希望再次遍历,只能再使用另一个循环从头开始。
//Lambda 的衍生物 Stream 能给我们带来怎样更加优雅的写法呢?
Stream 流的初体验
上面的案例如果使用 Stream 流来实现代码如何写呢?请看下面的案例。
案例步骤:1) 将数组转成一个 List,"张无忌","周芷若","赵敏","张强","张三丰"2) 将 list 转成 stream 流,调用两次 filter()方法,filter()方法的参数是 Predicate,使用 Lambda 传递参数。3) 首先筛选所有姓张的人,然后筛选名字有三个字的人,最后使用 forEach 进行对结果进行打印输出 案例代码:
import java.util.Arrays;
import java.util.List;
public class DemoBegin {
public static void main(String[] args) {
List<String> list = Arrays.asList("张无忌", "周芷若", "赵敏", "张强", "张三丰");
//首先筛选所有姓张的人,然后筛选名字有三个字的人,最后进行对结果进行打印输出
list.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() ==
3).forEach(System.out::println);
}
}
//直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:获取流、过滤姓张、过滤长度为 3、逐一打印。
//获取流、list.stream()
//过滤姓张、filter(s -> s.startsWith("张"))
//过滤长度为3、filter(s -> s.length() ==3)
//逐一打印。forEach(System.out::println)
获取流的方式
java.util.stream.Stream是 Java 8 新加入的最常用的流接口。//这不是一个函数式接口。
//获取一个流非常简单,有以下几种常用的方式:
1.collection 对象.stream()
所有的 Collection 集合都可以通过 stream()这个方法获取流,这是 Collection 接口中的默认方法。
2.Stream.of()
Stream 接口的静态方法 of()可以得到数组对应的流。
根据 Collection 获取流
首先,java.util.Collection 接口中加入了 default 方法 stream()用来获取流,
所以其所有子接口和实现类均可获取流。
//只要是 Collection 单列集合,都可以直接调用 stream()方法获得流对象。
案例说明:创建一个 List 和 Set 集合,添加几个元素,调用它们的 stream()方法得到它们的流对象。并且调用 forEach()方法输出每个元素。 案例代码:
public class Demo02 {
public static void main(String[] args){
// 创建 List 集合
List<String> list = new ArrayList<>();
// 创建 Set 集合
Set<String> set = new HashSet<>();
// 获得流对象
Stream<String> s1 = list.stream();
Stream<String> s2 = set.stream();
}
}
根据数组获取流
如果使用的不是集合或映射而是数组,
由于数组对象不可能添加默认方法,所以 Stream 接口中提供了静态方法 of,使用很简单:
static Stream of(T... values)
通过指定的值返回一个有顺序的流
参数:指定流中的所有元素
返回:一个新的流
示例:通过 Stream 类的静态方法 of 得到一组字符串的流对象
Stream<String> s = Stream.of("a","b","c");
Stream 常用方法
流操作的方法很丰富,这里介绍一些常用的方法,这些方法可以被分成两种://终结方法和非终结方法
非终结方法与终结方法
//凡是返回值仍然为 Stream 接口的为非终结方法(函数拼接方法),它们支持链式调用;
//而返回值不再为Stream 接口的为终结方法,不再支持链式调用。
如下表所示:
方法名 方法作用 方法种类 是否支持链式调用
forEach 逐一处理 终结 否
count 统计个数 终结 否
filter 过滤 函数拼接 是
limit 取用前几个 函数拼接 是
skip 跳过前几个 函数拼接 是
map 映射 函数拼接 是
concat 组合 函数拼接 是
Stream 流中逐一处理的方法:forEach
方法声明如下 说明
void forEach(Consumer action) 对此流的每个元素执行一个操作,这是一个终结方法。
对于并行流,此操作并不保证流的操作顺序,因为这样做将牺牲并行性的好处。
参数:对元素执行的操作
方法演示 案例说明:使用 of 方法将一组字符串转成流,调用 forEach 方法将每个元素转成大写,输出每个元素。 执行代码:
public static void main(String[] args){
// 通过 Stream 类的静态方法获得流对象
Stream<String> s1 = Stream.of("Jack","Rose","Hello","Select","Insert");
// 调用 forEach 方法
s1.forEach(s-> System.out.println(s.toUpperCase()));
}
过滤:filter
Stream filter(Predicate p) 返回一个与给定判断条件匹配的元素组成的流。
参数:谓词对象,指定判断条件,应用于每个元素以确定是否应该包含它。
返回:一个新的流
Predicate 接口
此前我们已经学习过 java.util.stream.Predicate 函数式接口,
其中唯一的抽象方法为:
boolean test(T t);
该方法将会产生一个 boolean 值结果,代表指定的条件是否满足。
如果结果为 true,那么 Stream 流的 filter方法将会留用元素;
如果结果为 false,那么 filter 方法将会舍弃元素。
基本使用
案例说明:1) 通过 Stream 类的 of 静态方法获得流对象:"Jack","Rose","Hello","Select","Insert"2) 得到所有长度是 4 个的元素生成一个新的流3) 使用 forEach()输出打印流对象 案例代码:
public static void main(String[] args){
// 通过 Stream 类的静态方法获得流对象
Stream<String> s = Stream.of("Jack", "Rose", "Hello", "Select", "Insert");
// filter 方法
Stream<String> ss = s.filter(s1 -> s1.length() == 4);
ss.forEach(System.out::println);
}
统计个数:count
long count() 返回此流中元素的计数。这是一个终结方法。
返回:此流中元素的个数。
基本使用 案例说明:统计一个流中元素的个数 案例代码:
public static void main(String[] args){
// 通过 Stream 类的静态方法获得流对象
Stream<String> s = Stream.of("abc","cc","cd","d","e");
// 调用 count 方法
System.out.println(s.count());
}
获取前几个:limit
Stream limit(long maxSize) 获得流中前 maxSize 个元素,将元素添加到另一个流中返回
如果 maxSize 大于等于当前流的元素个数,则所有元素都会获取到
如果 maxSize 等于 0,则会获得一个空流。
基本使用 案例说明:得到流中前 3 个元素输出,得到流中前 10 个元素输出,得到流中前 0 个元素输出 案例代码:
public static void main(String[] args){
// 通过 Stream 类的静态方法获得流对象
Stream<String> s = Stream.of("abc","cc","cd","d","e");
// 调用 limit 方法
//得到流中前 3 个元素输出
s.limit(3).forEach(System.out::println);
//得到流中前 10 个元素输出,大于等于当前流的元素个数,则所有元素都会获取到
//s.limit(10).forEach(System.out::println);
//得到流中前 0 个元素输出,则会获得一个空流
//s.limit(0).forEach(System.out::println);
}
跳过前几个:skip
Stream skip(long n) 跳过前面 n 元素,将后面元素添加到另一个流中
基本使用 案例说明:跳过前面 3 个元素得到一个新的流,并且使用 forEach 输出。
案例代码:
public static void main(String[] args){
// 通过 Stream 类的静态方法获得流对象
Stream<String> s = Stream.of("abc","cc","cd","d","e");
// 调用 skip 方法
s.skip(1).forEach(System.out::println);
}
映射:map
Stream map(Function mapper)
使用 map 可以遍历集合中的每个元素,并对其进行操作。
将一种类型映射成另一种类型,得到一个新的流。
该接口需要一个 Function 函数式接口参数,可以将当前流中的 T 类型数据转换为另一种 R 类型的流。
Function 接口
此前我们已经学习过 java.util.stream.Function 函数式接口,其中唯一的抽象方法为:
R apply(T t);这可以将一种 T 类型转换成为 R 类型,而这种转换的动作,就称为“映射”。
案例说明:1) 有一组字符串类型的数字流2) 使用 map 方法,将字符串类型全部转成整数,可以使用静态方法引用,映射成一个新的流3) 使用 map 方法,将整数类型每个元素加 1,映射成一个新的流4) 使用 forEach 输出新流中的每个元素。
注:流只能消费一次,即只能使用 forEach 一次,第二次使用会抛出异常。
java.lang.IllegalStateException: stream has already been operated upon or closed
案例代码:
public static void main(String[] args) {
// 通过 Stream 类的静态方法获得流对象
Stream<String> s1 = Stream.of("123","46","456","673","131");
//支持链式写法
Stream<Integer> s2 = s1.map(Integer::parseInt);
Stream<Integer> s3 = s2.map(m ->++m);
s3.forEach(System.out::println);
}
组合:concat
如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat:
public static Stream concat(Stream a,Stream b)
静态方法,通过类名调用,将两个流合并成一个流返回一个新的流
基本使用 案例说明1) 从两个字符串数组创建两个流2) 使用 concat 方法对两个流进行拼接3) 输出拼接后的流 案例代码:
public static void main(String[] args) {
// 通过 Stream 类的静态方法获得流对象
Stream<String> s1 = Stream.of("123","46","456","673","131");
Stream<String> s2 = Stream.of("abc","bbb","ccc","ddd");
// 调用 concat 方法
Stream.concat(s1,s2).forEach(s -> System.out.print(s + " "));
}
流的综合案例
案例说明现在有两个 ArrayList 集合存储队伍当中的多个成员姓名,两个队伍(集合)的代码如下:
List<String> one = new ArrayList<>();
one.add("迪丽热巴");
one.add("宋远桥");
one.add("苏星河");
one.add("老子");
one.add("庄子");
one.add("孙子");
one.add("洪七公");
List<String> two = new ArrayList<>();
two.add("古力娜扎");
two.add("张无忌");
two.add("张三丰");
two.add("赵丽颖");
two.add("张二狗");
two.add("张天爱");
two.add("张三");
Person 类的代码如下
public class Person {
private String name;
public Person() {}
public Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{name='" + name + "'}";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
要求如下:
- 使用传统的 for 循环(或增强 for 循环)依次进行以下若干操作步骤
- 使用 Stream 方式依次进行以下若干操作步骤1) 第一个队伍只要名字为 3 个字的成员姓名;2) 第一个队伍筛选之后只要前 3 个人;3) 第二个队伍只要姓张的成员姓名;4) 第二个队伍筛选之后不要前 2 个人;5) 将两个队伍合并为一个队伍;6) 根据姓名创建 Person 对象;7) 打印整个队伍的 Person 对象信息。 实现代码
public static void main(String[] args){ List<String> one = new ArrayList<>(); one.add("迪丽热巴"); one.add("宋远桥"); one.add("苏星河"); one.add("老子"); one.add("庄子"); one.add("孙子"); one.add("洪七公"); List<String> two = new ArrayList<>(); two.add("古力娜扎"); two.add("张无忌"); two.add("张三丰"); two.add("赵丽颖"); two.add("张二狗"); two.add("张天爱"); two.add("张三"); // 1. 第一个队伍只要名字为 3 个字的成员姓名; // 2. 第一个队伍筛选之后只要前 3 个人; Stream<String> s1 = one.stream().filter(s -> s.length() == 3).limit(3); // 3. 第二个队伍只要姓张的成员姓名; // 4. 第二个队伍筛选之后不要前 2 个人; Stream<String> s2 = two.stream().filter(s -> s.startsWith("张")).skip(2); // 5. 将两个队伍合并为一个队伍; Stream<String> s3 = Stream.concat(s1, s2); // 6. 根据姓名Person创建 Person 对象; Stream<Person> s4 = s3.map(Person::new); // 7. 打印整个队伍的 Person 对象信息。 s4.forEach(System.out::println); }