Java8 - Optional 类

防止 NPE ,是程序员的基本修养,注意 NPE 产生的场景,使用 JDK8 的 Optional 类来防止 NPE 问题。

Optional 类是Java8为了解决null值判断问题,借鉴google guava类库的Optional类而引入的一个同名Optional类,使用Optional类可以避免显式的null值判断(null的防御性检查),避免null导致的NPE(NullPointerException)。

Optional类的两个构造方法都是private型的,因此类外部不能显示的使用new Optional()的方式来创建Optional对象,但是Optional类提供了三个静态方法 empty()、of(T value)、ofNullable(T value) 来创建Optinal对象,示例如下:

package test;

import java.util.Optional;

public class MainTest {

	public static void main(String[] args) {
		// 1、创建一个包装对象值为空的Optional对象
		Optional<Student> studentOpt = Optional.empty();
		// 2、创建包装对象值非空的Optional对象
		Optional<Student> studentOpt1 = Optional.of(new Student());
		// 3、创建包装对象值允许为空的Optional对象
		Optional<Student> studentOpt2 = Optional.ofNullable(null);
		
		System.out.println(studentOpt.get().getName()); // 打印:异常,Exception in thread "main" java.util.NoSuchElementException: No value present 
		System.out.println(studentOpt.orElse(new Student()).getName()); // 打印:测试
		
		System.out.println(studentOpt1.get().getName()); // 打印:测试
		
		System.out.println(studentOpt2.get().getName()); // 打印:异常,Exception in thread "main" java.util.NoSuchElementException: No value present  
		System.out.println(studentOpt2.orElse(new Student()).getName()); // 打印:测试 
	}

}

class Student {
	private String sex = "男";
	private String name = "测试";
	
	public String getName() {
		return name;
	}
	
	public String getSex() {
		return sex;
	}
}

上图的运行示例可以看出,Optional 可以创建任意类型的对象,并且通过 get()方法获取特定的对象,创建 Optional 为空时,获得的对象就是空的,但是可以通过 ** orElse**来判断,如果为空,再通过orElse 的参数重新创建。

System.out.println(studentOpt.isPresent()); // 打印:false
System.out.println(studentOpt1.isPresent()); // 打印:true

isPresent() 方法用于判断 Optional 封装的对象是否为空。

Student studentNull = null;
Student student = new Student();
Optional.ofNullable(studentNull).ifPresent(u ->  System.out.println("这个对象是 null 的"));
Optional.ofNullable(student).ifPresent(u ->  System.out.println("这个对象是不是NULL的"));

// 打印结果:这个对象是不是NULL的

ifPresent() 方法接受一个Consumer对象(消费函数),如果包装对象的值非空,运行Consumer对象的accept()方法。这样的话就可以愉快的用 Lambda 表达式了

Student studentNull = null;
Student student = new Student();
Optional.ofNullable(studentNull).filter(u -> "测试".equals(u.getName())).ifPresent(u ->  System.out.println("studentNull,名字与“测试”目标相等"));
Optional.ofNullable(student).filter(u -> "测试".equals(u.getName())).ifPresent(u ->  System.out.println("student,名字与“测试”目标相等"));
Optional.ofNullable(student).filter(u -> "不测试不测试".equals(u.getName())).ifPresent(u ->  System.out.println("student,名字与“不测试不测试”目标相等"));

// 打印结果:student,名字与“测试”目标相等

filter()方法也是非常实用的,这个方法用于对 Optional 对象进行过滤,如果符合条件的,返回Optional对象本身,否则返回一个空的Optional对象。

System.out.println(Optional.ofNullable(student).map(u -> u.getName()).get());
System.out.println(Optional.ofNullable(studentNull).map(u -> u.getName()));
/*
* 打印结果
	测试
	Optional.empty
* */

map()方法的参数为Function(函数式接口)对象,map()方法将Optional中的包装对象用Function函数进行运算,并包装成新的Optional对象(包装对象的类型可能改变)。
上述代码中,先用ofNullable()方法构造一个Optional对象,返回Optional对象(如果 student 为 null, 返回 map() 方法返回一个空的 Optinal 对象)。

System.out.println(Optional.ofNullable(student).map(u -> u.getName()).get());
System.out.println(Optional.ofNullable(studentNull).map(u -> u.getName()).get());
		
/*
* 打印结果
	测试
    Exception in thread "main" java.util.NoSuchElementException: No value present
		at java.base/java.util.Optional.get(Optional.java:141)
		at test.MainTest.main(MainTest.java:21)
* */

运行上面两条语句,可以发现同样出现了空指针异常,这样的用法还是没有避免 NPE 的产生,因为Optional.ofNullable(studentNull).map(u -> u.getName()) 避免了 studentNull 是空带来的异常,但是 u.getName() 依旧获取不到正常的值(实际上是这个 Optional.empty),所以在get()时出现异常,这样用起来还是不爽,所有需要另一个方法来解决这个问题。我们对上面的代码进行修改。

System.out.println(Optional.ofNullable(student).map(u -> u.getName()).orElse("这个可能是空"));
System.out.println(Optional.ofNullable(studentNull).map(u -> u.getName()).orElse("这个一定是空"));
		
/*
* 打印结果
	测试
	这个一定是空
 * */

用 orElse 方法替代 get 方法,orElse()方法功能比较简单,即如果包装对象值非空,返回包装对象值,否则返回入参other的值(默认值)。
相似的还有 orElseGet() 方法,区别在于 orElseGet() 方法的入参为一个Supplier对象,用Supplier对象的get()方法的返回值作为默认值。如:

public static String getGender(Student student) {
 	return Optional.ofNullable(student).map(u -> u.getGender()).orElseGet(() -> "Unkown");      
}