https://www.liaoxuefeng.com/wiki/1252599548343744/1322402873081889(看这篇就够了)
https://www.runoob.com/java/java8-streams.html
看了Stream的能力才知道他有多强大:常见的对集合的操作 都能用stream更方便,更快捷的实现。实践中可以多尝试采用Stream编程
我们介绍了
Stream
的几个常见操作:map()
、filter()
、reduce()
。这些操作对Stream
来说可以分为两类,一类是转换操作,即把一个Stream
转换为另一个Stream
,例如map()
和filter()
,另一类是聚合操作,即对Stream
的每个元素进行计算,得到一个确定的结果,例如reduce()
。 区分这两种操作是非常重要的,因为对于Stream
来说,对其进行转换操作并不会触发任何计算! 而聚合操作则不一样,聚合操作会立刻促使Stream
输出它的每一个元素,并依次纳入计算,以获得最终结果
Stream
提供的常用操作有:
转换操作:map()
,filter()
,sorted()
,distinct()
;
合并操作:concat()
,flatMap()
;
并行处理:parallel()
;
聚合操作:reduce()
,collect()
,count()
,max()
,min()
,sum()
,average()
;
其他操作:allMatch()
, anyMatch()
, forEach()
。
List> 转 List 采用flatmap
list 按 某个属性分组
List<DspSellerAccountRequestDO> requestDOS = dspSellerAccountRequestDOMapper.selectByExample(sellerAccountRequest);
// group by channelType
Map<String, List<DspSellerAccountRequestDO>> map = requestDOS.stream().collect(
Collectors.groupingBy(DspSellerAccountRequestDO::getChannelType));
分组后,按另一个属性collect
cdsAppServiceDOS.stream().collect(Collectors.groupingBy(CdsAppServiceDO::getAppName, mapping(CdsAppServiceDO::getServiceKey, toSet())));
map的遍历
map.entrySet().stream().forEach(entry -> {
//
});
foreach内不能修改外层变量
private static void test1() {
List<Integer> list = Arrays.asList(1, 2, 3);
List<Integer> newList = new ArrayList<>();
list.forEach(a -> {
newList.add(a); // 这里不会报错,因为没有修改newList指针,只是增加元素
//newList = new ArrayList<>();// 而这里会报错的
});
System.out.println(newList);
}
public static void test2() {
List<Integer> list = Arrays.asList(1, 2, 3);
int sum = 0;
list.forEach(a -> {
// sum += a; //这里会报错的, 不能修改基本类型
});
}
Lambda提供的Steam流的规约操作,java.util.stream包提供了各种通用的和专用的规约操作(例如sum、min和max)
//正确的写法
public void test3() {
List<Integer> list = new ArrayList<>();
int sum = list.stream()
.mapToInt(e -> e)
.sum();
System.out.println(sum);
}
//同样,以下写法没问题
physicalFieldDO.forEach(columnBO -> {
ret.add(defaultDOBuilder(columnBO, logicalTableName, physicalTableName));
});
//但更好的遵循Lamda规范的写法是:
columnBOS.stream().map(columnBO -> defaultDOBuilder(columnBO, physicalTableName)).collect(Collectors.toList())
构造stream
// static方法
com.google.common.collect.Streams#stream(java.lang.Iterable<T>)
数组变Stream
把数组变成Stream
使用Arrays.stream()
方法。
集合变Stream
对于Collection
(List
、Set
、Queue
等),直接调用stream()
方法就可以获得Stream
。
map
可以理解为map是将一个输入转换为一个输出。输出必须是一个对象,可以是基础类型对象,也可以是自定义对象。 与foreach不同,foreach里的函数块 不需要一定return一个对象,而是执行一段逻辑而已。
map 方法用于映射每个元素到对应的结果,以下代码片段使用 map 输出了元素对应的平方数:
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
// 获取对应的平方数
List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());
reduce
map是stream的转换函数,reduce是stream的聚合函数。可以将stream的所有元素根据聚合函数聚合为一个结果
// 求所有元素累加值
public class Main {
public static void main(String[] args) {
int sum = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9).reduce(0, (acc, n) -> acc + n);
System.out.println(sum); // 45
}
}
以上reduce方法解析:
- 第一个参数0,代表计算的初始值
- 第二个参数是一个聚合函数定义,接收两个参数,第一个参数acc代表上一次计算结果值,第二个参数代表Stream的当前元素值。reduce(0, (acc, n) -> acc + n)的含义就是,设置初始acc=0,循环遍历stream的每个元素n,acc = acc + n
filter
filter 方法用于通过设置的条件过滤出元素。以下代码片段使用 filter 方法过滤出空字符串:List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 获取空字符串的数量
long count = strings.stream().filter(string -> string.isEmpty()).count();
输出集合
输出为数组
把Stream的元素输出为数组和输出为List类似,我们只需要调用toArray()
方法,并传入数组的“构造方法”:
List<String> list = List.of("Apple", "Banana", "Orange");
String[] array = list.stream().toArray(String[]::new);
注意到传入的“构造方法”是String[]::new
,它的签名实际上是IntFunction<String[]>
定义的String[] apply(int)
,即传入int
参数,获得String[]
数组的返回值。
输出为Map
如果我们要把Stream的元素收集到Map中,就稍微麻烦一点。因为对于每个元素,添加到Map时需要key和value,因此,我们要指定两个映射函数,分别把元素映射为key和value:
public class Main {
public static void main(String[] args) {
Stream<String> stream = Stream.of("APPL:Apple", "MSFT:Microsoft");
Map<String, String> map = stream
.collect(Collectors.toMap(
// 把元素s映射为key:
s -> s.substring(0, s.indexOf(':')),
// 把元素s映射为value:
s -> s.substring(s.indexOf(':') + 1)));
System.out.println(map);
}
}
分组输出
Stream
还有一个强大的分组功能,可以按组输出。我们看下面的例子
public class Main {
public static void main(String[] args) {
List<String> list = List.of("Apple", "Banana", "Blackberry", "Coconut", "Avocado", "Cherry", "Apricots");
Map<String, List<String>> groups = list.stream()
.collect(Collectors.groupingBy(s -> s.substring(0, 1), Collectors.toList()));
System.out.println(groups);
}
}
分组输出使用Collectors.groupingBy()
,它需要提供两个函数:一个是分组的key,这里使用s -> s.substring(0, 1)
,表示只要首字母相同的String
分到一组,第二个是分组的value,这里直接使用Collectors.toList()
,表示输出为List
,
连接多个字符串
// 多用在 stream.collect(Collectors.joining(","));
java.util.stream.Collectors#joining(java.lang.CharSequence)
判断集合是否有元素match某个规则
stream().anyMatch
其他操作
排序
对Stream
的元素进行排序十分简单,只需调用sorted()
方法:
import java.util.;
import java.util.stream.;
Run
此方法要求Stream
的每个元素必须实现Comparable
接口。如果要自定义排序,传入指定的Comparator
即可:
List<String> list = List.of("Orange", "apple", "Banana")
.stream()
.sorted(String::compareToIgnoreCase)
.collect(Collectors.toList());
注意sorted()
只是一个转换操作,它会返回一个新的Stream
。
去重
对一个Stream
的元素进行去重,没必要先转换为Set
,可以直接用distinct()
:
List.of("A", "B", "A", "C", "B", "D")
.stream()
.distinct()
.collect(Collectors.toList()); // [A, B, C, D]
截取
截取操作常用于把一个无限的Stream
转换成有限的Stream
,skip()
用于跳过当前Stream
的前N个元素,limit()
用于截取当前Stream
最多前N个元素:
List.of("A", "B", "C", "D", "E", "F")
.stream()
.skip(2) // 跳过A, B
.limit(3) // 截取C, D, E
.collect(Collectors.toList()); // [C, D, E]
合并
将两个Stream
合并为一个Stream
可以使用Stream
的静态方法concat()
:
Stream<String> s1 = List.of("A", "B", "C").stream();
Stream<String> s2 = List.of("D", "E").stream();
// 合并:
Stream<String> s = Stream.concat(s1, s2);
System.out.println(s.collect(Collectors.toList())); // [A, B, C, D, E]
flatMap
如果Stream
的元素是集合:
Stream<List<Integer>> s = Stream.of(
Arrays.asList(1, 2, 3),
Arrays.asList(4, 5, 6),
Arrays.asList(7, 8, 9));
而我们希望把上述Stream
转换为Stream<Integer>
,就可以使用flatMap()
:
Stream<Integer> i = s.flatMap(list -> list.stream());
因此,所谓flatMap()
,是指把Stream
的每个元素(这里是List
)映射为Stream
,然后合并成一个新的Stream
:
┌─────────────┬─────────────┬─────────────┐
│┌───┬───┬───┐│┌───┬───┬───┐│┌───┬───┬───┐│
││ 1 │ 2 │ 3 │││ 4 │ 5 │ 6 │││ 7 │ 8 │ 9 ││
│└───┴───┴───┘│└───┴───┴───┘│└───┴───┴───┘│
└─────────────┴─────────────┴─────────────┘
│
│flatMap(List -> Stream)
│
│
▼
┌───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │
└───┴───┴───┴───┴───┴───┴───┴───┴───┘
并行
通常情况下,对Stream
的元素进行处理是单线程的,即一个一个元素进行处理。但是很多时候,我们希望可以并行处理Stream
的元素,因为在元素数量非常大的情况,并行处理可以大大加快处理速度。
把一个普通Stream
转换为可以并行处理的Stream
非常简单,只需要用parallel()
进行转换:
Stream<String> s = ...
String[] result = s.parallel() // 变成一个可以并行处理的Stream
.sorted() // 可以进行并行排序
.toArray(String[]::new);
经过parallel()
转换后的Stream
只要可能,就会对后续操作进行并行处理。我们不需要编写任何多线程代码就可以享受到并行处理带来的执行效率的提升。
其他聚合方法
除了reduce()
和collect()
外,Stream
还有一些常用的聚合方法:
count()
:用于返回元素个数;max(Comparator<? super T> cp)
:找出最大元素;min(Comparator<? super T> cp)
:找出最小元素。
针对IntStream
、LongStream
和DoubleStream
,还额外提供了以下聚合方法:
sum()
:对所有元素求和;average()
:对所有元素求平均数。
还有一些方法,用来测试Stream
的元素是否满足以下条件:
boolean allMatch(Predicate<? super T>)
:测试是否所有元素均满足测试条件;boolean anyMatch(Predicate<? super T>)
:测试是否至少有一个元素满足测试条件。
最后一个常用的方法是forEach()
,它可以循环处理Stream
的每个元素,我们经常传入System.out::println
来打印Stream
的元素:
Stream<String> s = ...
s.forEach(str -> {
System.out.println("Hello, " + str);
});
orElseThrow
可传参的写法如下。注意:supplier不接收任何参数的写法:()
orElseThrow(() -> new RuntimeException("can not find nodeDataRepo"));