前言
【本文于2023年6月21日发表于掘金】
首先,为什么要写空指针异常这样一个司空见惯的话题呢?
阿里巴巴Java开发手册(嵩山版)中第二章-第(二)节-11条中提到:防止NPE,是程序员的基本素养。
同时我在工作中也发现很多的同事在平时写代码时,对空指针异常这种情况并不重视,也不知道应该怎么样去避免空指针异常,这引起了我的思考,应该怎么样让自己的代码不会出现空指针异常。
空指针异常是怎么产生的?
问题1:什么是空指针异常?
官方解释:空指针异常(NullPointerException)意思是指java中的异常类。当应用程序试图在需要对象的地方使用null时,抛出该异常。
相信很多人对空指针异常都有自己的理解,核心含义就是说:空指针异常是一种异常类(运行时异常),通常出现在我们需要调用某一个对象的方法或者属性时,对象为null,出现时的异常,在编译时,无法发现,在运行时可能出现的异常。
问题2:哪些情况下会出现空指针异常?
关于这个问题,我们使用代码来进行描述可能更加的容易理解。
1.当调用一个空对象中的方法时。
// 当调用一个空对象中的方法时
User user = null;
String name = user.getName();
运行结果:
java.lang.NullPointerException
at com.xxx.xxx.xx.xxxx.nullCheck(xxxTest.java:58)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
...
说明:在这段代码中我们将user对象赋予了null值,然后想使用其中的get方法。
2.当获取一个对象中某一个字段时。
User user = null;
String name = user.name;
运行结果:
java.lang.NullPointerException
at com.xxx.xxx.xxx.xxx.nullCheck(xxxxx.java:60)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
说明:同上,当一个对象为null时,我们不管是使用里面的方法还是字段时都会出现空指针异常,因为该对象为null,里面的字段和方法自然获取不到,那为什么Java又让我们使用这个字段或者方法呢?因为空指针异常属于运行时异常,Java并不知道这个对象是否为null。
3.自动拆箱
User user = new User();
user.setUserId(null);
Integer userId = user.getUserId();
if (1 == userId) {
System.out.println("管理员");
} else {
System.out.println("普通用户");
}
运行结果:
java.lang.NullPointerException
at com.xxx.xxx.xxx.xxx.nullCheck(xxxxx.java:60)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
说明:这个地方我们在写一个简单的判断时,使用到了userId这个字段,当使用==做校验时,会触发Java的自动拆箱机制,会将Integer包装类型拆箱成为基本数据类型,当包装类型拆箱成为基本数据类型时就有可能出现空指针异常。
这是阿里巴巴Java规范里面的一个例子,我觉得很形象就贴出来了。
返回类型为基本数据类型,return 包装数据类型的对象时,自动拆箱有可能产生 NPE。
反例:public int f() {
return Integer 对象;
}
// 如果为 null,自动解箱抛 NPE。
上面是一些在编写java代码时常见的一些问题,下面列举一些可能出现的情况。
4.数据库的查询结果可能为 null。
5.集合里的元素即使 isNotEmpty,取出的数据元素也可能为 null。
6.远程调用时。
7.级联调用 obj.getA().getB().getC();一连串调用,易产生 NPE。
8.其它情况,后续会补充,欢迎指正
怎么样避免空指针异常?
1.使用Optional类
要说Java中什么异常最容易出现,我想NullPointerException一定当仁不让,为了解决这种null值判断问题,Java8中提供了一个新的工具类Optional,用于提示程序员注意null值,并在特定场景中简化代码逻辑。
比如下面一段取深层属性值的代码:
Order order = getOrderById(orderId);
String userCode = "";
if(order != null){
if(order.getUser() != null){
if(order.getUser().getUserCode() != null){
userCode = order.getUser().getUserCode().toUpperCase();
}
}
}
这种场景还比较常见,但深层嵌套的if判断,让代码阅读者压力倍增。
看看用Optional后的写法,如下:
Order order = getOrderById(orderId);
String userCode = Optional.ofNullable(order)
.map(Order::getUser)
.map(User::getUserCode)
.map(String::toUpperCase)
.orElse("")
链式调用的写法,让代码可读性增强了不少,不用判断null,是因为Optional在内部已经做了null值判断了!
我在工作中常用的写法
User user = new User();
user.setName("");
Integer userId = Optional.ofNullable(user.getUserId()).orElse(RandomUtil.randomInt());
user.setUserId(userId);
System.out.println(user);
运行结果:
1.ofNullable
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
说明:ofNullable方法会使用三目运算符,判断对象是否为null,如果为null,会返回一个EMPTY枚举值
2.orElse
public T orElse(T other) {
return value != null ? value : other;
}
说明:这个方法也是使用的三目运算符,如果不为null则返回原值,为null,则返回我们传入的默认值。
2.jsr303校验
JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation,官方参考实现是Hibernate Validator。 Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint。
约束注解名称 | 约束注解说明 |
---|---|
@Null | 用于验证对象为null |
@NotNull | 用于对象不能为null,无法查检长度为0的字符串 |
@NotBlank | 只用于String类型上,不能为null且trim()之后的size>0 |
@NotEmpty | 用于集合类、String类不能为null,且size>0。但是带有空格的字符串校验不出来 |
@Size | 用于对象(Array,Collection,Map,String)长度是否在给定的范围之内 |
@Length | 用于String对象的大小必须在指定的范围内 |
@Pattern | 用于String对象是否符合正则表达式的规则 |
用于String对象是否符合邮箱格式 | |
@Min | 用于Number和String对象是否大等于指定的值 |
@Max | 用于Number和String对象是否小等于指定的值 |
@AssertTrue | 用于Boolean对象是否为true |
@AssertFalse | 用于Boolean对象是否为false |
3.工作中如何判null?
说明:在java中,””字符串是不为null的,所以对于字符串我们既要对null,也要对””进行判断。
1.使用字符串工具类对字符串判空
User user = new User();
user.setName("");
if (StringUtils.isEmpty(user.getName())) {
System.out.println("姓名不能为null");
}
运行结果:
2.在使用equals方式时,需要把常量值放前面。
错误示例:
User user = new User();
if (user.getName().equals("admin")) {
System.out.println("系统管理员");
}
user.getName()为null时,再与字符串常量进行比较,就会出现空指针异常,小细节,需要注意一下。
正确示例:
User user = new User();
if ("admin".equals(user.getName())) {
System.out.println("系统管理员");
}
3.对集合进行判空
List<Integer> list = new ArrayList<>();
if (list.isEmpty()) {
System.out.println("list为null");
}
不要使用list.size == 0对集合判空,特别是进行数据库查询,返回list的时候,size不为0;
最后
要说Java中什么异常最容易出现,我想NullPointerException一定当仁不让。就像阿里巴巴Java规范里面所说,避免空指针是程序员的基本素养,一个好的程序员应该知道如何去避免空指针异常,减少自己代码中的错误,让程序更加稳定的运行。
高级程序员和初级程序员的区别往往就体现在这些我们司空见惯的细节上。
希望这篇文章能对您有用,不足之处,欢迎指正。