Collectors.toMap'te Java 8 NullPointerException


331

Java 8 , değerlerden biri 'null' ise bir Collectors.toMapatar NullPointerException. Bu davranışı anlamıyorum, haritalar değer olarak boş göstericiler içerebilir sorunsuz. Değerlerin boş kalmamasının iyi bir nedeni var mı Collectors.toMap?

Ayrıca, bunu düzeltmek için güzel bir Java 8 yolu var mı, yoksa döngü için düz eski haline dönmeli miyim?

Sorunuma bir örnek:

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

Yığın izleme:

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)

Bu sorun hala Java 11'de bulunmaktadır.


5
nullTreeMap'teki gibi her zaman biraz sorunluydu. Belki denemek için güzel bir an Optional<Boolean>? Aksi takdirde ayırın ve filtre kullanın.
Joop Eggen

5
@ JoopEggen nullbir anahtar için sorun olabilir, ancak bu durumda değerdir.
gontard

Tüm haritalar ile sorun var null, HashMapbiri olabilir örnek için nullanahtar ve herhangi bir sayıda nulldeğerlerinin, bir özel oluşturmayı deneyebilirsiniz Collectorkullanarak HashMapyerine varsayılan birini kullanarak.
kajacx

2
@kajacx Ancak varsayılan uygulama HashMap- stacktrace öğesinin ilk satırında gösterildiği gibidir. Sorun, bir değeri Maptutamayacağı değil null, Map#mergefonksiyonun ikinci argümanının boş olamayacağıdır.
czerny

Şahsen, verilen koşullar altında, akış paralel olmayan çözümle veya giriş paralelse forEach () ile giderdim. Aşağıdaki güzel kısa akış tabanlı çözümler korkunç bir performansa sahip olabilir.
Ondra 30ižka

Yanıtlar:


302

OpenJDK'da bilinen bu hatayı bu yöntemle çalışabilirsiniz :

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

O kadar güzel değil, ama işe yarıyor. Sonuç:

1: true
2: true
3: null

( bu eğitim bana en çok yardımcı oldu.)


3
@ Jagger evet, bir tedarikçinin tanımı (ilk argüman) hiçbir parametre iletmeyen ve bir sonuç döndüren bir işlevdir, bu nedenle davanız için lambda, anahtarsız () -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER)bir büyük / küçük harf duyarsız Stringanahtar oluşturmak olacaktır TreeMap.
Brett Ryan

2
Bu doğru yanıttır ve IMKO, JDK'nın aşırı yüklenmemiş sürümü için ne yapması gerektiğini IMHO'dur. Belki birleşme daha hızlıdır, ama test etmedim.
Brett Ryan

1
Ben, bu şekilde derlemek amacıyla tip parametrelerini belirlemek zorundaydı: Map<Integer, Boolean> collect = list.stream().collect(HashMap<Integer, Boolean>::new, (m,v)->m.put(v.getId(), v.getAnswer()), HashMap<Integer, Boolean>::putAll);. Ben vardı: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)
Anthony O.

2
Bu, büyük bir girdi için oldukça yavaş olabilir. Bir oluşturursunuz HashMapve daha sonra putAll()her giriş için çağrı yaparsınız . Şahsen, belirli koşullarda, akış olmayan çözümle veya forEach()girdi paralelse giderdim .
Ondra Novižka

3
Bu çözümün orijinal toMap uygulamasından farklı davrandığına dikkat edin. Orijinal uygulama, yinelenen anahtarları algılar ve bir IllegalStatException özel durumu atar, ancak bu çözüm en son anahtarı sessizce kabul eder. Emmanuel Touzery'nin çözümü ( stackoverflow.com/a/32648397/471214 ) orijinal davranışa daha yakındır.
mmdemirbas

174

Statik yöntemlerle mümkün değildir Collectors. Javadoc, aşağıdakilere dayandığını toMapaçıklar :toMapMap.merge

@param mergeAşağıdakiyle aynı anahtarla ilişkili değerler arasındaki çakışmaları çözmek için kullanılan bir birleştirme işlevi işlevi Map#merge(Object, Object, BiFunction)}

ve javadoc Map.mergeder ki:

@throws Belirtilen anahtar null ise ve bu eşleme null anahtarları desteklemiyorsa veya değer veya remapping işlevi null ise NullPointerException

forEachListenizin yöntemini kullanarak for döngüsünü önleyebilirsiniz .

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

ama eski yoldan çok basit değil:

Map<Integer, Boolean> answerMap = new HashMap<>();
for (Answer answer : answerList) {
    answerMap.put(answer.getId(), answer.getAnswer());
}

3
Bu durumda, her biri için eski moda kullanmayı tercih ederim. Bunu toMerge'de bir hata olarak düşünmeli miyim? Bu birleştirme işlevinin kullanımı gerçekten bir uygulama ayrıntısı mıdır, yoksa toMap'in null değerleri işlemesine izin vermemek için iyi bir neden mi?
Jasper

6
Birleştirme javadosunda belirtilir, ancak toMap
Jasper

119
Haritadaki null değerlerin standart API üzerinde böyle bir etki yapacağını hiç düşünmemiştim, bunu bir kusur olarak görmeyi tercih ederim.
Askar Kalykov

16
Aslında API dokümanlarının kullanımı hakkında hiçbir şey belirtmez Map.merge. Bu IMHO, göz ardı edilen mükemmel kabul edilebilir bir kullanım durumunu kısıtlayan uygulamadaki bir kusurdur. Aşırı yüklenmiş yöntemler OP'nin kullandığı yöntemi değil, toMapkullanımını belirtir Map.merge.
Brett Ryan

11
@Jasper hata raporu bile var bugs.openjdk.java.net/browse/JDK-8148463
piksel

23

CollectorVarsayılan java bir aksine, nulldeğerleriniz olduğunda çökmez bir yazdı :

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

Sadece Collectors.toMap()bu fonksiyona yapılan bir çağrıyı değiştirin ve sorunu düzeltin.


1
Ancak nulldeğerlere izin vermek ve kullanmak putIfAbsentbirlikte iyi oynamıyor. Harita null
Holger

10

Evet, benden geç bir cevap, ama bence birisi başka bir Collectorşeyi kodlamak istiyorsa kaputun altında neler olduğunu anlamaya yardımcı olabilir .

Sorunu daha doğal ve doğrudan bir yaklaşımla kodlamaya çalıştım. Bence mümkün olduğunca doğrudan:

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

    };
  }

}

Ve JUnit ve assertj kullanarak yapılan testler:

  @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");
  }

Ve bunu nasıl kullanıyorsun? toMap()Testlerin gösterdiği gibi kullanmak yerine kullanmanız yeterli . Bu, arama kodunun olabildiğince temiz görünmesini sağlar.

EDIT:
Holger'in fikrini aşağıda uyguladı, bir test yöntemi ekledi


1
Birleştirici yinelenen anahtarları kontrol etmez. Her anahtarı kontrol etmekten kaçınmak istiyorsanız, gibi bir şey kullanabilirsiniz(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

@Holger Yep, bu doğru. Özellikle accumulator()aslında bunu kontrol ettiğinden beri . Belki bir kez paralel akışlar yapmalıyım :)
sjngm

7

İşte @EmmanuelTouzery tarafından önerilenden biraz daha basit bir koleksiyoncu. İsterseniz kullanın:

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

Sadece nullbazı özel nesnelerle değiştiriyoruz noneve son işlemcideki ters işlemi yapıyoruz.


5

Değer bir String ise, bu işe yarayabilir: map.entrySet().stream().collect(Collectors.toMap(e -> e.getKey(), e -> Optional.ofNullable(e.getValue()).orElse("")))


4
Bu, yalnızca verileri değiştirme konusunda sorun yaşıyorsanız işe yarar. Aşağı akım yöntemleri, boş dizeler yerine null değerler bekleyebilir.
Sam Buchmiller

3

Göre 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)

Ne zaman denir map.merge

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

İlk nullolarak bir çek yapacak

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

Java 8'i çok sık kullanmıyorum, bu yüzden düzeltmenin daha iyi bir yolu olup olmadığını bilmiyorum, ancak düzeltmek biraz zor.

Şunları yapabilirsiniz:

Tüm NULL değerleri filtrelemek için filtre kullanın ve Javascript kodunda, sunucunun bu kimlik için herhangi bir yanıt göndermediğini kontrol edin, yanıt vermediği anlamına gelir.

Bunun gibi bir şey:

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

Veya öğe için akış öğesini değiştirmek için kullanılan peek'i kullanın. Peek kullanarak cevabı harita için daha kabul edilebilir bir şeyle değiştirebilirsiniz, ancak bu mantığınızı biraz düzenlemek anlamına gelir.

Mevcut tasarımı korumak istiyorsanız kaçınmalısınız Collectors.toMap


3

Emmanuel Touzery'nin uygulamasını biraz değiştirdim .

Bu versiyon;

  • Boş anahtarlara izin verir
  • Boş değerlere izin verir
  • Yinelenen anahtarları (boş olsalar bile) algılar ve IllegalStateException özgün JDK uygulamasında olduğu gibi atar.
  • Anahtar zaten boş değere eşlendiğinde de yinelenen anahtarları algılar. Başka bir deyişle, null değeri olan bir eşlemeyi eşleme yapmadan ayırır.
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;
        }
    );
}

Birim testleri:

@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

Eski bir soruyu yeniden açtığım için üzgünüm, ancak yakın zamanda düzenlendiği için "sorun" hala Java 11'de kaldı, bunu belirtmek istedim gibi hissettim:

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

harita bir değer olarak null değerine izin vermediği için size boş gösterici istisnası verir. Bu mantıklıdır, çünkü anahtar için bir haritaya bakarsanız kve mevcut değilse, döndürülen değer zaten olur null(bkz. Javadoc). Eğer kdeğeri nullkoyabilseydiniz, harita tuhaf davranıyormuş gibi görünecektir.

Birinin yorumlarda söylediği gibi, filtreleme kullanarak bunu çözmek oldukça kolaydır:

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

bu şekilde nullharitaya hiçbir değer eklenmez ve STILL null, haritada cevabı olmayan bir kimlik ararken "değer" olarak alırsınız .

Umarım bu herkes için anlamlıdır.


1
Bir haritanın null değerlere izin vermemesi mantıklı olurdu, ancak izin verir. Sorunsuz bir şekilde yapabilirsiniz answerMap.put(4, null);. Önerilen çözümünüzle, değer null olarak eklenecekmiş gibi yoksa anserMap.get () için aynı sonucu elde edersiniz. Ancak, haritanın tüm girişlerini tekrarlarsanız, açıkça bir fark vardır.
Jasper

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
çünkü bu derleme. Map :: putAll öğesinin dönüş değeri olmadığından kabul edilen yanıt derlenmez.
Taugenichts

0

Tüm soru kimliklerini küçük ayarlarla tutma

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

Bence bu en iyi cevap - en özlü cevap ve NPE sorununu çözüyor.
LConrad

-3

NullPointerException açık ara en sık karşılaşılan istisnadır (en azından benim durumumda). Bunu önlemek için defansif gidip bir sürü boş kontrol ekledim ve sonunda şişkin ve çirkin bir kod aldım. Java 8, boş değerli ve boş olmayan değerleri tanımlayabilmeniz için boş başvuruları işlemek için İsteğe bağlı özelliğini sunar.

Bununla birlikte, tüm nullable referansları İsteğe bağlı kapsayıcıya sararım dedi. Ayrıca geriye dönük uyumluluğu da kırmamalıyız. İşte kod.

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
yararsız cevap, bunu düzeltmek için null neden kurtulmak gerekir? Bu Collectors.toMap()null değer değil sorunu
Enerccio

@Enerccio dostum sakin ol !! Boş değerlere güvenmek iyi bir uygulama değildir. İsteğe Bağlı'yı kullandıysanız, ilk etapta NPE ile karşılaşmazdınız. İsteğe bağlı kullanımlarla ilgili bilgi edinin.
TriCore

1
ve neden böyle? Boş değer gayet iyi, sorun olmayan belgelenmemiş kütüphane. İsteğe bağlı güzel ama her yerde değil.
Enerccio
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.