【Java8特性】之Optional详解

【Java8特性】汇总

Optional类介绍

Java 8 引入的一个很有趣的特性是 Optional 类。Optional 类主要解决的问题是臭名昭著的空指针异常(NullPointerException) —— 每个 Java 程序员都非常了解的异常。

本质上,这是一个包含有可选值的包装类,这意味着 Optional 类既可以含有对象也可以为空。

Optional 是 Java 实现函数式编程的强劲一步,并且帮助在范式中实现。但是 Optional 的意义显然不止于此。

我们从一个简单的用例开始。在 Java 8 之前,任何访问对象方法或属性的调用都可能导致 NullPointerException

String isocode = user.getAddress().getCountry().getIsocode().toUpperCase();

在这个小示例中,如果我们需要确保不触发异常,就得在访问每一个值之前对其进行明确地检查:

    // 查询国家码
    public void getIsoCode(User user) {
        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 类是怎么做的。从创建和验证实例,到使用其不同的方法,并与其它返回相同类型的方法相结合,下面是见证 Optional 奇迹的时刻。

    public String getIsoCode(User user) {
        return Optional.ofNullable(user)
                .map(a -> a.getAddress())
                .map(a -> a.getCountry())
                .map(a -> a.getIsocode())
                .map(a -> a.toUpperCase())
                .orElse("CHINA");
    }

Optional类的使用

Optional类有很多方法,通过of()ofNullable()构造Optional实例,Optional类拥有value属性,通过get()获取实例的value属性值,通过组合这些方法,最终实现上述解决NullPointerException问题的。下面我们来认识一些基础的方法,逐步揭秘。

创建Optional实例

Optional.empty()

empty()方法返回一个空的Optional对象,其value属性null,用法如下。

 Optional<User> emptyOpt = Optional.empty();

empty()方法之所以放在第一个介绍是有原因的,因为涉及到Optional的构造是私有的,这个知识很重要,我们来看下源码:

/*
 * @since 1.8   标记1.8版本后才有的
 */
public final class Optional<T> {
  //内部实例化一个Optional实例,命名为EMPTY ,全局唯一的
  private static final Optional<?> EMPTY = new Optional<>();
  //value属性
  private final T value;
  //empty()方法实际上 就是返回之前那个命名为EMPTY 实例
  public static<T> Optional<T> empty() {
    @SuppressWarnings("unchecked")
    Optional<T> t = (Optional<T>) EMPTY;
    return t;
  }
  //构造函数,注意是私有的,无法在外部用new访问
  private Optional() {
      this.value = null;
  }

Optional构造方法是私有的,也就是说不允许外部通过new的形式创建对象。构造方法是供Optional类内部使用。Optional内部维护这个一个value的变量,无参数构建的时候value为null

Optional.empty()返回的实例对象时全局唯一的,多次调用,结果是同一个对象:

 @org.junit.Test
  public void testEmpty() {
      Assert.assertEquals(Optional.empty(), Optional.empty());   //我们调用了2次empty(),并比较equals
     Assert.assertTrue(Optional.empty() == Optional.empty());  //比较对象地址
  }
Optional.of()

创建value属性为指定值的 Optional实例,就是用一个Optional对象,包裹了给定的值。用法如下:

 Optional<User> opt = Optional.of(user);

通过of方法所构造出的Optional对象有如下特点:

  • 当指定值为空时,会报NullPointerException异常
  • 当指定值不为空时,正常构造Optional对象

我们通过源码来分析下原因:

public final class Optional<T> {    //Optional类
    public static <T> Optional<T> of(T value) {
        return new Optional<>(value);     //调用私有的构造函数
    }
    private Optional(T value) {
        this.value = Objects.requireNonNull(value);   //调用Objects.requireNonNull限制指定值非空
    }
 }
 
public final class Objects {   //Objects工具类
     public static <T> T requireNonNull(T obj) {
        if (obj == null)
            throw new NullPointerException();  //如果为空,则抛出异常
        return obj;
    }   

注意: 只有你确定对象100%不为 null 的时候才能使用 of(),否则会报NullPointerException

Optional.ofNullable()

创建value属性为指定值的 Optional实例,如果指定为空,则返回一个空的Optional实例。

Optional<User> opt = Optional.ofNullable(user);

ofNullable用法和of()相似,但是区别在于ofNullable对指定值无限制,即使传入一个null值,也不会报错,通过源码来分析原因:

    public static <T> Optional<T> ofNullable(T value) {
        return value == null ? empty() : of(value); //给定值为空,返回空的Optional实例,不为空,则复用of()构造一个新的实例
    }

如果对象即可能是 null 也可能是非 null,你就应该使用 ofNullable() 方法,避免用of()方法

访问 Optional 对象的值

我们在前面知道Optional就是包裹了给定的值,进行了封装,自身有个value属性,那么获取对象的值,就是获取value属性

get()

从 Optional 实例中取回value属性

@Test
public void whenCreateOfNullableOptional_thenOk() {
    String name = "John";
    Optional<String> opt = Optional.ofNullable(name); //构建一个Optional 实例
 
    assertEquals("John", opt.get());   //获取Optional 实例的值
}

不过,需要注意的是,当value属性为空时会报错:

    @Test(expected = NoSuchElementException.class)
    public void testGetThrowException() {
        Optional.empty().get();
    }

通过源码分析下原因:

    public T get() {
        if (value == null) {
            throw new NoSuchElementException("No value present");  //判断value属性为空时抛出异常
        }
        return value;  //返回value属性
    }

为了避免出错,可以在调用get()之前先验证是否有值,isPresent()应运而生

isPresent()

判断value属性值是否存在。value属性== null 表示不存在,非null表示存在。

例子:

    @org.junit.Test
    public void testIsPresent() {
        Optional<Integer> optional1 = Optional.ofNullable(1);
        Optional<Integer> optional2 = Optional.ofNullable(null);
      
        Assert.assertEquals(optional1.isPresent(), true);   //存在
        Assert.assertEquals(optional2.isPresent(), false);  //不存在
    }

注意:方法的前缀是is,不是if,避免和下面的ifPresent()混淆

由于null、空字符串比较容易混淆 “存在”的含义,我们上源码:

    public boolean isPresent() {
        return value != null;     //与null进行比较
    }

由源码得出结论:空字符串表示存在

ifPresent()

方法定义:

public void ifPresent(Consumer<? super T> consumer)

ifPresent()不是获取值,该方法没有返回值。之所以放在此处介绍,是因为名称和前面的isPresent()容易混淆。

如果option对象的value属性值不是null,则调用consumer对象,并将value属性值作为入参传入,否则不调用。Consumer可以理解为一个Lambda 表达式(Lambda 章节专门讲解Consumer用法),并且限定只有一个入参:

Optional<User> opt = Optional.ofNullable(user);
opt.ifPresent( u -> assertEquals(user.getEmail(), u.getEmail()));

这个例子中,只有 user 用户不为 null 的时候才会执行断言。

orElse(T other)

结构为:

opt.orElse( obj);

orElse(T other)是一个工具方法,作用类似get(),可以获取value属性的值,之所以说是一个工具方法,是因为当value属性值为空时,返回的值是指定的other参数值:

    @org.junit.Test
    public void testOrElse() {
        Optional<String> optional1 = Optional.ofNullable("zhangsan");   //option实例非空
        Optional<String> optional2 = Optional.ofNullable(null);  //option实例为空

        Assert.assertEquals(optional1.orElse("lisi"), "zhangsan");     //option实例非空,返回value属性值
        Assert.assertEquals(optional2.orElse("lisi"), "lisi"); //option实例为空,则返回指定的新值
    }

源码也很简单:

    public T orElse(T other) {
        return value != null ? value : other; //value不为空,则返回value;为空则返回other
    }

这个方法很重要,比如我们经常在处理返回值时,如果值是一个null,我们希望转化为空字符串,这样返回值在外部被调用时就不会报空指针了


    public String getValue(){      //常规方式
        String result = doSomething();
        if(result==null){
            return "";   //把null转化为空字符串
        }
    }
    
    public String getValue(){  //Optional方式
        String result = doSomething();
        return   Optional.of(result).orElse("");   //自动转化为空字符串
    }
orElseGet(Supplier<? extends T> other)

用法为:

opt.orElseGet( () -> xxx);

从字面上就能看出作用与orElse()类似,也是获取value属性值,但行为略有不同。这个方法会在value属有值的时候返回value属值,如果没有值,它会执行作为参数传入的 Supplier(供应者) 函数式接口,并将返回其执行结果:

    @org.junit.Test
    public void testOrElseGet() {

        Optional<Integer> optional1 = Optional.ofNullable(1); //option实例非空,并且value属性值为1
        Optional<Integer> optional2 = Optional.ofNullable(null);  //option实例为空

        Assert.assertEquals(optional1.orElseGet(() -> {
            return Integer.valueOf(1000);        //表达式,执行结果为1000
        }), Integer.valueOf(1));   //option实例非空,返回value属性值1

        Assert.assertEquals(optional2.orElseGet(() -> {
            return Integer.valueOf(1000);  //表达式,执行结果为1000
        }), Integer.valueOf(1000));  //option实例为空,则返回Supplier Lambda结果,1000

    }
orElse() 和 orElseGet() 的不同之处

orElse() 和 orElseGet() 的不同之处在于前者传入一个值,后者是个Lambda表达式。然而,如果传入值的方式是通过调用其他方法形式就会产生一点细微区别,我们来解释下什么是调用其他方法形式传值:

public class Test {
    public String functionA() {   //方法A
        return ..;
    }
    public void functionB(){    
        Optional opt = ...
        opt.orElse(functionA())   //orElse()调用方法A传入值
    }
    public void functionC(){
        Optional opt = ...
        opt.orElseGet(()-> functionA())   //orElseGet调用方法A传入值
    }
}

区别在与,如果传入的value不为空,orElse()中的方法仍然要执行,而orElseGet() 不执行。

测试value属性为空情况,二者都会执行:


    @org.junit.Test
    public void testOrElseAndOrElseGet() {
        Optional<String> optional1 = Optional.ofNullable(null);    //value属性为空
        Optional<String> optional2 = Optional.ofNullable(null); //value属性为空

        optional1.orElse(getName());         //会执行getName()
        System.out.println("--------------");  
        optional2.orElseGet(() -> getName()); //会执行getName()
    }
    public String getName() {
       System.out.println("getName() executed !");     //打印执行标识
       return "lisi";
    }

我们看下输出:

getName() executed !   //orElse触发打印的
--------------
getName() executed !   //orElseGet触发打印的

测试value属性不为空情况,orElse会执行,orElseGet不执行:

    @org.junit.Test
    public void testOrElseAndOrElseGet2() {
        Optional<String> optional1 = Optional.ofNullable("lisi");  //value属性不为空
        Optional<String> optional2 = Optional.ofNullable("lisi");  //value属性不为空

        optional1.orElse(getName());    //会执行getName()
        System.out.println("--------------");
        optional2.orElseGet(() -> getName());   //不会执行getName()
    }

我们看下输出:

getName() executed !
--------------

在执行较密集的调用时,比如调用 Web 服务或数据查询,这个差异会对性能产生重大影响。

orElseThrow(Supplier<? extends X> exceptionSupplier)

它会在对象为空的时候抛出异常,而不是返回某个值,并且该异常由入参传入:

    @Test(expected = IllegalArgumentException.class)  //期望抛IllegalArgumentException异常
    public void testOrElseThrow() {
        Optional.ofNullable(null).orElseThrow(() -> new IllegalArgumentException()); // 我们定义了一个空的实例,orElseThrow传入一个异常
    }

这个方法让我们有更丰富的语义,可以决定抛出什么样的异常,而不总是抛出 NullPointerException。

过滤值

filter(Predicate<? super T> predicate)

filter()方法作用是当Optional中value属性不为空的时,对Optional中的值进行过滤。

注意:filter()方法返回值仍是一个Optional对象,并且对象value属性可能为空,而不是一个具体的值。

filter() 接受一个 Predicate 参数,这是个判断表达式,判断结果为true时,保留当前实例,判断结果为false时,返回一个空的Optional。

@org.junit.Test
 public void testFilter() {
     Optional<String> optional1 = Optional.ofNullable("abcd");  //非空实例
     Optional<String> optional2 = Optional.ofNullable(null);  //空实例

     //非空实例场景
     Assert.assertEquals(optional1.filter(a -> a.startsWith("ab")).get(), "abcd");      //表达式结果为true,保留当前实例
     Assert.assertEquals(optional1.filter(a -> a.startsWith("AB")), Optional.empty());   //表达式结果为fasle,返回空实例
     
     //空实例场景,直接返回空对象
     Assert.assertEquals(optional2.filter((a) -> (1 == 1)), Optional.empty());
 }

转换值

map()和flatMap()对Optional中的对象进行转换值的操作,这两个方法唯一的区别就是接受的参数不同。

map()
 public<U> Optional<U> map(Function<? super T, ? extends U> mapper)

对Optional中保存的值进行函数运算,并返回新的Optional(可以是任何类型)。入参是一个mapping函数,Optional的value属性作为mapping函数入参。

注意:map()返回值是Optional对象,mapper函数返回值是任意值,最终会被封装成Optional对象

我们看个例子:

    @org.junit.Test
    public void testMap() {
        Optional<String> optional1 = Optional.ofNullable("ab");  // 非空实例
        Optional<String> optional2 = Optional.ofNullable(null); //空实例

        Assert.assertEquals(optional1.map(a -> a + "cd").get(), "abcd");   //函数作用是拼接字符串
        Assert.assertEquals(optional2.map(a -> a + "cd"), Optional.empty());   //空实例返回空实例,不会调用函数表达式
    }

看下源码:

    public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
        Objects.requireNonNull(mapper);   //传入的函数必须存在,不能是null
        if (!isPresent())
            return empty();      //如果属性值为空,直接返回
        else {
            return Optional.ofNullable(mapper.apply(value));  //计算后的值,如果为空,通过ofNullable返回empty()
        }
    }

由源码,总结特点:

  • 传入的函数不能为null,否则报NullPointerException
    Objects.requireNonNull(mapper)起的作用
  • 如果实例本身为空,不会执行函数,直接返回空
  • 如果执行函数的计算结果为null,会返回empty()
    对mapping返回值调用ofNullable封装起的作用
  • mapper函数返回值是任意值,最终会被封装成Optional对象
    对mapping返回值调用ofNullable封装起的作用
flatMap()
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) 

flatMap方法与map方法类似,也是对Optional中保存的值进行函数运算,区别在于mapping函数的返回值不同。

map()方法的mapping函数返回值可以是任何类型T,而flatMap()方法的mapping函数必须是Optional

我们来看个例子,这个例子和map()非常相似,只是对mapping返回值进行封装:

@org.junit.Test
  public void testFlatMap() {
  
      Optional<String> optional1 = Optional.ofNullable("ab"); //构建一个非空实例
      Optional<String> optional2 = Optional.ofNullable(null); // 构建一个空实例

      Assert.assertEquals(optional1.flatMap(a -> Optional.of(a + "cd")).get(), "abcd"); //接收一个mapping函数,进行拼接,并手动封装成optional实例
      Assert.assertEquals(optional2.flatMap(a -> Optional.of(a + "cd")), Optional.empty());   //空实例直接返回空实例

  }

验证mapping返回值不能为null:

    @org.junit.Test(expected = NullPointerException.class)   //期望抛出异常
    public void testFlatMap_ReturnNotNull() {
        Optional<String> optional1 = Optional.ofNullable("ab");

        optional1.flatMap(a -> {
            return null;     //故意返回null
        });
    }

看下源码:

    public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();       //自身为空,直接返回空Optional
        else {
            return Objects.requireNonNull(mapper.apply(value));    //mapping 返回结果不能是null,可以是一个空的Optional实例
                              //由于mapping结果未经ofNullable封装,因此需要mapping自身封装一个Optional对象
        }
    }

由源码,总结特点:

  • mapping返回值是个Option对象
    flatMap最终返回值是一个Option对象,由于mapping结果未经ofNullable封装,因此需要mapping自身封装一个Option对象。这是与map()最大区别的根本原因。
  • mapping返回值不能是null
    由于mapping结果返回值被Objects.requireNonNull检查,因此格外注意返回值一定不能是null,可以返回一个空的Optional
  • 如果实例本身为空,不会执行函数,直接返回空Optional

Optional 类的最佳实践

经过上面的铺垫,我们再来回顾开篇的问题,为何可以简便的解决空指针问题?
User.java

public class User {
    private Address address;//省略get,set方法 

Address.java

public class Address {
    private Country country; //省略get,set方法 

Country.java

public class Country {
    private String isocode;//省略get,set方法 

TestOption.java

  public String getIsoCode2(User user) {
      return Optional.ofNullable(user)
              .map(a -> a.getAddress())
              .map(a -> a.getCountry())
              .map(a -> a.getIsocode())
              .map(a -> a.toUpperCase())
              .orElse("CHINA");
  }
    @Test
    public void test() {
        //#1 user为null,直接越过中间步骤,根据orElse,得到默认值
        User user = null;
        Assert.assertEquals(getIsoCode(user), "CHINA");
        //#2 user非null,但是address属性是null,直接越过中间步骤,根据orElse,得到默认值
        user = new User();
        Assert.assertEquals(getIsoCode(user), "CHINA");
        //#3 address属性非null,但是country属性是null,根据orElse,得到默认值
        Address address = new Address();
        user.setAddress(address);
        Assert.assertEquals(getIsoCode(user), "CHINA");
        //#4 country非null,那么最终结果就是其值
        Country country = new Country();
        country.setIsocode("Usa");
        address.setCountry(country);
        Assert.assertEquals(getIsoCode(user), "USA");
    }

我们来分析一下上面的单元测试 #1步骤:

  • 执行 Optional.ofNullable(user)
    ofNullable对user进行封装,得到一个Optional.empty实例
  • 执行 .map(a -> a.getAddress())
    由于此时Optional对象自身为空实例,map会直接返回空实例
  • 执行.map(a -> a.getCountry())
    由于此时Optional对象自身为空实例,map会直接返回空实例
  • 执行 .map(a -> a.getIsocode())
    由于此时Optional对象自身为空实例,map会直接返回空实例
  • 执行 .map(a -> a.toUpperCase())
    由于此时Optional对象自身为空实例,map会直接返回空实例
  • 执行 .orElse(“CHINA”);
    orElse作用是返回一个值,这个是最终值,传给return 语句,由于此时Optional对象自身为空实例,orElse返回给定的默认值,即CHINA

简单来说,通过map,把本来为null的属性转化为一个Optional.empty实例,在链式写法中,空实例调用方法map()不会报错,并且继续获的一个Optional.empty实例,直至链的尾部,通过orElse()收尾。

如果觉得对您有帮助,请不要吝惜您点赞和留言,呵呵,写的蛮辛苦的。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值