emanjusaka —— 彼岸花开可奈何

彼岸花开可奈何

对 JDK8 新出的Optional类的探索与思考

45
2023-08-23

by emanjusaka from https://www.emanjusaka.top/2023/08/jdk8-optional-thinking 彼岸花开可奈何
本文欢迎分享与聚合,全文转载请留下原文地址。

引言

所有的 Java 程序员基本都会遇到 NullPointerException 异常,一般处理这个问题可能不会是很难,但是有时去排查到底是哪引起的会耗费很长时间很是麻烦。最近了解到 JDK1.8 新增加了一个 Optional 类可以避免一些 NullPointerException 异常,下面让我们一起去了解一下它吧。

基于值的类(Value-based Classes)

有些类,如 java.util.Optional 和 java.time.LocalDateTime,是基于值的。基于值的类的实例:

  • 是最终的和不可变的(尽管可能包含对可变对象的引用);
  • 具有 equals、hashCode 和 toString 的实现,这些实现仅根据实例的状态计算,而不是根据实例的标识或任何其他对象或变量的状态计算;
  • 不使用对身份敏感的操作,例如实例之间的引用相等(==)、实例的身份哈希代码或对实例的内部锁进行同步;
  • 仅基于 equals() 而不是基于引用相等 (==) 被视为相等;
  • 没有可访问的构造函数,而是通过工厂方法进行实例化,这些方法不提交返回实例的标识;
  • 当相等时是可自由替换的,这意味着在任何计算或方法调用中,根据 equals() 交换任何两个相等的实例 x 和 y 都不会产生明显的行为变化。

如果程序试图区分对基于值的类的相等值的两个引用,无论是直接通过引用相等,还是间接通过调用同步、身份哈希、序列化或任何其他身份敏感机制,都可能产生不可预测的结果。在基于值的类的实例上使用这种对身份敏感的操作可能会产生不可预测的影响,应该避免。

简单地说,基于值的类的实例是最终的,不可变的,并且这些实例没有适当的状态和标识,因此某些操作是特定于标识的,因此不应使用。

一、Optional中的基本方法

Optional 类位于 java.util 包下,它是一个容器对象,可能包含也可能不包含非空值。

这是一个基于值的类;在Optional实例上使用身份敏感操作(包括引用相等(==)、身份哈希码或同步)可能会产生不可预测的结果,应该避免。

1、创建方法

  • empty()

    返回一个空的 Optional 实例

    public class Main {
        public static void main(String[] args) {
            System.out.println(Optional);
        }
    }
    // 输出
    Optional.empty
    

    **注意:**不要通过与Option.empty()返回的实例进行==比较来避免测试对象是否为空,因为不能保证它是单例的。

  • of(T value)

    返回一个带值的 Optional,如果 value 是 null 会抛出 NullPointerException 异常

    public static void main(String[] args) {
            Optional<String> emanjusaka = Optional.of("emanjusaka");
            System.out.println(emanjusaka);
        }
    // 输出
    Optional[emanjusaka]
    
  • ofNullable(T value)

    如果非空,返回描述指定值的 Optional,否则返回空 Optional。

    public static void main(String[] args) {
            Optional<String> emanjusaka = Optional.ofNullable("emanjusaka");
            System.out.println(emanjusaka);
    
            Optional<String> empty = Optional.ofNullable(null);
            System.out.println(empty);
        }
    // 输出
    Optional[emanjusaka]
    Optional.empty
    

2、判断方法

  • isPresent()

    如果存在值则返回 true,否则返回 false。

    public static void main(String[] args) {
            Optional<String> emanjusaka = Optional.ofNullable("emanjusaka");
            System.out.println(emanjusaka);
            System.out.println(emanjusaka.isPresent());
    
            Optional<String> empty = Optional.ofNullable(null);
            System.out.println(empty);
            System.out.println(empty.isPresent());
        }
    //输出
    Optional[emanjusaka]
    true
    Optional.empty
    false
    
  • ifPresent(Consumer<? super T> consumer)

    如果存在值,则使用该值调用指定的消费者,否则什么都不做。

    public static void main(String[] args) {
            Optional<String> emanjusaka = Optional.of("emanjusaka");
            Consumer<String> consumer = s -> {
                s = "hello " + s;
                System.out.println(s);
            };
            System.out.println(emanjusaka);
            emanjusaka.ifPresent(consumer);
        }
    // 输出
    Optional[emanjusaka]
    hello emanjusaka
    

    一般用于判断 Optional 是否为空,并在不为空的情况下执行相应的操作。

3、获取方法

  • get()

    如果这个 Optional 中存在一个值,则返回该值,否则抛出 NoSuchElementException。

    public static void main(String[] args) {
            Optional<String> emanjusaka = Optional.ofNullable("emanjusaka");
            System.out.println(emanjusaka);
            System.out.println(emanjusaka.get());
    
            Optional<String> empty = Optional.ofNullable(null);
            System.out.println(empty);
            System.out.println(empty.get());
        }
    // 输出
    Optional[emanjusaka]
    emanjusaka
    Optional.empty
    Exception in thread "main" java.util.NoSuchElementException: No value present
    	at java.util.Optional.get(Optional.java:135)
    	at org.example.Main.main(Main.java:13)
    
  • filter(Predicate<? super T> predicate)

    如果存在一个值,并且该值与给定的过滤条件匹配,则返回一个描述该值的 Optional,否则返回一个空的 Optional。

     public static void main(String[] args) {
            Optional<String> emanjusaka = Optional.of("emanjusaka");
            System.out.println(emanjusaka);
            System.out.println(emanjusaka.filter(s -> {
                return "emanjusaka".equals(s);
            }));
            System.out.println(emanjusaka.filter(s -> false));
        }
    // 输出
    Optional[emanjusaka]
    Optional[emanjusaka]
    Optional.empty
    
  • map(Function<? super T, ? extends U> mapper)

    如果存在值,则将提供的映射函数应用于该值,如果结果为非 null,则返回一个描述结果的 Optional。否则返回空的 Optional。

    public static void main(String[] args) {
            User user = new User();
            user.setName("emanjusaka");
            Optional<User> optional = Optional.of(user);
            System.out.println(optional);
            System.out.println(optional.map(User::getName));
    
            User userEmpty = new User();
            Optional<User> optionalEmpty = Optional.of(userEmpty);
            System.out.println(optionalEmpty);
            System.out.println(optionalEmpty.map(User::getName));
        }
    
        private static class User {
            private String name;
    
            public String getName() {
                return name;
            }
    
            public void setName(String name) {
                this.name = name;
            }
        }
    // 输出
    Optional[org.example.Main$User@2503dbd3]
    Optional[emanjusaka]
    Optional[org.example.Main$User@6d03e736]
    Optional.empty
    
  • flatMap(Function<? super T, Optionalu&gt; mapper)

    如果存在值,请将提供的 Optional 方位映射函数应用于该值,返回该结果,否则返回空的 Optional。此方法类似于map(Function),但提供的 mapper 的结果已经是 Optional,并且如果调用,flatMap 不会用额外的 Optional 包装它。

    public static void main(String[] args) {
            User user = new User();
            user.setName("emanjusaka");
            user.setAge(Optional.of(35));
            Optional<User> optional = Optional.of(user);
            System.out.println(optional);
            System.out.println(optional.map(User::getAge));
            System.out.println(optional.flatMap(User::getAge));
        }
    
        private static class User {
            private String name;
    
            private Optional<Integer> age;
    
            public String getName() {
                return name;
            }
    
            public void setName(String name) {
                this.name = name;
            }
    
            public Optional<Integer> getAge() {
                return age;
            }
    
            public void setAge(Optional<Integer> age) {
                this.age = age;
            }
        }
    // 输出
    Optional[org.example.Main$User@2503dbd3]
    Optional[Optional[35]]
    Optional[35]
    

    这个例子中体现出了 map 和 flatMap 方法的区别,map 方法会在返回时用 Optional 进行包装而 flatMap 方法不会再进行额外的包装。

  • orElse(T other)

    如果存在这个值则返回这个值,否则返回传入的值 other

    public static void main(String[] args) {
            Optional<String> optional = Optional.of("emanjusaka");
            System.out.println(optional);
            System.out.println(optional.orElse(null));
    
            Optional<String> optionalEmpty = Optional.ofNullable(null);
            System.out.println(optionalEmpty);
            System.out.println(optionalEmpty.orElse("empty"));
        }
    // 输出
    Optional[emanjusaka]
    emanjusaka
    Optional.empty
    empty
    
  • orElseGet(Supplier<? extends T> other)

    如果存在这个值则返回这个值,否则调用 other 并返回该调用的结果。

    public static void main(String[] args) {
            Optional<String> optional = Optional.of("emanjusaka");
            System.out.println(optional);
            System.out.println(optional.orElseGet(() -> "hello emanjusaka"));
    
            Optional<String> optionalEmpty = Optional.ofNullable(null);
            System.out.println(optionalEmpty);
            System.out.println(optionalEmpty.orElseGet(() -> "hello emanjusaka"));
        }
    // 输出
    Optional[emanjusaka]
    emanjusaka
    Optional.empty
    hello emanjusaka
    
  • orElseThrow(Supplier? extends X exceptionSupplier)

    返回包含的值(如果存在),否则抛出由所提供创建的异常。

    public static void main(String[] args) {
            Optional<String> optionalEmpty = Optional.ofNullable(null);
            System.out.println(optionalEmpty);
            System.out.println(optionalEmpty.orElseThrow(ArithmeticException::new));
        }
    // 输出
    Optional.empty
    Exception in thread "main" java.lang.ArithmeticException
    	at java.util.Optional.orElseThrow(Optional.java:290)
    	at org.example.Main.main(Main.java:9)
    

二、Optional 中方法的区别

1、map 和 flatMap 方法的区别

  • map 方法会在返回时用 Optional 进行包装而 flatMap 方法不会再进行额外的包装。

2、orElse 和 orElseGet 方法的区别

  • orElse():如果有值则将其返回,否则返回指定的 other。
  • orElseGet():如果有值则将其返回,否则调用函数 other 并将其返回调用结果。
  • orElse() 方法在 Optional 值为非空时,也会计算传入的参数,而 orElseGet() 方法只有在 Optional 值为空时才会执行传入的函数。

如果是传值可以选用 orElse(),如果传入的是方法选用orElseGet()

三、总结

在使用 Optional 时我觉得应该尽量避免一些情况:

  • 永远不要通过返回 Optional 的方法返回一个空值:它破坏 Optional 设计的初衷。
  • 并不是所有的返回类型都能从 Optional 的处理中获益。容器类型,包括集合、映射、Stream、数组和 Optional,不应该封装在 Optional 中。与其返回一个空的 Optional<List> ,不还如返回一个 空的 List 。
  • 除了「次要基本类型(minor primitive types)」Boolean,Byte,Character,Short 和 Float 之外,永远不应该返回装箱的基本类型 的 Optional。

总之,如果发现自己编写的方法不能总是返回值,并且认为该方法的用户在每次调用时考虑这种可能性很重要,那么或许应该返回一个 Optional 的方法。但是,应该意识到,返回 Optional 会带来实际的性能后果;对于性能关键的方法,最好返回 null 或抛出异常。最后,除了作为返回值之外,不应该在任何其他地方中使用 Optional。

使用 Optional 时要注意,我认为它并不能完全避免空指针。如果这个值是 null ,不做额外的判断,直接使用还是会有空指针的问题。使用 Optional 的好处是它可以帮助我们简化判空的操作,简洁我们的代码。用了 Optional 最后拿结果的时候还是要小心的,盲目 get 一样会抛错。

四、参考文献

  1. 《Effective Java》
  2. jdk11的文档

本文原创,才疏学浅,如有纰漏,欢迎指正。尊贵的朋友,如果本文对您有所帮助,欢迎点赞,并期待您的反馈,以便于不断优化。

原文地址:https://www.emanjusaka.top/2023/08/jdk8-optional-thinking