2017-05-27 21 views
5

Gần đây, khi làm việc với Java 8 suối, tôi tình cờ gặp một NullPointerException vào một hoạt động giảm trong khi làm việc với các trường hợp kiểm tra sau:NPE vào hoạt động java giảm hoạt động

private static final BinaryOperator<Integer> sum = (a, b) -> { 
    if (a == null) return b; 
    if (b == null) return a; 
    return Integer.sum(a, b); 
}; 

List<Integer> s = new ArrayList<>(); 
s.add(null); 
s.add(null); 
s.add(null); 

Integer i = s.stream().reduce(sum).orElse(null); 
// throws NPE 

Integer i = s.stream().reduce(sum).orElse(2); 
// throws NPE 

Integer i = s.stream().reduce(null,(a, b)->null); 
// returns a value i.e null 

Hoặc cách khác:

Integer i = s.stream().filter(Objects::nonNull).reduce(Integer::sum).orElse(null); 
// returns a value i.e null 

Khi kiểm tra hoạt động giảm, tôi đã xem qua lớp này thực hiện thao tác giảm:

class ReducingSink implements AccumulatingSink<T, Optional<T>, ReducingSink> { 
    private boolean empty; 
    private T state; 

    public void begin(long size) { 
     empty = true; 
     state = null; 
    } 

    @Override 
    public void accept(T t) { 
     if (empty) { 
      empty = false; 
      state = t; 
     } else { 
      state = operator.apply(state, t); 
     } 
    } 

    @Override 
    public Optional<T> get() { 
     return empty ? Optional.empty() : Optional.of(state); 
    } 

    @Override 
    public void combine(ReducingSink other) { 
     if (!other.empty) 
      accept(other.state); 
    } 
} 

Trong đoạn mã trên, bạn thấy rằng phương thức get() trả về giá trị tùy chọn nếu boolean empty là sai và trong trường hợp của tôi giá trị là false nhưng state là không, vì vậy Optional.of(null) ném một NullPointerException. Trong trường hợp của tôi, tôi có một toán tử nhị phân cho phép null.

Vì vậy, tôi nghĩ rằng mã

return empty ? Optional.empty() : Optional.of(state); 

nên được thay đổi để

return empty || state == null ? Optional.empty() : Optional.of(state); 

Như điều hành nhị phân của tôi (trong đó có nhiệm vụ giảm) và không quan trọng với null.

+3

Làm thế nào về 'Optional.ofNullable'? Dù sao ví dụ của bạn trông không đúng kể từ khi bạn đang sử dụng 'Danh sách ' với 'BinaryOperator '. – Pshemo

+2

@Pshemo 'ReducingSink' là một lớp jdk nội bộ – Eugene

+0

Dù sao - @Pshemo là đúng - ví dụ không biên dịch rất tốt :-) –

Trả lời

4

Tôi thực sự không thể biết tại sao bạn phải làm việc với giá trị rỗng, điều này có vẻ như là một ý tưởng tồi để bắt đầu. Và, như bạn đã thấy bạn không thể reduce sử dụng một null làm đầu vào. Bạn có thể tạo tùy chỉnh của riêng mình Collector (bạn không thể tự xây dựng Reducer của riêng mình).

gì bạn có tại chỗ:

Double result = s.stream() 
     .filter(Objects::nonNull) 
     .reduce(Double::sum) 
     .orElse(null); 

hoàn toàn tốt đẹp btw. Cách duy nhất để có được kết quả rỗng là khi tất cả các thành phần từ đầu vào của bạn là rỗng, do đó, lọc chúng ban đầu là cách để đi. Đối với những niềm vui của nó, tôi quyết định viết một nhà sưu tập tùy chỉnh (có thể không thực sự nói lý do tại sao, nghĩ rằng nó sẽ được vui vẻ tôi đoán)

Double result = s.stream() 
      .parallel() 
      .collect(
       () -> new Double[] { null }, 
       (left, right) -> { 
        if (right != null) { 
         if (left[0] != null) { 
          left[0] = right + left[0]; 
         } else { 
          left[0] = right; 
         } 
        } 
       }, 
       (left, right) -> { 
        if (right[0] != null) { 
         if (left[0] != null) { 
          left[0] = right[0] + left[0]; 
         } else { 
          left[0] = right[0]; 
         } 
       }})[0]; 

Bạn có thể đặt điều này vào một lớp học riêng của mình nếu cần thiết:

class NullableCollector implements Collector<Double, Double[], Double> { 

    @Override 
    public BiConsumer<Double[], Double> accumulator() { 
     return (left, right) -> { 
      if (right != null) { 
       if (left[0] != null) { 
        left[0] = right + left[0]; 
       } else { 
        left[0] = right; 
       } 
      } 
     }; 
    } 

    @Override 
    public Set<Characteristics> characteristics() { 
     return EnumSet.noneOf(Characteristics.class); 
    } 

    @Override 
    public BinaryOperator<Double[]> combiner() { 
     return (left, right) -> { 
      if (right[0] != null) { 
       if (left[0] != null) { 
        left[0] = right[0] + left[0]; 
       } else { 
        left[0] = right[0]; 
       } 
      } 
      return left; 
     }; 
    } 

    @Override 
    public Function<Double[], Double> finisher() { 
     return (array) -> array[0]; 
    } 

    @Override 
    public Supplier<Double[]> supplier() { 
     return() -> new Double[] { null }; 
    } 

} 
7

các documentation của giảm hoạt động mà bạn sử dụng trạng thái:

Ném: NullPointerException - nếu kết quả của việc giảm là null

Vì vậy, các NPE mà bạn thấy là tài liệu và kết quả dự định ngay cả khi toán tử nhị phân của bạn là tốt với null.

Các tài liệu được thậm chí tiết hơn cho thêm cái nhìn sâu sắc với một số mã tương đương:

 boolean foundAny = false; 
    T result = null; 
    for (T element : this stream) { 
     if (!foundAny) { 
      foundAny = true; 
      result = element; 
     } 
     else 
      result = accumulator.apply(result, element); 
    } 
    return foundAny ? Optional.of(result) : Optional.empty(); 

NPE được ném vào dòng cuối cùng trong trường hợp này.

Nếu thay đổi được đề xuất của bạn được áp dụng trong thư viện, chúng tôi sẽ không thể phân biệt kết quả của việc giảm luồng trống khỏi luồng mà kết quả của việc giảm là null.

+0

Tôi đồng ý với bạn về chỉ ra rằng nếu thay đổi được đề xuất được áp dụng thì chúng tôi sẽ không thể phân biệt giữa kết quả của việc giảm luồng trống từ luồng mà kết quả của việc giảm là null nhưng bạn có thể đề xuất trường hợp sử dụng cho nó. –

+0

Vâng ... một luồng với những thành tựu đáng kinh ngạc của bạn trong Game X và một ứng dụng in "Tổng số kết quả của bạn là ..." hoặc "Bạn vẫn chưa chơi trò chơi này, hãy dùng thử!", Những thứ như sau: -) –

Các vấn đề liên quan