文章目录
一、Optional概述
1、烦人的NullPointerException
在日常开发中,NullPointerException相信所有人都见过,不管你是刚入行的萌新还是骨灰级玩家,对于它都是耳熟能详的。它的出现可以说无处不在,总是能在各种场景下出现。那么对于如何防止它的出现,我们平时都是被动的采用各种非空校验,但是它还是经常能出现在我们的视线中。
public String getCompanyName(Student student){
if (student != null){
Job job = student.getJob();
if (job != null){
Company company = job.getCompany();
if (company != null){
String name = company.getName();
return name;
}else {
return "no company";
}
}else {
return "no job";
}
}else {
return "no student";
}
}
对于上述这段代码,相信大家平时工作类似的代码经常会有出现。每一次在获取到一个对象时都进行一个null的判断,然后才继续后续的实现。但是这种方式很不好,首先会存在大量的if-else判断嵌套,导致代码的可读性和扩展性极差。此时,有的同学可能就会使用卫语句这么改造,如下所示:
public String getCompanyName(Student student){
if (student == null){
return "no student";
}
Job job = student.getJob();
if (job == null){
return "no job";
}
Company company = job.getCompany();
if (company == null){
return "no company";
}
return company.getName();
}
这种判断已经有意识的避免了大量嵌套判断,但是同样存在多个不同的判断点,代码维护同样困难。
2、Optional简介
为了防止空指针异常的出现,Java8中引入了一个新类Optional
,对于它之前我们已经进行了简单的实现。其本质就是通过Optional类对值进行封装, 当有值的时候,会把该值封装到Optional类中。如果没有值的话,则会在该类封装一个Empty。
二、Optional使用
在Optional类中基于函数式接口
提供了一些用于操作值的方法。
1、创建Optional对象
创建Optional,该类提供了三种方法操作,分别为:empty()、of()、ofNullable()。使用方式如下所示:
Optional<Student> studentOptional = Optional.empty();
Optional<Student> studentOptional = Optional.of(student);
Optional<Student> studentOptional = Optional.ofNullable(student);
这三者有什么区别?根据源码分析如下:
// 源码分析
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY; // private static final Optional<?> EMPTY = new Optional<>();
return t;
}
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
根据源码可知,empty()
会直接返回一个空的Optional实例,内部不会存在任何值。
of()会返回一个存在值的Optional对象,并且该值不允许null的存在。如果调用该方法时传入参数是null,则立刻抛出NullPointerException
,而不是等到你用这个对象时才抛出,相当于进行了立即检查。
ofNullable()同样也会返回一个存在值的Optional对象,但是它和of()最大的不同在于,它会对传入的值进行判断,如果传入的值为null,其会调用empty()返回一个不包含内容的Optional,如果不为null,则会调用of()返回一个包含内容的Optional。
2、isPresent()与ifPresent()应用&源码解析
Optional类中提供了两个方法用于判断Optional是否有值,分别是isPresent()和ifPresent(Consumer<? super T> consumer)
。其一般与ofNullable()搭配使用,因为of()在创建时已经完成了判断,而empty()只是单纯了实例化了一个Optional对象。
// 使用实例
public class PresentDemo {
public static void getStudentName(Student student){
Optional<Student> optional = Optional.ofNullable(student);
if (optional.isPresent()){
//student不为null
Student student1 = optional.get();
System.out.println(student1);
}else {
System.out.println("student为null");
}
optional.ifPresent(s-> System.out.println(s));
}
public static void main(String[] args) {
Student student = new Student(1,"zhangsan","M");
getStudentName(student);
}
}
// 源码分析
// isPresent()内部非常简单,就是判断这个值是否为null。
public boolean isPresent() {
return value != null;
}
public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
consumer.accept(value);
}
ifPresent()方法在执行时,接收一个consumer函数式接口
,如果value不为null,则通过consumer中的accept方法获取该值。
3、get()应用&源码解析
get()的使用非常简单,但不安全,因为其在获取值的时候,如果值存在,则直接返回封装在Optional中的值,如果不存在,则抛出NoSuchElementException。因此它的使用前提是已经确定Optional中有值,否则没有使用意义。
// 使用实例
Optional<Student> studentOptional = Optional.ofNullable(student);
if (studentOptional.isPresent()){
Student result = studentOptional.get();
}
// 源码分析
public Tget() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
4、orElseThrow()应用&源码解析
该方法与get()类似,都是用于取值,但是当Optional中没有值时,get()会直接抛出NoSuchElementException,这样的话,就存在了一定的局限性,因为有时可能需要抛出自定义异常。此时就可以使用orElseThrow(),它在取值时,如果Optional中没有值时,可以抛出自定义异常。
// 使用实例
Optional<Student> optional = Optional.ofNullable(student);
try {
// null 就抛异常
Student result = optional.orElseThrow(MyException::new);
System.out.println(result);
} catch (MyException e) {
e.printStackTrace();
}
// 源码分析
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else { // null 的话就抛异常
throw exceptionSupplier.get();
}
}
5、map()应用&源码解析
map()可以实现类型转换,与JDK8的Stream的map类似,只不过一个是转换Stream的泛型,一个是转换Optional的泛型。
// 使用案例
if (studentOptional.isPresent()){
// Student类型转为String类型
Optional<String> nameOptional = studentOptional.map(Student::getName);
}
// 源码分析
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));
}
}
6、flatMap()应用&源码解析
刚才已经通过map()获取了学生的姓名,操作非常简单。但是当产生链路获取时,map可以使用么? 如:学生->工作->公司->公司名称。
现在可能大家脑袋里已经有了一个想法,就是通过map(),代码结构如下:
studentOptional.map(Student::getJob).map(Job::getCompany).map(Company::getName);
但是这段代码是无法通过编译的。因为根据map的学习,每一次在调用的时候,都会对Optional的泛型进行改变,最终产生多层Optional嵌套的结构。
对于这个问题的解决,Optional类中提供了另外一个获取值的方法flatMap()。它本身用于多层调用,同时对于结果它不会形成多个Optional,而是将结果处理成最终的一个类型的Optional。但是通过flatMap获取的返回值必须是Optional类型。而map则没有这个限制。
// 使用实例
Optional<String> nameOptional = studentOptional.flatMap(Student::getJob).flatMap(Job::getCompany).map(Company::getName);
// 源码分析
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {// 多次调用不会生成Optional嵌套
return Objects.requireNonNull(mapper.apply(value));
}
}
7、filter()应用&源码解析
Optional类中提供了数据过滤
的方法filter()来实现这个需求。其会根据传入的条件进行判断,如果匹配则返回一个Optional对象并包含对应的值,否则返回一个空值的Optional。
// 使用实例
Optional<Company> company = companyOptional.filter(c -> "bank".equals(c.getName()));
源码是传入一个Predicate,如果不为null的话,就会调用predicate的test方法做判断。
// 源码分析
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();
}
8、orElse()应用&源码解析
在取值的时候,如果值不存在,有时我们会考虑返回一个默认值
。该需求就可以通过orElse()实现。
其内部会判断值是否为null,如果不为null,则返回该值,如果为null,则返回传入的默认值。
// 使用案例
String value = studentOptional.flatMap(Student::getJob).flatMap(Job::getCompany).map(Company::getName).orElse("default value");
// 源码分析
publicT orElse(T other) {
// null的话取默认值
return value != null ? value : other;
}
9、orElseGet()应用&源码解析
orElseGet()也是用于当Optional中没有值时,返回默认值的方法。但是它与orElse()的区别在于,它是延迟加载
的。只有当Optional中没有值是才会被调用。
// 源码分析
public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}
// 代码案例
System.out.println(Optional.ofNullable("student").orElse(getStr("a")));
System.out.println(Optional.ofNullable(null).orElse(getStr("b")));
System.out.println(Optional.ofNullable("student").orElseGet(() ->getStr("a")));
System.out.println(Optional.ofNullable(null).orElseGet(() ->getStr("b")));
我们发现, 当数据不为null的时候,orElseGet不会执行。
所以在使用时,更加推荐使用orElseGet(),因为它使用延迟调用所以性能更加优异。
10、orElseThrow()应用&源码分析
orElseThrow很简单,如果为null的话,就抛一个异常。
// 源码分析
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}