Stream流
Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。 Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。 Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。 这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。 元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果
可以抽象的理解为将列表或集合中的元素像工厂流水线一样一个一个的拿出来放到工作流当中
Stream的API-创建Stream,文章内容会使用到函数式编程的内容,不了解的可以先看看对应的知识点
通过一个案例可以体验流的强大之处,当前有一个Class
名为User
,User
有name
和age
字段,需要从一个User
的List
中找出姓王的用户并且按照年龄排序,最后将这些人的名字返回
定义User Class
public class User {
private String name;
private Integer age;
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public Integer getAge() {
return age;
}
}
在没有Stream
之前,可能要写下面这么一长串的代码,定义一个返回测试数据的方法
public static List<User> getUsers() {
return Arrays.asList(
new User("王五",20),
new User("张三",21),
new User("王八",22),
new User("王福",18)
);
}
需要先拿到这个List
,使用for
循环筛选出所有name
是王开头的用户,接着再调用排序方法传入Comparator
(以前没有函数式编程,既不存在lambda
表达式)的实例按age
排序,最后把包含所有姓王的用户且排好序的User
列表for
执行循环把name
给挑出来放到结果数组中
List<User> users = getUsers(); // 获取User列表
ArrayList<User> wangList = new ArrayList<>();
// 先筛选出姓王的User
for (User user : users) {
if (user.getName().startsWith("王")) {
wangList.add(user);
}
}
// 按照age去排序
wangList.sort(new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
return o1.getAge() > o2.getAge() ? 1 : (o1.getAge().equals(o2.getAge())) ? 0 : -1;
}
});
ArrayList<String> result = new ArrayList<>();
for (User user : wangList) {
result.add(user.getName());
}
// 拿到排好序后的name
System.out.println(result);
接着使用Stream
的方式对比下,我们可以使用Collection
接口提供的stream
方法(List
实现了Collection
)去创建一个流,下面也使用了Java8
引入的lambda
表达式,几行代码完成同样的功能
List<String> result = users.stream()
.filter(user->user.getName().startsWith("王"))
.sorted(Comparator.comparing(User::getAge))
.map(User::getName)
.collect(Collectors.toList());
为了方便阅读换行了,但如果你想也可以写成一行,代码一下子简洁了
创建流后可以执行若干的中间操作,这些操作都会返回一个流
可以查看Stream
接口的源码,所有抽象方法只要返回值是Stream
,都可以链式执行调用a().b().c()...
,比如filter,sorted,map
方法等
Stream与原来的List没有任何关系
除了使用Collection.stream
方法创建流常用以外
Stream.of
方法,从数组创建一个流,这个方法定义很简单,实际仍然是通过Collection
的stream
方法创建,但是不用先创建一个Collection
,更方便操作
public static<T> Stream<T> of(T... values) {
return Arrays.stream(values);
}
一行代码即可实现输出流中的内容
Stream.of("test1","test2","test3").forEach(System.out::println);
String.chars
方法,可以将一个字符串变成一个一个的IntStream
,也就是每个字符对应的ASCⅡ码
System.out.println("sdjDFSDFdjfklsd".chars().filter(Character::isUpperCase).count());
这行代码会输出大写字母的数量,count
函数返回的是long
类型
IntStream.range
方法可以返回int
流,接受两个参数range(int startInclusive, int endExclusive)
,起始且包含值,结束但不包含值,传入(0,4)
,返回的就是0-3
的int
流
IntStream.range(0, 10).filter(value -> value % 2 != 0).forEach(System.out::println);
这段代码先生成0-9的数字流,过滤掉偶数,然后输出
Stream
提供的api有很多,只要看看文档和源码,就能猜到这些方法是做什么的,比如
concat,max,min,count,anyMatch
等等,感兴趣的看一看用一用即可
终结操作,返回非Stream
的操作,包括void
,用完流就被销毁,其中collect
操作最为重要
一个流可以有无数的中间操作,但只能有一次终结操作,collect
接受的是一个Collector
函数接口,但实际上我们并不需要自己编写这个函数,Collectors
工具类中,有许多可以拿来即用的方法,比如最常用的Collectors,toList()
,toSet()
,toCollect
,toMap
这里讲讲toCollect这个方法
如果想要返回比如TreeSet
,TreeMap
,用上面出现过的例子演示用法,toCollection
接受一个参数是Supplier
,需要收集成什么类型,就得传入什么类型,所以可以传入TreeSet::new
TreeSet<String> names = users.stream()
.filter(user -> user.getName().startsWith("王"))
.sorted(Comparator.comparing(User::getAge))
.map(User::getName)
.collect(Collectors.toCollection(TreeSet::new));
Collectors工具类集合中,提供了非常多的操作,比如groupingBy
,summingInt
,partitioningBy
groupingBy
,按照条件进行分组,下面代码中,根据User
对象的年龄排序,在根据姓名去分组,也就是按照同名人去分组,返回的是HashMap
类型
List<User> users = getUsers();
Map<String, List<User>> collect = users.stream().sorted(Comparator.comparing(User::getAge)).collect(Collectors.groupingBy(User::getName));
joining
,按照指定的参数进行字符串拼接,示例代码会对姓名进行拼接,如“张三,王五,李四”
,返回类型是String
String str = users.stream().map(User::getName).collect(Collectors.joining(","));
summingInt
,属性累计并返回结果,示例代码会对年龄进行计算总和,返回类型是Integer
Integer sum = users.stream().collect(Collectors.summingInt(User::getAge));
partitioningBy
,按照true
和false
划分区域,返回类型是Map<Boolean,List<T>>
Map<Boolean, List<User>> collect1 = users.stream().collect(Collectors.partitioningBy(user -> user.getAge() > 20));
其他的不列举了,用到时看看文档就行
Stream 并发流,正确的使用,可以通过并发提高互相独立的操作的性能,Collection.parallelStream
可以生成一个并发流,Stream.parallel
可以将一个普通流转换成并发流
如某些流操作之间没有依赖关系实际上可以并发的完成,可以使用Collection,可以通过一个比较观察性能上的提升
long l = System.currentTimeMillis();
IntStream.range(0, 100_0000_000).filter(i -> i % 2 == 0).count();
System.out.println(System.currentTimeMillis() - l);
long l2 = System.currentTimeMillis();
IntStream.range(0, 100_0000_000).parallel().filter(i -> i % 2 == 0).count();
System.out.println(System.currentTimeMillis() - l2);
l输出1338毫秒,而l2输出319毫秒,这个简单的测试快了接近4倍,但如果不能保证要执行的操作是线程安全的,不推荐使用
毫无疑问,学会使用Stream
,将会有助于提高编码效率 : )