让我们从
toMap()
的内部实现开始分析这个问题。
首先,
Collectors.toMap()
方法的定义如下:
public static >
Collector toMap(Function super T, ? extends K> keyMapper,
Function super T, ? extends U> valueMapper,
BinaryOperator mergeFunction,
Supplier mapSupplier) {
BiConsumer accumulator
= (map, element) -> map.merge(keyMapper.apply(element),
valueMapper.apply(element), mergeFunction);
return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID);
}
在这个实现中,
Collectors.toMap()
调用了
Map
接口的
merge()
方法。我们再来看看
merge()
方法的具体实现:
default V merge(K key, V value,
BiFunction super V, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
Objects.requireNonNull(value);
V oldValue = get(key);
V newValue = (oldValue == null) ? value :
remappingFunction.apply(oldValue, value);
if (newValue == null) {
remove(key);
} else {
put(key, newValue);
}
return newValue;
}
关键点在于
Objects.requireNonNull(value);
这一行代码,它会直接抛出
NullPointerException
,如果
value
为空的话。
来看一个实际的示例,假设我们有一个
Person
类,并希望将
List
转换成
Map
,以
name
作为
key
,
phoneNumber
作为
value
:
class Person {
private String name;
private String phoneNumber;
// 构造方法、getter 和 setter 省略
}
List bookList = new ArrayList<>();
bookList.add(new Person("jack", "18163138123"));
bookList.add(new Person("martin", null));
// 这行代码会抛出 NullPointerException
bookList.stream().collect(Collectors.toMap(Person::getName, Person::getPhoneNumber));
在
bookList
中,
martin
的
phoneNumber
为空,所以
toMap()
在
merge()
方法中会抛出
NullPointerException
。
如何避免这个问题?
1. 过滤掉
null
值
如果
value
可能为
null
,最简单的方式是先过滤掉
null
值:
Map bookMap = bookList.stream()
.filter(person -> person.getPhoneNumber() != null)
.collect(Collectors.toMap(Person::getName, Person::getPhoneNumber));
这种方法适用于不需要
null
值的场景。
2. 提供默认值
如果
value
可能为
null
,但仍然希望保留该键,可以使用
Optional
处理默认值,例如: