关于Java Stream的使用心得
最近在review代码,用stream代替一些for,加之用了stream也有一段时间,总结下stream的使用心得。
一开始使用目的是为了紧跟java 8的步伐,了解Java 8的特性,觉得很有趣,现在则是为了语义清晰,用少量的代码代替复杂的循环,顺便提高效率(主要也是项目用到了,不然也没机会练熟)。
常用的场景
目前我用到的场景最多的是ArrayList
或HashMap
拼接字符串、分组,有时会用于查找和求最值。常用的方法:中间处理有filter
、map
、结果处理有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>
转换单个字符串(这是我最喜欢的常规操作)。
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<TestComponent> testComponents
// --List<String> 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
还有许多好用的方法,如sorted
、distinct()
,其中就有parallel
,将Stream
并行化,提高效率,但要注意并发问题。一般常见于使用.stream().parallel()
或者.parallelStream()
,搭配.forEach()
时,会发生意想不到的情况。
我基本没用到并行流==,对于这方面的把握还是不够(主要是怂,尽量避免生产bug),刚好过年,希望下一年在这方面在并发上学习学习。