公司的项目中大量用到了Stream
API,或者说,能用到Stream
的地方都用了,写的人固然很爽,但有时维护的人还是会骂街。
骂归骂,自己爽比较重要。
于是记录了一些Stream
用法防止忘记,基础的forEach/filter/map
用法就略过了。
以后遇到有意思的Stream
用法,也会记录在这里。
经常会迷惑map
和flatMap
区别是啥,我也说不好,直接上代码吧。
它可以用在类似Map<K, List<T>>
的结构中(或者说MultiMap
),操作所有List<T>
的元素。
使用听不懂的说法,它将Stream<Collection<T>>
“拉平”成Stream<T>
。
注:为了简化基础集合类型的初始化,使用了Guava
API。
Map<String, List<Integer>> map = new HashMap<>();
map.put("a", Lists.newArrayList(1, 2, 3));
map.put("b", Lists.newArrayList(4, 5, 6));
// want [1, 2, 3, 4, 5, 6]
map.values()
.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList());
将List
转为Map
,约定Map
中每个key
对应的value
都是唯一的。
List<String> list = Lists.newArrayList("Aabc", "Bdef", "Cghi");
// want {A: abc, B:def, C:ghi}
Map<String, String> map = list
.stream()
.collect(
Collectors.toMap(
item -> item.substring(0, 1),
item -> item.substring(1)
)
);
上述的转换当key
对应的value
并非唯一时,可能希望转换成Map<K, List<T>>/MultiMap
的形式。
List<String> list = Lists.newArrayList("Aabc", "Adef", "Bghi");
// want {A: [abc, def], B:[ghi]}
Map<String, List<String>> map = list
.stream()
.collect(
Collectors.toMap(
item -> item.substring(0, 1),
item -> Lists.newArrayList(item.substring(1)),
// key 冲突时,两个value的处理
(oldVal, newVal) -> {
oldVal.addAll(newVal);
return oldVal;
}
)
);
// groupby
// but can't change value dirctly
map = list.stream().collect(Collectors.groupingBy(item -> item.substring(0, 1)));
System.out.println(map);
// partitioningBy
Map<Boolean, List<String>> partitioning = list.stream().collect(Collectors.partitioningBy(item -> item.startsWith("A")));
System.out.println(partitioning);
想改变MultiMap
中Entry
的类型。
Map<String, List<String>> map = new HashMap<>();
map.put("a", Lists.newArrayList("A", "B", "C"));
// want {A: [a, b, c]}
map = map.entrySet()
.stream()
.map(entry -> {
String newKey = entry.getKey().toUpperCase();
List<String> newVal = entry.getValue()
.stream()
.map(item -> item.toLowerCase())
.collect(Collectors.toList());
// or new AbstractMap.SimpleEntry<String, String>(newKey, newVal);
return Maps.immutableEntry(newKey, newVal);
}).collect(Collectors.toMap(Entry::getKey, Entry::getValue));
Guava
提供了很方便的集合类型和接口,下面用Guava
再实现一下上面的操作。
Guava
直接提供了MultiMap
类型。
Multimap<String, Integer> multimap = ArrayListMultimap.create();
multimap.putAll("a", Lists.newArrayList(1, 2, 3));
multimap.putAll("b", Lists.newArrayList(4, 5, 6));
// want [1, 2, 3, 4, 5, 6]
List<Integer> list = multimap.values();
这里的做法是分成两步骤:
key
提取出来;value
转换成所需的格式。测试代码使用了String
类型,实际使用中是对象的话,就可以将第二步省略,除非还要再转换对象。
List<String> list = Lists.newArrayList("Aabc", "Bdef", "Cghi");
// want {A: abc, B:def, C:ghi}
Map<String, String> map = Maps.uniqueIndex(list, item -> item.substring(0, 1));
map = Maps.transformValues(map, item -> item.substring(1));
大致逻辑和使用Stream
相同,只是返回类型变成了MultiMap
。
List<String> list = Lists.newArrayList("Aabc", "Adef", "Bghi");
// want {A: [abc, def], B:[ghi]}
Multimap<String, String> multimap = list
.stream()
.collect(ArrayListMultimap::create,
(m, item) -> m.put(item.substring(0, 1), item.substring(1)),
Multimap::putAll);
Guava
不提供transformKeys
的方法。
一个比较好的解释
所以这里使用Guava
只能对value
进行转换。
Multimap<String, String> multimap = ArrayListMultimap.create();
multimap.putAll("a", Lists.newArrayList("A", "B", "C"));
// want {A: [a, b, c]}
// but get {a: [a, b, c]}
multimap = Multimaps.transformValues(multimap, String::toLowerCase);
这是在工作中遇到的一个转换,记录下来。
/*
a -> foo1, foo3
foo1, tags=a,b,c b -> foo1
foo2, tags=c,d ---> c -> foo1, foo2, foo3
foo3, tags=a,c,e d -> foo2
e -> foo3
*/
List<String> list = Lists.newArrayList(
"foo1, tags=a,b,c",
"foo2, tags=c,d",
"foo3, tags=a,c,e"
);
ImmutableMultimap.Builder<String, String> builder = ImmutableMultimap.builder();
list.forEach(item -> {
String[] arr = item.split(", ");
String[] tags = arr[1].substring("tags=".length()).split(",");
Arrays.stream(tags).forEach(tag -> builder.put(tag, arr[0]));
});
Multimap<String, String> multimap = builder.build();
一句话版本:
Multimap<String, String> multimap = list
.stream()
.collect(
ImmutableMultimap.Builder<String, String>::new,
(builder, item) -> {
String[] arr = item.split(", ");
String[] tags = arr[1].substring("tags=".length()).split(",");
Arrays.stream(tags).forEach(tag -> builder.put(tag, arr[0]));
},
(builder1, builder2) -> builder1.putAll(builder2.build())
).build();
其实原理都和List
转MultiMap
一样:先提取出key
,将value
转为集合,当key
冲突时,合并两个集合。
// pojo ZodiacStar
// id index value
// 1 1 star1
// 1 2 star2
// ...
// want Map<Integer, Map<Integer, ZodiacStar>> id -> index -> value
result = zodiacs.steam()
.collect(Collectors.groupingBy(ZodiacStar::getId, Collectors.toMap(ZodiacStar::getIndex, Function.identity())));
// want Map<Integer, Map<Integer, List<ZodiacStar>>> ?
result = zodiacs.steam()
.collect(Collectors.groupingBy(ZodiacStar::getId, Collectors.groupingBy(ZodiacStar::getIndex)));
年轻的生命中突然出现Stream
,玩法超多逼格又高,吸引码农们不好好写代码。
学不动求不更。
9月了,专升本这边一开学,估计正式要学位考了,这段时间会复习,力扣每日一题很遗憾只能断签。工作上也不乐观,写代码占20%时间,单元测试需要80%时间,每天的工作完不成,得加班了。