了不起的Java-Optional替代null处理

Optional 类主要解决的问题是臭名昭著的空指针异常(NullPointerException)。是一个包含有可选值的包装类,这意味着 Optional 类既可以含有对象也可以为空。
在这段代码就可能产生空异常;

String isocode = user.getAddress().getCountry().getIsocode().toUpperCase();
//需要检查:
if (user != null) {
    Address address = user.getAddress();
    if (address != null) {
        Country country = address.getCountry();
        if (country != null) {
            String isocode = country.getIsocode();
            if (isocode != null) {
                isocode = isocode.toUpperCase();
            }
        }
    }
}

Optional类的依赖依然还是函数接口那一套东西:

import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

 

是要把面向接口编程走到底了。私有字段只有一个:

private final T value;

原生代码还是相当简单的:

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

    public static <T> Optional<T> of(T value) {
        return new Optional<>(value);
    }

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

    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));
        }
    }
    ......

创建 Optional 包装实例

建空

        Optional<Insurance> emptyOpt = Optional.empty();
        try {
            Insurance obj = emptyOpt.get();
            System.out.println("不为空");
        } catch (NoSuchElementException ex) {
            System.out.println("为空");
        }

尝试访问 emptyOpt 变量的值会导致 NoSuchElementException,因为原声代码:

    public T get() {
        if (value == null) {
            throw new NoSuchElementException("No value present");
        }
        return value;
    }

建可能为空(ofNullable不抛异常)

Optional<Insurance> opt = Optional.ofNullable(null);

不会异常
如此神奇,原因在于原生代码已经进行了判空,把私有value定成了empty(),这种不抛异常也会产生一个问题,那就是在消费实例的时候,还是要过滤掉空值。

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

建为空时抛异常(即of不可为空)

    public static void whenCreateOfEmptyOptional_thenNullPointerException() {        
        Insurance ins = new Insurance();        
        Optional<Insurance> opt = Optional.of(ins);
        ins=null;
        try {
            opt = Optional.of(ins);
            System.out.println("不为空");
        } catch (NullPointerException ex) {
            System.out.println("为空");
        }        
    }

会进入异常。跟踪原生代码发现了原因:

    public static <T> T requireNonNull(T obj) {
        if (obj == null)
            throw new NullPointerException();
        return obj;
    }

访问 Optional 对象的值

get

从 Optional 实例中取回实际值对象的方法之一是使用 get() 方法:

@Test
public void whenCreateOfNullableOptional_thenOk() {
String name = "John";
Optional<String> opt = Optional.ofNullable(name);

assertEquals("John", opt.get());
}

这个方法会在值为 null 的时候抛出异常。要避免异常,你可以选择首先验证是否有值:

@Test
public void whenCheckIfPresent_thenOk() {
User user = new User("john@gmail.com", "1234");
Optional<User> opt = Optional.ofNullable(user);
assertTrue(opt.isPresent());

assertEquals(user.getEmail(), opt.get().getEmail());
}

ifPresent的使用

原生代码:

public void ifPresent(Consumer<? super T> consumer) {
    if (value != null)
        consumer.accept(value);
}

只有当值不为空,才执行消费者方法accept,这是比较安全的。

    public static void printName(Insurance obj)
    {
        Optional.ofNullable(obj).ifPresent(u ->  System.out.println("The name is : " + u.getName()));
    }

        Insurance obj = new Insurance();
        obj.setName("张三");
        Insurance objNull = null;        
        printName(obj);
        printName(objNull);

//输出:The name is : 张三

isPresent的使用

感觉有了ifPresent,不需要再用isPresent来判断空了,否则感觉非常啰嗦。

orElse的使用

在对象为空的时候返回默认值。它的工作方式非常直接,如果有值则返回该值,否则返回传递给它的参数值。

Insurance obj = new Insurance();
obj.setName("张三");
Insurance objNull = null;    
Insurance result = Optional.ofNullable(objNull).orElse(obj);
System.out.println(obj.hashCode());
System.out.println(result.hashCode());

result实际是返回的obj,而不是null。

filter的使用

filter()方法接受参数为Predicate对象,用于对Optional对象进行过滤,如果符合Predicate的条件,返回Optional对象本身,否则返回一个空的Optional对象

public static void filterAge(Student student)
{
    Optional.ofNullable(student).filter( u -> u.getAge() > 18).ifPresent(u ->     System.out.println("The student age is more than 18."));
}

map的使用

map()方法的参数为Function(函数式接口)对象,map()方法将Optional中的包装对象用Function函数进行运算,并包装成新的Optional对象(包装对象的类型可能改变)
原生代码:

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));
        }
    }

下面代码中,先用ofNullable()方法构造一个Optional<Student>对象,然后用map()计算学生的年龄,返回Optional<Integer>对象(如果student为null, 返回map()方法返回一个空的Optinal对象)

public static Optional<Integer> getAge(Student student)
{
    return Optional.ofNullable(student).map(u -> u.getAge()); 
}

flatMap的使用

跟map()方法不同的是,入参Function函数的返回值类型为Optional<U>类型,而不是U类型,这样flatMap()能将一个二维的Optional对象映射成一个一维的对象,
总而言之,map进去是什么,出来维度不变,flatMap是为了拍扁结果。

public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Objects.requireNonNull(mapper.apply(value));
        }
    }

//例子
        Optional<String> nonEmptyGender = Optional.of("male");
        Optional<String> emptyGender = Optional.empty();

        System.out.println("Non-Empty Optional:: " + nonEmptyGender.map(String::toUpperCase));
        System.out.println("Empty Optional    :: " + emptyGender.map(String::toUpperCase));

        Optional<Optional<String>> nonEmptyOtionalGender = Optional.of(Optional.of("male"));
        System.out.println("Optional value   :: " + nonEmptyOtionalGender);
        System.out.println("Optional.map     :: " + nonEmptyOtionalGender.map(gender -> gender.map(String::toUpperCase)));
        System.out.println("Optional.flatMap :: " + nonEmptyOtionalGender.flatMap(gender -> gender.map(String::toUpperCase)));

封装可能的空值

使用ofNullable,比如在一些map中的key为空的情况下使用:

Optional<Insurance> opt = Optional.ofNullable(map.get("someNullKey"));

 

posted @ 2020-03-04 17:42  昕友软件开发  阅读(1421)  评论(0编辑  收藏  举报
欢迎访问我的开源项目:xyIM企业即时通讯