本文在理清Optional的基本用法的同時,附帶對比Optional中的map/flatMap和stream中的map/flatMap。
問題
Java 8中的Optional號稱可以避免NPE(Null PointerException) 。但在日常工作中看到很多代碼中Optional的使用,是用 isPresent() 函數(shù)來判別是否為null。下面這樣的判斷方式,還不如直接 bean !=null 這樣的判斷,區(qū)別只是沒有出現(xiàn)null這個關(guān)鍵詞而已。更不用說鏈式的,不停的調(diào)用isPresent()。
如下:
if(beanOpt.isPresent()){
do somethings..
}
if(beanOpt.isPresent() && beanOpt.get().getBean1().isPresent()){
beanOpt.get().getBean1().get() ... do somethings
}
這樣的代碼讓閱讀的人不想讀,修改的人不愿意修改。
這是起到了規(guī)避Null判斷的作用,但是多此一舉,顯然,也不是Optional的正確用法。避免NPE是要Optional配合map / flatmap函數(shù)使用的。
map / flatMap的基本含義
map/flatMap在函數(shù)式編程中非常常用(map與reduce組成經(jīng)典的map-reduce編程范式,flatMap是map的一種語法糖)。通常map是做元素映射,flatMap是做映射的同時去掉多層級的集合,只保留一層(所謂flat——打平)。Optional也延用了這兩個名稱,基本含義保持,特別的是Optional中僅一個元素,打平是去optional的包裝,只保留一層。
Optional的map, flatmap的用法
- map / flatmap 函數(shù)
- map/flatMap的函數(shù)參數(shù)中,函數(shù)的輸入是optional內(nèi)被包裝的對象,因此不需要顯式處理optional的拆箱操作(用拆箱表達從optional中g(shù)et元素的動作)
- map/flatMap的返回值會被自動包裝到optional內(nèi)返回,可以使用orElse(),ifPresent()來避免if-else的null判斷
- 因為map/flatMap的這個自動拆optional的這個特性,可以對于多層optional嵌套可以鏈式map搞定
- 這個自動拆箱的過程不會拋出NPE,為null的元素會作為Optional.empty()向后傳遞
- map/flatMap的區(qū)別在于,如果函數(shù)參數(shù)中的返回值為optional,flatMap只會留一層option包裝,而map會直接保留原有optional的包裝(見第二段代碼示例),因此flatMap的函數(shù)參數(shù)中的返回值必須是optional包裝對象(這個限制在函數(shù)聲明中已經(jīng)限制)
這些就是optional為處理NPE的工作。除此外optional還有很多方法,都非常簡潔表意,不再贅述。
// map 處理NPE
public static void main(String[] args) {
Optional<String> strOpt = Optional.of("hello world!");
Optional<String> emptyOpt = Optional.empty();
System.out.println(getStrLength(strOpt));
System.out.println(getStrLength(emptyOpt));
}
private static Integer getStrLength(Optional<String> strOpt) {
return strOpt.map(String::length).orElse(0);
}
--輸出------
12
0
Process finished with exit code 0
// map / flatMap 的對比
public static void main(String[] args) {
Optional<Bean> beanOpt = Optional.of(new Bean("beanId"));
Optional<Bean> emptyOpt = Optional.empty();
System.out.println(testFlatMap(beanOpt));
System.out.println(testFlatMap(emptyOpt));
System.out.println(testMap(beanOpt));
System.out.println(testMap(emptyOpt));
}
private static String testFlatMap(Optional<Bean> strOpt) {
return strOpt.flatMap(Bean::getId).orElse("noneId");
}
private static Optional<String> testMap(Optional<Bean> strOpt) {
return strOpt.map(Bean::getId).orElse(Optional.of("noneId"));
}
static class Bean{
String id;
public Bean(String id) {
this.id = id;
}
public Optional<String> getId() {
return Optional.ofNullable(id);
}
public void setId(String id) {
this.id = id;
}
}
---輸出--------
beanId
noneId
Optional[beanId]
Optional[noneId]
Process finished with exit code 0
延展:函數(shù)式中的map, flatmap
- map的基本用法
public static void main(String[] args) {
List<Bean> beans = new ArrayList<>();
beans.add(new Bean("bean1"));
beans.add(new Bean("bean2"));
beans.add(new Bean());
// 對每個beans中的bean,獲取他們的id字段,轉(zhuǎn)換為對應(yīng)的集合
List<String> ids = beans.stream()
.map(bean -> bean.getId())
.map(opt -> opt.orElse("noneId"))
.collect(Collectors.toList());
System.out.println(ids.stream().collect(Collectors.joining(",")));
}
static class Bean{
String id;
public Bean(String id) {
this.id = id;
}
public Bean() {
}
public Optional<String> getId() {
return Optional.ofNullable(id);
}
public void setId(String id) {
this.id = id;
}
}
---- 輸出-------------------
bean1,bean2,noneId
Process finished with exit code 0
- flatMap的基本用法
public static void main(String[] args) {
Stream<Bean> beanStream = Stream.of(
new Bean("bean1"),
new Bean("bean2"),
new Bean());
// 對每個beans中的bean,獲取他們的id字段,轉(zhuǎn)換為對應(yīng)的集合
List<String> chars = beanStream
.map(bean -> bean.getId().orElse("noneId"))
.flatMap(id -> Arrays.stream(id.split("")))
.collect(Collectors.toList());
System.out.println(chars.stream().collect(Collectors.joining(",")));
}
static class Bean{
String id;
public Bean(String id) {
this.id = id;
}
public Bean() {
}
public Optional<String> getId() {
return Optional.ofNullable(id);
}
public void setId(String id) {
this.id = id;
}
}
-----輸出----------------
b,e,a,n,1,b,e,a,n,2,n,o,n,e,I,d
Process finished with exit code 0