453

Collectors.toMapNullPointerException値の 1 つが の場合、 をスローしますnull。私はこの動作を理解していません。マップには問題なく null ポインターを値として含めることができます。の値を null にできない正当な理由はありますCollectors.toMapか?

また、これを修正する素敵なJava 8の方法はありますか、それとも単純な古いforループに戻す必要がありますか?

私の問題の例:

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;


class Answer {
    private int id;

    private Boolean answer;

    Answer() {
    }

    Answer(int id, Boolean answer) {
        this.id = id;
        this.answer = answer;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public Boolean getAnswer() {
        return answer;
    }

    public void setAnswer(Boolean answer) {
        this.answer = answer;
    }
}

public class Main {
    public static void main(String[] args) {
        List<Answer> answerList = new ArrayList<>();

        answerList.add(new Answer(1, true));
        answerList.add(new Answer(2, true));
        answerList.add(new Answer(3, null));

        Map<Integer, Boolean> answerMap =
        answerList
                .stream()
                .collect(Collectors.toMap(Answer::getId, Answer::getAnswer));
    }
}

スタックトレース:

Exception in thread "main" java.lang.NullPointerException
    at java.util.HashMap.merge(HashMap.java:1216)
    at java.util.stream.Collectors.lambda$toMap$168(Collectors.java:1320)
    at java.util.stream.Collectors$$Lambda$5/1528902577.accept(Unknown Source)
    at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
    at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1359)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502)
    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 Main.main(Main.java:48)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)

この問題は、Java 11 にも存在します。

4

12 に答える 12

424

OpenJDK のこの既知のバグを回避するには、次のようにします。

Map<Integer, Boolean> collect = list.stream()
        .collect(HashMap::new, (m,v)->m.put(v.getId(), v.getAnswer()), HashMap::putAll);

それほどきれいではありませんが、機能します。結果:

1: true
2: true
3: null

(このチュートリアルが一番役に立ちました。)

編集:

とは異なりCollectors.toMap、@mmdemirbas がコメントで指摘したように、同じキーが複数回ある場合、これは値をサイレントに置き換えます。これが不要な場合は、コメント内のリンクを参照してください。

于 2014-07-08T14:09:22.827 に答える
197

の静的メソッドでは不可能ですCollectors。のjavadocは、に基づいているとtoMap説明しています:toMapMap.merge

@param mergeFunction マージ関数。同じキーに関連付けられた値間の衝突を解決するために使用されます。Map#merge(Object, Object, BiFunction)}

そしてのjavadocはMap.merge言う:

@throws NullPointerException 指定されたキーが null で、このマップが null キーをサポートしていないか、値または remappingFunction nullの場合

forEachリストのメソッドを使用して for ループを回避できます。

Map<Integer,  Boolean> answerMap = new HashMap<>();
answerList.forEach((answer) -> answerMap.put(answer.getId(), answer.getAnswer()));

しかし、それは古い方法よりも単純ではありません:

Map<Integer, Boolean> answerMap = new HashMap<>();
for (Answer answer : answerList) {
    answerMap.put(answer.getId(), answer.getAnswer());
}
于 2014-07-08T12:37:37.050 に答える
31

CollectorデフォルトのJavaのものとは異なり、値がある場合にクラッシュしないものを書きましたnull

public static <T, K, U>
        Collector<T, ?, Map<K, U>> toMap(Function<? super T, ? extends K> keyMapper,
                Function<? super T, ? extends U> valueMapper) {
    return Collectors.collectingAndThen(
            Collectors.toList(),
            list -> {
                Map<K, U> result = new HashMap<>();
                for (T item : list) {
                    K key = keyMapper.apply(item);
                    if (result.putIfAbsent(key, valueMapper.apply(item)) != null) {
                        throw new IllegalStateException(String.format("Duplicate key %s", key));
                    }
                }
                return result;
            });
}

Collectors.toMap()呼び出しをこの関数の呼び出しに置き換えるだけで、問題が解決します。

于 2015-09-18T09:21:46.590 に答える
4

値が文字列の場合、これは機能する可能性があります。 map.entrySet().stream().collect(Collectors.toMap(e -> e.getKey(), e -> Optional.ofNullable(e.getValue()).orElse("")))

于 2016-04-27T20:36:44.757 に答える