第一章:噩梦的开始
jdk8 引入了让人欲罢不能的 stream 流处理 -- 楔子
每当遇到 List<Object> 对象需要各种花样的处理时,你就会想到你的梦中情人Stream了吧。
先造点数据,下面都要用 考试重点:
List<User> list = new ArrayList<>();
list.add(new User(1, "a"));
list.add(new User(1, "b"));
list.add(new User(2, "c"));
list.add(new User(2, null));
@Data
class User{
private int id;
private String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
}
比如过滤 + 筛选属性:Collectors.toList()
List<String> nameList = list.stream().filter(item -> StringUtils.hasText(item.getName())).collect(Collectors.toList());
而当你需要将List转为Map的时候,兄dei,你可要小心了!!!
路人甲:这有什么难的,我直接:Collectors.toMap()
Map<Integer, String> nameMap = list.stream().collect(Collectors.toMap(User::getId, User::getName));
自信,直接启动~~
woc 报错了
第二章:重生之我是技术博主
噢噢噢 java.lang.IllegalStateException: Duplicate key a key重复了
怎么解决呢?点进toMap()方法,看看源码
public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper) {
return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
}
噢噢,这里调用了另外一个方法,传了一个 throwingMerger() 参数
点过去看看
/**
返回一个合并函数,适合在 Map. merge() 中使用 或 toMap(),该函数总是抛出 IllegalStateException。这可用于强制执行所收集的元素是不同的假设。
返回:
一个总是抛出的合并函数 IllegalStateException
*/
private static <T> BinaryOperator<T> throwingMerger() {
return (u,v) -> { throw new IllegalStateException(String.format("Duplicate key %s", u)); };
}
哦豁,gg了?
这时候你又发现(google,baidu),刚好有另外一个方法可以解决这个重复key的问题
手动提供key重复解决策略
public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction) {
return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
}
于是你修改了下
Map<Integer, String> nameMap = list.stream().collect(Collectors.toMap(User::getId, User::getName, (k1, k2) -> k1));
这次不太自信了,debug启动~~
啊?又又报错了-翻车
还是老朋友NullPointerException 空指针,哪里受过这种委屈。
第三章:已老实,求放过!!!
打两个断点,debug debug
断点进入这个merge方法
WTF? HashMap 不是支持null的value值吗?--隐约记得八股文是如此啊
这里作者也去google了一下,放一下stackoverflow的链接 stackoverflow.com/questions/2…
第四章:道法自然
好的,没办法,那就的处理一下value为null了
这里借鉴一下其他大佬的博文(小声bb:实在好笑)
这里作者我就不这么优雅了。我们filter处理一波
Map<Integer, String> nameMap = list.stream()
.filter(user -> StringUtils.hasText(user.getName()))
.collect(Collectors.toMap(User::getId, User::getName, (k1, k2) -> k1));
嗯?又是stream,又是filter,又是Collectors.toMap()...我丢,看起来似乎有点复杂
要是不装这个stream的逼呢?
Map<Integer, String> nameMap = new HashMap<>();
list.forEach(user -> nameMap.put(user.getId(), user.getName()));
// 亦或者
for (User user : list) {
nameMap.put(user.getId(), user.getName());
}
嗯~~~ 舒服了
完结篇:小声BB + 个人揣测
再多看看这个merge()方法
这是Map接口的default方法,那么其他子Map如果没有重写,都会调用这个default的merge方法。
这里联想到既然是default的方法,是不是jdk作者是为了兼容各种类型的Map(例如HashMap,ConcurrentHashMap)呢?
JDK作者: 哎你小子,可真会琢磨
然后打脸就来了,HashMap和ConcurrentHashMap都重写了这个merge()方法
备注:google了很多文章,也没有找到官方一点的 value判空的说明。
那我就只能继续揣测了
HashMap的get方法,就算key不存在,拿到的value也会是null。- 那么如果允许
value为null存储,多个相同的 key ,其value都为null。 - 在使用的时候,某些场景下,跟直接不存入HashMap的结果是一样的。
一般我们使用 Hash 表,大多数情况是为了使用 get(key) 方法,避免 for 循环,if 判断key 执行业务,那么这种场景下,将 value 值为 null 的 key 存进来,就是单纯的浪费空间了。
路人乙:我要抬杠,我要用map的keys(去重的key),或者 values(为去重的value)
如果是keys 可以直接用
Set<Integer> ids = list.stream().map(User::getId).collect(Collectors.toSet());
如果是values 可以直接用
List<String> nameList = list.stream().map(User::getName).collect(Collectors.toList());
这二者都比先转map,再拿keys或者values性能好。且这种非空的value 也能兼容其他map,就算是强转类型,也能使用其merge方法。