JAVA8給我帶了什麽——Optional和CompletableFuture
不管是JAVA,還是.NET。我們常常會看到空異常(NullPointerException)。這種異常都是在運行的過程中出現。往往是變量是一個null值。但是你引用這個變量的後繼字段或是方法。所以我們代碼裏面常常會出現if (變量!=null)的相關操作。
如果你是一個.NET開發人員的話,那麽你一定知道.NET的可以為空的數據類型。同樣子java8引入了一個Optional類型,目地是為了決解為空帶來的一系列問題。Optional類提供了倆個靜態的方法
- of方法:創建一個非空的Optional類型。
- ofNullable方法:創建一個可以為空的Optional類型。
讓我們一起看一下用法吧。
1 package com.aomi; 2 3 import java.util.HashMap; 4 import java.util.Map; 5 import java.util.Optional; 6 7 public class Main { 8 private static Map<String, String> maps = new HashMap<String, String>(); 9 10 public static void main(String[] args) {11 // TODO Auto-generated method stub 12 13 maps.put("aomi", "val1"); 14 maps.put("key1", "val2"); 15 16 String val = getValue("aaa"); 17 18 Optional<String> optionalVal = Optional.ofNullable(val); 19 20 if(optionalVal.isPresent())21 { 22 System.out.println(val.replace("a", "b")); 23 } 24 else 25 { 26 System.out.println("拿到變量值為空"); 27 } 28 } 29 30 public static String getValue(String key) { 31 if (maps.containsKey(key)) 32 return maps.get(key); 33 return null; 34 } 35 }
運行結果:
isPresent方法用於判斷當前的變量是否為空。從某意義上來講筆者覺得這好像並沒有多大的好處。同樣子我們要用isPresent來判斷是否為空。那麽跟寫if(變量!=null)有什麽分別。所以筆者打算換一換。
1 package com.aomi; 2 3 import java.util.HashMap; 4 import java.util.Map; 5 import java.util.Optional; 6 7 public class Main { 8 private static Map<String, String> maps = new HashMap<String, String>(); 9 10 public static void main(String[] args) { 11 // TODO Auto-generated method stub 12 13 maps.put("aomi", "val1"); 14 maps.put("key1", "val2"); 15 16 String notNullVal = getValue("aomi"); 17 String nullVal = getValue("aaa"); 18 19 Optional<String> optNotNullVal = Optional.ofNullable(notNullVal); 20 Optional<String> optNullVal = Optional.ofNullable(nullVal); 21 22 System.out.println(optNotNullVal.orElse("拿到變量值為空")); 23 System.out.println(optNullVal.orElse("拿到變量值為空")); 24 } 25 26 public static String getValue(String key) { 27 if (maps.containsKey(key)) 28 return maps.get(key); 29 return null; 30 } 31 }
上面的代碼是這樣子的。筆者拿倆個變量,一個變量是為空的。一個不為空。然後筆者用Optional類的orElse來做文章。顯示如下。
當然Optional類裏面提供了幾個用於獲得值的方法。
- get方法:就是用於獲得值,如果當前的Optional類是一個有值的變量,那麽就返回值。如果沒有的話,不好意思!他會報錯。
- orElse方法:表示如果為空的話,我就返回方法給定的值。否則返回當前的值。
- orElseGet方法:表示如果為空的話,執行一個回調的方法函數。你可以傳入一個lambda表達。
- orElseThrow方法:表示如果為空的話,回返一個異常,可以是一個自定義的異常。
以上這些方法,筆者認為並不能說明Optional類的特別之處。如下
1 package com.aomi; 2 3 import java.util.HashMap; 4 import java.util.Map; 5 import java.util.Optional; 6 7 public class Main { 8 private static Map<String, String> maps = new HashMap<String, String>(); 9 10 public static void main(String[] args) { 11 // TODO Auto-generated method stub 12 13 maps.put("aomi", "val1"); 14 maps.put("key1", "val2"); 15 16 String notNullVal = getValue("aomi"); 17 18 19 Optional<String> optNotNullVal = Optional.ofNullable(notNullVal); 20 21 Optional<String> optNullNewVal = optNotNullVal.map(ss -> ss.replace("a", "b")); 22 23 System.out.println(optNullNewVal.orElse("拿到變量值為空")); 24 } 25 26 public static String getValue(String key) { 27 if (maps.containsKey(key)) 28 return maps.get(key); 29 return null; 30 } 31 }
運行結果:
我們可以到Optional類提供了一個map方法。這個功能跟以前講到的流的map有一點類似。你們可以看到筆者在上面通過map方法來把‘a‘字符替換為‘b’。最後val1變成為vbl1。如果筆者還想把‘l‘替換為’r‘。後面在增加一個Map如下
Optional<String> optNullNewVal = optNotNullVal.map(ss -> ss.replace("a", "b")).map(ss->ss.replace("l","r"));
即然有map方法了。是不是也主是有filter方法。沒有錯。還真的有。如下。
1 package com.aomi; 2 3 import java.util.HashMap; 4 import java.util.List; 5 import java.util.Map; 6 import java.util.Optional; 7 8 public class Main { 9 private static Map<String, String> maps = new HashMap<String, String>(); 10 11 public static void main(String[] args) { 12 // TODO Auto-generated method stub 13 14 maps.put("aomi", "vbl1"); 15 maps.put("key1", "val2"); 16 17 String notNullVal = getValue("aomi"); 18 19 Optional<String> optNotNullVal = Optional.ofNullable(notNullVal); 20 21 Optional<String> optNullNewVal = optNotNullVal.filter( ss->ss.contains("a")).map(ss->ss.replace("a", "b")); 22 23 System.out.println(optNullNewVal.orElse("拿到變量值為空")); 24 } 25 26 public static String getValue(String key) { 27 28 if(maps.containsKey(key)) 29 return maps.get(key); 30 return "map"; 31 } 32 }
筆者找出含有’a‘字符的字串符。然後"a"替換 "b"。主要修改代碼倆個地方。如下
1.把val1修改為vbl1。主要是讓他有值,卻不含有‘a‘字符。了為證明他可以過濾掉有‘a’的字符串
maps.put("aomi", "vbl1");
2.增加filter方法進行過濾。條件必須含有‘a‘
.filter( ss->ss.contains("a"))
運行結果:
JAVA為了空值的問題增加了Optional類。提功能了一系列功能。大家可以試著用用感覺如何。
筆者記得好像是在JAVA5的時候,JAVA引一個Future接口。如果你沒有印像的話,那你們有沒有用到過FutureTask類呢。 以前要創建一個多線程的話,一般有倆種。一種是繼承Thread;一種是實現Runnable
1 package com.aomi; 2 3 public class Main { 4 5 public static void main(String[] args) { 6 // TODO Auto-generated method stub 7 8 Thread th1 = new Thread() { 9 @Override 10 public void run() { 11 System.out.println("這是一個Thread副線程"); 12 } 13 }; 14 15 th1.start(); 16 17 18 Thread th2 = new Thread(new Runnable() { 19 20 @Override 21 public void run() { 22 System.out.println("這是一個Runnable副線程"); 23 } 24 }); 25 26 th2.start(); 27 28 try { 29 Thread.sleep(2000); 30 } catch (InterruptedException e) { 31 // TODO Auto-generated catch block 32 e.printStackTrace(); 33 } 34 35 System.out.println("這是一個主線程"); 36 } 37 38 }
運行結果:
我們可以看到代碼中的倆種方式了吧。這倆個方式都只有一個毛病。沒有辦法實現返回值的功能。所以引入了Future接口。如下
1 package com.aomi; 2 3 import java.util.concurrent.Callable; 4 import java.util.concurrent.ExecutionException; 5 import java.util.concurrent.FutureTask; 6 7 public class Fmain { 8 public static void main(String[] args) { 9 10 FutureTask<String> task = new FutureTask<>(new Callable<String>() { 11 @Override 12 public String call() throws Exception { 13 Thread.sleep(1000); 14 return "i am aomi"; 15 } 16 }); 17 18 new Thread(task).start(); 19 20 try { 21 System.out.println("主線程: 結果=" + task.get()); 22 } catch (InterruptedException e) { 23 // TODO Auto-generated catch block 24 e.printStackTrace(); 25 } catch (ExecutionException e) { 26 // TODO Auto-generated catch block 27 e.printStackTrace(); 28 } 29 30 } 31 }
運行結果:
我可以看到一個叫Callable接口。是裏面的call方法和Runnable方法有一點像。只是一個有返回值,一個沒有。FutureTask類同時也提了很多方法。比如上的代碼筆者在改改。加入判斷是否取消了。如果沒有取消的話,就取消掉他。然後也去獲取他的值。
1 package com.aomi; 2 3 import java.util.concurrent.Callable; 4 import java.util.concurrent.ExecutionException; 5 import java.util.concurrent.FutureTask; 6 7 public class Fmain { 8 public static void main(String[] args) { 9 10 FutureTask<String> task = new FutureTask<>(new Callable<String>() { 11 @Override 12 public String call() throws Exception { 13 Thread.sleep(1000); 14 System.out.println("副線程:返回值=i am aomi"); 15 return "i am aomi"; 16 } 17 }); 18 19 new Thread(task).start(); 20 21 try { 22 if (!task.isCancelled()) 23 { 24 task.cancel(true); 25 System.out.println("取消副線程"); 26 System.out.println("主線程: 結果=" + task.get()); 27 } 28 else 29 { 30 System.out.println("主線程: 結果=" + task.get()); 31 } 32 33 } catch (InterruptedException e) { 34 // TODO Auto-generated catch block 35 e.printStackTrace(); 36 } catch (ExecutionException e) { 37 // TODO Auto-generated catch block 38 e.printStackTrace(); 39 } 40 41 } 42 }
運行結果:
看到了沒有被取消了。同時你去獲得取消線程的結果時,會發生異常。有沒有.NET的程序員,感覺像不像.NET的任務(Task)。 事實上有上面的功能大部業務都可以實現了。但是JAVA8還是又引一個叫CompletableFuture類。相對於Future接口增加了很多方法。如下獲得異步裏面的結果。
1 package com.aomi; 2 3 import java.util.concurrent.CompletableFuture; 4 import java.util.concurrent.ExecutionException; 5 6 public class Cmain { 7 8 public static void main(String[] args) { 9 // TODO Auto-generated method stub 10 11 CompletableFuture<String> future = new CompletableFuture<>(); 12 13 new Thread(new Runnable() { 14 15 @Override 16 public void run() { 17 18 try { 19 Thread.sleep(2000); 20 } catch (InterruptedException e) { 21 // TODO Auto-generated catch block 22 e.printStackTrace(); 23 } 24 25 future.complete("i am a CompletableFuture"); 26 27 } 28 }).start(); 29 30 31 try { 32 33 System.out.println(future.get()); 34 35 } catch (InterruptedException e) { 36 // TODO Auto-generated catch block 37 e.printStackTrace(); 38 } catch (ExecutionException e) { 39 // TODO Auto-generated catch block 40 e.printStackTrace(); 41 } 42 43 } 44 45 }
運行結果:
筆者在線程裏面睡了2000秒。所以你們運行之個例子的時候,會發現慢了2秒才顯示結果。說明future.get()會等線程的結果。事實上FutureTask類也是一樣子。所以CompletableFuture類提供一系列的功能組合。只要設計好的話,性能會提高很多。
1 package com.aomi; 2 3 import java.util.concurrent.CompletableFuture; 4 import java.util.concurrent.ExecutionException; 5 import java.util.concurrent.Executor; 6 import java.util.concurrent.Executors; 7 import java.util.concurrent.ThreadFactory; 8 9 public class Dmain { 10 11 public static void main(String[] args) { 12 // TODO Auto-generated method stub 13 14 CompletableFuture<String> oneFuture = CompletableFuture.supplyAsync(() -> { 15 16 System.out.println("supplyAsync用於新建"); 17 return "2011"; 18 }); 19 20 CompletableFuture<Long> twoFuture = oneFuture.thenApply((ss) -> { 21 System.out.println("thenApply用於轉化"); 22 return Long.parseLong(ss); 23 }); 24 25 CompletableFuture<Long> threeFuture = twoFuture.thenCompose(val -> { 26 System.out.println("thenCompose用於組合倆個CompletableFuture,但是依賴上一個CompletableFuture"); 27 try { 28 Thread.sleep(2000); 29 } catch (InterruptedException e) { 30 // TODO Auto-generated catch block 31 e.printStackTrace(); 32 } 33 34 return CompletableFuture.supplyAsync(() -> 35 { 36 long result = val * 2; 37 System.out.println("thenCompose的結果是:"+ result); 38 return result; 39 } 40 41 ); 42 }); 43 44 CompletableFuture<String> otherFuture = CompletableFuture.supplyAsync(() -> { 45 System.out.println("用於thenCombine的測試 上面的結果+4"); 46 return "4"; 47 }); 48 49 CompletableFuture<Long> finalFuture = threeFuture.thenCombine(otherFuture, (arg1, arg2) -> { 50 return arg1 + Long.parseLong(arg2); 51 }); 52 53 finalFuture.thenAccept((ss) -> { 54 System.out.println("thenAccept用於處理相關的結果數據"); 55 }); 56 57 finalFuture.thenRun(() -> { 58 System.out.println("thenRun用於異步完成,執行相關的操作"); 59 }); 60 61 try { 62 63 System.out.println(finalFuture.get()); 64 } catch (InterruptedException e) { 65 // TODO Auto-generated catch block 66 e.printStackTrace(); 67 } catch (ExecutionException e) { 68 // TODO Auto-generated catch block 69 e.printStackTrace(); 70 } 71 72 } 73 74 }
運行結果:
這個個方法的作用筆者略微的列了出來。想要加深的話,你們可能最好在去找一些資料。關於CompletableFuture的教程網絡上很多。
JAVA8給我帶了什麽——Optional和CompletableFuture