您的位置:首页 > 运维架构

空指针异常与Optional类

2019-12-26 11:11 2221 查看

一、什么是空指针异常

当程序需要对象实例的时候返回

null
就会抛出空指针异常(
NullPointerException
,简称NPE)。包括以下情况:

  • 调用一个
    null
    对象实例的方法
  • 访问或修饰
    null
    对象的字段
  • 获取数组为
    null
    时的长度
  • 访问或修饰数组为
    null
    时的索引值
  • 抛出
    Throwable
    对象为
    null
    时的异常

虽然代码很难万无一失地避免所有NPE,但是也要尽量减少。所以一些防御性的编程技巧,可以将NPE控制在一个很好的水平上。

空指针案例

1. 调用业务方法的返回值对象

在不清楚一个方法的返回值是否存在返回

null
的情况,直接使用对象返回值。

People people = new People();
People user = people.getUser("name");
String name = user.getName();
System.out.println(name);

上面示例中的

people.getUser("name");
调用返回的对象不清楚是否为
null
,后面直接调用该对象的方法造成NPE。

2. 包装类自动拆箱的值

在包装类对象的值为

null
的情况下,进行自动拆箱操作。

Integer a = null;
int b = a;// System.out.println(1 == a);

上面示例中包装类对象

a
定义时的初始化值为
null
,在将
a
赋值给基本数据类型的
b
的时候,以及与基本数据类型
1
进行相等逻辑操作的时候,都进行了自动拆箱操作,
a
null
时造成NPE。

3. 集合和数组空对象的遍历

在不清楚集合或数组是否为

null
的时候,对它们进行遍历操作。

List<String> list = null;// String[] list = null;
for (String string : list) {
System.out.println(string);
}

在方法返回或者自己定义的数组和集合,只要有

null
的情况(不包括数组和集合长度为0的情况),进行遍历操作时造成NPE。

4. Spring没注入实例的使用

在使用Spring框架时,如果注入对象实例失败,此时该对象也是

null

public class BeanExample {

@Autowired
private BeanProvider beanProvider;

public void run() {
this.beanProvider.sayHello(this.name, this.age);
}
}

当因为操作不当导致

beanProvider
没有注入,在调用
sayHello()
方法的时候造成NPE。

5. ConcurrentHashMap和Hashtable的值

对某些不支持

null
值的集合添加
null
值元素,比如
ConcurrentHashMap
Hashtable

ConcurrentHashMap<String, String> map = new ConcurrentHashMap();
map.put("a", null);
Hashtable<String, String> hashtable = newHashtable<>();
hashtable.put("a", null);

这些集合的底层

put(K key,V value)
方法中,在
key
或者
value
null
的情况下会造成NPE。

二、怎样防止空指针异常

既然NPE难以避免,我们就要去找各种方法来解决。既要有良好的编码习惯,也要细心的去把控业务。

1. 普通处理

在针对调用业务方法进行NPE普通地防御,可以简单的添加非空判断。

People people = new People();
People user = people.getUser("name");
if (user != null) {
String name = user.getName();
System.out.println(name);
}

2. 定义对象时的初始化

在自己定义对象的时候,注意初始化的值可不可以为

null

// String str = "";
// 初始化为空字符串People people = newPeople();
// 初始化为对象
People user = people.getUser("name");

3. 使用 equals() 方法注意

已知非空对象为调用方,比如将常量值、枚举值作为调用方,避免使用未知对象去调用方法,可有效避免NPE。

String str = "123", string = null;
System.out.println(str.equals(string));

4. 使用 valueOf() 代替 toString() 方法

使用

toString()
方法要利用对象去调用方法,而对象在不清楚是否为
null
的情况下,会抛出NPE。使用
valueOf()
方法可以避免使用未知对象去调用方法来避免。

People people = null;
System.out.println(String.valueOf(people));   // print:null
System.out.println(people.toString());        // NPE

5. 使用开源库非空判断方法

推荐使用各大开源库的

StringUtils
字符串和
CollectionUtils
集合等工具进行非空判断。

String str = null;
List<String> list = null;
if (!StringUtils.isEmpty(str)) {
System.out.println(str);
}
if (!CollectionUtils.isEmpty(list)) {
System.out.println(list);
}

6. 方法返回空集合或空数组

在方法中返回空数组和空集合而不是返回

null
,JDK自带的
Collections
集合工具类提供了多种空集合的定义。

public People[] getUsersArr() {
return new People[]{};
}
public List<People> getUsers() {
// return Collections.emptyMap();
// return Collections.emptySet();
return Collections.emptyList();
}

7. 定义数据库字段是否为空

在一些特定字段根据业务确定是否可为空,以及合理设置默认值。比如:表示业务状态的字段。

CREATE TABLE user{    ...    status NOT NULL DEFAULT 0    ...}

8. 使用JDK1.8的Optional类

在JDK1.8后提供了防止NPE特定的容器,后面会讲到。

三、Optional

Optional
是一个可以包含
null
或者非
null
的容器对象。根据源码分析方法功能:

1. 定义方法

1.1 empty

返回一个空的

Optaional
实例,在这个实例中没有值存在。

public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}

1.2 of

返回一个值不能为空的

Optional
实例,在这个实例中值为
null
时抛出NPE。

public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}

1.3 ofNullable

返回一个值可以为空的

Optional
实例,在这个实例中值为
null
时返回一个空的
Optaional
实例。

public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}

2. 功能方法

2.1 isPresent

如果有值存在,返回

true
,否则返回
false

People people = new People();
System.out.println(Optional.ofNullable(people).isPresent());// print: true
people = null;
System.out.println(Optional.ofNullable(people).isPresent());// print: false

2.2 get()

如果有值存在,返回值,否则抛出

NoSuchElementException

People people = new People();
System.out.println(Optional.ofNullable(people).get());// print: People@61bbe9ba
people = null;
System.out.println(Optional.ofNullable(people).get());// print: Exception in thread "main" java.util.NoSuchElementException: No value present

尽量不要使用该方法获取对象。

2.3 orElse

如果有值存在,返回值,否则返回该

orElse
方法的参数,可以用来定义默认值。

String str = null;
String result = Optional.ofNullable(str).orElse("default");//print:default
System.out.println(result);

2.4 orElseGet

如果有值存在,返回值,否则返回提供者函数,可以用函数返回值来定义默认值。

String str = null;
String result = Optional.ofNullable(str).orElseGet(() -> "ajn");//print:ajn
System.out.println(result);

2.5 orElseThrow

如果有值存在,返回值,否则返回函数接口参数提供的异常。

String str = null;
String result = Optional.ofNullable(str).orElseThrow(IllegalArgumentException::new);// print: Exception in thread "main" java.lang.IllegalArgumentException
System.out.println(result);

关于更多函数接口的内容,关注Java函数式编程

2.6 ifPresent

如果有值存在,方法参数提供的函数接口会进行处理,否则不做任何操作。

Optional.ofNullable(getPeople()).ifPresent(people -> {
System.out.println("the people is nut null: " + people);
});

上面代码等价于:

People people = getPeople();
if (people != null) {
System.out.println("the people is nut null: " + people);
}

2.7 filter

如果有值存在,并且值符合给定的函数条件,返回当前

Optional
,否则返回一个空的
Optaional
实例,可以用来过滤特殊值。

String name = "AiJiangnan";// 给定的条件是字符串包含Ai
String result = Optional.of(name).filter(str -> str.contains("Ai")).orElse("字符串不包含Ai");
System.out.println(result);

2.8 map

如果有值存在,可以将该值的类型转换成其他类型,并返回转换后类型的

Optional
实例,否则返回一个空的
Optaional
实例,可以链式判空,非常实用。

People people = null;
String result = Optional.ofNullable(people)
.map(People::getName)
.map(String::toUpperCase)
.orElse("default");
System.out.println(result);

只有当

people
对象不为
null
,并且
people.getName()
不为
null
,才返回name全部转换为大写的字符串,否则都返回 default。

2.9 flatMap

如果有值存在,可以将该值的类型转换成其他类型,但最终只能转成

Optional
实例,否则返回一个空的
Optaional
实例。该方法与
map
方法类似,只是该方法返回的
Optional
实例由函数参数返回。

People people = new People();
people.setName(" ajn ");
String result = Optional.ofNullable(people)
.flatMap(peo -> Optional.ofNullable(peo.getName()))
.flatMap(str -> Optional.of(str.toUpperCase()))
.orElse("default");
System.out.println(result);
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: