Java8新特性之五:Optional
nullpointerexception相信每个java程序员都不陌生,是java应用程序中最常见的异常。之前,google guava项目曾提出用optional类来包装对象从而解决nullpointerexception。受此影响,jdk8的类中也引入了optional类,在新版的springdata jpa和spring redis data中都已实现了对该方法的支持。
1、optional类
1 /** 2 * a container object which may or may not contain a non-null value. 3 * if a value is present, {@code ispresent()} will return {@code true} and 4 * {@code get()} will return the value. 5 * 6 * @since 1.8 7 */ 8 public final class optional<t> { 9 /** 10 * common instance for {@code empty()}. 11 */ 12 private static final optional<?> empty = new optional<>(); 13 14 /** 15 * if non-null, the value; if null, indicates no value is present 16 */ 17 private final t value; 18 19 // 其他省略 20 }
该方法的注释大致意思是:optional是一个容器对象,它可能包含空值,也可能包含非空值。当属性value被设置时,ispesent()方法将返回true,并且get()方法将返回这个值。
该类支持泛型,即其属性value可以是任何对象的实例。
2、optional类的方法
序号 |
方法 |
方法说明 |
1 |
private optional() |
无参构造,构造一个空optional |
2 |
private optional(t value) |
根据传入的非空value构建optional |
3 |
public static<t> optional<t> empty() |
返回一个空的optional,该实例的value为空 |
4 |
public static <t> optional<t> of(t value) |
根据传入的非空value构建optional,与optional(t value)方法作用相同 |
5 |
public static <t> optional<t> ofnullable(t value) |
与of(t value)方法不同的是,ofnullable(t value)允许你传入一个空的value, 当传入的是空值时其创建一个空optional,当传入的value非空时,与of()作用相同 |
6 |
public t get() |
返回optional的值,如果容器为空,则抛出nosuchelementexception异常 |
7 |
public boolean ispresent() |
判断当家optional是否已设置了值 |
8 |
public void ifpresent(consumer<? super t> consumer) |
判断当家optional是否已设置了值,如果有值,则调用consumer函数式接口进行处理 |
9 |
public optional<t> filter(predicate<? super t> predicate) |
如果设置了值,且满足predicate的判断条件,则返回该optional,否则返回一个空的optional |
10 |
public<u> optional<u> map(function<? super t, ? extends u> mapper) |
如果optional设置了value,则调用function对值进行处理,并返回包含处理后值的optional,否则返回空optional |
11 |
public<u> optional<u> flatmap(function<? super t, optional<u>> mapper) |
与map()方法类型,不同的是它的mapper结果已经是一个optional,不需要再对结果进行包装 |
12 |
public t orelse(t other) |
如果optional值不为空,则返回该值,否则返回other |
13 |
public t orelseget(supplier<? extends t> other) |
如果optional值不为空,则返回该值,否则根据other另外生成一个 |
14 |
public <x extends throwable> t orelsethrow(supplier<? extends x> exceptionsupplier) |
如果optional值不为空,则返回该值,否则通过supplier抛出一个异常 |
3、optional类的方法举例
《java 8 features tutorial – the ultimate guide》中给出了两个简单的例子,我们从这两个例子入手来简单了解一下optional容器的使用。
示例一:
1 public static void main(string[] args) { 2 optional< string > fullname = optional.ofnullable( null ); 3 system.out.println( "full name is set? " + fullname.ispresent() ); 4 system.out.println( "full name: " + fullname.orelseget( () -> "[none]" ) ); 5 system.out.println( fullname.map( s -> "hey " + s + "!" ).orelse( "hey stranger!" ) ); 6 }
运行结果:
full name is set? false full name: [none] hey stranger!
说明:
ifpresent()方法当optional实例的值非空时返回true,否则返回false;
orelseget()方法当optional包含非空值时返回该值,否则通过接收的function生成一个默认的;
map()方法转换当前optional的值,并返回一个新的optional实例;
orelse()方法与orelseget方法相似,不同的是orelse()直接返回传入的默认值。
示例二:修改示例一,使其生成一个非空值的optional实例
1 optional< string > firstname = optional.of( "tom" ); 2 system.out.println( "first name is set? " + firstname.ispresent() ); 3 system.out.println( "first name: " + firstname.orelseget( () -> "[none]" ) ); 4 system.out.println( firstname.map( s -> "hey " + s + "!" ).orelse( "hey stranger!" ) );
输出结果:
first name is set? true first name: tom hey tom!
可以清晰地看出与示例一的区别。这不但简洁了我们的代码,而且使我们的代码更便于阅读。
下面看一下例子中使用到的几个方法的源码:
1)、of
1 public static <t> optional<t> of(t value) { 2 return new optional<>(value); 3}
2)、ispresent
1 public boolean ispresent() { 2 return value != null; 3 }
3)、orelseget
1 public t orelseget(supplier<? extends t> other) { 2 return value != null ? value : other.get(); 3 }
4)、orelse
1 public t orelse(t other) { 2 return value != null ? value : other; 3 }
其他方法源码,读者可以去optional源码中查看。
4、使用optional避免空指针
在我们日常开发过程中不可避免地会遇到空指针问题,在以前,出现空指针问题,我们通常需要进行调试等方式才能最终定位到具体位置,尤其是在分布式系统服务之间的调用,问题更难定位。在使用optional后,我们可以将接受到的参数对象进行包装,比如,订单服务要调用商品服务的一个接口,并将商品信息通过参数传入,这时候,传入的商品参数可能直接传入的就是null,这时,商品方法可以使用optional.of(t)对传入的对象进行包装,如果t为空,则会直接抛出空指针异常,我们看到异常信息就能立即知道发生空指针的原因是参数t为空;或者,当传入的参数为空时,我们可以使用optional.orelse()或optional.orelseget()方法生成一个默认的实例,再进行后续的操作。
下面再看个具体例子:在user类中有个address类,在address类中有个street类,street类中有streetname属性,现在的需求是:根据传入的user实例,获取对应的streetname,如果user为null或address为null或street为null,返回“nothing found”,否则返回对应的streetname。
实现一:
1 @data 2 public class user { 3 private string name; 4 private integer age; 5 private address address; 6 }
1 @data 2 public class address { 3 private street street; 4 }
1 @data 2 public class street { 3 private string streetname; 4 private integer streetno; 5 }
1 public string getusersteetname(user user) { 2 3 if(null != user) { 4 5 address address = user.getaddress(); 6 7 if(null != address) { 8 9 street street = address.getstreet(); 10 11 if(null != street) { 12 return street.getstreetname(); 13 } 14 } 15 } 16 17 return "nothing found"; 18 }
实现二,使用optional:
在实现一中明显的问题是if判断层级太深,下面复用optional改写:
1 @data 2 public class user { 3 private string name; 4 private integer age; 5 private optional<address> address = optional.empty(); 6 }
1 @data 2 public class address { 3 private optional<street> street = optional.empty(); 4 }
1 @data 2 public class street { 3 private string streetname; 4 private integer streetno; 5 }
1 public string getusersteetname(user user) { 2 3 optional<user> useroptional = optional.ofnullable(user); 4 final string streetname = useroptional.orelse(new user()).getaddress().orelse(new address()).getstreet().orelse(new street()).getstreetname(); 5 return stringutils.isempty(streetname) ? "nothing found" : streetname; 6 }
利用orelse()方法给定默认值的方式确保不会报空指针问题问题,同时也能实现需求。