Java 8 NullPulumException trong Collector.toMap


330

Java 8 Collectors.toMapném một NullPointerExceptionnếu một trong các giá trị là 'null'. Tôi không hiểu hành vi này, bản đồ có thể chứa con trỏ null là giá trị mà không có bất kỳ vấn đề nào. Có một lý do chính đáng tại sao các giá trị không thể là null cho Collectors.toMap?

Ngoài ra, có một cách tốt để sửa lỗi Java 8 này hay tôi nên trở lại đơn giản cho vòng lặp cũ?

Một ví dụ về vấn đề của tôi:

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

Ngăn xếp:

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)

Vấn đề này vẫn tồn tại trong Java 11.


5
nullluôn luôn có một chút vấn đề, như trong TreeMap. Có lẽ một khoảnh khắc đẹp để thử Optional<Boolean>? Nếu không thì tách và sử dụng bộ lọc.
Eggen

5
@JoopEggen nullcó thể là một vấn đề đối với khóa, nhưng trong trường hợp này là giá trị.
Gontard

Không phải tất cả các bản đồ đều có vấn đề với null, HashMapví dụ có thể có một nullkhóa và bất kỳ số lượng nullgiá trị nào, bạn có thể thử tạo một tùy chỉnh Collectorbằng cách sử dụng HashMapthay vì sử dụng một mặc định.
kajacx

2
@kajacx Nhưng việc triển khai mặc định là HashMap- như được hiển thị trong dòng stacktrace đầu tiên. Vấn đề không phải là giá trị Mapkhông thể giữ null, mà là đối số thứ hai của Map#mergehàm không thể rỗng.
czerny

Cá nhân, với các trường hợp cụ thể, tôi sẽ đi với giải pháp không phát trực tuyến hoặc forEach () nếu đầu vào là song song. Các giải pháp dựa trên luồng ngắn đẹp dưới đây có thể có một hiệu suất khủng khiếp.
Ondra Žižka

Câu trả lời:


301

Bạn có thể khắc phục lỗi đã biết này trong OpenJDK với điều này:

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

Nó không phải là nhiều đẹp, nhưng nó hoạt động. Kết quả:

1: true
2: true
3: null

( hướng dẫn này đã giúp tôi nhiều nhất.)


3
@Jagger có, định nghĩa của nhà cung cấp (đối số đầu tiên) là hàm không có tham số và trả về kết quả, do đó lambda cho trường hợp của bạn sẽ () -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER)tạo trường hợp Stringkhóa không nhạy TreeMap.
Brett Ryan

2
Đây là câu trả lời đúng và IMHO những gì JDK nên làm cho phiên bản không quá tải mặc định của nó. Có thể hợp nhất là nhanh hơn, nhưng tôi chưa thử nghiệm.
Brett Ryan

1
Tôi đã phải xác định các tham số loại để biên dịch, theo cách đó : Map<Integer, Boolean> collect = list.stream().collect(HashMap<Integer, Boolean>::new, (m,v)->m.put(v.getId(), v.getAnswer()), HashMap<Integer, Boolean>::putAll);. Tôi đã có: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
Điều này có thể khá chậm trên một đầu vào lớn. Bạn tạo một HashMapvà sau đó gọi putAll()cho mỗi mục duy nhất. Cá nhân, trong các trường hợp cụ thể, tôi sẽ đi với giải pháp không phát trực tuyến hoặc forEach()nếu đầu vào là song song.
Ondra Žižka

3
Coi chừng giải pháp này hoạt động khác với triển khai toMap ban đầu. Triển khai ban đầu phát hiện các khóa trùng lặp và ném IllegalStatException, nhưng giải pháp này âm thầm chấp nhận khóa mới nhất. Giải pháp của Emmanuel Touzery ( stackoverflow.com/a/32648397/471214 ) gần với hành vi ban đầu hơn.
mmdemirbas

173

Nó là không thể với các phương thức tĩnh của Collectors. Javadoc toMapgiải thích toMapdựa trên Map.merge:

@param mergeFunction một hàm hợp nhất, được sử dụng để giải quyết xung đột giữa các giá trị được liên kết với cùng một khóa, như được cung cấp cho Map#merge(Object, Object, BiFunction)}

và javadoc Map.mergenói:

@throw NullPulumException nếu khóa được chỉ định là null và bản đồ này không hỗ trợ các khóa null hoặc giá trị hoặc ánh xạ lại null

Bạn có thể tránh vòng lặp for bằng cách sử dụng forEachphương thức danh sách của bạn.

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

nhưng nó không thực sự đơn giản hơn cách cũ:

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

3
Trong trường hợp đó, tôi muốn sử dụng cho từng kiểu cũ. Tôi có nên coi đây là một lỗi trong toMerge không? vì việc sử dụng hàm hợp nhất này thực sự là một chi tiết triển khai, hay lý do chính đáng cho việc không cho phép Bản đồ xử lý các giá trị null?
Jasper

6
Nó được chỉ định trong javadoc của hợp nhất, nhưng nó không được nêu trong tài liệu của toMap
Jasper

119
Không bao giờ nghĩ rằng các giá trị null trong bản đồ sẽ tạo ra ảnh hưởng như vậy đối với API tiêu chuẩn, tôi muốn coi đó là một lỗ hổng.
Askar Kalykov

16
Trên thực tế, các tài liệu API không nêu bất cứ điều gì về việc sử dụng Map.merge. IMHO này là một lỗ hổng trong việc triển khai hạn chế trường hợp sử dụng hoàn toàn chấp nhận được đã bị bỏ qua. Các phương thức quá tải toMaplàm rõ việc sử dụng Map.mergenhưng không phải là phương thức mà OP đang sử dụng.
Brett Ryan


23

Tôi đã viết một Collectorcái, không giống như java mặc định, không bị sập khi bạn có nullcác giá trị:

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

Chỉ cần thay thế Collectors.toMap()cuộc gọi của bạn đến một cuộc gọi đến chức năng này và nó sẽ khắc phục vấn đề.


1
Nhưng cho phép nullcác giá trị và sử dụng putIfAbsentkhông chơi tốt với nhau. Nó không phát hiện trùng lặp phím khi họ lập bản đồ để null...
Holger

10

Đúng, một câu trả lời muộn từ tôi, nhưng tôi nghĩ nó có thể giúp hiểu những gì đang xảy ra dưới mui xe trong trường hợp bất cứ ai muốn mã hóa một số thông tin khác Collector.

Tôi đã cố gắng giải quyết vấn đề bằng cách mã hóa một cách tiếp cận bản địa và thẳng thắn hơn. Tôi nghĩ rằng nó càng trực tiếp càng tốt:

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

    };
  }

}

Và các bài kiểm tra sử dụng JUnit và assertj:

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

Và làm thế nào để bạn sử dụng nó? Vâng, chỉ sử dụng nó thay vì toMap()như các thử nghiệm cho thấy. Điều này làm cho mã gọi trông sạch nhất có thể.

EDIT:
thực hiện ý tưởng của Holger bên dưới, đã thêm một phương pháp thử nghiệm


1
Bộ kết hợp không kiểm tra các phím trùng lặp. Nếu bạn muốn tránh để kiểm tra mọi phím, bạn có thể sử dụng một cái gì đó như(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, đó là sự thật. Đặc biệt là vì accumulator()thực sự không kiểm tra điều đó. Có lẽ tôi nên thực hiện một số luồng song song một lần :)
sjngm 23/07/19

7

Đây là trình thu thập có phần đơn giản hơn so với đề xuất của @EmmanuelTouzery. Sử dụng nó nếu bạn thích:

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

Chúng tôi chỉ thay thế nullbằng một số đối tượng tùy chỉnh nonevà thực hiện thao tác đảo ngược trong bộ hoàn thiện.


5

Nếu giá trị là một Chuỗi, thì điều này có thể hoạt động: map.entrySet().stream().collect(Collectors.toMap(e -> e.getKey(), e -> Optional.ofNullable(e.getValue()).orElse("")))


4
Điều đó chỉ hoạt động nếu bạn ổn với việc sửa đổi dữ liệu. Các phương thức hạ lưu có thể mong đợi các giá trị null thay vì các chuỗi rỗng.
Sam Buchmiller

3

Theo 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)

Khi được gọi là map.merge

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

Nó sẽ làm một nullkiểm tra như điều đầu tiên

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

Tôi không sử dụng Java 8 thường xuyên vì vậy tôi không biết có cách nào tốt hơn để sửa nó không, nhưng sửa nó hơi khó.

Bạn có thể làm:

Sử dụng bộ lọc để lọc tất cả các giá trị NULL và trong kiểm tra mã Javascript nếu máy chủ không gửi bất kỳ câu trả lời nào cho id này có nghĩa là anh ta đã không trả lời nó.

Một cái gì đó như thế này:

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

Hoặc sử dụng peek, được sử dụng để thay đổi thành phần luồng cho phần tử. Sử dụng peek bạn có thể thay đổi câu trả lời thành thứ gì đó dễ chấp nhận hơn cho bản đồ nhưng điều đó có nghĩa là chỉnh sửa logic của bạn một chút.

Âm thanh như nếu bạn muốn giữ thiết kế hiện tại, bạn nên tránh Collectors.toMap


3

Tôi đã sửa đổi một chút việc thực hiện của Emmanuel Touzery .

Phiên bản này;

  • Cho phép khóa null
  • Cho phép giá trị null
  • Phát hiện các khóa trùng lặp (ngay cả khi chúng là null) và ném IllegalStateException như trong triển khai JDK ban đầu.
  • Cũng phát hiện các khóa trùng lặp khi khóa đã được ánh xạ tới giá trị null. Nói cách khác, tách một ánh xạ với giá trị null từ không ánh xạ.
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;
        }
    );
}

Bài kiểm tra đơn vị:

@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

Xin lỗi để mở lại một câu hỏi cũ, nhưng vì nó đã được chỉnh sửa gần đây nói rằng "vấn đề" vẫn còn trong Java 11, tôi cảm thấy như tôi muốn chỉ ra điều này:

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

cung cấp cho bạn ngoại lệ con trỏ null vì bản đồ không cho phép null làm giá trị. Điều này có ý nghĩa bởi vì nếu bạn tìm trong bản đồ cho khóa kvà nó không có mặt, thì giá trị trả về đã có sẵn null(xem javadoc). Vì vậy, nếu bạn có thể đặt kgiá trị null, bản đồ sẽ trông giống như hành vi kỳ quặc.

Như ai đó đã nói trong các bình luận, thật dễ dàng để giải quyết vấn đề này bằng cách sử dụng bộ lọc:

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

theo cách này, nullsẽ không có giá trị nào được chèn vào bản đồ và VẪN bạn sẽ nhận được nulllà "giá trị" khi tìm kiếm một id không có câu trả lời trong bản đồ.

Tôi hy vọng điều này có ý nghĩa với tất cả mọi người.


1
Sẽ có ý nghĩa nếu một bản đồ không cho phép các giá trị null, nhưng nó có. Bạn có thể làm answerMap.put(4, null);mà không có bất kỳ vấn đề. Bạn đúng rằng với giải pháp được đề xuất của bạn, bạn sẽ nhận được kết quả tương tự cho anserMap.get () nếu nó không xuất hiện như thể giá trị sẽ được chèn là null. Tuy nhiên, nếu bạn lặp đi lặp lại tất cả các mục trên bản đồ thì rõ ràng có một sự khác biệt.
Jasper

Đó là giải pháp đơn giản nhất cho trường hợp của tôi, cảm ơn bạn
ahmkara

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
nâng cao vì điều này biên dịch. Câu trả lời được chấp nhận không biên dịch vì Map :: putAll không có giá trị trả về.
Taugenichts

0

Giữ lại tất cả id câu hỏi với tinh chỉnh nhỏ

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

Tôi nghĩ rằng đây là câu trả lời tốt nhất - đó là câu trả lời ngắn gọn nhất và nó khắc phục vấn đề NPE.
LConrad

-3

NullPulumException cho đến nay là ngoại lệ thường gặp nhất (ít nhất là trong trường hợp của tôi). Để tránh điều này, tôi đi phòng thủ và thêm một loạt các kiểm tra null và cuối cùng tôi đã có mã bị lỗi và xấu xí. Java 8 giới thiệu Tùy chọn để xử lý các tham chiếu null để bạn có thể xác định các giá trị nullable và không nullable.

Điều đó nói rằng, tôi sẽ gói tất cả các tham chiếu nullable trong vùng chứa tùy chọn. Chúng ta cũng không nên phá vỡ tính tương thích ngược. Đây là mã.

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
Câu trả lời vô ích, tại sao bạn nên loại bỏ null để sửa lỗi này? Đây là vấn đề Collectors.toMap()không có giá trị null
Enerccio

@Enerccio bình tĩnh nào bạn ạ !! Dựa vào các giá trị null không phải là một thực hành tốt. Nếu bạn đã sử dụng Tùy chọn, bạn sẽ không gặp NPE ở nơi đầu tiên. Đọc lên cách sử dụng tùy chọn.
TriCore

1
và tại sao vậy? Giá trị Null là tốt, đó là thư viện không có giấy tờ đó là vấn đề. Tùy chọn là tốt đẹp nhưng không phải ở khắp mọi nơi.
Enerccio
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.