开发原则:只要是对象,我们就要判断对象是否为null。
java空指针异常:java.lang.NullPointException
java中的null
null是Java中一个很重要的概念。null设计初衷是为了表示一些缺失的东西,例如缺失的用户、资源或其他东西。但是,一年后,令人头疼的空指针异常给Java程序员带来不少的骚扰。
null是java中的关键字,因此,它不能写成NULL,Null,只能是null。
null是所有引用类型的默认值,如果没有让一个引用指向一个实际存在的对象,它的默认值就是null。null本质上是一个值,这跟int的默认值是0,boolean的默认值是false一样。现在,我们通常都使用像eclipse等的集成开发环境进行开发,一般在定义变量的时候都会进行初始化(这也是写代码的一个良好的习惯),如果没有进行初始化,系统会进行提示。
报空指针异常的原因有以下几种:
1.字符串变量未初始化;
初始化或赋值后则不会报错了
2.接口类型的对象没有用具体的类初始化,比如:
List it;会报错
List it = new ArrayList();则不会报错了
3.当一个对象的值为空时,你没有判断为空的情况。你可以试着把下面的代码前加一行代码:
if(rb!=null && rb!=””)
改成:
if(rb == null) 或者 if(“”).equals(rb))
空指针的解决办法:
重点关注报错发生的所在行,通过空指针异常产生的两条主要原因诊断具体的错误。同时为了避免空指针的发生,最好在做判断处理时将“null”或者空值放于设定的值之前。
常见空指针异常的简要分析:
(1)空指针错误
Java中的8种基本数据类型,变量的值可以有其默认值,假如没有对其正常赋值,java虚拟机是不能正确编译通过的,因此使用基本的Java数据类型一般是不会引起空指针异常的。实际开发中,大多数的空指针异常主要与对象的操作相关。
下面列出可能发生空指针异常的几种情况及相应解决方案:
代码段1:
out.println(request.getParameter(“username”));
分析:代码段1的功能十分简单,就是输出用户输入”username”的值。
说明:看上去,上面的语句找不出什么语法错误,而且在大多数情况下也遇不到什么问题。但是,如果某个用户在输入数据时并没有提供表单 域”username” 的值,或通过某种途径绕过表单直接输入时,此request.getParameter(“username”)的值为空(注意不是空字符串,是空对象 null。),out对象的println方法是无法直接对空对象操作的,因此代码段1所在的JSP页面将会抛出 “Java.lang.NullPointerException”异常。而且即使对象可能为空时,也调用Java.lang.Object或 Object对象本身的一些方法如toString(), equal(Object obj)等操作。
代码段2:
String userName = request.getParameter(“username”);
If (userName.equals(“root”))
{….}
分析:代码段2的功能是检测用户提供的用户名,如果是用户名称为”root”的用户时,就执行一些特别的操作。
说明:在代码段2中,如果有用户没有提供表单域”username”的值时,字符串对象userName为null值,不能够将一个null的对象与另一 个对象直接比较,同样,代码段2所在的JSP页面就会抛空指针错误。
一个小技巧:如果要把某个方法的返回值与常量做比较,把常量放在前面,可以避免调用null对象的equals方法。譬如:
If (“root”.equals(userName))
{….}
即使userName对象返回了null对象,这里也不会有空指针异常,可以照常运转。
代码段3:
String userName = session.getAttribute(“session.username”).toString();
分析:代码段3的功能是将session中session.username的值取出,并将该值赋给字符串对象userName。
说明:在一般情况下,如果在用户已经进行某个会话,则不会出现什么问题;但是,如果此时应用服务器重新启动,而用户还没有重新登录,(也可能是用户关闭浏 览器,但是仍打开原来的页面。)那么,此时该session的值就会失效,同时导致session中的session.username的值为空。对一个 为 null的对象的直接执行toString()操作,就会导致系统抛出空指针异常。
代码段4:
public static void main(String args[]){
Person p=null;
p.setName(“张三”);
System.out.println(p.getName());
}
分析:声明一个Person对象,并打印出该对象的中的Name名字。
说明:这个时候你的p就出现空指针异常,因为你只是声明了这个Person类型的对象并没有创建对象,所以它的堆里面没有地址引用,切忌你要用对象调用方法的时候一定要创建对象。
为了避免空指针调用,我们经常会看到这样的语句
1 | ...if (someobject != null) { |
最终,项目中会存在大量判空代码,多么丑陋繁冗!如何避免这种情况?我们是否滥用了判空呢?
在方法中返回null,在调用这些方法时,也不得不去判空。另外,也许受此习惯影响,他们总潜意识地认为,所有的返回都是不可信任的,为了保护自己程序,就加了大量的判空
进行判空前,请区分以下两种情况:
1、null 是一个有效有意义的返回值(Where null is a valid response in terms of the contract)
2、null是无效有误的(Where it isn’t a valid response)
先说第2种情况
null就是一个不合理的参数,就应该明确地中断程序,往外抛错误。这种情况常见于api方法。例如你开发了一个接口,id是一个必选的参数,如果调用方没传这个参数给你,当然不行。你要感知到这个情况,告诉调用方“嘿,哥们,你传个null给我做甚”。
相对于判空语句,更好的检查方式有两个
(1)assert语句,你可以把错误原因放到assert的参数中,这样不仅能保护你的程序不往下走,而且还能把错误原因返回给调用方,岂不是一举两得
(2)也可以直接抛出空指针异常。上面说了,此时null是个不合理的参数,有问题就是有问题,就应该大大方方往外抛。
第1种情况会更复杂一些。
这种情况下,null是个”看上去“合理的值,例如,我查询数据库,某个查询条件下,就是没有对应值,此时null算是表达了“空”的概念。
这里给一些实践建议:
1、假如方法的返回类型是collections,当返回结果是空时,你可以返回一个空的collections(empty list),而不要返回null,这样调用侧就能大胆地处理这个返回,例如调用侧拿到返回后,可以直接print list.size(),又无需担心空指针问题。(什么?想调用这个方法时,不记得之前实现该方法有没按照这个原则?所以说,代码习惯很重要!如果你养成习惯,都是这样写代码(返回空collections而不返回null),你调用自己写的方法时,就能大胆地忽略判空)
2、返回类型不是collections,又怎么办呢?
那就返回一个空对象(而非null对象),下面举个“栗子”,假设有如下代码
1 | public interface Action { |
其中,Parse有一个接口FindAction,这个接口会依据用户的输入,找到并执行对应的动作。假如用户输入不对,可能就找不到对应的动作(Action),因此findAction就会返回null,接下来action调用doSomething方法时,就会出现空指针。
解决这个问题的一个方式,就是使用Null Object pattern(空对象模式)
我们来改造一下
类定义如下,这样定义findAction方法后,确保无论用户输入什么,都不会返回null对象
1 | public class MyParser implements Parser { |
对比下面两份调用实例
1、冗余:每获取一个对象,就判一次空
1 | Parser parser = ParserFactory.getParser(); |
2、精简
1 | ParserFactory.getParser().findAction(someInput).doSomething(); |
因为无论什么情况,都不会返回空对象,因此通过findAction拿到action后,可以放心地调用action的方法。
其他回答精选:
1、如果要用equal方法,请用object<不可能为空>.equals(object<可能为空>))
例如:
使用
1 | "bar".equals(foo) |
而不是
1 | foo.equals("bar") |
2、Java8或者guava lib中,提供了Optional类,这是一个元素容器,通过它来封装对象,可以减少判空。不过代码量还是不少。不爽。
3、如果你想返回null,请挺下来想一想,这个地方是否更应该抛出一个异常
null与””的区别
null是没有地址
“”是有地址但是里面的内容是空的
1 | 问题一: |
spring的判空
Spring-core中提供了大量的工具类,常用的有StringUtils、ObjectUtils、NumberUtils、Base64Utils等,Spring工具类在spring-core.jar中的org.springframework.util包下。
1.isEmpty 没有忽略空格参数,是以是否为空和是否存在为判断依据。
2.isBlank 是在 isEmpty 的基础上进行了为空(字符串都为空格、制表符、tab 的情况)的判断。(一般更为常用)
这里推荐使用isBlank方法,因为可能会存在“ ”的情况,当这种情况下,需要根据自己的业务判断是不是为null的情况,避免出现错误,影响业务。
1 | StringUtils.isEmpty("yyy") = false |
null只有=和==方法可以使用,如果使用其他方法会报空指针异常的错误。
总结:
1.null==判断的对象:只判断null的情况
2.StringUtils.isEmpty(判断的对象):判断null和””的情况
3.StringUtils.isBlank(判断的对象):判断null和””和” “的情况
判空方法:
1.字符串与已知值进行比较,已知值放在前面
1 | “123”.equals(value) |
2.两个字符串比较,不确定是否有空值,使用Object.equals() 方法
1 | boolean r = Objects.equals(a,b); |
3.使用 java8 中的 Optional 类代替显示判空:Java 8中,每当一个函数需要返回 null,应该返回 Optional
1 | Optional.ofNullable(null).orElse("0") |
首先,创建第二个getter返回 Optional:
1 | public Optional getMemberCardOpt() { return Optional.ofNullable(memberCard); } |
其次,更改原始的getter以委托给新的getter:
1 | public String getMemberCard() { return getMemberCardOpt().orElse(null); } |
- 经典 if 判断
- 使用Java 8的检查 java.util.Objects -在当今开发中的项目中使用最广泛
- Lombok @NonNull 导致将 if 检查添加到生成的字节码中。
- assert关键字:我个人不喜欢它,因为可以通过JVM参数全局禁用断言
写业务代码的时候,通常会遇到数据库POJO对象转换为前端需要的VO对象,这时经常会遇到烦人的空指针问题,Java 8之前,我们可能这么写:
对于对象field复制风格:
a.setCreateTime(b.getCreateTime().getTime());
a.setAmount(b.getPayed()+b.getVoucher());
1
2
对于装饰器风格:
public Long getCreateTime() {
return b.getCreateTime().getTime();
}
public Long getAmount() {
return b.getPayed()+b.getVoucher()
}
1
2
3
4
5
6
很明显,这些代码会有空指针异常的风险.一般我们会这么修改:
对于对象field复制风格:
if (b.getCreateTime()!= null) {
a.setCreateTime(b.getCreateTime().getTime());
}
if (b.getPayed() != null) {
a.setAmount(b.getPayed();
} else {
a.setAmount(0L);
}
if (b.getVoucher() != null) {
a.setAmount(a.getAmount()+b.getVoucher());
}
1
2
3
4
5
6
7
8
9
10
11
对于装饰器风格:
public Long getCreateTime() {
if (b.getCreateTime()!= null) {
return b.getCreateTime().getTime();
} else {
return null;
}
}
public Long getAmount() {
Long result = 0L;
if (b.getPayed() != null) {
result += b.getPayed();
}
if (b.getVoucher() != null) {
result += b.getVoucher();
}
return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Java8引入的Optional写法:
对于对象field复制风格:
Optional.ofNullable(b.getCreateTime()).ifPresent(timestamp -> a.setCreateTime(timestamp.getTime()));
a.setAmount(Optional.ofNullable(b.getPayed()).orElse(0L) + Optional.ofNullable(b.getVoucher()).orElse(0L));
1
2
对于装饰器风格:
public Long getCreateTime() {
Optional
if (createTime.isPresent()) {
return createTime.get();
} else {
return null;
}
}
public Long getAmount() {
return Optional.ofNullable(b.getPayed()).orElse(0L) + Optional.ofNullable(b.getVoucher()).orElse(0L)
}
1
2
3
4
5
6
7
8
9
10
11
这样依然很复杂,尤其是嵌套多层之后。
更进一步,有没有通用简便的方法呢?联想到Java8的Functional Interface以及我们需要处理的异常只有空指针异常,可以写工具类:
import java.util.function.Supplier;
public class OptionalUtil {
/**
* 忽略NullPointerException的获取
* @param supplier
* @param
* @return 如果有空指针,返回null
*/
public static
try {
return supplier.get();
} catch (NullPointerException e) {
return null;
}
}
/**
* 忽略NullPointerException的获取
*
* @param supplier
* @param or
* @param
* @return 如果有空指针,返回or
*/
public static
try {
T t = supplier.get();
if (t != null) {
return t;
}
return or;
} catch (NullPointerException e) {
return or;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
这样,我们的代码就变得简洁多了:
对于对象field复制风格:
a.setCreateTime(OptionalUtil.orNull(() -> b.getCreateTime().getTime()));
a.setAmount(OptionalUtil.or(() -> b.getPayed(), 0L) + OptionalUtil.or(() -> b.getAmount(), 0L));
1
2
对于装饰器风格:
public Long getCreateTime() {
return OptionalUtil.orNull(() -> b.getCreateTime().getTime());
}
public Long getAmount() {
return OptionalUtil.or(() -> b.getPayed(), 0L) + OptionalUtil.or(() -> b.getAmount(), 0L);
}