关于Java Stream的使用心得

关于Java Stream的使用心得

最近在review代码,用stream代替一些for,加之用了stream也有一段时间,总结下stream的使用心得。

一开始使用目的是为了紧跟java 8的步伐,了解Java 8的特性,觉得很有趣,现在则是为了语义清晰,用少量的代码代替复杂的循环,顺便提高效率(主要也是项目用到了,不然也没机会练熟)。

常用的场景

目前我用到的场景最多的是ArrayListHashMap拼接字符串、分组,有时会用于查找和求最值。常用的方法:中间处理有filtermap、结果处理有collect,使用的方式举例说明,mark。

demo model

//这里用到了lombok(自动生成getter/setter、toString)
@lombok.Data
public class TestModel {
    private String id;
    private String name;
    private int level;
    private List<String> tags;
    private List<TestComponent> testComponents;

    @lombok.Data
    public static class TestComponent{
        private String id;
        private String name;
        private int level;
        private List<String> tags;
    }
} 

拼接字符串

拼接字符串,比如将List转换单个字符串(这是我最喜欢的常规操作)。

String str = testModel.getTags().stream()
                .collect(Collectors.joining(","));

如果是复杂点的,可以先map转换为String再处理。

String str = testModel.getTestComponents().stream()
                .map(testComponent->testComponent.getId())
                .collect(Collectors.joining(","));

分组

一般在循环用多个if或者switch内实现根据属性进行分组,而stream下使用Collectors.groupingBy即可,会生成形如<需要分组的属性, 该分组下的对象的ArrayList>Map

Map<Integer, List<TestModel.TestComponent>> groupByLevel = testModel.getTestComponents().stream()
    //TestModel.TestComponent::getLevel 为 testComponent -> testComponent.getLevel() 的简化形式
    //如果是基本类型,可以使用Function.identity()作为groupingBy的参数
    .collect(Collectors.groupingBy(TestModel.TestComponent::getLevel));
//1为level的枚举值的其中之一,注意可能为空
List<TestModel.TestComponent> level1 = groupByLevel.get(1);
System.out.println(String.format("%s", level1));

如果想要分组统计,可以为Collectors.groupingBy传入第二个参数,此时生成的Map的value就是该组的数量。

Map<Integer, Long> sizeGroupByLevel = testModel.getTestComponents().stream()
    .collect(Collectors.groupingBy(TestModel.TestComponent::getLevel, Collectors.counting()));
Long level1Size = sizeGroupByLevel.get(1);
System.out.println(String.format("%s", level1Size));

最值

获取最大/小值,非基本类型的需要传入Comparator,也就是比较方法,这样可获取包含最值属性的对象。

Optional<TestModel.TestComponent> optionalMaxLevel = testModel.getTestComponents().stream()
                .max((o1,o2)->o1.getLevel()-o2.getLevel());
//判断是否存在最值
if(optionalMaxLevel.isPresent()){
    //包含最值的对象
    TestModel.TestComponent testComponent = optional.get();
    int level = testComponent.getLevel();
    System.out.println(String.format("%s", level));
}

也可以转换为基本类型再获取,不过这里得出的结果也只能是基本类型了。

int level = testModel.getTestComponents().stream().mapToInt(TestModel.TestComponent::getLevel).max()

判断

判断集合是否全匹配或部分匹配。

//是否全部level的值大于10
boolean isGreater = testModel.getTestComponents().stream()
                .allMatch(testComponent -> testComponent.getLevel()>10);
//是否存在level的值大于10
boolean isGreater = testModel.getTestComponents().stream()
                .anyMatch(testComponent -> testComponent.getLevel()>10);

双重循环

适用于只对最内层的循环对象进行操作。对于双重(或多重)循环,可以通过flatMap转换为单个Stream对象再进行处理。

// List testComponents
//    --List tags
Stream<String> stream = testModel.getTestComponents().stream()
    .flatMap(testComponent -> testComponent.getTags().stream());
//此时相当于双重变单重循环
String tags = stream
    .filter( tag -> null!=tag && !tag.isEmpty() )
    .distinct()
    .collect(Collectors.joining(","));

查找

先通过filter将需要的对象筛选下来,然后可以使用findFirst获取首个符合条件的对象,或者是collect(Collectors.toList())返回符合条件的List

Optional<TestModel.TestComponent> optional = testModel.getTestComponents().stream()
    //unordered()是用来声明操作与顺序无关,便于优化,而非结果是无序的
    .unordered()
    .filter(testComponent -> testComponent.getLevel()>10).findFirst();
if(optional.isPresent()){
    int level = optional.get().getLevel();
    System.out.println(String.format("%s", level));
}

慎用stream

不要轻易修改原有的代码,不用强求一定要用上,复杂的逻辑用循环能解析清楚就行,不要做多余的事。review过程中,尝试将for(int i=0;;)改为list.forEach,看起来也蛮不错的。

比如下列形式的,双重循环下需要用到外层循环的属性,我就不会使用Stream(主要是菜:-),还没想到怎么用到Stream)。

List<String> names = new ArrayList<>();
for(TestModel.TestComponent testComponent : testModel.getTestComponents()){
    String id = testComponent.getId();
    for(String tag: testComponent.getTags()){
        names.add(String.format("%s-%s",id,tag));
    }
}

并行流线程非安全

Stream还有许多好用的方法,如sorteddistinct(),其中就有parallel,将Stream并行化,提高效率,但要注意并发问题。一般常见于使用.stream().parallel()或者.parallelStream(),搭配.forEach()时,会发生意想不到的情况。

我基本没用到并行流==,对于这方面的把握还是不够(主要是怂,尽量避免生产bug),刚好过年,希望下一年在这方面在并发上学习学习。

你可能感兴趣的:(java,stream,java)