CodeArena

Java8新特性之Optional类

2023-04-13
Java
最后更新:2024-05-23
17分钟
3332字

Optional类

引入

NPE问题就是在开发中经常碰到的NullPointerException,即空指针问题,Optional类就是用来优雅解决该问题的方案。

比如大家可能都有这样的经历:调用一个方法得到了返回值却不能直接将返回值作为参数去调用别的方法。我们首先要判断这个返回值是否为null,只有在非空的前提下才能将其作为其他方法的参数。

以用户类和地址类举例说明其用法:

1
public class User {
2
private String userName;
3
private String phoneNumber;
4
private Address address;
5
//无参、部分参数、全参数构造器方法...
6
//setter、getter方法...
7
}
1
public class Address {
2
private String province;
3
private String city;
4
private String area;
5
//无参、部分参数、全参数构造器方法...
6
//setter、getter方法...
7
}
1
//这行代码可能会出问题
2
String province = user.getAddress().getProvince();
3
System.out.println(province);

这种代码可能会出现空指针问题,在实际开发中,如果不使用Optional类,极其不优雅的处理方式如下:

1
//极其不优雅的处理方式
2
if (user != null) {//对user对象的null值判断
3
Address address = user.getAddress();
4
if (address != null) {//对address对象的null值判断
5
String province = address.getProvince();
6
if (province != null) {//对province对象的null值判断
7
System.out.println(province);
8
} else {
9
System.out.println("province==null");
10
}
11
}
12
}

上面的代码保证了代码的第三行、第五行、第七行肯定不会出现空指针,但是这个代码真的是非常的冗长和丑陋。

java.util包下面的Optional类提供了一套API来处理一个对象是否为null值的问题。

源码解读及各API的使用

部分源码:

1
public final class Optional<T> {
2
//value为null的Optinal对象,类加载的时候就已经初始化完成该Optional对象
3
private static final Optional<?> EMPTY = new Optional<>();
4
//存储需要判断null的对象
5
private final T value;
6
//无参构造函数
7
private Optional() {
8
this.value = null;
9
}
10
//有参构造函数
11
private Optional(T value) {
12
this.value = Objects.requireNonNull(value);
13
}
14
}

其本质是内部有一个泛型容器存储外部需要判断null值的对象,同时提供了两个私有的构造函数,不能被外部所调用,只能由类内部的函数调用

  • 无参数的构造函数提供一个value=nullOptional对象
  • 有参数的构造函数提供一个value一定不能为nullOptional对象,因为它调用了Objects类的requireNonNull方法。
1
//源码
2
public final class Objects {
3
public static <T> T requireNonNull(T obj) {
4
if (obj == null)
5
throw new NullPointerException();
6
return obj;
7
}
8
}

of

1
//源码
2
public static <T> Optional<T> of(T value) {
3
return new Optional<>(value);
4
}

这是一个静态方法,调用有参数的构造函数,返回的是value值一定不为nullOptional对象,因为有参数的构造方法底层调用了ObjectsrequireNonNull方法,如果传入的valuenull值,那么一定会报空指针异常。不允许valuenull,实际开发中不常用。

empty

1
//源码
2
public static<T> Optional<T> empty() {
3
@SuppressWarnings("unchecked")
4
Optional<T> t = (Optional<T>) EMPTY;
5
return t;
6
}

这是一个静态方法,直接将类初始化时加载的valuenullOptional对象给用户。

ofNullable

1
//源码
2
public static <T> Optional<T> ofNullable(T value) {
3
return value == null ? empty() : of(value);
4
}

这是一个静态方法,代表value值是可为空的。如果为null值,那么返回一个valuenullOptional对象;如果不为null值,那么返回一个value不为nullOption对象。允许valuenull,实际开发中常用。

of的区别:当value值为null时,of会报NullPointerException异常;ofNullable不会throw ExceptionofNullable直接返回一个EMPTY对象(valuenullOptional对象)。

::: tip 那是不是意味着,我们在项目中只用ofNullable函数而不用of函数呢?

  • 不是的,一个东西存在那么自然有存在的价值。
  • 当我们在运行过程中,不想隐藏NullPointerException,而是要立即报告,这种情况下就用of函数。
  • 但是不得不承认,这样的场景真的很少。

:::

orElse

1
//源码
2
public T orElse(T other) {
3
return value != null ? value : other;
4
}

这是一个实例方法,会首先判断调用它的Optional对象中的value值,如果为不为null,那么就返回该value值,如果为null,就返回传入的other对象。

1
@Test
2
public void orElseTest() {
3
//1 user不为null
4
User user1 = new User();
5
user1.setUserName("1");
6
//2 user为null
7
User user2 = null;
8
//测试第一种
9
User user3 = Optional.ofNullable(user1).orElse(new User("2"));
10
System.out.println(user3.getUserName());
11
//测试第二种
12
User user4 = Optional.ofNullable(user2).orElse(new User("3"));
13
System.out.println(user4.getUserName());
14
}
15
//运行结果
2 collapsed lines
16
1
17
3

orElseGet

1
//源码
2
public T orElseGet(Supplier<? extends T> other) {
3
return value != null ? value : other.get();
4
}
1
@Test
2
public void orElseGetTest() {
3
//1 user不为null
4
User user1 = new User();
5
user1.setUserName("1");
6
//2 user为null
7
User user2 = null;
8
//测试第一种
9
User user3 = Optional.ofNullable(user1).orElseGet(new Supplier<User>() {
10
@Override
11
public User get() {
12
return new User("2");
13
}
14
});
15
System.out.println(user3.getUserName());
12 collapsed lines
16
//测试第二种
17
User user4 = Optional.ofNullable(user2).orElseGet(new Supplier<User>() {
18
@Override
19
public User get() {
20
return new User("3");
21
}
22
});
23
System.out.println(user4.getUserName());
24
}
25
//运行结果
26
1
27
3

该方法与orElse方法类似,只不过传入的other对象可以通过一个提供者函数式接口提供,这里可以改成lambda表达式的形式。为了方便对代码的理解,所以上面写的测试代码稍显复杂,实际开发中可以使用lambda表达式简化。

orElseThrow

1
//源码
2
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
3
if (value != null) {
4
return value;
5
} else {
6
throw exceptionSupplier.get();
7
}
8
}

如果value值不为null,那么直接返回,如果value值为null,可以自定义业务逻辑功能说明语句抛出异常,不影响后续代码执行。

1
@Test
2
public void orElseThrowTest() {
3
//1 user不为null
4
User user1 = new User();
5
user1.setUserName("1");
6
//2 user为null
7
User user2 = null;
8
//测试第一种
9
User user3 = null;
10
try {
11
user3 = Optional.ofNullable(user1).orElseThrow(new Supplier<Throwable>() {
12
@Override
13
public Throwable get() {
14
return new Throwable("user1为null的业务逻辑功能说明");
15
}
26 collapsed lines
16
});
17
} catch (Throwable throwable) {
18
throwable.printStackTrace();
19
}
20
System.out.println(user3.getUserName());
21
//测试第二种
22
User user4 = null;
23
try {
24
user4 = Optional.ofNullable(user2).orElseThrow(new Supplier<Throwable>() {
25
@Override
26
public Throwable get() {
27
return new Throwable("user2为null的业务逻辑功能说明");
28
}
29
});
30
} catch (Throwable throwable) {
31
throwable.printStackTrace();
32
}
33
System.out.println("不影响后续业务逻辑的执行...");
34
}
35
//执行结果
36
1
37
java.lang.Throwable: user2为null的业务逻辑功能说明
38
at com.ouc.ystong.test.TestMain$4.get(TestMain.java:82)
39
at com.ouc.ystong.test.TestMain$4.get(TestMain.java:79)
40
...
41
不影响后续业务逻辑的执行...

map

1
//源码
2
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
3
Objects.requireNonNull(mapper);
4
if (!isPresent())
5
return empty();
6
else {
7
return Optional.ofNullable(mapper.apply(value));
8
}
9
}

如果有值,则对其执行调用mapper函数得到返回值,将创建包含mapper返回值的Optional对象作为map方法返回值,否则返回空Optional对象。

1
@Test
2
public void mapTest() {
3
//1 user不为null
4
User user1 = new User();
5
user1.setUserName("1");
6
//2 user为null
7
User user2 = null;
8
//测试第一种
9
Optional<String> stringOptional1 = Optional.ofNullable(user1).map(new Function<User, String>() {
10
@Override
11
public String apply(User user) {
12
return user.getUserName() + "xiaotongtong";
13
}
14
});
15
System.out.println(stringOptional1);
12 collapsed lines
16
//测试第二种
17
Optional<String> stringOptional2 = Optional.ofNullable(user2).map(new Function<User, String>() {
18
@Override
19
public String apply(User user) {
20
return user.getUserName() + "xiaotongtong";
21
}
22
});
23
System.out.println(stringOptional2);
24
}
25
//执行结果
26
Optional[1xiaotongtong]
27
Optional.empty

map方法用来对Optional实例的值执行一系列操作。通过一组实现了Function接口的lambda表达式传入操作。

flatMap

1
//源码
2
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
3
Objects.requireNonNull(mapper);
4
if (!isPresent())
5
return empty();
6
else {
7
return Objects.requireNonNull(mapper.apply(value));
8
}
9
}

如果有值,为其执行mapper函数返回Optional对象类型返回值,否则返回空Optional对象。flatMapmap方法类似,区别在于flatMap中的mapper返回值必须是Optional对象。调用结束时,flatMap不会对结果用Optional封装。

1
@Test
2
public void flatMapTest() {
3
//1 user不为null
4
User user1 = new User();
5
user1.setUserName("1");
6
//2 user为null
7
User user2 = null;
8
//测试第一种
9
Optional<String> stringOptional1 = Optional.ofNullable(user1).flatMap(new Function<User, Optional<String>>() {
10
@Override
11
public Optional<String> apply(User user) {
12
return Optional.ofNullable(user.getUserName() + "xiaotongtong");
13
}
14
});
15
System.out.println(stringOptional1);
12 collapsed lines
16
//测试第二种
17
Optional<String> stringOptional2 = Optional.ofNullable(user2).flatMap(new Function<User, Optional<String>>() {
18
@Override
19
public Optional<String> apply(User user) {
20
return Optional.ofNullable(user.getUserName() + "xiaotongtong");
21
}
22
});
23
System.out.println(stringOptional2);
24
}
25
//执行结果
26
Optional[1xiaotongtong]
27
Optional.empty

flatMap方法与map方法类似,区别在于mapper函数的返回值不同。map方法的mapper函数返回值可以是任何类型T,而flatMap方法的mapper函数必须是Optional对象。

filter

1
//源码
2
public Optional<T> filter(Predicate<? super T> predicate) {
3
Objects.requireNonNull(predicate);
4
if (!isPresent())
5
return this;
6
else
7
return predicate.test(value) ? this : empty();
8
}

如果有值并且满足断言条件返回包含该值的Optional对象,否则返回空Optional对象。

1
@Test
2
public void filterTest() {
3
4
Optional<String> stringOptional1 = Optional.of("xiaotongtong").filter(new Predicate<String>() {
5
@Override
6
public boolean test(String s) {
7
return s.toCharArray().length > 8;
8
}
9
});
10
//满足条件,返回包含该值的Option对象
11
System.out.println(stringOptional1);
12
Optional<String> stringOptional2 = Optional.of("xiaotongtong").filter(new Predicate<String>() {
13
@Override
14
public boolean test(String s) {
15
return s.toCharArray().length > 15;
8 collapsed lines
16
}
17
});
18
//不满足条件,返回值为空的Optional对象
19
System.out.println(stringOptional2);
20
}
21
//执行结果
22
Optional[xiaotongtong]
23
Optional.empty

isPresent

1
//源码
2
public boolean isPresent() {
3
return value != null;
4
}

ifPresent

1
//源码
2
public void ifPresent(Consumer<? super T> consumer) {
3
if (value != null)
4
consumer.accept(value);
5
}

get

1
//源码
2
public T get() {
3
if (value == null) {
4
throw new NoSuchElementException("No value present");
5
}
6
return value;
7
}

equals

1
//源码
2
@Override
3
public boolean equals(Object obj) {
4
//两者指向的内存地址相同,那么Optional对象肯定相同
5
if (this == obj) {
6
return true;
7
}
8
//如果obj不是Optional类型的,那肯定是不相同的
9
if (!(obj instanceof Optional)) {
10
return false;
11
}
12
//已经确定是Optional类型的,所以可以强转
13
Optional<?> other = (Optional<?>) obj;
14
//比较两个Optional的value值是不是相同
15
return Objects.equals(value, other.value);
1 collapsed line
16
}

hashCode

1
//源码
2
@Override
3
public int hashCode() {
4
return Objects.hashCode(value);
5
}

toString

1
//源码
2
@Override
3
public String toString() {
4
return value != null
5
? String.format("Optional[%s]", value)
6
: "Optional.empty";
7
}

使用例子

  • 基本使用
1
public class OptionalDemo {
2
3
public static void main(String[] args) {
4
//创建Optional实例,也可以通过方法返回值得到。
5
Optional<String> name = Optional.of("Sanaulla");
6
7
//创建没有值的Optional实例,例如值为'null'
8
Optional empty = Optional.ofNullable(null);
9
10
//isPresent方法用来检查Optional实例是否有值。
11
if (name.isPresent()) {
12
//调用get()返回Optional值。
13
System.out.println(name.get());
14
}
15
53 collapsed lines
16
try {
17
//在Optional实例上调用get()抛出NoSuchElementException。
18
System.out.println(empty.get());
19
} catch (NoSuchElementException ex) {
20
System.out.println(ex.getMessage());
21
}
22
23
//ifPresent方法接受lambda表达式参数。
24
//如果Optional值不为空,lambda表达式会处理并在其上执行操作。
25
name.ifPresent((value) -> {
26
System.out.println("The length of the value is: " + value.length());
27
});
28
29
//如果有值orElse方法会返回Optional实例,否则返回传入的错误信息。
30
System.out.println(empty.orElse("There is no value present!"));
31
System.out.println(name.orElse("There is some value!"));
32
33
//orElseGet与orElse类似,区别在于传入的默认值。
34
//orElseGet接受lambda表达式生成默认值。
35
System.out.println(empty.orElseGet(() -> "Default Value"));
36
System.out.println(name.orElseGet(() -> "Default Value"));
37
38
try {
39
//orElseThrow与orElse方法类似,区别在于返回值。
40
//orElseThrow抛出由传入的lambda表达式/方法生成异常。
41
empty.orElseThrow(ValueAbsentException::new);
42
} catch (Throwable ex) {
43
System.out.println(ex.getMessage());
44
}
45
46
//map方法通过传入的lambda表达式修改Optonal实例默认值。
47
//lambda表达式返回值会包装为Optional实例。
48
Optional<String> upperName = name.map((value) -> value.toUpperCase());
49
System.out.println(upperName.orElse("No value found"));
50
51
//flatMap与map(Funtion)非常相似,区别在于lambda表达式的返回值。
52
//map方法的lambda表达式返回值可以是任何类型,但是返回值会包装成Optional实例。
53
//但是flatMap方法的lambda返回值总是Optional类型。
54
upperName = name.flatMap((value) -> Optional.of(value.toUpperCase()));
55
System.out.println(upperName.orElse("No value found"));
56
57
//filter方法检查Optiona值是否满足给定条件。
58
//如果满足返回Optional实例值,否则返回空Optional。
59
Optional<String> longName = name.filter((value) -> value.length() > 6);
60
System.out.println(longName.orElse("The name is less than 6 characters"));
61
62
//另一个示例,Optional值不满足给定条件。
63
Optional<String> anotherName = Optional.of("Sana");
64
Optional<String> shortName = anotherName.filter((value) -> value.length() > 6);
65
System.out.println(shortName.orElse("The name is less than 6 characters"));
66
67
}
68
}

运行结果:

1
Sanaulla
2
No value present
3
The length of the value is: 8
4
There is no value present!
5
Sanaulla
6
Default Value
7
Sanaulla
8
No value present in the Optional instance
9
SANAULLA
10
SANAULLA
11
Sanaulla
12
The name is less than 6 characters
  • Java8中提高对象的null值安全性

假设有如下的类层次结构:

1
class Outer {
2
Nested nested;
3
Nested getNested() {
4
return nested;
5
}
6
}
7
class Nested {
8
Inner inner;
9
Inner getInner() {
10
return inner;
11
}
12
}
13
class Inner {
14
String foo;
15
String getFoo() {
3 collapsed lines
16
return foo;
17
}
18
}

解决这种结构的深层嵌套路径是有点麻烦的,我们必须编写一堆null检查来确保不会导致一个 NullPointerException

我们可以通过利用Optional类型来摆脱所有这些null检查。map方法接收一个Function类型的lambda表达式,并自动将每个function的结果包装成一个Optional对象,这使我们能够在一行中进行多个 map 操作。

1
Optional.of(new Outer())
2
.map(Outer::getNested)
3
.map(Nested::getInner)
4
.map(Inner::getFoo)
5
.ifPresent(System.out::println);

使用总结

使用Optional工具类判断一个对象的NPE问题,一定要先通过其静态方法(of、empty、ofNullable)获得Optional对象,进而通过一些实例方法进行一系列的操作获得最后的对象。

API方法名称用处
of为非null的值创建一个Optional对象。
emptynull的值创建一个Optional对象。
ofNullable为指定的值创建一个Optional对象,如果指定的值为null,则返回一个空的Optional对象。
isPresent如果值不为null,则返回true,否则返回false
get如果Optional对象的值并不为空则将其返回,否则抛出NoSuchElementException
ifPresent如果Optional对象有值(不为空)则为其调用Consumer,否则不做处理
orElse如果有值则将其返回,否则返回指定的其它值。
orElseGetorElseGet方法可以接受Supplier接口的实现用来生成默认值。
orElseThrow如果有值则将其返回,否则抛出Supplier接口创建的异常。
map如果有值,则对其执行调用mapper函数得到返回值,并且将创建包含mapper返回值的Optional对象作为map方法返回值,否则返回空Optional对象。
flatMap如果有值,为其执行mapper函数返回Optional类型返回值,否则返回空Optional对象。flatMapmap方法类似,区别在于flatMap中的mapper返回值必须是Optional对象。调用结束时,flatMap不会对结果用Optional封装。
filter如果有值并且满足断言条件返回包含该值的Optional对象,否则返回空Optional对象。
本文标题:Java8新特性之Optional类
文章作者:Echoidf
发布时间:2023-04-13
感谢大佬送来的咖啡☕
alipayQRCode
wechatQRCode