Stream
中的操作可以分为两大类:中间操作 与 结束操作。
中间操作 只会进行操作记录,只有结束操作才会触发实际的计算,可以理解为懒加载,这也是Stream
在操作大对象迭代计算的时候如此高效的原因之一。
中间操作分为:
distinct()
去重方法,它必须拿到所有元素才知道当前迭代的元素是否被重复。结束操作可以分为:
操作 | 类型 | 返回类型 | 使用的类型/函数式接口 | 函数描述符 |
---|---|---|---|---|
filter | 中间 | Stream |
Predicate |
T -> boolean |
distinct | 中间(有状态-无界) | Stream |
||
skip | 中间(有状态-有界) | Stream |
long |
|
limit | 中间(有状态-有界) | Stream |
long |
|
map | 中间 | Stream |
Function |
T -> R |
flatMap | 中间 | Stream |
Function |
T -> Stream |
sorted | 中间(有状态-无界) | Stream |
Comparator |
(T, T) -> int |
anyMatch | 终端 | boolean |
Predicate |
T -> boolean |
noneMatch | 终端 | boolean |
Predicate |
T -> boolean |
allMatch | 终端 | boolean |
Predicate |
T -> boolean |
findAny | 终端 | Optional |
||
findFirst | 终端 | Optional |
||
forEach | 终端 | void |
Consumer |
T -> void |
collect | 终端 | R |
Collector |
T -> void |
reduce | 终端(有状态-无界) | Optional |
BinaryOperator |
(T, T) -> T |
count | 终端 | long |
介绍完Stream
的核心操作之后,我们来认识一下Stream
创建流的几种方式:
2.1 通过java.util.Collection.stream()方法创建流
List intList = Arrays.asList(1, 2, 3, 4, 5, 6);
Stream stream = intList.stream();
Stream parallelStream = intList.parallelStream();
复制代码
2.2 使用 java.util.Arrays.stream(T[]array)方法用数组创建流
int[] array = {1, 2, 3, 4, 5, 6};
IntStream intStream = Arrays.stream(array);
复制代码
2.3 使用Stream.of()、iterate()、generate()静态方法创建流
Stream intStream = Stream.of(1, 5, 3, 4, 2, 6).sorted();
// 输出结果:123456
Stream stream2 = Stream.iterate(0, (x) -> x + 2).limit(3);
stream2.forEach(System.out::println);
// 输出结果:
0
2
4
Stream stream3 = Stream.generate(Math::random).limit(3);
stream3.forEach(System.out::println)
//输出结果:
0.9620319103852426
0.8303672905658537
0.09203215202737569
复制代码
Optional
是Java8
提供的为了解决NPE
安全问题的一个API
。善用Optional
可以使我们代码中很多繁琐、丑陋的设计变得十分优雅。
方法 | 描述 |
---|---|
of | 把指定的值封装为Optional 对象,如果指定的值为null ,则抛出NullPointException 异常 |
empty | 创建一个空的Optional 对象 |
ofNullable | 把指定的值封装为Optional 对象,如果指定的值为null ,则创建一个空的Optional 对象 |
get | 如果创建的Optional 对象中有值存在,则返回此值,否则抛出NoSuchElementException |
orElse | 如果创建的Optional 对象中有值存在,则返回此值,否则返回一个默认值 |
orElseGet | 如果创建的Optional 对象中有值存在,则返回此值,否则返回一个由Supplier 接口生成的值 |
orElseThrow | 如果创建的Optional 对象中有值存在,则返回此值,否则返回一个由Supplier 接口生成的异常 |
filter | 如果创建的Optional 的值满足filter 中的条件,则返回该值的Optional 对象,否则返回一个空的Optional 对象 |
map | 如果创建的Optional 对象中有值存在,对该值执行提供的Function 的函数调用 |
flatMap | 如果创建的Optional 对象中有值存在,对该值执行提供的Function 的函数调用,返回一个Optional 类型的值,否则就返回一个空的Optional 对象 |
isPresent | 如果创建的Optional 对象中有值存在,返回true ,否则返回false |
ifPresent | 如果创建的Optional 对象中有值存在,则执行该方法的调用,否则什么都不做 |
常用写法:
Set set = Optional.ofNullable(xxxSet).orElse(Sets.newHashSet());
List list = Optional.ofNullable(xxxList).orElse(Lists.newArrayList());
复制代码
举个:
public class StreamAdvance {
public static void main(String[] args) {
Set codeSet = Sets.newHashSet("N23652", "N63221", "N82372", "N23721", "R34373", "R12922", "R72322", "R43984", "T93849", "T23728", "T72322", "T23829");
Map> codeGroupMap = Optional.ofNullable(codeSet).orElse(Sets.newHashSet()).stream()
// 根据首字符分组,分组的映射对象为codeSet里的code,返回Map>
.collect(Collectors.groupingBy(i -> i.substring(0, 1), Collectors.mapping(i -> i, Collectors.toSet())));
Map> codeGroupList = Optional.ofNullable(codeSet).orElse(Sets.newHashSet()).stream()
// 根据首字符分组,分组的映射对象为codeSet里的code,返回Map>
.collect(Collectors.groupingBy(i -> i.substring(0, 1), Collectors.mapping(i -> i, Collectors.toList())));
System.out.println(codeGroupMap);
System.out.println(codeGroupList);
}
}
复制代码
Collectors提供了一系列用户数据统计的静态方法:
描述 | 方法 |
---|---|
计数 | counting |
最大最小值 | maxBy、minBy |
求和 | reducing、summingInt、summingLong、summingDouble |
平均值 | averagingInt、averagingLong、averagingDouble |
收集 | toMap、toList、toSet |
字符串拼接 | joining |
分组 | groupingBy |
分区 | partitioningBy |
1. 获取List列表的某个Object对象属性Set集合
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private String id;
private String name;
private Integer age;
private BigDecimal salary;
}
List list = new ArrayList<>();
list.add(new Person("1", "Ram", 30, new BigDecimal("10000")));
list.add(new Person("2", "Shyam", 21, new BigDecimal("12000")));
list.add(new Person("3", "Shiv", 20, new BigDecimal("16000")));
list.add(new Person("4", "Mahesh", 35, new BigDecimal("20000")));
list.add(new Person("5", "Austin", 26, new BigDecimal("8000")));
list.add(new Person("6", "Jacklin", 24, new BigDecimal("24000")));
//将list转成以id为key的map,value是id对应的Person对象
Map map1 = list.stream().collect(Collectors.toMap(Person::getId, Function.identity()));
System.out.println("map1=" + map1);
//输出结果:
map1={1=Person(id=1, name=Ram, age=30), 2=Person(id=2, name=Shyam, age=20), 3=Person(id=3, name=Shiv, age=20), 4=Person(id=4, name=Mahesh, age=30), 5=Person(id=5, name=Austin, age=26), 6=Person(id=6, name=Jacklin, age=10)}
复制代码
如果id存在重复值,则会报错 Duplicate key xxx
,所以解决id重复的写法是:
//解决方案1:只取后一个key及value -> (k1, k2) -> k2
list.add(new Person("6", "Tom", 10));
Map map2 = list.stream().collect(Collectors.toMap(Person::getId, Function.identity(), (k1, k2) -> k2));
System.out.println("map2=" + map2);
//输出结果,同样id为6的,输出用户Tom:
map2={1=Person(id=1, name=Ram, age=30), 2=Person(id=2, name=Shyam, age=20), 3=Person(id=3, name=Shiv, age=20), 4=Person(id=4, name=Mahesh, age=30), 5=Person(id=5, name=Austin, age=26), 6=Person(id=6, name=Tom, age=10)}
//解决方案2:只取前一个key及value -> (k1, k2) -> k1
Map map3 = list.stream().collect(Collectors.toMap(Person::getId, Function.identity(), (k1, k2) -> k1));
System.out.println("map3=" + map3);
//输出结果,同样id为6的,输出用户Jacklin:
map3={1=Person(id=1, name=Ram, age=30), 2=Person(id=2, name=Shyam, age=20), 3=Person(id=3, name=Shiv, age=20), 4=Person(id=4, name=Mahesh, age=30), 5=Person(id=5, name=Austin, age=26), 6=Person(id=6, name=Jacklin, age=10)}
复制代码
2. 获取id和name对应的Map
Map idNameMap = list.stream().collect(Collectors.toMap(Person::getId, Person::getName));
System.out.println("idNameMap=" + idNameMap);
//输出结果
idNameMap={1=Ram, 2=Shyam, 3=Shiv, 4=Mahesh, 5=Austin, 6=Jacklin}
复制代码
当存在name为null的时候,这种方式获取会报NPE空指针异常
,所以解决value为null的写法是:
list.add(new Person("7", null, 10));
//解决空指针异常
Map idNameMapPreventNPE = list.stream().collect(Collectors.toMap(Person::getId, p -> p.getName() == null ? "" : p.getName()));
System.out.println("idNameMapPreventNPE=" + idNameMapPreventNPE);
复制代码
3. 将集合的属性转成对象集合
List ids = Arrays.asList("1", "2", "3");
List personList = ids.stream().map(id -> {
Person person = new Person();
person.setId(id);
person.setName("name" + id);
return person;
}).collect(Collectors.toList());
//输出结果:
personList: [Person(id=1, name=name1, age=null), Person(id=2, name=name2, age=null), Person(id=3, name=name3, age=null)]
复制代码
4. 判断集合中是否有一个对象包含某个属性
boolean flag = list.stream().anyMatch(person -> "Austin".equals(person.getName()));
// ...allMatch和...anyMatch类似
复制代码
5. 对集合的某个对象求和
BigDecimal reduce = list.stream().map(Person::getSalary).reduce(BigDecimal.ZERO, BigDecimal::add);
System.out.println("总薪酬reduce: " + reduce);
复制代码
6. 集合转Map(value为对象本身)
//List集合转Map
Map mapCollect = list.stream().collect(Collectors.toMap(Person::getId, person -> person));
//输出结果:
mapCollect: {1=Person(id=1, name=Ram, age=30, salary=10000), 2=Person(id=2, name=Shyam, age=20, salary=12000), 3=Person(id=3, name=Shiv, age=20, salary=14000), 4=Person(id=4, name=Mahesh, age=30, salary=20000), 5=Person(id=5, name=Austin, age=26, salary=9000), 6=Person(id=6, name=Jacklin, age=10, salary=18000)}
复制代码
7. 集合转Map(key存在重复)
//List集合转Map, key重复时,value取值取前面的
Map mapCollect = list.stream().collect(Collectors.toMap(Person::getId, person -> person, (before, after) -> before));
复制代码
8. 集合分组转Map
Map> mapGroupBySalary = list.stream().collect(Collectors.groupingBy(Person::getSalary));
//输出结果:
mapGroupBySalary: {10000=[Person(id=1, name=Ram, age=30, salary=10000)], 12000=[Person(id=2, name=Shyam, age=20, salary=12000), Person(id=6, name=Jacklin, age=10, salary=12000)], 14000=[Person(id=3, name=Shiv, age=20, salary=14000)], 20000=[Person(id=4, name=Mahesh, age=30, salary=20000)], 9000=[Person(id=5, name=Austin, age=26, salary=9000)]}
复制代码
9. 集合分区转Map
Map> mapPartitionBySalary = list.stream().collect(Collectors.partitioningBy(person -> person.getSalary().compareTo(new BigDecimal("12000")) == 0));
//输出结果(两个区,一个是salary=12000的,另一个区是salary!=12000的):
mapPartitionBySalary: {false=[Person(id=1, name=Ram, age=30, salary=10000), Person(id=3, name=Shiv, age=20, salary=14000), Person(id=4, name=Mahesh, age=30, salary=20000), Person(id=5, name=Austin, age=26, salary=9000)], true=[Person(id=2, name=Shyam, age=20, salary=12000), Person(id=6, name=Jacklin, age=10, salary=12000)]}
复制代码
10. 集合分组转某个属性集合
Map> mapList = list.stream().collect(Collectors.groupingBy(Person::getId, Collectors.mapping(Person::getSalary, Collectors.toList())));
System.out.println("mapList: " + mapList);
//输出结果:
mapList: {Shiv=[14000], Jacklin=[12000], Mahesh=[20000], Austin=[12000, 9000], Ram=[10000]}
复制代码
11. 获取集合某个对象属性返回String类型,并用[]包括返回
String nameStr1 = list.stream().map(person -> person.getName()).collect(Collectors.joining(",", "[", "]"));
System.out.println("nameStr1: "+ nameStr1);
String nameStr2 = list.stream().collect(Collectors.mapping(Person::getName, Collectors.joining(",", "[", "]")));
System.out.println("nameStr2: "+ nameStr2);
//输出结果:
nameStr1: [Ram,Shyam,Shiv,Mahesh,Austin,Jacklin]
nameStr2: [Ram,Shyam,Shiv,Mahesh,Austin,Jacklin]
复制代码
12. 将List
转Map
按照key的某个前缀分组返回
举个:现在有一些产品code:"N1111", "N2222", "N3333", "S1223", "S2323", "S7462", "L2382", "L2323", "L3232", 不同的首字母区分不同产品,现在需要将不同的产品区分开来,并返回。
List list = Arrays.asList("N1111", "N2222", "N3333", "S1223", "S2323", "S7462", "L2382", "L2323", "L3232");
Map> codeMap = list.parallelStream().collect(Collectors.groupingBy(k -> k.substring(0, 1), Collectors.mapping(k -> k, Collectors.toList())));
System.out.println(codeMap);
//输出结果
{S=[S1223, S2323, S7462], L=[L2382, L2323, L3232], N=[N1111, N2222, N3333]}
复制代码
// map和flatMap
// 假设现在有一个句子列表,需要将句子中的每个单词都提取出来得到一个所有单词列表,这种情况下map就搞不定了。
String[] strings = {"Hello boy", "Welcome to the world!"};
List words = Stream.of(strings).map(s -> s.split(" ")).flatMap(s -> Stream.of(s)).distinct().collect(Collectors.toList());
words.forEach(c -> {
System.out.println(c);
});
//输出结果
Hello
boy
Welcome
to
the
world!
复制代码
//升序方式一
List sortListAsc = list.stream().sorted((a, b) -> a.getAge() - b.getAge()).collect(Collectors.toList());
System.out.println("sortListAsc=" + sortListAsc);
//升序方式二
List sortListAsc2 = list.stream().sorted(Comparator.comparing(Person::getAge)).collect(Collectors.toList());
System.out.println("sortListAsc2=" + sortListAsc2);
//降序方式一
List sortListDesc = list.stream().sorted((a, b) -> b.getAge() - a.getAge()).collect(Collectors.toList());
System.out.println("sortListDesc=" + sortListDesc);
//降序方式二
List sortListDesc2 = list.stream().sorted(Comparator.comparing(Person::getAge).reversed()).collect(Collectors.toList());
System.out.println("sortListDesc2=" + sortListDesc2);
//输出结果
sortListAsc=[Person(id=6, name=Jacklin, age=10), Person(id=7, name=null, age=10), Person(id=2, name=Shyam, age=20), Person(id=3, name=Shiv, age=20), Person(id=5, name=Austin, age=26), Person(id=1, name=Ram, age=30), Person(id=4, name=Mahesh, age=30)]
sortListAsc2=[Person(id=6, name=Jacklin, age=10), Person(id=7, name=null, age=10), Person(id=2, name=Shyam, age=20), Person(id=3, name=Shiv, age=20), Person(id=5, name=Austin, age=26), Person(id=1, name=Ram, age=30), Person(id=4, name=Mahesh, age=30)]
sortListDesc=[Person(id=1, name=Ram, age=30), Person(id=4, name=Mahesh, age=30), Person(id=5, name=Austin, age=26), Person(id=2, name=Shyam, age=20), Person(id=3, name=Shiv, age=20), Person(id=6, name=Jacklin, age=10), Person(id=7, name=null, age=10)]
sortListDesc2=[Person(id=1, name=Ram, age=30), Person(id=4, name=Mahesh, age=30), Person(id=5, name=Austin, age=26), Person(id=2, name=Shyam, age=20), Person(id=3, name=Shiv, age=20), Person(id=6, name=Jacklin, age=10), Person(id=7, name=null, age=10)]
复制代码
Person person1 = Person.builder().id("1").age(26).name("austin").salary(20000).build();
Person person2 = Person.builder().id("2").age(30).name("jacklin").salary(15000).build();
Person person3 = Person.builder().id("3").age(18).name("tony").salary(8000).build();
Person person4 = Person.builder().id("4").age(22).name("lucy").salary(30000).build();
Person person5 = Person.builder().id("5").age(38).name("Mica").salary(12000).build();
Person person6 = Person.builder().id("6").age(22).name("mike").salary(15000).build();
复制代码
4.1 按薪资升序排序输出
List salary = list.stream().sorted(Comparator.comparing(Person::getSalary)).map(Person::getSalary).collect(Collectors.toList());
// 输出结果:[8000, 12000, 15000, 15000, 20000, 30000]
复制代码
4.2 按薪资倒序排序输出
List salaryReversed = list.stream().sorted(Comparator.comparing(Person::getSalary).reversed()).map(Person::getSalary).collect(Collectors.toList());
// 输出结果:[30000, 20000, 15000, 15000, 12000, 8000]
复制代码
4.3 筛选出薪资大于等于20000的姓名并按薪资升序输出
//筛选出薪资大于等于20000的姓名并按薪资升序输出
List nameListWhichSalaryMoreThen20k = list.stream()
.filter(p -> p.getSalary() >= 20000)
.sorted(Comparator.comparing(Person::getSalary))
.map(Person::getName).collect(Collectors.toList());
System.out.println("nameListWhichSalaryMoreThen20k: " + nameListWhichSalaryMoreThen20k);
//输出结果:
nameListWhichSalaryMoreThen15k: [austin, lucy]
复制代码
4.4 先按薪资升序排序再按年龄由大到小(降序)排序
// 先按薪资升序排序在按年龄有大到小(降序)排序
List collect = list.stream().sorted((p1, p2) -> {
if (p1.getSalary().compareTo(p2.getSalary()) == 0) {
// 如果薪资相同,年龄降序输出(大的先输出)
return p2.getAge() - p1.getAge();
} else {
// 按薪资升序输出
return p1.getSalary() - p2.getSalary();
}
}).map(Function.identity()).collect(Collectors.toList());
System.out.println(collect);
//输出结果:
[Person(id=3, name=tony, age=18, salary=8000), Person(id=5, name=Mica, age=38, salary=12000), Person(id=2, name=jacklin, age=30, salary=15000), Person(id=6, name=mike, age=22, salary=15000), Person(id=1, name=austin, age=26, salary=20000), Person(id=4, name=lucy, age=22, salary=30000)]
复制代码
4.5 求两个list集合的差集和交集
// 求两个集合的差集和交集
List list1 = Arrays.asList(1, 3, 4, 5, 6, 8);
List list2 = Arrays.asList(2, 3, 5, 7, 9);
// list1存在,list2不存在的数据
List diffList = list1.stream().filter(i -> !list2.contains(i)).collect(Collectors.toList());
System.out.println("diffList: " + diffList);
// 公共数据
List unionList = list1.stream().filter(i -> list2.contains(i)).collect(Collectors.toList());
System.out.println("unionList: " + unionList);
复制代码
4.6 实现数据的平方(按倒序)输出
List numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
List squareList = numbers.stream().map(i -> i * i)
.sorted((x, y) -> y - x)
.collect(Collectors.toList());
System.out.println("squareList: " + squareList);
// 输出结果:
squareList: [49, 25, 9, 9, 9, 4, 4]
复制代码
Java 8 API
添加了一个新的抽象称为流Stream
,可以让你以一种声明的方式处理数据。Stream
使用一种类似用SQL
语句从数据库查询数据的直观方式来提供一种对Java
集合运算和表达的高阶抽象。Stream API
可以极大提高Java
程序员的生产力,让程序员写出高效率、干净、简洁的代码。这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。元素流在管道中经过 中间操作(intermediate operation) 的处理,最后由最终操作(terminal operation) 得到前面处理的结果。