Java8 Optional 源码阅读

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

内容简介: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 源码阅读》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

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

The Definitive Guide to HTML5 WebSocket

The Definitive Guide to HTML5 WebSocket

Vanessa Wang、Frank Salim、Peter Moskovits / Apress / 2013-3 / USD 26.30

The browser is, hands down, the most popular and ubiquitous deployment platform available to us today: virtually every computer, smartphone, tablet, and just about every other form factor imaginable c......一起来看看 《The Definitive Guide to HTML5 WebSocket》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

SHA 加密
SHA 加密

SHA 加密工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具