java8进阶-stream编程
什么是Stream
Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。Stream 使用一种类似用 SQL语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
StreamAPI可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。这种风格将要处理的元素集合看作一种流, 流在管道中传输,并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。元素流在管道中经过中间操作(intermediateoperation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
总之,中间操作只是创建另一个流,不会执行任何处理,直到最终操作被调用。一旦最终操作被调用,则开始遍历所有的流,并且相关的函数会逐一应用到流上。中间操作是惰性操作,所以,流支持惰性,下面是一些函数的分类。
1.Stream操作分类
1.1 创建流
可以通过多种方式创建流,例如从集合、数组或通过生成器和供应商。
1.2 中间操作
这些操作会返回一个新的流,允许你链式调用其他操作。常见的中间操作包括:
filter
:根据给定的条件筛选元素。
map
:将流中的每个元素映射到另一个元素。
sorted
:将流中的元素进行排序。
distinct
:去除流中的重复元素。
1.3 最终操作
这些操作会消耗流,并产生一个最终的结果或副作用。常见的最终操作包括:
forEach
:对流中的每个元素执行给定的操作。
collect
:将流中的元素累积到一个结果容器中。
reduce
:通过某个连接动作将所有元素汇总成一个汇总结果。
anyMatch
/ allMatch
/ noneMatch
:检查流中的元素是否与给定的谓词匹配。
2.流的使用
获取Stream在使用流之前,首先需要拥有一个数据源,并通过StreamAPI提供的一些方法获取该数据源的流对象。数据源可以有多种形式:
2.1集合
这种数据源较为常用,通过stream()方法即可获取流对象:
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
2.2 数组
通过Arrays类提供的静态函数stream()获取数组的流对象:
String[] array = {"a","b","c"};
Stream<String> stream = Arrays.stream(array);
2.3 值
直接将几个值变成流对象:
Stream<String> stream = Stream.of("a", "b", "c");
2.4 forEach
对流中的每个元素执行一些操作。
List<String> list = new ArrayList<>(Arrays.asList("a","b","c"));
//写法一
list.stream().forEach(x -> {
System.out.println(x);
});
//写法二
list.stream().forEach(System.out::println);
2.5 map
接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
List<String> list = new ArrayList<>(Arrays.asList("a","b","c"));
//写法一
list.stream().forEach(x -> {
System.out.println(x);
});
//写法二
list.stream().forEach(System.out::println);
2.6 flatMap
接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
List<String> list = new ArrayList<>(Arrays.asList("a,1","b,2","c,3"));
List<String> newList = list.stream().flatMap(x->{
String[] array = x.split(",");
return Arrays.stream(array);
}).collect(Collectors.toList());
结果:[a, 1, b, 2, c, 3]
2.7 reduce()
通过一个二进制操作将流中的元素合并到一起。
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
//写法一,累加不带初始值
Optional accResult = list.stream().reduce((acc, item) -> {
acc += item;
return acc;
});
输出:6
//写法一,累加带初始值10
int result1 = list.stream().reduce(10, (acc, item) -> {
acc += item;
return acc;
});
输出:16
2.8 filter()
过滤元素
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3,4));
List<Integer> newList = list.stream().filter(x -> x >=3).collect(Collectors.toList());
输出:[3,4]
2.9 distinct()
去除重复元素
List<Integer> list = new ArrayList<>(Arrays.asList(1, 1, 1,2,2));
List<Integer> newList = list.stream().distinct().collect(Collectors.toList());
输出结果:[1,2]
2.10 collect()
collect()
方法是一个非常重要的终端操作(terminal operation),它的作用是将流中的元素累积成一个汇总的结果。collect()
方法通常与一个收集器(Collector)一起使用,收集器定义了如何将流中的元素累积和组合。
2.10.1 toList
将流中的元素收集到一个列表中。
List<String> fruits = Arrays.asList("apple", "banana", "cherry", "date");
List<String> filteredFruits = fruits.stream()
.filter(fruit -> fruit.startsWith("a"))
.collect(Collectors.toList());
System.out.println(filteredFruits);
2.10.2 toSet
将元素转成Set
List<Integer> list = new ArrayList<>(Arrays.asList(1, 1, 1, 2, 2));
Set<Integer> set = list.stream().collect(Collectors.toSet());
2.10.3 toMap
将元素转成Map,用法可以如下
public Map<Long, String> getIdNameMap(List<Account> accounts) {
return accounts.stream().collect(Collectors.toMap(Account::getId, Account::getUsername));
}
收集成实体本身map,代码如下:
public Map<Long, Account> getIdAccountMap(List<Account> accounts) {
return accounts.stream().collect(Collectors.toMap(Account::getId, account -> account));
}
account -> account是一个返回本身的lambda表达式,其实还可以使用Function接口中的一个默认方法代替,使整个方法更简洁优雅:
public Map<Long, Account> getIdAccountMap(List<Account> accounts) {
return accounts.stream().collect(Collectors.toMap(Account::getId, Function.identity()));
}
重复key的情况,代码如下:
public Map<String, Account> getNameAccountMap(List<Account> accounts) {
return accounts.stream().collect(Collectors.toMap(Account::getUsername, Function.identity()));
}
这个方法可能报错(java.lang.IllegalStateException: Duplicatekey),因为name是有可能重复的。toMap有个重载方法,可以传入一个合并的函数来解决key冲突问题:
public Map<String, Account> getNameAccountMap(List<Account> accounts) {
return accounts.stream().collect(Collectors.toMap(Account::getUsername, Function.identity(), (key1, key2) -> key2));
}
这里只是简单的使用后者覆盖前者来解决key重复问题。
指定具体收集的map,toMap还有另一个重载方法,可以指定一个Map的具体实现,来收集数据:
public Map<String, Account> getNameAccountMap(List<Account> accounts) {
return accounts.stream().collect(Collectors.toMap(Account::getUsername, Function.identity(), (key1, key2) -> key2, LinkedHashMap::new));
}
2.11 min()
根据一个比较器找到流中元素的最小值。
BigDecimal min = planList.stream().min((a,b)->a.getPrice().compareTo(b.getPrice())).get().getPrice();
2.22 max()
根据一个比较器找到流中元素的最大值。
BigDecimal max = planList.stream().max((a,b)->a.getPrice().compareTo(b.getPrice())).get().getPrice();
2.23 count()
计算流中元素的数量。
List<Integer> list = new ArrayList<>(Arrays.asList(14, 23, 3,10, 1,6));
long min = list.stream().distinct().count();