Làm cách nào chúng ta nên quản lý luồng jdk8 cho các giá trị null


88

Xin chào các nhà phát triển Java,

Tôi biết chủ đề có thể là một chút in advancevì JDK8 vẫn chưa được phát hành (và dù sao thì không phải bây giờ ..) nhưng tôi đã đọc một số bài báo về các biểu thức Lambda và đặc biệt là phần liên quan đến API bộ sưu tập mới được gọi là Stream.

Đây là ví dụ được đưa ra trong bài báo của Tạp chí Java (nó là một thuật toán quần thể rái cá ..):

Set<Otter> otters = getOtters();
System.out.println(otters.stream()
    .filter(o -> !o.isWild())
    .map(o -> o.getKeeper())
    .filter(k -> k.isFemale())
    .into(new ArrayList<>())
    .size());

Câu hỏi của tôi là điều gì sẽ xảy ra nếu ở giữa lần lặp nội bộ Đặt, một trong những con rái cá là rỗng?

Tôi mong đợi một NullPointerException được ném nhưng có lẽ tôi vẫn bị mắc kẹt trong mô hình phát triển trước đó (phi chức năng), ai đó có thể chỉ cho tôi cách xử lý điều này không?

Nếu điều này thực sự ném NullPointerException, tôi thấy tính năng này khá nguy hiểm và sẽ chỉ được sử dụng như bên dưới:

  • Nhà phát triển để đảm bảo không có giá trị null (có thể sử dụng .filter trước đó (o -> o! = Null))
  • Nhà phát triển để đảm bảo ứng dụng không bao giờ tạo rái cá null hoặc một đối tượng NullOtter đặc biệt để xử lý.

Tùy chọn tốt nhất là gì, hoặc bất kỳ tùy chọn nào khác?

Cảm ơn!


3
Tôi muốn nói rằng việc làm đúng ở đây là tùy thuộc vào lập trình viên; JVM và trình biên dịch chỉ có thể làm được nhiều như vậy. Tuy nhiên, lưu ý rằng một số triển khai bộ sưu tập sẽ không cho phép các giá trị rỗng.
fge

18
Bạn có thể sử dụng filter(Objects::nonNull)với Objectstừjava.utils
Benj

Câu trả lời:


45

Suy nghĩ hiện tại dường như là "chịu đựng" null, nghĩa là cho phép chúng nói chung, mặc dù một số hoạt động kém khoan dung hơn và cuối cùng có thể ném NPE. Xem cuộc thảo luận về null trong danh sách gửi thư của nhóm chuyên gia Lambda Libraries, cụ thể là thư này . Sự đồng thuận xung quanh lựa chọn số 3 sau đó đã xuất hiện (với sự phản đối đáng chú ý từ Doug Lea). Vì vậy, có, mối quan tâm của OP về việc các đường ống bị nổ tung với NPE là có cơ sở.

Không phải vô cớ mà Tony Hoare gọi nulls là "Sai lầm hàng tỷ đô la". Đối phó với vô hiệu là một nỗi đau thực sự. Ngay cả với các bộ sưu tập cổ điển (không tính đến lambdas hoặc luồng), giá trị rỗng cũng có vấn đề. Như fge đã đề cập trong một bình luận, một số bộ sưu tập cho phép null và những bộ khác thì không. Với các bộ sưu tập cho phép null, điều này đưa ra những điều không rõ ràng trong API. Ví dụ, với Map.get () , trả về null chỉ ra rằng khóa có mặt và giá trị của nó là null hoặc khóa vắng mặt. Người ta phải làm thêm công việc để phân biệt những trường hợp này.

Việc sử dụng thông thường cho null là biểu thị sự không có giá trị. Cách tiếp cận để xử lý vấn đề này được đề xuất cho Java SE 8 là giới thiệu một java.util.Optionalkiểu mới , bao gồm sự hiện diện / vắng mặt của một giá trị, cùng với các hành vi cung cấp giá trị mặc định hoặc ném một ngoại lệ hoặc gọi một hàm, v.v. nếu giá trị không có. Optionalchỉ được sử dụng bởi các API mới, tuy nhiên, mọi thứ khác trong hệ thống vẫn có khả năng có giá trị rỗng.

Lời khuyên của tôi là tránh các tham chiếu rỗng thực tế ở mức độ lớn nhất có thể. Thật khó để thấy từ ví dụ được đưa ra làm thế nào có thể có một con Rái cá "null". Nhưng nếu cần thiết, các gợi ý của OP về việc lọc ra các giá trị null hoặc ánh xạ chúng tới một đối tượng sentinel ( Mẫu đối tượng Null ) là những cách tiếp cận tốt.


3
Tại sao tránh nulls? Chúng được sử dụng rộng rãi trong cơ sở dữ liệu.
Raffi Khatchadourian

5
@RaffiKhatchadourian Có, null được sử dụng trong cơ sở dữ liệu, nhưng cũng có vấn đề như nhau. Xem cái nàycái này và đọc tất cả các câu trả lời và nhận xét. Cũng nên xem xét tác động của SQL null đối với biểu thức boolean: en.wikipedia.org/wiki/… ... đây là nguồn lỗi truy vấn phong phú.
Stuart Marks

1
@RaffiKhatchadourian Bởi vì quá nulltệ, đó là lý do tại sao. Acc. trong một cuộc khảo sát mà tôi đã thấy (không thể tìm thấy), NPE là ngoại lệ số 1 trong Java. SQL không phải là ngôn ngữ lập trình cấp cao, chắc chắn không có chức năng, điều này coi thường null, vì vậy nó không quan tâm.
Abhijit Sarkar,

91

Mặc dù các câu trả lời đều đúng 100%, nhưng một gợi ý nhỏ để cải thiện nullviệc xử lý trường hợp của chính danh sách với Tùy chọn :

 List<String> listOfStuffFiltered = Optional.ofNullable(listOfStuff)
                .orElseGet(Collections::emptyList)
                .stream()
                .filter(Objects::nonNull)
                .collect(Collectors.toList());

Phần Optional.ofNullable(listOfStuff).orElseGet(Collections::emptyList)này sẽ cho phép bạn xử lý tốt trường hợp khi nào listOfStufflà null và trả về một Danh sách trống thay vì thất bại với NullPointerException.


2
Tôi thích điều này, tránh kiểm tra null rõ ràng.
Chris

1
cái này trông rõ ràng nhất .. đẹp và chính xác những gì tôi cần
Abdullah Al Noman

Nó kiểm tra rõ ràng giá trị null, chỉ theo một cách khác. Nếu nó được phép là null, nó phải là Optional, đó là ý tưởng, phải không? Cấm tất cả các giá trị null với các tùy chọn. Hơn nữa, việc trả về null thay vì danh sách trống là một thực tiễn không tốt, vì vậy nó sẽ hiển thị hai mùi mã nếu tôi thấy điều này: không có Tùy chọn nào đang được sử dụng và không có dòng trống nào được trả về. Và sau này là có sẵn trong hơn 20 năm, do đó của trưởng thành ...
Koos Gadellaa

điều gì sẽ xảy ra nếu tôi muốn trả về một danh sách rỗng thay vì trống?
Ashburn RK

@AshburnRK đó là một thực tiễn tồi. Bạn sẽ trả lại một danh sách trống.
Johnny

69

Câu trả lời của Stuart cung cấp một lời giải thích tuyệt vời, nhưng tôi muốn cung cấp một ví dụ khác.

Tôi đã gặp phải sự cố này khi cố gắng thực hiện reducetrên Luồng chứa các giá trị null (thực tế là như vậy LongStream.average(), đó là một loại giảm). Vì trả về OptionalDoublegiá trị trung bình () , tôi giả sử Luồng có thể chứa null nhưng thay vào đó, một NullPointerException đã được ném ra. Điều này là do sự giải thích của Stuart về null v. Rỗng.

Vì vậy, như OP đề xuất, tôi đã thêm một bộ lọc như sau:

list.stream()
    .filter(o -> o != null)
    .reduce(..);

Hoặc như tangens đã chỉ ra bên dưới, hãy sử dụng vị từ được cung cấp bởi Java API:

list.stream()
    .filter(Objects::nonNull)
    .reduce(..);

Từ cuộc thảo luận về danh sách gửi thư Stuart đã liên kết: Brian Goetz về nulls trong Streams


19

Nếu bạn chỉ muốn lọc các giá trị null ra khỏi luồng, bạn có thể chỉ cần sử dụng một tham chiếu phương thức tới java.util.Objects.nonNull (Object) . Từ tài liệu của nó:

Phương thức này tồn tại để được sử dụng như một Vị từ ,filter(Objects::nonNull)

Ví dụ:

List<String> list = Arrays.asList( null, "Foo", null, "Bar", null, null);

list.stream()
    .filter( Objects::nonNull )  // <-- Filter out null values
    .forEach( System.out::println );

Điều này sẽ in:

Foo
Bar

7

Ví dụ về cách tránh null, ví dụ: sử dụng bộ lọc trước khi nhóm

Lọc ra các trường hợp rỗng trước khi nhómBy.

Đây là một ví dụ

MyObjectlist.stream()
            .filter(p -> p.getSomeInstance() != null)
            .collect(Collectors.groupingBy(MyObject::getSomeInstance));
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.