Java8 Optional 源码阅读

栏目: Java · 发布时间: 5年前

内容简介:Optional 是 Java8 引入的一个重要特性,它是一个容器,里面装着一个可能为空可能不为空的对象。在它出现之前,为避免空指针异常我们可能会这样编码:显然代码嵌套层次很深不够整洁。而用 Optional 将值包裹起来后,我们可以不再关注于值会不会为更确切的说,Optional 是一个

前言

Optional 是 Java 8 引入的一个重要特性,它是一个容器,里面装着一个可能为空可能不为空的对象。在它出现之前,为避免空指针异常我们可能会这样编码:

public String getLastFour(Employee employee) {
    if(employee != null) {
        Address address = employee.getPrimaryAddress();
        if(address != null) {
            ZipCode zip = address.getZipCode();
            if(zip != null) {
                return zip.getLastFour()
            }
        }
    }
    throw new FMLException("Missing data");
}

显然代码嵌套层次很深不够整洁。而用 Optional 将值包裹起来后,我们可以不再关注于值会不会为 null ,会不会抛空指针,而将注意力集中在 对数据的操作 。并且 Optional 提供了 mapflatMapfilter 等方法让我们可以进行 函数式风格 的编码。那么上诉代码可以如此改写:

public String getLastFour(Optional<Employee> employee) {
    return employee.flatMap(employee -> employee.getPrimaryAddress())
                   .flatMap(address -> address.getZipCode())
                   .flatMap(zip -> zip.getLastFour())
                   .orElseThrow(() -> new FMLException("Missing data"));
}

更确切的说,Optional 是一个 Monad 容器。那什么是 Monad 呢?可以参考下面这段话: Think of monads as an object that wraps a value and allows us to apply a set of transformations on that value and get it back out with all the transformations applied. 简单的说,Monad 是一个包裹了一个值的容器(值可以是单个对象也可以是集合),允许我们对该值进行一系列的转换(函数操作)后返回给m我们期望的值。也就是说,Monad 封装了接收函数作为入参的一些方法(filter、map、flatMap 等)。

那么现在,让我们从源码开始一步一步揭开它的神秘面纱。

工厂方法

Optional 使用私有化的构造函数和单例模式提供了 值为空的单例 ,暴露的静态工厂方法为 Optional.empty() 。该种设计感觉和 Java 设计模式中的 空对象模式 有异曲同工之妙,也是避免空指针的核心所在,后面我们将会提到。

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

private Optional() {
    this.value = null;
}

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

同时提供 Optional.of(value)Optional.ofNullable(value) 两个工厂方法用于将 value 装入 Optional 容器中。注意 of() 方法在值为空时会抛出 空指针异常 。所以在允许入参值为空时使用 ofNullable() ,它在值为空时调用 Optional.empty() 返回 EMPTY 单例。

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

/**
 * @throws NullPointerException if value is null
 */
private Optional(T value) {
    this.value = Objects.requireNonNull(value);
}

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

isPresent 和 get

Optional 提供的 isPresent() 用于判断容器中的值是否为空, get() 方法用来获取容器中的值。

public boolean isPresent() {
    return value != null;
}

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

这两个方法通常会组合起来使用:

Optional<String> optString = Optional.of("aaa");

if (optString.isPresent()) {
    System.out.println(optString.get());
}

但我想说的是,Optional 的初衷不是为了让我们仅仅使用这两个方法,事实上也应当尽量避免使用这两个方法,而是多使用 ifPresent()orElse() 等方法代替。因为一旦你这样用,就和使用 if(xxx != null){} 没什么区别了。

Optional 真正的核心就在于下面这些方法。

Optional 与函数接口

Optional 对于 Java8 提供的四种 函数接口FunctionPredicateConsumerSupplier 都包装了相应的方法,以满足我们可以进行 函数式 风格的编码。本部分介绍这些方法的实现和使用,为了直观,部分代码使用匿名内部类的方式。

关于函数接口( Functional Interfaces ),我的上一篇博客Java8 函数接口中有详细介绍。

ifPresent

ifPresent(consumer) 方法接受一个 消费者函数 Consumer 入参,如果 Optional 容器中的值不为空,则调用 Consumeraccept(value) 方法消费容器中的值。若值为空则什么都不做。

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

如下代码会打印容器中的值 “abc”

Optional.of("abc")
        .ifPresent(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        }); // print abc

// Lambda
Optional.of("abc").ifPresent(s -> System.out.println(s));

filter

filter(predicate) 方法接受一个 谓词函数 Predicate 入参,调用谓词的 test(value) 判断容器中的值是否满足谓词条件, 满足则返回该容器本身 ,不满足或值为空时返回 Optional.empty() 单例。

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

以下代码分别展示了 filter 通过和不通过的情况:

Optional.of("abc")
        .filter(new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s.length() > 5;
            }
        })
        .ifPresent(System.out::println); // do nothing

// Lambda
Optional.of("abc")
        .filter(s -> s.length() == 3)
        .ifPresent(System.out::println); // print abc

map

map(mapper) 方法接受一个 函数 Function 入参,将容器中的值执行 apply(value) 操作后返回,返回的依然是 Optional 包裹的值,值的类型为 apply() 方法的返回类型,即 map 可能 改变容器中值的类型 。同样,值为空或 apply() 为空则返回 empty()

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

以下代码展示了 map 的用法,通过 .map(s -> s.length()) 返回类型已经从 Optional<String> 变为 Optional<Integer>

Optional.of("abc")
        .map(new Function<String, Integer>() {
            @Override
            public Integer apply(String s) {
                return s.length();
            }
        })
        .ifPresent(System.out::println); // 3

// Lambda
Optional.of("abc").map(s -> s.length()); // Optional<Integer>

flatMap

flatMap 可以视作一种特殊的 map 操作,可以视为 map + flatten 操作。仔细观察,它的入参接受的 Function 函数的输出泛型为 Optional<U> ,所以不需要像 map 方法中再用 ofNullable() 包装成 Optional 容器。

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

mapflatMap 的区别如下。自定义的 optionalLength(string) 返回 Optional<Integer> 类型,所以 map 会返回 Optional<Optional<Integer>> 类型,只有调用 flatMap 才返回 Optional<Integer> 类型,相当于多做了一步 flatten 操作将其拍平为一维纬度。

static Optional<Integer> optionalLength(String s) {
    if (s == null) {
        return Optional.empty();
    }
    return Optional.of(s.length());
}

Optional.of("abc").map(s -> optionalLength(s)); // Optional<Optional<Integer>>
Optional.of("abc").flatMap(s -> optionalLength(s)); // Optional<Integer>

orElseGet 和 orElseThrow

orElseGet(other) 接受 提供者函数 Supplier 作为入参,当容器中的值为空时调用 Supplierget() 方法获取值。 orElseThrow(supplier) 则是值为空时抛出异常。

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

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

使用时如下,若当前容器中的值为空则返回 anotherString() 的值。注意这一步已经跳出了 Optional 容器,返回的类型为容器中值的类型。

static String anotherString() {
    return "anotherString";
}

Optional.of("abc")
        .filter(s -> s.startsWith("zzz")) // Optional.empty()
        .orElseGet(new Supplier<String>() {
            @Override
            public String get() {
                return anotherString();
            }
        }); // "anotherString"
        
Optional.of("abc")
        .filter(s -> s.length() == 3) // Optional["abc"]
        .orElseGet(() -> anotherString()); // "abc"

Optional.of("abc")
        .filter(s -> s.startsWith("zzz")) // Optional.empty()
        .orElseThrow(() -> new RuntimeException("no eligible values"));

总结

Optional 提供的这些方法让我们不再关注于是否会抛出空指针异常,而是通过函数的自由组合专注于对数据的操作,比如如下代码:

Optional.ofNullable(people)
        .flatMap(people -> people.getName())
        .map(name -> name.toUpperCase())
        .filter(name -> name.startsWith("T"))
        .orElse("TONY");

其实 Optional 的核心在于提供的 empty() 单例,使得如上代码整条链路不会在某一步中断抛出空指针异常,而是只要其中一步值为空后, Optional.empty() 将在这条链路上继续向下传播 ,直到最后 orElse() 等方法指名默认处理并跳出容器。

Optional 源码时有诸多值得我们借鉴取经的地方,诸如:

  • Monad 容器的思想。容器接收一个值,进行函数操作后依然返回该容器,比如 mapflatMapfilter 。这就使得我们可以随意组合以上方法,编写函数式风格的代码。类似的还有 Java 8 的 Stream 接口。

  • 提供 Optional.empty() 类似空对象模式的空值单例。

  • 严谨的编码,源码中有许多 Objects.requireNonNull() 对极端情况进行验证,比如 flatMap 方法中的 Objects.requireNonNull(mapper);Optional.ofNullable(mapper.apply(value)); 对于入参函数不为空和调用 apply() 后返回值不为空的验证。

  • API 的全面性,当容器中没有你期待的值时提供 orElseorElseGetorElseThrow 等其他处理手段,不管是设置默认值还是抛出异常,API 都考虑到了。

目前在一些 ORM 框架中也加入了 Optional 的支持,比如 Hibernate 从 5.2 版本后提供 loadOptional() 方法返回 Optional<T> 类型。有兴趣的可以自己去研究。

参考


以上所述就是小编给大家介绍的《Java8 Optional 源码阅读》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Servlet与JSP核心编程

Servlet与JSP核心编程

[美]Marty Hall、Larry Brown、Yaakov Chalkin / 胡书敏 / 2009-6 / 68.00元

《Servlet与JSP核心编程(第2卷 第2版)》在第l卷的基础上,广泛涉及自定义标签库、过滤器、声明式安全、JSTL和Struts等主题,并沿袭深受读者喜爱的写作风格,通过完整、有效、资料丰富的程序来演绎目前最流行的技术和最佳实践。Java EE已经成为电子商务网站、动态网站和Web应用与服务开发的首选,作为这一平台的基础,servlet与JSP的重要性日益突出,并在极短的时间内得以迅速普及。......一起来看看 《Servlet与JSP核心编程》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

html转js在线工具
html转js在线工具

html转js在线工具