Java8 Optional用法和最佳实践

根据Oracle文档,Optional是一个容器对象,可以包含也可以不包含非null值。Optional在Java 8中引入,目的是解决  NullPointerExceptions的问题。本质上,Optional是一个包装器类,其中包含对其他对象的引用。在这种情况下,对象只是指向内存位置的指针,并且也可以指向任何内容。从其它角度看,Optional提供一种类型级解决方案来表示可选值而不是空引用。

在Optional之前

在Java 8之前,程序员将返回null而不是Optional。这种方法有一些缺点。一种是没有明确的方法来表示null可能是一个特殊值。如果我们要确保不会出现空指针异常,则需要对每个引用进行显式的空检查。

另外还有一些开发人员喜欢通过非空检查来实现业务逻辑,空对象不应该用来决定系统的行为,它们是意外的Exceptional值,应当被看成是错误,而不是业务逻辑状态。

当我们一个方法返回List集合时,应该总是返回一个空的List,而不是Null,这就允许调用者能够遍历它而不必检查Null,否则就抛出NPE。

private void getCode( User user){
        if (user != null) {
            Address address = user.getAddress();
            if (address != null) {
                Country country = address.getCountry();
                if (country != null) {
                    String code = country.getCode();
                    if (code != null) {
                        code = code.toUpperCase();
                    }
                }
            }
        }
    }

Optional

构造Optional的三种方式

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

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

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


创建一个Optional类

返回一个空的Optional实例

// Creating an empty optional
Optional<String> empty = Optional.empty();

在返回一个空的{Optional}实例时,Optional的值不存在。不过,这样做可能很有诱惑力,如果对象为空,请避免与Option.empty()返回的实例的{==}比较 。因为不能保证它是一个单例,反之,应该使用isPresent()。

返回特定的非空值Optional

// Creating an optional using of

String name = "java";

Optional<String> opt = Optional.of(name);

静态方法需要一个非null参数;否则,将引发空指针异常。因此,如果我们不知道参数是否为null,那就是我们使用 ofNullable的时候,下面将对此进行介绍。

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

// Possible null value

 Optional<String> optional = Optional.ofNullable(name());

  private  String  name(){

  String name = "Java";

  return (name.length() > 5) ? name : null;

 }

常用API

ifPresent()

如果存在值,则返回true;反之,返回false。如果所包含的对象不为null,则返回true,反之返回false。通常在对对象执行任何其他操作之前,先在Optional上调用此方法。

 Stream<String> names = Stream.of("Lamurudu", "Okanbi", "Oduduwa");
 Optional<String> longest = names
                .filter(name -> name.startsWith("L"))
                .findFirst();
longest.ifPresent(name -> {
            String s = name.toUpperCase();
            System.out.println("The longest name is "+ s);
        });
		
//The longest name is LAMURUDU

这里ifPresent() 是将一个Lambda表达式作为输入,T值如果不为空将传入这个lambda。那么这个lambda将不为空的单词转为大写输出显示。在前面names单词流寻找结果中,有可能找不到开始字母为L的单词,返回为空,也可能找到不为空,这两种情况都传入lambda中,无需我们打开盒子自己编写代码来判断,它自动帮助我们完成了,无需人工干预。

map()

使用Optional的map方法能够返回另外一个Optional

Stream<String> names = Stream.of("Lamurudu", "Okanbi", "Oduduwa");
        Optional<String> longest = names
                .filter(name -> name.startsWith("L"))
                .findFirst();
Optional<String> lNameInCaps = longest.map(String::toUpperCase);
        lNameInCaps.ifPresent(name -> {
            System.out.println("The name  is "+ name);
        });
		
//The name  is LAMURUDU

orElse()

返回值(如果存在);反之,返回其他。

Stream<String> names = Stream.of("Lamurudu", "Okanbi", "Oduduwa");
Optional<String> longest = names
                .filter(name -> name.startsWith("Q"))
                .findFirst();
String alternate = longest.orElse("Nimrod");
System.out.println(alternate);

//Nimrod

orElseGet()

返回值(如果存在);否则,调用other并返回该调用的结果。
该orElseGet() 方法类似于 orElse()。但是,如果没有Optional值,则不采用返回值,而是采用供应商功能接口,该接口将被调用并返回调用的值。

Stream<String> names = Stream.of("Lamurudu", "Okanbi", "Oduduwa");
Optional<String> longest = names
                    .filter(name -> name.startsWith("Q"))
                    .findFirst();
 String alternate = longest.orElseGet(() -> {
            return "Nimrod";
        });
 System.out.println(alternate);
 
 //Nimrod
 

orElseThrow()

orElseThrow()是在当遭遇Null时,决定抛出哪个Exception时使用

Stream<String> names = Stream.of("Lamurudu", "Okanbi", "Oduduwa");
Optional<String> longest = names
                    .filter(name -> name.startsWith("Q"))
                   .findFirst();
longest.orElseThrow(NoSuchElementStartingWithQException::new);

orElse() 和orElseGet()之间有什么区别

我们可能考虑的问题是:何时使用orElse和何时使用orElseGet?看起来可以使用orElseGet的时候,使用orElse也可以代替(因为Supplier接口没有入参),而且使用orElseGet还需要将计算过程额外包装成一个 lambda 表达式。

一个关键的点是,使用Supplier能够做到懒计算,即使用orElseGet时。它的好处是,只有在需要的时候才会计算结果。具体到我们的场景,使用orElse的时候,每次它都会执行计算结果的过程,而对于orElseGet,只有Optional中的值为空时,它才会计算备选结果。这样做的好处是可以避免提前计算结果的风险。

class User {
    // 中文名
	private String chineseName;
	// 英文名
	private EnglishName englishName;
}

class EnglishName {
    // 全名
    private String fullName;
    // 简写
    private String shortName;
}

假如我们现在有User类,用户注册账号时,需要提供自己的中文名或英文名,或都提供,我们抽象出一个EnglishName类,它包含英文名的全名和简写(因为有的英文名确实太长了)。现在,我们希望有一个User#getName()方法,它可以像下面这样实现:

class User {
    // ... 之前的内容
    public String getName1() {
        return Optional.ofNullable(chineseName)
                .orElse(englishName.getShortName());
    }
    public String getName2() {
        return Optional.ofNullable(chineseName)
                .orElseGet(() -> englishName.getShortName());
    }
}

两个版本,分别使用orElse和orElseGet。现在可以看出getName1()方法有什么风险了吗?它会出现空指针异常吗?

答案是:是的。当用户只提供了中文名时,此时englishName属性是null,但是在orElse中,englishName.getShortName()总是会执行。而在getName2()中,这个风险却没有。

posted @ 2022-11-17 15:26  leepandar  阅读(152)  评论(0编辑  收藏  举报