Java8-Stream API
Stream是java8推出的一个新的抽象的流,可以使得想操作数据库一样操作java中的集合。
创建Stream
共有四种方法创建Stream
//1. 通过 Collection 系列集合的 stream() <串行流> 或 paralleStream() <并行流>
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
//2. 通过 Arrays 静态方法 stream() 获取数组流
String[] ss = {"1","2","3"};
Stream<String> stream2 = Arrays.stream(ss);
//3. 通过 Stream 的静态方法 of()
Stream<String> stream3 = Stream.of("a","b","c");
//4. 创建无限流
// 迭代
Stream.iterate(0, (x) -> x + 2)
.limit(5) // 限制 5 个
.forEach(System.out::println); // 调用一个函数式接口
System.out.println("------------------");
// 生成器
Stream.generate(()->Math.random())
.limit(5)
.forEach(System.out::println);
Stream的中间操作
创建了Stream之后要对Stream进行一些中间操作,例如排序,选择,切割之类,官方给我们提供了很多的工具类来进行这些操作。
切片和筛选
filter : 接收 Lambda ,从流中排除某些元素
limit : 截断流,使其元素不超过给定数量
- skip(n) : 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。
- distinct : 筛选,通过流生成的元素的 hashCode() 和 equals() 去除重复的元素
先创建一个测试类,并初始化一些集合数据:
public class Employee {
private Integer id;
private String name;
private Double salary;
public Employee(){}
public Employee(Integer id){
this.id = id;
}
public Employee(Integer id,String name){
this.id = id;
this.name =name;
}
public Employee(Integer id,String name,Double salary){
this.id = id;
this.name = name;
this.salary = salary;
}
// getter 和 setter 和 toString 和 equals 和 hashcode 方法省略 ...
测试类中初始化一些数据
List<Employee> emps = Arrays.asList(
new Employee(1,"bart",13000D),
new Employee(2,"lisa",19000D),
new Employee(2,"lisa",19000D),
new Employee(2,"lisa2",19000D),
new Employee(3,"maggie",16000D),
new Employee(3,"maggie",16000D),
new Employee(3,"maggie2",16000D),
new Employee(4,"homer",23000D),
new Employee(5,"marge",16000D),
new Employee(5,"marge",16000D),
new Employee(6,"marge",16000D)
);
filter
按照指定规则过滤掉一些不符合的元素
@Test
public void test2() {
emps.stream()
.filter((e)-> e.getId() > 4) // 过滤掉一个年龄大于 4 的元素
.forEach(System.out::println);
}
输出结果:
Employee [id=5, name=marge, salary=16000.0]
Employee [id=5, name=marge, salary=16000.0]
Employee [id=6, name=marge, salary=16000.0]
distinct
初始化的测试集合中有重复数据
@Test
public void test2() {
emps.stream()
.distinct()
.forEach(System.out::println);
}
输出:
Employee [id=1, name=bart, salary=13000.0]
Employee [id=2, name=lisa, salary=19000.0]
Employee [id=2, name=lisa2, salary=19000.0]
Employee [id=3, name=maggie, salary=16000.0]
Employee [id=3, name=maggie2, salary=16000.0]
Employee [id=4, name=homer, salary=23000.0]
Employee [id=5, name=marge, salary=16000.0]
Employee [id=6, name=marge, salary=16000.0]
可以发现已经去掉重复值了。
skip
跳过若干元素:
@Test
public void test2() {
emps.stream()
.skip(emps.size()-2)
.forEach(System.out::println);
}
输出:
Employee [id=5, name=marge, salary=16000.0]
Employee [id=6, name=marge, salary=16000.0]
limit
限制若干个元素
@Test
public void test2() {
emps.stream()
.limit(3)
.forEach(System.out::println);
}
输出了前三个元素:
Employee [id=1, name=bart, salary=13000.0]
Employee [id=2, name=lisa, salary=19000.0]
Employee [id=2, name=lisa, salary=19000.0]
映射
- map : 接受一个 lambda 将元素转化为其他形式或提取信息,接受一个函数作为参数,该函数会应用到每一个元素上面,并将其映射为一个新的元素。
- flatMap:接受一个函数作为参数,将流中的每个值都转化为另外一个流,然后把所有的流连接成一个新的流
map
List<String> list = Arrays.asList("aaa","bbb","ccc","ddd","eee");
// map
list.stream()
.map((s) -> s.toUpperCase()) // 将每个元素转化为大写
.forEach(System.out::println); // 输出
输出:
AAA
BBB
CCC
DDD
EEE
flatMap
还是上面的list
的集合,我们现在想把每个字符串元素转化为字符元素,然后把转化好的字符元素集合转化为Stream流,然后遍历输出。
/** 将一个字符串转化为一个 Character 的 Stream */
public static Stream<Character> convertStream(String str) {
List<Character> list = new ArrayList<>(str.length());
for(Character c:str.toCharArray()) {
list.add(c);
}
return list.stream();
}
测试方法:
/* 使用flatMap 将每个字符元素放到一个新的 Character 流中 */
list.stream()
.flatMap(TestStreamCreate::convertStream) // TestStreamCreate 类是当前的测试类
.forEach(System.out::println);
输出:
a
a
... ... ...
e
e
e
排序
sorted() : 自然排序 Comparable
sorted(Comparator com):定制排序
自然排序:
List<String> list = Arrays.asList("aaa","bbb","ccc","ddd","eee");
list.stream().sorted().forEach(System.out::println);
输出:
aaa
bbb
ccc
ddd
eee
定制排序:
emps.stream().sorted((x,y) -> {
if(x.getName().equals(y.getName())) {
return x.getName().compareTo(y.getName()); // 比较姓名
}else {
return x.getSalary().compareTo(y.getSalary()); // 比较工资
}
}).forEach(System.out::println);
输出:
Employee [id=1, name=bart, salary=13000.0]
Employee [id=3, name=maggie, salary=16000.0]
Employee [id=3, name=maggie, salary=16000.0]
Employee [id=3, name=maggie2, salary=16000.0]
Employee [id=5, name=marge, salary=16000.0]
Employee [id=5, name=marge, salary=16000.0]
Employee [id=6, name=marge, salary=16000.0]
Employee [id=2, name=lisa, salary=19000.0]
Employee [id=2, name=lisa, salary=19000.0]
Employee [id=2, name=lisa2, salary=19000.0]
Employee [id=4, name=homer, salary=23000.0]
查找
- allMatch : 检查是否匹配所有元素
- anyMatch : 检查是否至少匹配一个元素
- noneMatch : 检查是否没有匹配所有元素
- findFirst : 返回第一个元素
- findAny : 返回当前流中的任意元素
- count : 返回流中元素的总个数
- max : 返回流中的最大值
- min : 返回流中的最小值
@Test
public void test5() {
boolean b1 = emps.stream().allMatch((e) -> e.getSalary() >10000); //所有的工资大于10000
System.out.println(b1);//true
boolean b2 = emps.stream().anyMatch((e) -> e.getName().length() > 5); // 至少有一个姓名长度大于 5
System.out.println(b2);//true
boolean b3 = emps.stream().noneMatch((e) -> e.getName().length() > 9); // 没有人的姓名长度大于 9
System.out.println(b3);//true
Optional<Employee> op1 = emps.stream().findFirst();
System.out.println(op1.get());
Optional<Employee> op2 = emps
.parallelStream() // 并行流,利用多线程
// .stream() // 串行流
.findAny();
System.out.println(op2.get());
long count = emps.stream().count();
System.out.println(count);
// 返回工资最该的那个 Employee 对象
Optional<Employee> op3 = emps.stream().max((x,y) -> Double.compare(x.getSalary(), y.getSalary()));
System.out.println(op3.get()); //Employee [id=4, name=homer, salary=23000.0]
// 返回工资最少的那个人的工资
Optional<Double> op4 = emps.stream()
.map((e) -> e.getSalary()) // 将每个元素转化为工资
.min((x,y) -> Double.compare(x,y)); // 按照工资排序
System.out.println(op4.get()); // 13000.0
}
输出:
true
true
true
Employee [id=1, name=bart, salary=13000.0]
Employee [id=3, name=maggie2, salary=16000.0]
11
Employee [id=4, name=homer, salary=23000.0]
13000.0
规约
reduce(T identity,BinaryOperator)
reduce(BinaryOperator)可以将流中的元素反复结合起来,得到一个值
@Test
public void test6() {
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
Integer sum = list.stream()
.reduce(0,(x,y) -> x+y); // 第一个是默认值,第二个是lambda表达式求和
System.out.println(sum); // 45
System.out.println("--------------------");
Optional<Double> op = emps.stream()
.map((e) -> e.getSalary())
.reduce(Double::sum); // 求所有 employee 的工资总和
System.out.println(op.get()); // 119000.0
System.out.println("--------------------");
}
收集
Collectors 的 API
方法 | 含义 |
---|---|
Collectors.toList() | 转化为 list 集合 |
Collectors.toSet() | 转化为Set集合 |
Collectors.toCollection(Lambda表达式) | 转化为任意类型集合,使用构造函数的引用确定类型 |
Collectors.counting() | 计数 |
Collectors.averagingDouble(Lambda表达式) | 平均值 |
Collectors.summingDouble(Lambda表达式) | 总和 |
Collectors.minBy(Lambda表达式) | 最小值,由Lambda表达式确定排序规则 |
Collectors.groupingBy(Lambda表达式) | 按照Lambda表达式规则分组 |
Collectors.summarizingDouble(Lambda表达式) | 返回 DoubleSummaryStatistics 对象直接获得分组函数结果值 |
Collectors.joining(Lambda表达式) | 连接字符串方法 |
@Test
public void test7() {
List<Integer> list = emps.stream()
.map((e) -> e.getId()) // 获得所有的 ID
.collect(Collectors.toList());// 转化为 list 集合
System.out.println(list);//[1, 2, 3, 4, 5, 5, 5]
System.out.println("----------------------------");
Set<String> set = emps.stream()
.map((e) -> e.getName())
.collect(Collectors.toSet());// 转化为 set 集合
System.out.println(set);//[marge, maggie, lisa, homer, bart]
System.out.println("----------------------------");
HashSet<String> hashSet = emps.stream()
// .map((e) -> e.getName()
.map(Employee::getName)
.collect(Collectors.toCollection(HashSet::new));// 转化为 set 集合
System.out.println(hashSet);//[marge, maggie, lisa, homer, bart]
}
@Test
public void test8() {
//1. count 计数
Long collect = emps.stream()
.collect(Collectors.counting());
System.out.println("总共: "+collect);//总共: 11
//2. 平均值
Double avgSalary = emps.stream()
.collect(Collectors.averagingDouble(Employee::getSalary));
System.out.println("工资平均值:"+avgSalary);//工资平均值:17181.81818181818
//3. 总和
Double collect2 = emps.stream()
.collect(Collectors.summingDouble(Employee::getSalary));
System.out.println("工资总和:"+collect2);//工资总和:189000.0
//4. 最小值
Optional<Employee> op = emps.stream()
.collect(Collectors.minBy((x,y) -> Double.compare(x.getSalary(), y.getSalary())));
System.out.println(op.get()); // Employee [id=1, name=bart, salary=13000.0]
}
@Test
public void test9() {
// 按照姓名分组
Map<String, List<Employee>> collect = emps.stream()
.collect(Collectors.groupingBy(Employee::getName));
for (Map.Entry<String, List<Employee>> entry : collect.entrySet()) {
System.out.println(entry.getKey()+" : "+entry.getValue());
}
System.out.println("++++++++++++++++++++++++++++++++ss");
// 先按照姓名分组,再按照id分组
Map<String, Map<Integer, List<Employee>>> collect2 = emps.stream()
.collect(
Collectors.groupingBy(
Employee::getName,Collectors.groupingBy(Employee::getId)));
for(Map.Entry<String, Map<Integer, List<Employee>>> entry : collect2.entrySet()) {
String k1 = entry.getKey();
System.out.println(k1);
Map<Integer, List<Employee>> v1 = entry.getValue();
for(Map.Entry<Integer, List<Employee>> e : v1.entrySet()) {
System.out.println("\t"+e.getKey());
for(Employee emp : e.getValue()) {
System.out.println("\t\t"+emp);
}
}
System.out.println("~~~~~~~~~~~~~~~~~~~~~~");
}
}
输出:
marge : [Employee [id=5, name=marge, salary=16000.0], Employee [id=5, name=marge, salary=16000.0], Employee [id=6, name=marge, salary=16000.0]]
lisa2 : [Employee [id=2, name=lisa2, salary=19000.0]]
maggie : [Employee [id=3, name=maggie, salary=16000.0], Employee [id=3, name=maggie, salary=16000.0]]
lisa : [Employee [id=2, name=lisa, salary=19000.0], Employee [id=2, name=lisa, salary=19000.0]]
maggie2 : [Employee [id=3, name=maggie2, salary=16000.0]]
homer : [Employee [id=4, name=homer, salary=23000.0]]
bart : [Employee [id=1, name=bart, salary=13000.0]]
++++++++++++++++++++++++++++++++ss
marge
5
Employee [id=5, name=marge, salary=16000.0]
Employee [id=5, name=marge, salary=16000.0]
6
Employee [id=6, name=marge, salary=16000.0]
~~~~~~~~~~~~~~~~~~~~~~
lisa2
2
Employee [id=2, name=lisa2, salary=19000.0]
~~~~~~~~~~~~~~~~~~~~~~
maggie
3
Employee [id=3, name=maggie, salary=16000.0]
Employee [id=3, name=maggie, salary=16000.0]
~~~~~~~~~~~~~~~~~~~~~~
lisa
2
Employee [id=2, name=lisa, salary=19000.0]
Employee [id=2, name=lisa, salary=19000.0]
~~~~~~~~~~~~~~~~~~~~~~
maggie2
3
Employee [id=3, name=maggie2, salary=16000.0]
~~~~~~~~~~~~~~~~~~~~~~
homer
4
Employee [id=4, name=homer, salary=23000.0]
~~~~~~~~~~~~~~~~~~~~~~
bart
1
Employee [id=1, name=bart, salary=13000.0]
~~~~~~~~~~~~~~~~~~~~~~
// 返回 DoubleSummaryStatistics 对象直接获得分组函数结果值
@Test
public void test10() {
DoubleSummaryStatistics dss = emps.stream()
.collect(Collectors.summarizingDouble(Employee::getSalary));
System.out.println(dss.getCount());//11
System.out.println(dss.getSum());//189000.0
System.out.println(dss.getAverage());//17181.81818181818
System.out.println(dss.getMax());//23000.0
System.out.println(dss.getMin());//13000.0
}
@Test
public void test11() {
System.out.println(
emps.stream()
.map(Employee::getName)
.distinct()
// 参数1:连接符, 参数2:前缀, 参数3:后缀
.collect(Collectors.joining(" , ", "begin ->\n" , "\n<- end"))
);
}
输出:
begin ->
bart , lisa , lisa2 , maggie , maggie2 , homer , marge
<- end
并行流和串行流
并行流和串行流,顾名思义,一个并行,一个串行,前者充分利用CPU的多核心优势,后者为单线程运行。在Java8中的并行流实际上就是Java7中的ForkJoin框架的再封装。
小例子:
@Test
public void test2() {
OptionalLong res = LongStream.range(0, 100000000L)
.parallel() // 转化为并行流
.reduce(Long::sum);
System.out.println(res.getAsLong());
}
@Test
public void test2() {
OptionalLong res = LongStream.range(0, 100000000L)
.sequential() // 转化为串行流
System.out.println(res.getAsLong());
}