Chỉ lọc các giá trị nếu không null bằng lambda trong Java8


160

Tôi có một danh sách các đối tượng nói car. Tôi muốn lọc danh sách này dựa trên một số tham số bằng Java 8. Nhưng nếu tham số là null, nó sẽ ném NullPointerException. Làm thế nào để lọc ra các giá trị null?

Mã hiện tại như sau

requiredCars = cars.stream().filter(c -> c.getName().startsWith("M"));

Điều này ném NullPointerExceptionnếu getName()trở lại null.


Bạn có muốn lọc các giá trị bộ lọc chỉ khi không null hay bộ lọc ra các giá trị null không? Điều đó nghe có vẻ mâu thuẫn với tôi.
Holger

3
Tôi có thể đề nghị bạn chấp nhận câu trả lời của Tunaki vì nó dường như là người duy nhất trả lời câu hỏi của bạn.
Đánh dấu gian hàng

Câu trả lời:


322

Trong ví dụ cụ thể này, tôi nghĩ rằng @Tagir đúng 100%, hãy đưa nó vào một bộ lọc và thực hiện hai kiểm tra. Tôi sẽ không sử dụng Optional.ofNullableCông cụ tùy chọn thực sự cho các kiểu trả về không phải là logic ... nhưng thực sự không phải ở đây cũng không ở đó.

Tôi muốn chỉ ra rằng java.util.Objectscó một phương pháp hay cho trường hợp này, vì vậy bạn có thể làm điều này:

cars.stream()
    .filter(Objects::nonNull)

Mà sẽ xóa các đối tượng null của bạn. Đối với bất kỳ ai không quen thuộc, đó là bàn tay ngắn cho những điều sau đây:

cars.stream()
    .filter(car -> Objects.nonNull(car))

Để trả lời một phần câu hỏi trong tay để trả về danh sách tên xe bắt đầu bằng "M":

cars.stream()
    .filter(car -> Objects.nonNull(car))
    .map(car -> car.getName())
    .filter(carName -> Objects.nonNull(carName))
    .filter(carName -> carName.startsWith("M"))
    .collect(Collectors.toList());

Khi bạn đã quen với lambdas, bạn cũng có thể làm điều này:

cars.stream()
    .filter(Objects::nonNull)
    .map(Car::getName)        // Assume the class name for car is Car
    .filter(Objects::nonNull)
    .filter(carName -> carName.startsWith("M"))
    .collect(Collectors.toList());

Thật không may một khi bạn .map(Car::getName)sẽ chỉ trả lại danh sách tên, không phải những chiếc xe. Vì vậy, ít đẹp hơn nhưng trả lời đầy đủ các câu hỏi:

cars.stream()
    .filter(car -> Objects.nonNull(car))
    .filter(car -> Objects.nonNull(car.getName()))
    .filter(car -> car.getName().startsWith("M"))
    .collect(Collectors.toList());

1
lưu ý rằng xe null không phải là vấn đề. Trong trường hợp này, tên tài sản gây ra vấn đề. Vì vậy, tôi Objects::nonNullkhông thể sử dụng ở đây, và theo lời khuyên cuối cùng thì tôi nên cars.stream() .filter(car -> Objects.nonNull(car.getName()))tin rằng
kiedysktos

1
BTW, tôi nghĩ cars.stream() .filter(car -> Objects.nonNull(car.getName()) && car.getName().startsWith("M"))sẽ là bản tóm tắt lời khuyên của bạn trong bối cảnh câu hỏi này
kiedysktos

3
@kiedysktos Đó là một điểm tốt khi gọi .startWithcũng có thể gây ra một con trỏ rỗng. Điểm tôi đã cố gắng thực hiện là Java cung cấp một phương thức đặc biệt để lọc ra các đối tượng null từ các luồng của bạn.
xbakesx

@Mark Gian hàng có, rõ ràng Objects.nonNulllà tương đương với != null, tùy chọn của bạn ngắn hơn
kiedysktos

1
Không phải bạn đang tạo một danh sách tên xe ( String) thay vì ô tô ( Car)?
dùng1804551

59

Bạn chỉ cần lọc những chiếc xe có nulltên:

requiredCars = cars.stream()
                   .filter(c -> c.getName() != null)
                   .filter(c -> c.getName().startsWith("M"));

3
Thật xấu hổ khi câu trả lời này không được đánh giá cao hơn vì nó dường như là câu trả lời duy nhất thực sự trả lời câu hỏi.
Đánh dấu gian hàng

@MarkBooth Câu hỏi "Làm thế nào để lọc ra các giá trị null?" dường như được trả lời tốt bởi xbakesx.
vegemite4me

@MarkBooth Nhìn vào ngày bạn đúng. Lỗi của tôi.
vegemite4me

Hiệu suất khôn ngoan là tốt để lọc luồng hai lần Hoặc tốt hơn là sử dụng biến vị ngữ để lọc? Chỉ muốn biết.
Vaibhav_Sharma

51

Các câu trả lời đề xuất là rất tốt. Chỉ muốn đề xuất một cải tiến để xử lý trường hợp danh sách null bằng cách sử dụng Optional.ofNullable, tính năng mới trong Java 8 :

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

Vì vậy, câu trả lời đầy đủ sẽ là:

 List<String> carsFiltered = Optional.ofNullable(cars)
                .orElseGet(Collections::emptyList)
                .stream()
                .filter(Objects::nonNull) //filtering car object that are null
                .map(Car::getName) //now it's a stream of Strings
                .filter(Objects::nonNull) //filtering null in Strings
                .filter(name -> name.startsWith("M"))
                .collect(Collectors.toList()); //back to List of Strings

5
Sử dụng không đúng cách. null không bao giờ được sử dụng làm từ đồng nghĩa cho Bộ sưu tập trống ở vị trí đầu tiên.
VGR

4
@VGR Tất nhiên, nhưng đó không phải là điều xảy ra trong thực tế. Đôi khi (hầu hết thời gian) bạn cần làm việc với mã mà rất nhiều người đã làm việc. Đôi khi bạn nhận được dữ liệu của bạn từ các giao diện bên ngoài. Đối với tất cả các trường hợp, Tùy chọn là một sử dụng tuyệt vời.
Johnny

1
lưu ý rằng xe null không phải là vấn đề. Trong trường hợp này, tên tài sản gây ra vấn đề. Vì vậy, Objects::nonNullkhông giải quyết được vấn đề vì xe không null có thể có tên == null
kiedysktos

Tất nhiên là @kiedysktos, nhưng đó không phải là điều tôi muốn thể hiện trong câu trả lời. Nhưng, tôi chấp nhận những gì bạn nói và chỉnh sửa câu trả lời :)
Johnny

24

Bạn có thể làm điều này trong bước lọc đơn:

requiredCars = cars.stream().filter(c -> c.getName() != null && c.getName().startsWith("M"));

Nếu bạn không muốn gọi getName()nhiều lần (ví dụ: đó là cuộc gọi đắt tiền), bạn có thể thực hiện việc này:

requiredCars = cars.stream().filter(c -> {
    String name = c.getName();
    return name != null && name.startsWith("M");
});

Hoặc theo cách tinh vi hơn:

requiredCars = cars.stream().filter(c -> 
    Optional.ofNullable(c.getName()).filter(name -> name.startsWith("M")).isPresent());

Việc mở rộng nội tuyến trong ví dụ thứ hai có giá trị cho trường hợp sử dụng của tôi
Paul

3

Tận dụng sức mạnh của java.util.Optional#map():

List<Car> requiredCars = cars.stream()
  .filter (car -> 
    Optional.ofNullable(car)
      .map(Car::getName)
      .map(name -> name.startsWith("M"))
      .orElse(false) // what to do if either car or getName() yields null? false will filter out the element
    )
  .collect(Collectors.toList())
;

1

bạn có thể sử dụng cái này

List<Car> requiredCars = cars.stream()
    .filter (t->  t!= null && StringUtils.startsWith(t.getName(),"M"))
    .collect(Collectors.toList());
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.