使用Java8中的lambda表达式仅过滤非空值

251

我有一个对象列表,比如car。我想使用Java 8根据某些参数过滤这个列表。但是如果参数是null,它会抛出NullPointerException异常。如何过滤掉空值?

当前代码如下

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

如果getName()返回null,则会抛出NullPointerException


你想要“仅过滤非空值”还是“过滤掉空值”?这听起来有些矛盾。 - Holger
3
我可以建议您接受Tunaki的答案,因为它似乎是唯一一个真正回答了您的问题。 - Mark Booth
Kotlin怎么样?)) requiredCars = cars.filter {c -> c?.name?.startsWith("M"))}; - JustAnotherCoder
6个回答

513
在这个特定的例子中,我认为@Tagir是100%正确的,将其放入一个过滤器并进行两次检查。 我不会使用Optional.ofNullable,因为Optional的东西确实是用于返回类型而不是用于执行逻辑...但实际上这些都不重要。我想指出,在一个广泛的情况下,java.util.Objects有一个很好的方法,所以你可以这样做:
    cars.stream()
        .filter(Objects::nonNull)

这将清除您的空对象。 对于不熟悉的人,这是以下内容的简写:

    cars.stream()
        .filter(car -> Objects.nonNull(car))
为了部分回答手头问题,返回以 "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());

一旦你熟悉了简写的lambda表达式,你也可以这样做:

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

不幸的是,一旦你使用.map(Car::getName),你只会返回名称列表,而不是汽车。所以,虽然不太美观,但完全回答了问题:

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

1
请注意,空汽车不是问题所在。在这种情况下,是名称属性引起了问题。因此,Objects::nonNull 不能在这里使用,在最后的建议中应该是 cars.stream().filter(car -> Objects.nonNull(car.getName()))。我相信。 - kiedysktos
1
顺便提一下,我认为cars.stream() .filter(car -> Objects.nonNull(car.getName()) && car.getName().startsWith("M"))会是你在这个问题上的建议总结。 - kiedysktos
4
很好的观点,调用.startWith也可能导致空指针异常。我想表达的是,Java提供了一种特定的方法来过滤掉流中的空对象。 - xbakesx
@Mark Booth 是的,显然 Objects.nonNull 等同于 != null,你的选项更短。 - kiedysktos
1
你是不是在创建一个汽车名称列表(String),而不是汽车对象列表(Car)? - user1803551
显示剩余2条评论

89

您只需要筛选出名称为null的汽车:

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

5
很遗憾这个答案没有得到更高的投票,因为它似乎是唯一一个真正回答了问题的答案。 - Mark Booth
2
@MarkBooth 问题“如何过滤掉空值?”似乎已经被xbakesx很好地回答了。 - vegemite4me
@MarkBooth 看了日期,你是对的。我的错误。 - vegemite4me
从性能角度来看,两次过滤流好还是更好地使用谓词进行过滤?只是想知道。 - Vaibhav_Sharma

67

提出的答案很好。只是想建议一种改进方法,使用Java 8中的Optional.ofNullable处理null列表的情况,这是一个Java 8新功能

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

因此,完整的答案将是:

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

7
Optional 的使用不当。首先,null 不应该被用作空集合的同义词。 - VGR
7
当然,但实际上并不总是这样。有时候(大多数情况下)你需要处理许多人共同编写的代码。有时候你从外部接口获取数据。对于所有这些情况,使用 Optional 是一个好主意。 - Johnny
3
请注意,空汽车不是问题所在。在这种情况下,是名称属性导致了问题。因此,“Objects::nonNull”并不能解决问题,因为非空汽车的名称可能为null。 - kiedysktos
1
当然,@kiedysktos,但这不是我想在回答中展示的内容。但是,我接受你的意见并编辑了答案 :) - Johnny

32

您可以在单个筛选器步骤中完成此操作:

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

如果您不想多次调用getName()(例如,它是昂贵的调用),您可以这样做:

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

或者更精细的方式:

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

第二个示例中的内联扩展对我的用例非常有价值。 - Paul

4
利用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())
;

使用Optional的复杂性来替代简单的布尔表达式是不好的实践,我个人认为。 - Florian F

4

你可以使用这个

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接