如何处理让人头疼的空指针异常

最近代码中频繁出现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<U> Optional<U> 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类怎么做

1.创建Optional对象

User user = new User("fan",123);
Optional<User> optionalUser = Optional.ofNullable(null);
//Optional<User> optionalUser = Optional.of(null);
optionalUser.ifPresent(oUser->{
System.out.println(oUser.age);
});

如果用Optional.of(),传入的是null的情况会报空指针,而Optional.ofNullabl()不会,可以同过isPresent()方法判断是否为空,也可以通过ifPresent()加lamda表达式直接写为空情况下的逻辑。

2.获取Optional的值

User user = new User("john@gmail.com", 123);
Optional<User> opt = Optional.ofNullable(user);
assertTrue(opt.isPresent());

assertEquals(user.getEmail(), opt.get().getEmail());

3.返回默认值

orElse()的使用方法也非常简单,如果不为null,直接在后面赋值。
orElseGet()也可以

User u  = (User) Optional.ofNullable(null).orElseGet(()->new User("sdf",123));

4.Optional+map()

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;
    }
}

5.Optional+flatmap()

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<String> 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;
}
}

6.实例用法

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

7.map/flatmap的区别

参考: https://www.baeldung.com/java-difference-map-and-flatmap)

看Option类下面的方法,

map返回值是Option

public<V> Optional<V> map(Function<? super T, ? extends V> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent())
        return empty();
    else {
        return Optional.ofNullable(mapper.apply(value));
    }
}

flatmap返回的值U

public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent())
        return empty();
    else {
        return Objects.requireNonNull(mapper.apply(value));
    }
}

对于普通场景

Optional<String> s = Optional.of("test");
assertEquals(Optional.of("TEST"), s.map(String::toUpperCase));

如果是Option<Option>这种场景,会让代码显得嵌套太多,flatmap可以对这种情况进行优化:

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

祝大家编码愉快,工作愉快,欢迎关注我的公众号,一起分享交流,下篇再见!
Java技术小栈

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值