在 Java8 中使用 lambda 仅在不为空时过滤值
Posted
技术标签:
【中文标题】在 Java8 中使用 lambda 仅在不为空时过滤值【英文标题】:Filter values only if not null using lambda in Java8 【发布时间】:2015-12-29 07:23:22 【问题描述】:我有一个对象列表,比如car
。我想使用 Java 8 根据一些参数过滤这个列表。但是如果参数是null
,它会抛出NullPointerException
。如何过滤掉空值?
当前代码如下
requiredCars = cars.stream().filter(c -> c.getName().startsWith("M"));
如果getName()
返回null
,则抛出NullPointerException
。
【问题讨论】:
您想要“仅过滤非空值”还是“过滤掉空值”?这对我来说听起来很矛盾。 我可以建议您接受Tunaki's answer,因为它似乎是唯一真正回答您问题的人。 科特林呢? )) requiredCars = cars.filter c -> c?.name?.startsWith("M")); 【参考方案1】:在这个特定的例子中,我认为@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());
【讨论】:
请注意,空车不是问题。在这种情况下,它的 name 属性会导致问题。所以Objects::nonNull
不能在这里使用,在最后的建议中应该是cars.stream() .filter(car -> Objects.nonNull(car.getName()))
我相信
顺便说一句,我认为cars.stream() .filter(car -> Objects.nonNull(car.getName()) && car.getName().startsWith("M"))
将是您在此问题上下文中的建议摘要
@kiedysktos 很好,调用.startWith
也可能导致空指针。我试图说明的一点是,Java 提供了一种专门用于从流中过滤掉空对象的方法。
@Mark Booth 是的,显然Objects.nonNull
等价于!= null
,您的选择更短
您不是在创建汽车名称列表 (String
) 而不是汽车 (Car
) 吗?【参考方案2】:
您只需要过滤具有null
名称的汽车:
requiredCars = cars.stream()
.filter(c -> c.getName() != null)
.filter(c -> c.getName().startsWith("M"));
【讨论】:
很遗憾,这个答案没有得到更高的投票,因为它似乎是唯一真正回答问题的答案。 @MarkBooth 问题“如何过滤掉空值?” xbakesx 似乎回答得很好。 @MarkBooth 查看日期,您是正确的。我的错。 性能明智,过滤两次流更好还是更好地使用谓词进行过滤?只是想知道。【参考方案3】:建议的答案很棒。只是想提出一个改进,以使用Optional.ofNullable
、new feature in Java 8 处理null
列表的情况:
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
【讨论】:
Optional 使用不当。 null 一开始就不应该用作空集合的同义词。 @VGR 当然,但实际情况并非如此。有时(大部分时间)您需要使用很多人都在编写的代码。有时您会从外部接口接收数据。对于所有这些情况,Optional 都非常有用。 请注意,空车不是问题。在这种情况下,它的 name 属性会导致问题。所以Objects::nonNull
没有解决问题,因为非空汽车可以有 name==null
当然@kiedysktos,但这不是我想在答案中显示的。但是,我接受你所说的并编辑答案:)【参考方案4】:
您可以在单个过滤步骤中执行此操作:
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());
【讨论】:
第二个例子中的内联扩展对我的用例很有价值【参考方案5】:利用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())
;
【讨论】:
【参考方案6】:你可以用这个
List<Car> requiredCars = cars.stream()
.filter (t-> t!= null && StringUtils.startsWith(t.getName(),"M"))
.collect(Collectors.toList());
【讨论】:
以上是关于在 Java8 中使用 lambda 仅在不为空时过滤值的主要内容,如果未能解决你的问题,请参考以下文章