Java8 Optional用法和最佳实践

2022-11-22 14:33:23 浏览数 (1)

根据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<T>的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()中,这个风险却没有。

0 人点赞