對於無法在工作中使用Scala和kotlin開發的人,Vavr是一個很好的折中的方案,提供了持久的數據類型和功能控制結構。這里對Vavr里面的常用模塊做一些簡單的介紹,需要詳細了解的請去官網查看文檔(https://www.vavr.io/vavr-docs/)。
vavr支持多種數據結構,彌補了常見collection的不足,擴展了數據集合的操作方式。
一、支持不可變的數據結構
對於多線程的操作,不同線程異步對共享數據的修改會存在一定的風險,稍不留意可能導致數據的錯誤。而不可變數據集合,避免了對數據的修改,減少了多線程修改數據導致錯誤的可能性。同時通過map和flatMap又完美的實現了數據的轉換,代替了修改操作。
@Test public void test() { java.util.List<String> otherList = new ArrayList<>(); java.util.List<String> list = Collections.unmodifiableList(otherList); /** * 這里會拋出 UnsupportedOperationException * Collections.unmodifiableListN 返回的是 * java.util.Collections.UnmodifiableList#UnmodifiableList */ list.add("a"); //通過 map實現對數據的修改 List<String> listNew = list.parallelStream().map(i -> i +"--").collect(Collectors.toList()); }
Collections.unmodifiableList返回的是一個UnmodifiableList的子類,而該方法中所對元素進行增加/刪除的操作進行了屏蔽.
list 的一些簡單操作
//指定元素數量 List<Integer> list1 =List.of(1,2,3,4); //循環10次填充1 List list2 =List.fill(10,1); //填充1-> 100 的數據 List list3 = List.range(1,100); //指定步長為20 List list4 = List.rangeBy(1,100,20); // vavr List 轉換為 java list java.util.List<String> list5 = list4.asJava(); // list5.add("A"); //不可變list,無法添加元素 //轉變為可變list, java.util.List<String> list6 = list4.asJavaMutable(); list6.add("A"); int a = list1.map(i -> (i * 10)).flatMap(i-> List.fill(i,i)).foldLeft(0,(m,n)->m + n); // 3000
執行結果:
二,支持元組
Java里面的方法是無法返回具有不同元素的集合的,同時也無法像Python一樣返回多個參數。但在某些場景下,有時候需要返回多個不同類型的結果值。此時對於Java就又如下幾種方案:1、把結果放在 Collection<Object>中返回;2、封裝一個Java對象,把結果當做對象的參數返回;3、用Object [] 數組來存儲不同的類型數據。而Vavr借鑒了Scala里面的元組Tuple類型,支持封裝多個不同類型結果。
Tuple0 entry0 = Tuple.empty(); Tuple1<Integer> entry1 = Tuple.of(1); Tuple2<Integer, String> entry2 = Tuple.of(1, "A"); Tuple3<Integer, String, A> entry3 = Tuple.of(1, "A", new A()); //entry3._1 = 1 //entry3._2 = "A" //entry3._1 = A() //通過map實現了類型的轉換 Tuple2<String,Integer> newTuple2 = entry2.map( s -> "s = "+s, i -> (int)(i.charAt(0)));
元組的類型為Tuple0,Tuple1,Tuple2,Tuple3等。當前有8個元素的上限。要訪問元組的元素t
,可以使用方法t._1
訪問第一個元素,t._2
訪問第二個元素,依此類推。
三、函數的操作
對於函數式編程,最重要的就是函數的組合,函數作為第一公民,可以當做變量和參數進行傳遞和調用。在vavr中同樣是支持了andThen、compose等。
Function1<Integer, Integer> plusOne = a -> a + 1; Function1<Integer, Integer> multiplyByTwo = a -> a * 2; Function1<Integer, Integer> add1AndMultiplyBy2 = plusOne.andThen(multiplyByTwo); Integer result = add1AndMultiplyBy2.apply(2); // result = 6 Function1<Integer, Integer> add1AndMultiplyBy3 = multiplyByTwo.compose(plusOne); add1AndMultiplyBy3.apply(2);
同樣,對於柯里化,vavr也是支持的。通過柯里化,可以減少函數的入參。
//三個入參 Function3<Integer, Integer, Integer, Integer> sum = (a, b, c) -> a + b * c; //轉換為兩個入參,a默認設置為2 final Function1<Integer, Function1<Integer, Integer>> add2 = sum.curried().apply(2); //轉換為一個參數,a默認為2. 此時 mulit3 = (c) -> 2 + 3 * c final Function1<Integer, Integer> mulit3 = add2.curried().apply(3); Integer result = mulit3.apply(5); //17
四、記憶化
記憶化是緩存的一種形式。記憶功能僅執行一次,然后從緩存返回結果。
下面的示例在第一次調用時計算一個隨機數,並在第二次調用時返回緩存的數字。
Function0<Double> hashCache = Function0.of(Math::random).memoized(); double randomValue1 = hashCache.apply(); //首次觸發生成 hashCache double randomValue2 = hashCache.apply(); //第二次調用時返回第一次生成的 hashCache System.out.printf(String.valueOf(randomValue1 - randomValue2)); // 0.0
五、對Option 進行支持
Option使用的場景還是比較多的,為了避免NPE的情況,通過Option可以對null值進行包裝,返回empty或者None對象,減少了在代碼中通過 if(value == null)的判斷邏輯。
//java 的 Optional Optional<String> javaMaybeFoo = Optional.of("foo"); Optional<String> javaMaybeFooBar = javaMaybeFoo.map(s -> (String)null) .map(s -> s.toUpperCase() + "bar"); System.out.println(javaMaybeFooBar); //Optional.empty //vavr 同樣支持 Option,這里的Option和Scala的Option幾乎一模一樣 Option<String> maybeFoo = Option.of("foo"); Option<String> maybeFooBar = maybeFoo.flatMap(s -> Option.of((String)null)) .map(s -> s.toUpperCase() + "bar"); System.out.println(maybeFooBar); //None
六、Try對異常的處理
在Java中,如果發生異常且未進行捕獲,則當前線程就會被中斷,后續的調用就會停止。而Java中通過try{}catch(){}對可能出現異常的代碼塊進行捕獲,保證了發生異常之后能夠進行有效的處理,且順利的執行完余下的工作。但問題是通過大量的try{}catch代碼塊看起來較為臃腫。vavr中通過Try類型則很好的解決了try{}catch的臃腫問題。
Try result = Try.of(() -> 0) .map((a) -> 10 / a) //即使此處拋出異常,不會導致當前線程結束。這里無需使用 try{}catch()對代碼進行捕獲 .andThen(() -> System.out.printf("--拋出異常此處不會執行--")) //執行一個動作,不修改結果 .map(i -> { System.out.println("當前值:" + i); return i + 10; }) .onFailure(e -> e.printStackTrace())//失敗時會觸發onFailure .recover(ArithmeticException.class, 1000) //如果遇到 Exception類型的異常,則返回1000 .map((a) -> a + 1); System.out.println("是否拋出異常:" + result.isFailure()); System.out.println("執行結果:" + result.getOrElse(100)); //如果有異常發生,則返回100
七、延遲計算Lazy
Lazy 類型數據在不進行get前是不會觸發計算的,只有在調用get方法時,才會觸發整個流程的計算,起到延遲計算的作用。
Lazy<Double> lazy = Lazy.of(Math::random) .map(i -> { System.out.println("-----正在進行計算,此處只會執行一次------"); return i * 100; }); System.out.println(lazy.isEvaluated()); System.out.println(lazy.get()); //觸發計算 System.out.println(lazy.isEvaluated()); System.out.println(lazy.get());//不會重新計算,返回上次結果
八、線程利器Future
vavr通過Future簡化了線程的使用方式,不用再像Java異常進行創建Callable,無需進行submit,直接創建一個Future對象即可。Future提供的所有操作都是非阻塞的,其底層的ExecutorService用於執行異步處理程序
System.out.println("當前線程名稱:" + Thread.currentThread().getName()); Integer result = Future.of(() -> { System.out.println("future線程名稱:" + Thread.currentThread().getName()); Thread.sleep(2000); return 100; }) .map(i -> i * 10) .await(3000, TimeUnit.MILLISECONDS) //等待線程執行3000毫秒 .onFailure(e -> e.printStackTrace()) .getValue() //返回Optional<Try<Integer>>類型結果 .getOrElse(Try.of(() -> 100)) //如果Option 為 empty時,則返回Try(100) .get(); System.out.println(result); // 1000
執行結果:
當前線程名稱:main
future線程名稱:ForkJoinPool.commonPool-worker-1
1000
九、減少if else的模式匹配
Java的switch case是具有很大的局限性的,僅僅對簡單的基本類型進行了支持,而對於包裝類型是無法使用的。而在Scala中是支持各種類型的模式匹配,不僅如此,其還具有對象解構、前置條件,守衛,而vavr支持了這些功能。
vavr中用$表達不同的匹配模式。
-
$()
-通配符模式 -
$(value)
-等於模式 -
$(predicate)
-條件模式
int i = 1; String s = Match(i).of( Case($(1), "one"), //等值匹配 Case($(2), "two"), Case($(), "?") ); String b = Match(i).of( Case($(is(1)), "one"), //謂詞表達式匹配 Case($(is(2)), "two"), Case($(), "?") ); String arg = "-h"; Match(arg).of( Case($(isIn("-h", "--help")), o -> run(this::displayHelp)), //支持在成功匹配后執行動作 Case($(isIn("-v", "--version")), o -> run(this::displayVersion)), Case($(), o -> run(() -> { throw new IllegalArgumentException(arg); })) ); A obj = new A(); Number plusOne = Match(obj).of( Case($(instanceOf(Integer.class)), i -> i + 1), //根據值類型進行匹配 Case($(instanceOf(Double.class)), d -> d + 1), Case($(), o -> { throw new NumberFormatException(); }) ); Tuple2 tuple2 = Tuple("a",2); Try<Tuple2<String,Integer>> _try = Try.success(tuple2); Match(_try).of( Case($Success($Tuple2($("a"), $())), tuple22 ->{}), Case($Failure($(instanceOf(Error.class))), error -> error.fillInStackTrace()) ); Option option = Option.some(1); Match(option).of( Case($Some($()), "defined"), Case($None(), "empty") );
除了上面比較常用的數據類型,vavr還有其他各種便捷的數據結構,感興趣的可以深入了解一下。
=========================================
原文鏈接:Java函數式編程類庫-Vavr
=========================================