需求:
原始集合操作
package com.lb;
import java.util.ArrayList;
import java.util.List;
/**
* 1. 首先筛选所有姓张的人;
* 2. 然后筛选名字有三个字的人;
* 3. 最后进行对结果进行打印输出。
*/
public class Demo02NormalFilter {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
List<String> zhangList = new ArrayList<>();
//首先筛选所有姓张的人
for (String name : list) {
if (name.startsWith("张")) {
zhangList.add(name);
}
}
//然后筛选名字有三个字的人
List<String> shortList = new ArrayList<>();
for (String name : zhangList) {
if (name.length() == 3) {
shortList.add(name);
}
}
//最后进行对结果进行打印输出
for (String name : shortList) {
System.out.println(name);
}
}
}
Stream流的操作
package com.lb;
import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;
import java.util.ArrayList;
import java.util.List;
/**
* 1. 首先筛选所有姓张的人;
* 2. 然后筛选名字有三个字的人;
* 3. 最后进行对结果进行打印输出。
*/
public class Demo02NormalFilter {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
list.stream().filter(s -> s.startsWith("张")).filter(s -> s.length()==3).forEach(s -> System.out.println(s));
}
}
Stream流相对于原始集合操作的优点: 格式简洁, 逻辑清晰。
// 集合 -> stream()
// List
ArrayList<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
// Set
HashSet<String> set = new HashSet<>();
Stream<String> stream2 = set.stream();
// Map
HashMap<String, String> map = new HashMap<>();
// 所有键的集合
Stream<String> stream3 = map.keySet().stream();
// 所有值的集合
Stream<String> stream4 = map.values().stream();
// 所有键值对的集合
Stream<Map.Entry<String, String>> stream5 = map.entrySet().stream();
// 数组 -> Stream.of Arrays
String[] arr = {"abc"};
Stream<String> stream6 = Stream.of(arr);
// 一堆数据 -> Stream.of
Stream<Integer> stream7 = Stream.of(1, 2, 3, 4, 5);
注意事项
- 获取数组对应的流, 数组需要是引用数据类型的数组
- Stream.of()传入多个参数的时候, 参数需要是同一种数据类型
流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种:
备注:本小节之外的更多方法,请自行参考API文档
对流中的数据进行判断, 如果结果为true, 就将这个数据留下; 如果结果为false, 就将这个数据从流中删除。
基本使用
public class StreamFilter {
public static void main(String[] args) {
Stream<String> original = Stream.of("张无忌", "周芷若", "张三丰");
//在这里通过Lambda表达式来指定了筛选的条件:必须姓张。
Stream<String> result = original.filter(s -> s.startsWith("张"));
//收集Stream结果到数组中并指定为String类型
//同:String[] strings= result.toArray(s -> new String[s]);
//String[] strings = result.toArray(String[]::new);
}
}
正如旧集合 Collection 当中的 size 方法一样,流提供 count 方法来数一数其中的元素个数:
long count()
该方法返回一个long值代表元素个数(不再像旧集合那样是int值)。基本使用:
public class StreamFilter {
public static void main(String[] args) {
Stream<String> original = Stream.of("张无忌", "周芷若", "张三丰");
//在这里通过Lambda表达式来指定了筛选的条件:必须姓张。
Stream<String> result = original.filter(s -> s.startsWith("张"));
//count() 该方法返回一个long值代表元素个数
long count = result.count();
//收集Stream结果到数组中并指定为String类型
//同:String[] strings= result.toArray(s -> new String[s]);
//String[] strings = result.toArray(String[]::new);
}
}
limit 方法可以对流进行截取,只取用前n个。方法签名:
Stream<T> limit(long maxSize);
参数是一个long型,如果集合当前长度大于参数则进行截取;否则不进行操作。基本使用:
public static void main(String[] args) {
Stream<String> original = Stream.of("张无忌", "周芷若", "张三丰");
Stream<String> result = original.limit(2);
System.out.println(result.count());//2
}
如果希望跳过前几个元素,可以使用 skip 方法获取一个截取之后的新流:
Stream<T> skip(long n);
如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。基本使用:
public static void main(String[] args) {
Stream<String> original = Stream.of("张无忌", "周芷若", "张三丰");
//skip跳过前几个
Stream<String> result = original.skip(2);
System.out.println(result.count());//1
}
如果需要将流中的元素映射到另一个流中,可以使用 map 方法。方法签名:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
该接口需要一个 Function 函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。
此前我们已经学习过 java.util.stream.Function 函数式接口,其中唯一的抽象方法为:
R apply(T t);
这可以将一种T类型转换成为R类型,而这种转换的动作,就称为“映射”。
Stream流中的 map 方法基本使用的代码如:
public static void main(String[] args) {
Stream<String> stringStream = Stream.of("1", "2", "3");
//同:Stream integerStream = stringStream.map(Integer::new);
Stream<Integer> integerStream = stringStream.map(s -> Integer.parseInt(s));
}
如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat :
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
备注:这是一个静态方法,与 java.lang.String 当中的 concat 方法是不同的
该方法的基本使用代码如:
public static void main(String[] args) {
Stream<String> streamA = Stream.of("1", "2", "3");
Stream<String> streamB = Stream.of("4", "5", "6");
Stream<String> result = Stream.concat(streamA, streamB);
System.out.println(result.collect(Collectors.toList()));//[1, 2, 3, 4, 5, 6]
}
虽然方法名字叫 forEach ,但是与for循环中的“for-each”昵称不同,该方法并不保证元素的逐一消费动作在流中是
被有序执行的。
void forEach(Consumer<? super T> action);
该方法接收一个 Consumer 接口函数,会将每一个流元素交给该函数进行处理。例如:
public static void main(String[] args) {
Stream<String> streamA = Stream.of("1", "2", "3");
//同: streamA.forEach(s -> System.out.println(s));
streamA.forEach(System.out::println);
}
在这里,方法引用 System.out::println 就是一个 Consumer 函数式接口的示例
对流操作完成之后,如果需要将其结果进行收集,例如获取对应的集合、数组等,如何操作?
Stream流提供 collect 方法,其参数需要一个 java.util.stream.Collector
集合中。幸运的是, java.util.stream.Collectors 类提供一些方法,可以作为 Collector 接口的实例:
public static <T> Collector<T, ?, List<T>> toList() :转换为 List 集合。
public static <T> Collector<T, ?, Set<T>> toSet() :转换为 Set 集合。
下面是这两个方法的基本使用代码:
public static void main(String[] args) {
Stream<String> streamA = Stream.of("1", "2", "3");
List<String> list = streamA.collect(Collectors.toList());
Set<String> set = streamA.collect(Collectors.toSet());
}
Stream提供 toArray 方法来将结果放到一个数组中,由于泛型擦除的原因,返回值类型是Object[]的:
Object[] toArray();
其使用场景如:
public static void main(String[] args) {
Stream<String> streamA = Stream.of("1", "2", "3");
//收集到数组中
Object[] objects = streamA.toArray();
}
有了Lambda和方法引用之后,可以使用 toArray 方法的另一种重载形式传递一个 IntFunction 的函数,继
而从外面指定泛型参数。方法签名:
<A> A[] toArray(IntFunction<A[]> generator);
有了它,上例代码中不再局限于 Object[] 结果,而可以得到 String[] 结果:
public static void main(String[] args) {
Stream<String> streamA = Stream.of("1", "2", "3");
//收集到数组中,指定类型
//同:String[] strings = streamA.toArray(s->new String[s]);
String[] strings = streamA.toArray(String[]::new);
}
既然数组也是有构造器的,那么传递一个数组的构造器引用即可。
备注:Java仍然没有泛型数组,原因同样是泛型擦除。
当需要对存在于集合或数组中的若干元素进行并发操作时,简直就是噩梦!我们需要仔细考虑多线程环境下的原子
性、竞争甚至锁问题,即便是 java.util.concurrent.ConcurrentMap
而对于Stream流来说,这很简单。
Stream 的父接口 java.util.stream.BaseStream 中定义了一个 parallel 方法:
S parallel();
只需要在流上调用一下无参数的 parallel 方法,那么当前流即可变身成为支持并发操作的流,返回值仍然为
Stream 类型。例如:
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(10, 20, 30, 40, 50).parallel();
}
在通过集合获取流时,也可以直接调用 parallelStream 方法来直接获取支持并发操作的流。方法定义为:
default Stream<E> parallelStream() {...}
public static void main(String[] args) {
Collection<String> coll = new ArrayList<>();
Stream<String> stream = coll.parallelStream();
}
多次执行下面这段代码,结果的顺序在很大概率上是不一定的:
forEach该方法并不保证元素的逐一消费动作在流中是
被有序执行的。
public static void main(String[] args) {
Stream.of(10,20,30,40,50,60,70,80,90,100).parallel().forEach(System.out::println);
//同:
//获取流
//Stream integerStream = Stream.of(10, 20, 30, 40, 50, 60, 70, 80, 90, 100);
//转为并发流
//Stream parallel = integerStream.parallel();
//遍历打印
//parallel.forEach(s-> System.out.println(s));
}
在上述介绍的各种方法中,凡是返回值仍然为 Stream 接口的为函数拼接方法,它们支持链式调用;而返回值不再
为 Stream 接口的为终结方法,不再支持链式调用。如下表所示: