JAVA Stream流实战开发经验总结

一、Stream流的基本操作

Stream中的操作可以分为两大类:中间操作 与 结束操作。

中间操作 只会进行操作记录,只有结束操作才会触发实际的计算,可以理解为懒加载,这也是Stream在操作大对象迭代计算的时候如此高效的原因之一。

中间操作分为:

  • 有状态操作:有状态是指该操作只有拿到所有元素之后才能继续下去,这也比较好理解,比如有状态的distinct()去重方法,它必须拿到所有元素才知道当前迭代的元素是否被重复。
  • 无状态操作:无状态是指元素的处理不受之前元素的影响。

结束操作可以分为:

  • 短路操作:这个应该很好理解,短路是指遇到某些符合条件的元素就可以得到最终结果
  • 非短路操作:而非短路是指必须处理所有元素才能得到最终结果。

JAVA Stream流实战开发经验总结_第1张图片

Stream流的中间操作和终端操作API

操作 类型 返回类型 使用的类型/函数式接口 函数描述符
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的核心操作之后,我们来认识一下Stream创建流的几种方式:

  • stream() - 为集合创建串行流
  • parallelStream() - 为集合创建并行流

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

OptionalJava8提供的为了解决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);
    }
}
复制代码

四、Stream不同场景演示

Collectors收集

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. 将ListMap>按照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:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
  • flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
// 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!
复制代码

sort排序

//升序方式一
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数据

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) 得到前面处理的结果。

你可能感兴趣的:(java,算法,数据结构)