侧边栏壁纸
  • 累计撰写 101 篇文章
  • 累计创建 89 个标签
  • 累计收到 9 条评论

Java8新特性 - 判空的Optional 类

bearjun
2022-04-06 / 0 评论 / 0 点赞 / 1,219 阅读 / 8,123 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-04-11,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

你还在为空指针异常(NullPointerException)而烦恼吗?你在为判空的代码冗余而寻找新的解决方案吗?今天它来了:Java8中Optional类

Optional类是Java8为了解决null值判断问题,借鉴google guava类库的Optional类而引入的一个同名Optional类。这时Java 8的Optional就发挥作用了,允许我们返回一个空的对象。

Optional类

在没有Optional之前,我们获取对象都需要判断,不然就会出现恶心的NPE。

// 获取学生的分数,容易出现空指针
int score = school.getClasses().getStudent().getScore();

// 加入判空之后的代码
if (school != null) {
    Classes classes = school.getClasses();
    if (classes != null) {
        Student student = classes.getStudent();
        if (student != null){
            int score = student.getScore();
        }
    }
}

使用了optional之后会变成啥样呢?我们最后再看。这里,我们先看看Optional的部分源码:

public final class Optional<T> {
    /**
     * Common instance for {@code empty()}.
     */
    private static final Optional<?> EMPTY = new Optional<>();

    /**
     * If non-null, the value; if null, indicates no value is present
     */
    private final T value;

    /**
     * Constructs an empty instance.
     *
     * @implNote Generally only one empty instance, {@link Optional#EMPTY},
     * should exist per VM.
     */
    private Optional() {
        this.value = null;
    }

    /**
     * Returns an empty {@code Optional} instance.  No value is present for this
     * Optional.
     *
     * @apiNote Though it may be tempting to do so, avoid testing if an object
     * is empty by comparing with {@code ==} against instances returned by
     * {@code Option.empty()}. There is no guarantee that it is a singleton.
     * Instead, use {@link #isPresent()}.
     *
     * @param <T> Type of the non-existent value
     * @return an empty {@code Optional}
     */
    public static<T> Optional<T> empty() {
        @SuppressWarnings("unchecked")
        Optional<T> t = (Optional<T>) EMPTY;
        return t;
    }

    /**
     * Constructs an instance with the value present.
     *
     * @param value the non-null value to be present
     * @throws NullPointerException if value is null
     */
    private Optional(T value) {
        this.value = Objects.requireNonNull(value);
    }

    /**
     * Returns an {@code Optional} with the specified present non-null value.
     *
     * @param <T> the class of the value
     * @param value the value to be present, which must be non-null
     * @return an {@code Optional} with the value present
     * @throws NullPointerException if value is null
     */
    public static <T> Optional<T> of(T value) {
        return new Optional<>(value);
    }

    /**
     * Returns an {@code Optional} describing the specified value, if non-null,
     * otherwise returns an empty {@code Optional}.
     *
     * @param <T> the class of the value
     * @param value the possibly-null value to describe
     * @return an {@code Optional} with a present value if the specified value
     * is non-null, otherwise an empty {@code Optional}
     */
    public static <T> Optional<T> ofNullable(T value) {
        return value == null ? empty() : of(value);
    }
    // 
    // ......
}

通过上面的部分源码可以看出,Optional 的构造方法都是私有的。但是我们可以通过empty()of()ofNullable()来创建Optional。Optional的源码比较简单,就是对判断对象多了一层封装,建议小伙伴们看看源码。

empty、ofNullable、of

我们先来说说结论:

  • empty:创建一个包装对象值为空的Optional对象
  • of:创建包装对象值允许为空的Optional对象
  • ofNullable:创建包装对象值非空的Optional对象

实际开发中,看个人的选择,其实ofNullable用的更多一些。

empty

// empty
Optional<Object> empty = Optional.empty();

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

empty就是创建一个空的Optional实例。那你肯定会问,那这个没用啊,我想创建有值的呢?不着急,慢慢往后看。

of

// of
Optional<Object> of1 = Optional.of(1);
System.out.println(of1); // Optional[1]

Optional<Object> of2 = Optional.of("string");
System.out.println(of2); // Optional[string]

Optional<Object> of3 = Optional.of(null);
System.out.println(of3); // 抛出异常:java.lang.NullPointerException

// Optional部分源码
private Optional(T value) {
    this.value = Objects.requireNonNull(value);
}

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

我们可以看出,Optional可以构造任意一个对象,Optional.of(T t)传入一个非空的对象老构建一个Option。

ofNullable

// ofNullable
Optional<Integer> integer = Optional.ofNullable(1);
System.out.println(integer); // Optional[1]

Optional<String> string = Optional.ofNullable("string");
System.out.println(string); // Optional[string]

Optional<Object> o = Optional.ofNullable(null);
System.out.println(o); // Optional.empty

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

我们可以看出,当传入的对象为null的时候,调用了empty()构造,当对象不为空时,调用了of()来进行构造,也就是为什么允许为空的构造。

get、isPresent

上面对象都构造出来了,那肯定需要判断对象是有值或者取值。

  • get:get()方法主要用于返回包装对象的实际值。为null则抛出NPE。
  • isPresent:用于判断包装对象的值是否非空。
// get 从Optional获取值
Integer integer = Optional.ofNullable(1).get();
System.out.println(integer); // 1

String s = Optional.ofNullable("字符串").get();
System.out.println(s); // 字符串

Object o = Optional.ofNullable(null).get();
System.out.println(o); // 抛出异常 java.util.NoSuchElementException: No value present

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


// isPresent 判断Optional是否为空
boolean present = Optional.ofNullable(1).isPresent();
System.out.println(present); // true

boolean present1 = Optional.ofNullable(null).isPresent();
System.out.println(present1); // false

// isPresent的源码
public boolean isPresent() {
    return value != null;
}

看着上面的代码,是不是感觉很简单。那有小伙伴说了,那我这样写:

Optional<User> u = Optional.ofNullable(user);
if (u.isPresent()){
    return u.get().getUserId();
}
return null;

我看你真是大聪明,都这样了,那Optional还封装啥。不着急,我们慢慢往后看,有解决方法的。

orElse、orElseGet、orElseThrow

这三个都是在Optional不存在的前提下,进行的给默认值操作。

  • orElse:当Optional为空时,默认的值。
  • orElseGet:当Optional为空时,默认的进行函数式操作的。
  • orElseThrow:当Optional为空时,抛出异常。
Student s = null;
// 当Student对象为空时,默认创建一个新的对象
Student student = Optional.ofNullable(s).orElse(new Student("1", "n=bearjun", "100"));
System.out.println(student);

// 当Student对象为空时,创建一个新的对象
Student student2 = Optional.ofNullable(s).orElseGet(() -> {
    return new Student("1", "n=bearjun", "100");
});
System.out.println(student2);

// 当Student对象为空时,抛出异常
Optional.ofNullable(s).orElseThrow(() -> {
    throw new RuntimeException("学生信息不存在");
});

// orElse的源码
public T orElse(T other) {
    return value != null ? value : other;
}

// orElseGet的源码
public T orElseGet(Supplier<? extends T> other) {
    return value != null ? value : other.get();
}

// orElseThrow的源码
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
    if (value != null) {
        return value;
    } else {
        throw exceptionSupplier.get();
    }
}

filter、map、flatMap

这三个都是在Optional存在的前提下,进行一些操作。

  • filter:过滤值。接受一个Predicate参数,判断是否成立。
  • map:转换值。接受一个Function参数,通过传入的值转换操作成需要的值。
  • flatMap:接受一个Function参数,通过传入的值转换操作成需要的值。

filter

filter() 接受一个 Predicate 参数,返回测试结果为 true 的值。如果测试结果为 false,会返回一个空的 Optional。

// filter过滤
User user = new User();
Optional<User> user1 = Optional.ofNullable(user).filter(u -> "bearjun".equals(u.getUserId()));
System.out.println(user1.isPresent()); // false

// filter源码
public Optional<T> filter(Predicate<? super T> predicate) {
    Objects.requireNonNull(predicate);
    if (!isPresent())
        return this;
    else
        return predicate.test(value) ? this : empty();
}

map、flatMap

map() 对值Function作为参数的函数,然后将返回的值包装在 Optional 中。这样,改返回值进行链试调用的时候,orElse()可以对值进行操作。
相比于map,flatMap() 也需要Function作为参数,并对值调用这个函数,然后直接返回调用后的结果。

School school = new School(new Classes(new Student("1", "bearjun", "100")));

String s1 = Optional.ofNullable(school)
        .map(School::getClasses)
        .map(Classes::getStudent)
        .map(Student::getScore)
        .orElse("0");
System.out.println(s1); // 100

String s2 = Optional.ofNullable(school)
        .flatMap(s -> {
            return Optional.ofNullable(s.getClasses());
        })
        .flatMap(c -> {
            return Optional.ofNullable(c.getStudent());
        })
        .flatMap(stu -> {
            return Optional.ofNullable(stu.getScore());
        })
        .orElse("0");

System.out.println(s2); // 100

// map的源码
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));
    }
}

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

看到这里,相信小伙伴们已经知道了flatMap和map的区别。

  • map的返回值是Optional<T>flatMap相对于map多了一层Optional<T>封装。
  • flatMap的返回值是Optional<Optional<T>>

实战建议

实战一:当List查询为null时,默认返回一个空的集合

public List<Student> stuList() {
    List<Student> stuList = studentMapper.list();
    return Optional.ofNullable(stuList)
            // 做一些其他的操作
            //.map()
            //.filter()
            .orElse(new ArrayList<>());
}

反正我们的前端是一直在和我说这个问题,当集合为空时,最好默认返回一个空的集合。

没用Optional前那个很多if判断的代码改进

public int getScore(School school){
    return Optional.ofNullable(school)
        .map(School::getClasses)
        .map(Classes::getStudent)
        .map(Student::getScore)
        .orElse("0");
}

这样写有点优雅,有没有。

总结

Optional是Java语言的对我们在代码中减少NullPointerExceptions的一个补充,并不能完全消除这些异常。

Optional虽然有的代码写起来很优雅和美观。但是代码的可读性较差,所以选择的时候还要考虑到自身的原因是否使用。

0

评论区