संग्राहक की सूची में Java 8 NullPointerException


331

यदि कोई मान 'शून्य' है, तो जावा 8 Collectors.toMapफेंकता है NullPointerException। मैं इस व्यवहार को नहीं समझता, नक्शे में बिना किसी समस्या के मूल्य के रूप में अशक्त बिंदु हो सकते हैं। क्या कोई अच्छा कारण है जिसके लिए मान शून्य नहीं हो सकते Collectors.toMap?

इसके अलावा, क्या इसे ठीक करने का एक अच्छा जावा 8 तरीका है, या क्या मुझे लूप के लिए सादे पुराने पर वापस जाना चाहिए?

मेरी समस्या का एक उदाहरण:

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)

यह समस्या अभी भी जावा 11 में मौजूद है।


5
nullहमेशा की तरह थोड़ा सा समस्याग्रस्त था, जैसे कि TreeMap। शायद एक अच्छा पल बाहर की कोशिश करने के लिए Optional<Boolean>? अन्यथा विभाजन और फ़िल्टर का उपयोग करें।
जोप इगेनन

5
@JoopEggen nullएक कुंजी के लिए एक समस्या हो सकती है, लेकिन इस मामले में यह मूल्य है।
जोंट g

नहीं सभी नक्शे के साथ समस्या है null, HashMapउदाहरण के लिए एक हो सकता है nullकुंजी और के किसी भी संख्या nullमूल्यों, आप एक कस्टम बनाने की कोशिश कर सकते Collectorका उपयोग कर एक HashMapके बजाय डिफ़ॉल्ट एक का उपयोग कर।
काजाक्स

2
@kajacx लेकिन डिफ़ॉल्ट कार्यान्वयन है HashMap- जैसा कि स्टैकट्रेस की पहली पंक्ति में दिखाया गया है। समस्या यह नहीं है कि मूल्य Mapनहीं रखा जा सकता है null, लेकिन यह कि Map#mergeफ़ंक्शन का दूसरा तर्क शून्य नहीं हो सकता है।
czerny

व्यक्तिगत रूप से, दिए गए परिस्थितियों के साथ, मैं गैर-धारा समाधान, या forEach () के साथ जाऊंगा यदि इनपुट समानांतर है। नीचे दिए गए अच्छे शॉर्ट स्ट्रीम आधारित समाधान एक भयानक प्रदर्शन हो सकते हैं।
ओंद्रा Oižka

जवाबों:


301

आप इस के साथ 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

( इस ट्यूटोरियल ने मुझे सबसे अधिक मदद की।)


3
@ जैगर हाँ, एक आपूर्तिकर्ता की परिभाषा (पहला तर्क) एक ऐसा फ़ंक्शन है जो कोई मापदंडों को पारित नहीं करता है और एक परिणाम देता है, इस प्रकार आपके मामले के लिए लंबर () -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER)एक केस असंवेदनशील Stringकुंजी बनाने के लिए होगा TreeMap
ब्रेट रायन

2
यह सही उत्तर है, और आईएमएचओ इसके बजाय अपने डिफ़ॉल्ट गैर-अतिभारित संस्करण के लिए जेडीके को क्या करना चाहिए। शायद मर्ज और तेज हो गया है, लेकिन मैंने परीक्षण नहीं किया है।
ब्रेट रायन

1
मुझे संकलन करने के लिए टाइप पैरामीटर निर्दिष्ट करना था, इस तरह से Map<Integer, Boolean> collect = list.stream().collect(HashMap<Integer, Boolean>::new, (m,v)->m.put(v.getId(), v.getAnswer()), HashMap<Integer, Boolean>::putAll);:। मेरे पास था:incompatible types: cannot infer type-variable(s) R (argument mismatch; invalid method reference no suitable method found for putAll(java.util.Map<java.lang.Integer,java.lang.Boolean>,java.util.Map<java.lang.Integer,java.lang.Boolean>) method java.util.Map.putAll(java.util.Map) is not applicable (actual and formal argument lists differ in length)
एंथनी ओ।

2
यह एक बड़े इनपुट पर काफी धीमा हो सकता है। आप एक बनाएँ HashMapऔर फिर putAll()हर एक प्रविष्टि के लिए कॉल करें । व्यक्तिगत रूप से, दी गई परिस्थितियों में, मैं गैर-स्ट्रीम समाधान के साथ जाऊंगा, या forEach()यदि इनपुट समानांतर है।
ओंद्रा kaižka

3
खबर है कि यह समाधान मूल toMap कार्यान्वयन की तुलना में अलग तरह से व्यवहार करता है। मूल कार्यान्वयन डुप्लिकेट कुंजियों का पता लगाता है और एक IllegalStatException को फेंकता है, लेकिन यह समाधान चुपचाप नवीनतम कुंजी को स्वीकार करता है। इमैनुएल टोज़री का समाधान ( stackoverflow.com/a/32648397/471214 ) मूल व्यवहार के करीब है।
mmdemirbas

174

के स्थैतिक तरीकों से यह संभव नहीं है Collectors। इस पर आधारित है toMapकि javadoc की व्याख्या करता toMapहै Map.merge:

@ अपरम मर्जफंक्शन एक मर्ज फ़ंक्शन है, जिसका उपयोग उसी कुंजी से जुड़े मूल्यों के बीच टकराव को हल करने के लिए किया जाता है, जैसा कि आपूर्ति किया जाता है Map#merge(Object, Object, BiFunction)}

और Map.mergeकहते हैं:

@throws NullPointerException यदि निर्दिष्ट कुंजी रिक्त है और इस नक्शे अशक्त कुंजी का समर्थन नहीं करता या मूल्य या remappingFunction है अशक्त

आप forEachअपनी सूची की विधि का उपयोग करके लूप के लिए बच सकते हैं ।

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());
}

3
उस मामले में मैं बल्कि पुराने जमाने के प्रत्येक का उपयोग करूंगा। मैं इस inMerge में एक बग पर विचार करना चाहिए? के रूप में इस मर्ज फ़ंक्शन का उपयोग वास्तव में एक कार्यान्वयन विवरण है, या शून्य मानों को संसाधित करने के लिए इनहाउस को अनुमति नहीं देने का एक अच्छा तर्क है?
जैस्पर

6
यह मर्ज के जेवाडॉक में निर्दिष्ट है, लेकिन इसे टॉक ऑफ द डॉक में नहीं बताया गया है
जैस्पर

119
कभी नहीं सोचा था कि नक्शे में अशक्त मूल्य मानक एपीआई पर ऐसा प्रभाव डालेंगे, मैं इसे एक दोष के रूप में मानता हूं।
अस्कर कल्योकोव

16
वास्तव में एपीआई डॉक्स के उपयोग के बारे में कुछ भी नहीं बताता है Map.merge। यह IMHO कार्यान्वयन में एक दोष है जो एक बिल्कुल स्वीकार्य उपयोग-मामले को प्रतिबंधित करता है जिसे अनदेखा कर दिया गया है। ओवरलोड तरीकों से toMapराज्य का उपयोग होता है, Map.mergeलेकिन ओपी का उपयोग नहीं किया जाता है।
ब्रेट रायन

11
@ जैस्पर बग रिपोर्ट भी है बगसोपेनजडक। Java.net/browse/JDK-8148463
पिक्सेल

23

मैंने एक लिखा है Collector, डिफ़ॉल्ट जावा एक के विपरीत, आपके पास 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()इस फ़ंक्शन के लिए अपने कॉल को कॉल में बदलें और यह समस्या को ठीक कर देगा।


1
लेकिन nullमूल्यों को अनुमति देना और उपयोग करना putIfAbsentएक साथ अच्छा नहीं खेलता है। यह डुप्लिकेट चाबी पता नहीं लगा पाया है, जब वे करने के लिए नक्शे null...
होल्गर

10

हां, मुझसे एक देर से जवाब, लेकिन मुझे लगता है कि यह समझने में मदद मिल सकती है कि हुड के तहत क्या हो रहा है अगर कोई भी किसी अन्य- Collectorक्लॉजिक को कोड करना चाहता है ।

मैंने अधिक मूल और सीधे आगे के दृष्टिकोण को कोड करके समस्या को हल करने की कोशिश की। मुझे लगता है कि यह यथासंभव प्रत्यक्ष है:

public class LambdaUtilities {

  /**
   * In contrast to {@link Collectors#toMap(Function, Function)} the result map
   * may have null values.
   */
  public static <T, K, U, M extends Map<K, U>> Collector<T, M, M> toMapWithNullValues(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper) {
    return toMapWithNullValues(keyMapper, valueMapper, HashMap::new);
  }

  /**
   * In contrast to {@link Collectors#toMap(Function, Function, BinaryOperator, Supplier)}
   * the result map may have null values.
   */
  public static <T, K, U, M extends Map<K, U>> Collector<T, M, M> toMapWithNullValues(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, Supplier<Map<K, U>> supplier) {
    return new Collector<T, M, M>() {

      @Override
      public Supplier<M> supplier() {
        return () -> {
          @SuppressWarnings("unchecked")
          M map = (M) supplier.get();
          return map;
        };
      }

      @Override
      public BiConsumer<M, T> accumulator() {
        return (map, element) -> {
          K key = keyMapper.apply(element);
          if (map.containsKey(key)) {
            throw new IllegalStateException("Duplicate key " + key);
          }
          map.put(key, valueMapper.apply(element));
        };
      }

      @Override
      public BinaryOperator<M> combiner() {
        return (left, right) -> {
          int total = left.size() + right.size();
          left.putAll(right);
          if (left.size() < total) {
            throw new IllegalStateException("Duplicate key(s)");
          }
          return left;
        };
      }

      @Override
      public Function<M, M> finisher() {
        return Function.identity();
      }

      @Override
      public Set<Collector.Characteristics> characteristics() {
        return Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH));
      }

    };
  }

}

और JUnit और मुखर का उपयोग कर परीक्षण:

  @Test
  public void testToMapWithNullValues() throws Exception {
    Map<Integer, Integer> result = Stream.of(1, 2, 3)
        .collect(LambdaUtilities.toMapWithNullValues(Function.identity(), x -> x % 2 == 1 ? x : null));

    assertThat(result)
        .isExactlyInstanceOf(HashMap.class)
        .hasSize(3)
        .containsEntry(1, 1)
        .containsEntry(2, null)
        .containsEntry(3, 3);
  }

  @Test
  public void testToMapWithNullValuesWithSupplier() throws Exception {
    Map<Integer, Integer> result = Stream.of(1, 2, 3)
        .collect(LambdaUtilities.toMapWithNullValues(Function.identity(), x -> x % 2 == 1 ? x : null, LinkedHashMap::new));

    assertThat(result)
        .isExactlyInstanceOf(LinkedHashMap.class)
        .hasSize(3)
        .containsEntry(1, 1)
        .containsEntry(2, null)
        .containsEntry(3, 3);
  }

  @Test
  public void testToMapWithNullValuesDuplicate() throws Exception {
    assertThatThrownBy(() -> Stream.of(1, 2, 3, 1)
        .collect(LambdaUtilities.toMapWithNullValues(Function.identity(), x -> x % 2 == 1 ? x : null)))
            .isExactlyInstanceOf(IllegalStateException.class)
            .hasMessage("Duplicate key 1");
  }

  @Test
  public void testToMapWithNullValuesParallel() throws Exception {
    Map<Integer, Integer> result = Stream.of(1, 2, 3)
        .parallel() // this causes .combiner() to be called
        .collect(LambdaUtilities.toMapWithNullValues(Function.identity(), x -> x % 2 == 1 ? x : null));

    assertThat(result)
        .isExactlyInstanceOf(HashMap.class)
        .hasSize(3)
        .containsEntry(1, 1)
        .containsEntry(2, null)
        .containsEntry(3, 3);
  }

  @Test
  public void testToMapWithNullValuesParallelWithDuplicates() throws Exception {
    assertThatThrownBy(() -> Stream.of(1, 2, 3, 1, 2, 3)
        .parallel() // this causes .combiner() to be called
        .collect(LambdaUtilities.toMapWithNullValues(Function.identity(), x -> x % 2 == 1 ? x : null)))
            .isExactlyInstanceOf(IllegalStateException.class)
            .hasCauseExactlyInstanceOf(IllegalStateException.class)
            .hasStackTraceContaining("Duplicate key");
  }

और आप इसका उपयोग कैसे करते हैं? ठीक है, बस toMap()परीक्षणों की तरह दिखाने के बजाय इसका उपयोग करें । यह कॉलिंग कोड को यथासंभव स्वच्छ बनाता है।

EDIT:
नीचे दिए गए होल्गर के विचार को लागू किया, एक परीक्षण विधि जोड़ी


1
कंबाइनर डुप्लिकेट कुंजियों की जांच नहीं करता है। यदि आप हर कुंजी की जाँच करने से बचना चाहते हैं, तो आप कुछ उपयोग कर सकते हैं जैसे(map1, map2) -> { int total = map1.size() + map2.size(); map1.putAll(map2); if(map1.size() < total.size()) throw new IllegalStateException("Duplicate key(s)"); return map1; }
Holger

@ हाँगर हाँ, यह सच है। खासकर जब से वह accumulator()वास्तव में जाँच करता है। शायद मुझे एक बार कुछ समानांतर धाराएँ करनी चाहिए :)
sjngm

7

यहाँ @EmmanuelTouzery द्वारा प्रस्तावित की तुलना में कुछ सरल कलेक्टर है। पसंद आने पर इसका प्रयोग करें:

public static <T, K, U> Collector<T, ?, Map<K, U>> toMapNullFriendly(
        Function<? super T, ? extends K> keyMapper,
        Function<? super T, ? extends U> valueMapper) {
    @SuppressWarnings("unchecked")
    U none = (U) new Object();
    return Collectors.collectingAndThen(
            Collectors.<T, K, U> toMap(keyMapper,
                    valueMapper.andThen(v -> v == null ? none : v)), map -> {
                map.replaceAll((k, v) -> v == none ? null : v);
                return map;
            });
}

हम सिर्फ nullकुछ कस्टम ऑब्जेक्ट के साथ प्रतिस्थापित noneकरते हैं और फिनिशर में रिवर्स ऑपरेशन करते हैं।


5

यदि मान एक स्ट्रिंग है, तो यह काम कर सकता है: map.entrySet().stream().collect(Collectors.toMap(e -> e.getKey(), e -> Optional.ofNullable(e.getValue()).orElse("")))


4
यह तभी काम करता है जब आप डेटा को संशोधित करने के साथ ठीक हों। डाउनस्ट्रीम विधियाँ रिक्त तारों के बजाय अशक्त मूल्यों की अपेक्षा कर सकती हैं।
सैम बुचमिलर

3

के मुताबिक Stacktrace

Exception in thread "main" java.lang.NullPointerException
at java.util.HashMap.merge(HashMap.java:1216)
at java.util.stream.Collectors.lambda$toMap$148(Collectors.java:1320)
at java.util.stream.Collectors$$Lambda$5/391359742.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 com.guice.Main.main(Main.java:28)
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)

जब कहा जाता है map.merge

        BiConsumer<M, T> accumulator
            = (map, element) -> map.merge(keyMapper.apply(element),
                                          valueMapper.apply(element), mergeFunction);

यह nullपहली बात के रूप में एक जाँच करेगा

if (value == null)
    throw new NullPointerException();

मैं जावा 8 का इतनी बार उपयोग नहीं करता, इसलिए मुझे नहीं पता कि इसे ठीक करने का कोई बेहतर तरीका है, लेकिन इसे ठीक करना थोड़ा कठिन है।

तुम यह कर सकते थे:

सभी NULL मानों को फ़िल्टर करने के लिए फ़िल्टर का उपयोग करें, और जावास्क्रिप्ट कोड की जाँच में यदि सर्वर ने इस आईडी के लिए कोई उत्तर नहीं भेजा है तो इसका मतलब है कि उसने इसका उत्तर नहीं दिया है।

कुछ इस तरह:

Map<Integer, Boolean> answerMap =
        answerList
                .stream()
                .filter((a) -> a.getAnswer() != null)
                .collect(Collectors.toMap(Answer::getId, Answer::getAnswer));

या झांकना उपयोग करें, जिसका उपयोग तत्व के लिए धारा तत्व को बदलने के लिए किया जाता है। झांकना का उपयोग करके आप नक्शे के लिए कुछ अधिक स्वीकार्य उत्तर बदल सकते हैं लेकिन इसका मतलब है कि अपने तर्क को थोड़ा संपादित करें।

लगता है कि यदि आप वर्तमान डिजाइन को बनाए रखना चाहते हैं तो आपको बचना चाहिए Collectors.toMap


3

मैंने इमैनुअल टाउज़री के कार्यान्वयन को थोड़ा संशोधित किया है

यह संस्करण;

  • अशक्त कुंजियों की अनुमति देता है
  • अशक्त मूल्यों की अनुमति देता है
  • डुप्लिकेट कुंजियों का पता लगाता है (भले ही वे अशक्त हों) और अवैध JDK कार्यान्वयन के रूप में IllegalStateException को फेंकता है।
  • डुप्लिकेट कुंजियों का भी पता लगाता है जब कुंजी पहले से ही शून्य मान पर मैप की जाती है। दूसरे शब्दों में, एक मानचित्रण को बिना मानचित्रण के शून्य मान से अलग करता है।
public static <T, K, U> Collector<T, ?, Map<K, U>> toMapOfNullables(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper) {
    return Collectors.collectingAndThen(
        Collectors.toList(),
        list -> {
            Map<K, U> map = new LinkedHashMap<>();
            list.forEach(item -> {
                K key = keyMapper.apply(item);
                if (map.containsKey(key)) {
                    throw new IllegalStateException(String.format("Duplicate key %s", key));
                }
                map.put(key, valueMapper.apply(item));
            });
            return map;
        }
    );
}

इकाई परीक्षण:

@Test
public void toMapOfNullables_WhenHasNullKey() {
    assertEquals(singletonMap(null, "value"),
        Stream.of("ignored").collect(Utils.toMapOfNullables(i -> null, i -> "value"))
    );
}

@Test
public void toMapOfNullables_WhenHasNullValue() {
    assertEquals(singletonMap("key", null),
        Stream.of("ignored").collect(Utils.toMapOfNullables(i -> "key", i -> null))
    );
}

@Test
public void toMapOfNullables_WhenHasDuplicateNullKeys() {
    assertThrows(new IllegalStateException("Duplicate key null"),
        () -> Stream.of(1, 2, 3).collect(Utils.toMapOfNullables(i -> null, i -> i))
    );
}

@Test
public void toMapOfNullables_WhenHasDuplicateKeys_NoneHasNullValue() {
    assertThrows(new IllegalStateException("Duplicate key duplicated-key"),
        () -> Stream.of(1, 2, 3).collect(Utils.toMapOfNullables(i -> "duplicated-key", i -> i))
    );
}

@Test
public void toMapOfNullables_WhenHasDuplicateKeys_OneHasNullValue() {
    assertThrows(new IllegalStateException("Duplicate key duplicated-key"),
        () -> Stream.of(1, null, 3).collect(Utils.toMapOfNullables(i -> "duplicated-key", i -> i))
    );
}

@Test
public void toMapOfNullables_WhenHasDuplicateKeys_AllHasNullValue() {
    assertThrows(new IllegalStateException("Duplicate key duplicated-key"),
        () -> Stream.of(null, null, null).collect(Utils.toMapOfNullables(i -> "duplicated-key", i -> i))
    );
}

1

एक पुराने प्रश्न को फिर से खोलने के लिए क्षमा करें, लेकिन चूंकि हाल ही में यह कहते हुए संपादित किया गया था कि "मुद्दा" अभी भी जावा 11 में बना हुआ है, मुझे ऐसा लगा जैसे मैं इसे इंगित करना चाहता था:

answerList
        .stream()
        .collect(Collectors.toMap(Answer::getId, Answer::getAnswer));

आपको अशक्त सूचक अपवाद देता है क्योंकि मानचित्र शून्य को मान के रूप में अनुमति नहीं देता है। यह समझ में आता है क्योंकि यदि आप कुंजी के लिए एक मानचित्र देखते हैं kऔर यह मौजूद नहीं है, तो लौटा मूल्य पहले से ही है null(देखें javadoc)। इसलिए यदि आप kमूल्य में सक्षम थे null, तो नक्शा ऐसा लगेगा जैसे यह अजीब व्यवहार कर रहा हो।

जैसा कि किसी ने टिप्पणी में कहा है, फ़िल्टरिंग का उपयोग करके इसे हल करना बहुत आसान है:

answerList
        .stream()
        .filter(a -> a.getAnswer() != null)
        .collect(Collectors.toMap(Answer::getId, Answer::getAnswer));

इस तरह nullसे मानचित्र में कोई मान नहीं डाला जाएगा, और nullजब भी मानचित्र में उत्तर नहीं होगा, तब आपको "मूल्य" के रूप में "मूल्य" मिलेगा ।

मुझे उम्मीद है कि यह सभी के लिए समझ में आता है।


1
यह समझ में आता है कि अगर कोई मानचित्र अशक्त मूल्यों की अनुमति नहीं देता है, लेकिन यह करता है। आप answerMap.put(4, null);बिना किसी समस्या के कर सकते हैं । आप सही हैं कि आपके प्रस्तावित समाधान से आपको anserMap.get () के लिए एक ही परिणाम मिलेगा यदि यह मौजूद नहीं है जैसे कि मान शून्य के रूप में डाला जाएगा। हालाँकि, यदि आप नक्शे की सभी प्रविष्टियों पर पुनरावृति करते हैं तो स्पष्ट रूप से अंतर है।
जैस्पर

1
public static <T, K, V> Collector<T, HashMap<K, V>, HashMap<K, V>> toHashMap(
        Function<? super T, ? extends K> keyMapper,
        Function<? super T, ? extends V> valueMapper
)
{
    return Collector.of(
            HashMap::new,
            (map, t) -> map.put(keyMapper.apply(t), valueMapper.apply(t)),
            (map1, map2) -> {
                map1.putAll(map2);
                return map1;
            }
    );
}

public static <T, K> Collector<T, HashMap<K, T>, HashMap<K, T>> toHashMap(
        Function<? super T, ? extends K> keyMapper
)
{
    return toHashMap(keyMapper, Function.identity());
}

1
upvoting क्योंकि यह संकलन करता है। स्वीकृत उत्तर संकलित नहीं करता है क्योंकि मानचित्र :: putAll में कोई वापसी मान नहीं है।
ताओगेनिचट्स

0

छोटे ट्वीक के साथ सभी प्रश्न आईडी को बनाए रखना

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

मुझे लगता है कि यह सबसे अच्छा जवाब है - यह सबसे संक्षिप्त जवाब है और यह एनपीई मुद्दे को ठीक करता है।
LConrad

-3

NullPointerException अब तक सबसे अधिक बार सामना किए गए अपवाद (कम से कम मेरे मामले में) है। इससे बचने के लिए मैं रक्षात्मक हो जाता हूं और अशक्त जांचों का गुच्छा जोड़ता हूं और मैं फूला हुआ और बदसूरत कोड समाप्त करता हूं। जावा 8 अशक्त संदर्भों को संभालने के लिए वैकल्पिक परिचय देता है ताकि आप अशक्त और गैर-अशक्त मूल्यों को परिभाषित कर सकें।

उस ने कहा, मैं वैकल्पिक कंटेनर में सभी अशक्त संदर्भों को लपेटूंगा। हमें पिछड़ी अनुकूलता को भी नहीं तोड़ना चाहिए। यहाँ कोड है।

class Answer {
    private int id;
    private Optional<Boolean> answer;

    Answer() {
    }

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

    public int getId() {
        return id;
    }

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

    /**
     * Gets the answer which can be a null value. Use {@link #getAnswerAsOptional()} instead.
     *
     * @return the answer which can be a null value
     */
    public Boolean getAnswer() {
        // What should be the default value? If we return null the callers will be at higher risk of having NPE
        return answer.orElse(null);
    }

    /**
     * Gets the optional answer.
     *
     * @return the answer which is contained in {@code Optional}.
     */
    public Optional<Boolean> getAnswerAsOptional() {
        return answer;
    }

    /**
     * Gets the answer or the supplied default value.
     *
     * @return the answer or the supplied default value.
     */
    public boolean getAnswerOrDefault(boolean defaultValue) {
        return answer.orElse(defaultValue);
    }

    public void setAnswer(Boolean answer) {
        this.answer = Optional.ofNullable(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 with optional answers (i.e. with null)
        Map<Integer, Optional<Boolean>> answerMapWithOptionals = answerList.stream()
                .collect(Collectors.toMap(Answer::getId, Answer::getAnswerAsOptional));

        // map in which null values are removed
        Map<Integer, Boolean> answerMapWithoutNulls = answerList.stream()
                .filter(a -> a.getAnswerAsOptional().isPresent())
                .collect(Collectors.toMap(Answer::getId, Answer::getAnswer));

        // map in which null values are treated as false by default
        Map<Integer, Boolean> answerMapWithDefaults = answerList.stream()
                .collect(Collectors.toMap(a -> a.getId(), a -> a.getAnswerOrDefault(false)));

        System.out.println("With Optional: " + answerMapWithOptionals);
        System.out.println("Without Nulls: " + answerMapWithoutNulls);
        System.out.println("Wit Defaults: " + answerMapWithDefaults);
    }
}

1
बेकार उत्तर, आपको इसे ठीक करने के लिए अशक्त क्यों होना चाहिए? यह Collectors.toMap()अशक्त मूल्यों की समस्या नहीं है
Enerccio

@Enerccio शांत हो जाओ दोस्त !! अशक्त मूल्यों पर भरोसा करना एक अच्छा अभ्यास नहीं है। यदि आपने वैकल्पिक का उपयोग किया है, तो आपको पहले स्थान पर एनपीई का सामना नहीं करना पड़ेगा। वैकल्पिक usages पर पढ़ें।
त्रिकोणीय

1
और यही वजह है कि? अशक्त मूल्य ठीक है, यह अनिर्दिष्ट पुस्तकालय है जो समस्या है। वैकल्पिक अच्छा है, लेकिन हर जगह नहीं है।
Enerccio
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.