最近代码中频繁出现NPE,一次两次还好,监控软件上一下子来很多个报错的时候还是略显尴尬。实际上为了防止冗余的代码块,我已经尽力在代码里加上Optional,然而还是频频出问题。
一个如下的典型场景让我对NPE有了重新认识,可能属于疏忽大意,也可能属于对Optional了解不够深入。
-shopNoteUgcDo.setFansNum(Optional.ofNullable(fansFriendsMap.get(noteInfo.getUserId())).orElse(0)); //改动前
+shopNoteUgcDo.setFansNum(Optional.ofNullable(fansFriendsMap).map(map->map.get(noteInfo.getUserId())).orElse(0)); //改动后
打开监控系统,发现很多NPE,赶紧看哪行代码出错,如图上面那行报错。初步分析
1. shopNoteUgcDo不可能为null,是上面代码新建的
2. fansFriendsMap有可能为null
3. noteInfo不可能为null,是从前面一个List里面遍历的
4. noteInfo.getUserId()有可能为null
5. fansFriendsMap.get(noteInfo.getUserId())有可能为null
初步判断可以筛出1,3
实际上,第5条可以排除,因为结果为null,Map.get(null)返回Null,被包装在Optional里面,也是可以正常返回的。
所以去掉1、3、5,问题就出在2,4
2.fansFriendsMap有可能为null
4.noteInfo.getUserId()有可能为null
我将代码改造成第二行的样式,将fansFriendsMap放在Optional中,先判断是否为Null,再执行map。
发布的过程中,忽然发现,我没有处理noteInfo.getUserId()为null。然而发布完成后发现没有NPE了。
仔细思考一下,假如noteInfo.getUserId()==null
map->map.get(null) //这里会返回null
查看Optional.map的源码
public Optional map(Function super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Optional.ofNullable(mapper.apply(value)); //这里即使mapper.apply(value)的结果为null,依然被Optional包装了一层
}
}
即使为null,也会继续链式调用后面的orElse(0)的逻辑。
这样梳理过后,对空指针的控制有了更多的认识,主要还是在控制传入的参数是否为空,以及传入参数进行后续操作,又会引入的其他参数是否为空的判断。当然除了及其严谨的判断,还得排除那些参数不会为空,例如从集合中遍历的元素,自己新建的元素,这些基本是不为空的,排除逻辑上的干扰。
回顾一下这个NPE的产生,是因为我依赖的这个服务的返回值fansFriendsMap为null,而在测试环境未出现过这种情况,所以一直没发现。这提醒我代码要写的足够健壮,不能说测试没问题就是好的代码,可能隐含了更危险的线上问题。
以下是对Optional知识点的一些总结
Optional 是 Java 实现函数式编程的强劲一步,并且帮助在范式中实现。但是 Optional 的意义显然不止于此。
我们从一个简单的用例开始。在 Java 8 之前,任何访问对象方法或属性的调用都可能导致 NullPointerException:
String isocode = user.getAddress().getCountry().getIsocode().toUpperCase();
要确保不抛出NullPointerException,需要
if (user != null) {
Address address = user.getAddress();
if (address != null) {
Country country = address.getCountry();
if (country != null) {
String isocode = country.getIsocode();
if (isocode != null) {
isocode = isocode.toUpperCase();
}
}
}
}
下面看Optional类怎么做
User user = new User("fan",123);
Optional optionalUser = Optional.ofNullable(null);
//Optional optionalUser = Optional.of(null);
optionalUser.ifPresent(oUser->{
System.out.println(oUser.age);
});
如果用Optional.of(),传入的是null的情况会报空指针,而Optional.ofNullabl()不会,可以同过isPresent()
方法判断是否为空,也可以通过ifPresent()
加lamda表达式直接写为空情况下的逻辑。
User user = new User("[email protected]", 123);
Optional opt = Optional.ofNullable(user);
assertTrue(opt.isPresent());
assertEquals(user.getEmail(), opt.get().getEmail());
orElse()
的使用方法也非常简单,如果不为null,直接在后面赋值。
orElseGet()
也可以
User u = (User) Optional.ofNullable(null).orElseGet(()->new User("sdf",123));
User user = null;
String name = Optional.ofNullable(user).map(u -> u.getName()).orElse("defaultname");
System.out.println(name);
//user类
static class User {
User(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
private String name;
private Integer age;
@Override
public String toString() {
return "[name]:" + name + ",[age]:" + age;
}
}
public static void main(String[] args) {
User user = null;
String name = Optional.ofNullable(user).flatMap(u -> u.getName()).orElse("defaultname");
System.out.println(name);
}
//user类
static class User {
User(String name, Integer age) {
this.name = name;
this.age = age;
}
public Optional getName() {
return Optional.ofNullable(this.name);
}
public int getAge() {
return this.age;
}
private String name;
private Integer age;
@Override
public String toString() {
return "[name]:" + name + ",[age]:" + age;
}
}
@Data
@AllArgsConstructor
public class User {
private Address address;
}
@Data
@AllArgsConstructor
public class Address {
private Country country;
}
@Data
@AllArgsConstructor
public class Country {
private String name;
}
//main
User user = null;
String address = Optional.ofNullable(user).
map(User::getAddress).
map(Address::getCountry).
map(Country::getName).orElse("default-value");
System.out.println(address);
参考: https://www.baeldung.com/java-difference-map-and-flatmap)
看Option类下面的方法,
map返回值是Option
public Optional map(Function super T, ? extends V> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Optional.ofNullable(mapper.apply(value));
}
}
flatmap返回的值U
public Optional flatMap(Function super T, Optional> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Objects.requireNonNull(mapper.apply(value));
}
}
对于普通场景
Optional s = Optional.of("test");
assertEquals(Optional.of("TEST"), s.map(String::toUpperCase));
如果是Option
assertEquals(Optional.of(Optional.of("STRING")),
Optional
.of("string")
.map(s -> Optional.of("STRING")));
换成flatmap,体现出flat即扁平化的意思,可以是代码更加扁平化,不用那么多嵌套。
assertEquals(Optional.of("STRING"), Optional
.of("string")
.flatMap(s -> Optional.of("STRING")));