Collectors方法常见的异常

2024-01-02 13:30:39

1. Collectors.toMap常见的异常

1.1 Collectors.toMap 报错 NullPointerException

最近线上偶尔会报一个 NPE,是 Collectors.toMap 导致的,这里小记一下,防止再次踩坑。

场景:批量查询用户信息,查询结果为 List<User>,然后将其转换成 Map<Integer, String>,以供其他地方使用,但在 Collectors.toMap 时抛出了异常 NullPointerException

1.1.1复现问题

public class ToMapTest {
    public static void main(String[] args) {
        List<User> users = query();
        Map<Integer, String> userMap = users.stream()
        .collect(Collectors.toMap(User::getId, User::getGirlfriend));
    }

    public static List<User> query() {
        List<User> list = new ArrayList<>();
        // girlfriend 为 null
        list.add(new User(1, null));
        return list;
    }
}

@Data
class User {
    private final Integer id;
    private final String girlfriend;
}

当 User 的 girlfriend 为 null 时,会抛出 NPE(单身狗落泪!!!)

Exception in thread "main" java.lang.NullPointerException
	at java.util.HashMap.merge(HashMap.java:1225)
	at java.util.stream.Collectors.lambda$toMap$58(Collectors.java:1320)
	at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
	at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1384)
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
	at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
	at com.example.collectors.ToMapTest.main(ToMapTest.java:19)

原因
在第 16 行,会调用 map.merge,且两个参数的 toMap 方法默认是创建 HashMap(第 5 行)。

// Collectors.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);
}

// Collectors.toMap
public static <T, K, U, M extends Map<K, U>>
    Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
                             Function<? super T, ? extends U> valueMapper,
                             BinaryOperator<U> mergeFunction,
                             Supplier<M> mapSupplier) {
    BiConsumer<M, T> accumulator
        // 调用 map.merge 方法
        = (map, element) -> map.merge(keyMapper.apply(element),
                                      valueMapper.apply(element), mergeFunction);
    return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID);
}

原因很简单,第 4 行判断了 value == null,所以抛出了 NullPointerException。

@Override
public V merge(K key, V value,
               BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
    if (value == null)
        throw new NullPointerException();
    if (remappingFunction == null)
        throw new NullPointerException();
    ......
}

1.1.2 解决办法

以下两种方法与 Collectors.toMap 方法的区别是:当出现重复的 key 时, Collectors.toMap 默认会抛异常,而以下两种方式会用新值覆盖旧值。

// Collectors.toMap 默认抛异常
private static <T> BinaryOperator<T> throwingMerger() {
    return (u,v) -> { throw new IllegalStateException(String.format("Duplicate key %s", u)); };
}

Stream.collect 方法
Stream 的 collect 可以实现简易的 Collector 功能:

// Stream.collect()
<R> R collect(Supplier<R> supplier,
              BiConsumer<R, ? super T> accumulator,
              BiConsumer<R, R> combiner);

public static void main(String[] args) {
    List<User> users = query();

    Map<Integer, String> userMap2 = users.stream()
        .collect(HashMap::new, (map, user)-> 
        map.put(user.getId(), user.getGirlfriend()), HashMap::putAll);
    System.out.println(userMap2);
}
// 输出
// {1=null}

直接使用 Collector
与使用 Stream.collect 方法相比,Collector多了一个 Characteristics 参数,这里用不到所以给了空数组:

public static void main(String[] args) {
    List<User> users = query();
    Collector<User, Map<Integer, String>, Map<Integer, String>> collector = Collector.of(
        HashMap::new,
        (map, user)-> map.put(user.getId(), user.getGirlfriend()),
        (map1, map2) -> {
            map1.putAll(map2);
            return map1;
        },
        new Collector.Characteristics[]{}
    );
    Map<Integer, String> userMap2 = users.stream()
        .collect(collector);

    System.out.println(userMap2);
}

// 输出
// {1=null}

鸣谢:https://blog.csdn.net/qq_20919883/article/details/119654002

文章来源:https://blog.csdn.net/CNCDXX_88/article/details/135332871
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。