Optional是Guava提出的概念,通过使用检查空值的方式来防止代码污染,鼓励程序员写更干净的代码,解决空指针异常NullPointerException。受到Google Guava的启发,Optional在Java8正式加入Java豪华套餐。Optional实际上是个容器,它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。由于Java8的Optional用起来比Guava的Optional更方便,所以在本篇学习笔记中,我介绍一下Java8的Optional的使用。
Optional容器构造方法:
S.N. | 方法及说明 |
---|---|
1 | static<T> Optional<T> empty() null包装成的Optional对象 |
2 | static <T> Optional<T> of(T value) 构造一个Optional对象,参数不能是null |
3 | static <T> Optional<T> ofNullable(T value) 构造一个Optional对象,参数允许为null |
Optional容器常用方法:
S.N. | 方法及说明 |
---|---|
1 | boolean isPresent() 检查持有的value是否为null |
2 | void ifPresent(Consumer<? super T> consumer) 如果持有的value不为null,通过Consumer函数式接口,对持有的value做相应的操作,比如打印等void操作 |
3 | T orElse(T other) 如果持有的Optional对象isPresent,则返回持有的value,否则返回orElse方法的参数,可用来设置默认值 |
4 | T orElseGet(Supplier<? extends T> other) 如果持有的Optional对象isPresent,则返回持有的value,否则通过Supplier函数式接口取回一个对象作为默认值返回 |
5 | <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X 如果持有的Optional对象isPresent,则返回持有的value,否则抛出一个异常 |
6 | T get() 取回持有的value,如果value为null,则抛出异常NoSuchElementException |
7 | Optional<T> filter(Predicate<? super T> predicate) 通过Predicate函数式接口定义的规则测试value,如果函数式接口返回true,则返回原Optional对象,否则通过 empty()方法返回一个Optional对象,可用来对Optional进行过滤 |
8 | Optional<U> map(Function<? super T, ? extends U> mapper) 对value通过Function函数式接口定义的规则返回一个对象,然后将返回的对象包装成一个Optional对象返回 |
9 | Optional<U> flatMap(Function<? super T, Optional<U>> mapper) 对value通过Function函数式接口定义的规则返回一个Optional对象,然后将返回的Optional对象返回 |
当从其他方法获取一个Optional对象或者自己创建一个Optional对象,可以使用isPresent判断Optional持有的value对象是否存在
@Test
public void testIsPresent() {
Optional<String> opt = Optional.of("zhuoli");
assertTrue(opt.isPresent());
opt = Optional.ofNullable(null);
assertFalse(opt.isPresent());
}
当Optional对象持有的value不为null时,可以使用ifPresent接口针对value做一些操作。比如没有使用Optional时,我们有可能回做如下操作:
if(name != null){
System.out.println(name.length);
}
当使用Optional后,我们可以如下操作:
@Test
public void testIfPresent() {
Optional<String> opt = Optional.of("zhuoli");
opt.ifPresent(name -> System.out.println(name.length()));
}
orElse可以用来获取Optional持有的value值,方法的参数作为默认值。如果Optional持有的value值不为null,返回value值,否则返回默认值。
@Test
public void orElseTest() {
String nullName = null;
String name = Optional.ofNullable(nullName).orElse("zhuoli");
assertEquals("zhuoli", name);
}
orElseGet功能与orElse相似,也是用来获取Optional持有的value值。不同的是当Optional持有的value值不为null时,它不是直接返回一个值,而是通过函数式接口的调用返回一个值。
@Test
public void orElseGetTest() {
String nullName = null;
String name = Optional.ofNullable(nullName).orElseGet(() -> "zhuoli");
assertEquals("zhuoli", name);
}
-
orElse与orElseGet的异同点
orElseGet功能与orElse相似,但是,这两者之间也存在一个非常重要的差异。如果不能很好地理解,会大幅度地影响代码的性能。通过如下的代码,大家可以看到两者之间的不同,首先定义一个取默认值得方法:
public String getMyDefault() {
System.out.println("Getting Default Value");
return "Default Value";
}
通过如下两个测试,看一下它们之间的差异:
@Test
public void orElseAndOrElseTestWhenOptionalValueIsNull() {
String text = null;
System.out.println("Using orElseGet:");
String defaultText =
Optional.ofNullable(text).orElseGet(this::getMyDefault);
assertEquals("Default Value", defaultText);
System.out.println("Using orElse:");
defaultText = Optional.ofNullable(text).orElse(getMyDefault());
assertEquals("Default Value", defaultText);
}
运行结果:
Connected to the target VM, address: '127.0.0.1:14005', transport: 'socket'
Using orElseGet:
Getting Default Value
Using orElse:
Getting Default Value
Disconnected from the target VM, address: '127.0.0.1:14005', transport: 'socket'
Process finished with exit code 0
@Test
public void orElseAndOrElseTestWhenOptionalValueIsNotNull() {
String text = "zhuoli";
System.out.println("Using orElseGet:");
String defaultText =
Optional.ofNullable(text).orElseGet(this::getMyDefault);
assertEquals("zhuoli", defaultText);
System.out.println("Using orElse:");
defaultText = Optional.ofNullable(text).orElse(getMyDefault());
assertEquals("zhuoli", defaultText);
}
运行结果:
Connected to the target VM, address: '127.0.0.1:14022', transport: 'socket'
Using orElseGet:
Using orElse:
Getting Default Value
Disconnected from the target VM, address: '127.0.0.1:14022', transport: 'socket'
Process finished with exit code 0
通过上述两个例子可以看出,当Optional持有的value为null时,都会去调用getMyDefault()方法,去获取默认值,这一点没什么好讲的。但当Optional持有的value不为null时,orEleseGet不会去调用getMyDefault()方法,直接返回了Optional持有的value。orElse虽然也返回了Optional持有的value,但是同时也去调用了getMyDefault()方法。上述简单的方法在性能上不会有什么区别,但是当getMyDefault()是一个非常复杂的方法,或者外部调用时,多调用一次无谓的方法,确实也是一种负担。所以当使用方法调用取默认值时,建议统一使用orElseGet。
orElseThrow也可以用来取默认值,不同的是,当Optional持有的value为null时,直接抛出异常。
@Test(expected = IllegalArgumentException.class)
public void orElseThrowTest() {
String nullName = null;
String name = Optional.ofNullable(nullName).orElseThrow(
IllegalArgumentException::new);
}
取回Optional持有的value,如果value为null,则抛出异常NoSuchElementException。
@Test
public void getTest() {
Optional<String> opt = Optional.of("zhuoli");
String name = opt.get();
assertEquals("zhuoli", name);
}
@Test(expected = NoSuchElementException.class)
public void getTestWhenValueIsNull() {
Optional<String> opt = Optional.ofNullable(null);
String name = opt.get();
}
通过Predicate函数式接口定义的规则测试value,如果函数式接口返回true,则返回原Optional对象,否则通过 empty()方法返回一个Optional对象,可用来对Optional进行过滤,如下:
@Test
public void filterTest() {
Integer year = 2016;
Optional<Integer> yearOptional = Optional.of(year);
boolean is2016 = yearOptional.filter(y -> y == 2016).isPresent();
assertTrue(is2016);
boolean is2017 = yearOptional.filter(y -> y == 2017).isPresent();
assertFalse(is2017);
}
filter常用于对Optional对象持有的value做一些判断,返回一个boolean值,会比原来直接使用POJO方便很多,如下定义一个POJO:
public class Modem {
private Double price;
public Modem(Double price) {
this.price = price;
}
//standard getters and setters
}
在不适用Optional对象时,我们判断一个Modem对象的price是否在某个范围之内,我们做如下很麻烦的操作:
public static boolean priceIsInRange1(Modem modem) {
boolean isInRange = false;
if (modem != null && modem.getPrice() != null
&& (modem.getPrice() >= 10
&& modem.getPrice() <= 15)) {
isInRange = true;
}
return isInRange;
}
但在使用Optional后,可以做如下简化:
public static boolean priceIsInRange2(Modem modem2) {
return Optional.ofNullable(modem2)
.map(Modem::getPrice)
.filter(p -> p >= 10)
.filter(p -> p <= 15)
.isPresent();
}
Optional带来的好处就是不用在显式的层层去检查一个对象是否为null了,并且支持链式操作,代码看起来要比原来“优雅”很多。
map可以用来转化Optional对象,对value通过Function函数式接口定义的规则返回一个对象,然后将返回的对象包装成一个Optional对象返回。
@Test
public void mapTest() {
List<String> companyNames = Lists.newArrayList(
"paypal", "oracle", "", "microsoft", "", "apple");
Optional<List<String>> listOptional = Optional.of(companyNames);
int size = listOptional
.map(List::size)
.orElse(0);
assertEquals(6, size);
}
假设我们想要检查用户输入的密码的正确性,我们可以通过map去除密码中非法的空格字符,然后通过filter过滤密码是否正确:
@Test
public void filterMapTest1() {
String password = " 123456 ";
Optional<String> passOpt = Optional.of(password);
boolean correctPassword = passOpt.filter(
pass -> pass.equals("password")).isPresent();
assertFalse(correctPassword);
correctPassword = passOpt
.map(String::trim)
.filter(pass -> pass.equals("password"))
.isPresent();
assertTrue(correctPassword);
}
flatMap也可以用来转化Optional对象,我也一直很迷惑两者之间的区别,通过看源码,发现它与Map的区别在于,对value通过Function函数式接口定义的规则的返回是不同的,Map直接返回一个对象,但是flatMap返回的是个Optional对象。源码如下
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
/*将函数式接口返回的对象调用ofNullable包装为optional对象返回*/
return Optional.ofNullable(mapper.apply(value));
}
}
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
/*直接将函数式接口返回的Optional对象返回*/
return Objects.requireNonNull(mapper.apply(value));
}
}
通过如下代码相信大家都可以看出区别所在,首先定义一个Person类:
public class Person {
private String name;
private int age;
private String password;
public Optional<String> getName() {
return Optional.ofNullable(name);
}
public Optional<Integer> getAge() {
return Optional.ofNullable(age);
}
public Optional<String> getPassword() {
return Optional.ofNullable(password);
}
// normal constructors and setters
}
@Test
public void filterMapTest1() {
Person person = new Person("zhuoli",18,"123");
Optional<Person> personOptional = Optional.of(person);
Optional<Optional<String>> nameOptionalWrapper
/*Person::getName返回Optional<String>,map方法嵌套Optional<String>对象返回*/
= personOptional.map(Person::getName);
Optional<String> nameOptional
= nameOptionalWrapper.orElseThrow(IllegalArgumentException::new);
String name1 = nameOptional.orElse("");
assertEquals("zhuoli", name1);
String name = personOptional
/*Person::getName返回Optional<String>,flatMap直接将Optional<String>对象返回*/
.flatMap(Person::getName)
.orElse("");
assertEquals("zhuoli", name);
}
当Optional对象持有的value对象包含Optional成员变量或者get方法返回Optional对象时,使用flatMap操作,可以直接获取不嵌套的Optional对象。
最后,给大家分享一篇Optional相关的博客使用 Java8 Optional 的正确姿势,感觉写的很不错。其中的观点绝大多数我都是认同的,除了博主讲使用isPresent()方法,说明你对Optional的使用姿势是不对的。比如我在本文中提到的,通过和filter配合,最后通过isPresent判断对象是不是满足条件就是isPresent很不错的一个应用。
测试代码:码云 – 卓立 – Java8 Optional测试代码
楼主,我是 【Java】jdk8 Optional 的正确姿势 原作者,原始链接在我的博客 https://unmi.cc/proper-ways-of-using-java8-optional/。写的文章经几番转载,原链接就不说了,连标题都走样了。
感谢对 isPresent 方法使用上的点评,当时对 filter 确实未作深入。
感谢评论,楼主那篇文章写的确实很好,希望可以共同进步,文章中的链接已修改,这个几经转载确实有点无奈==