Java函數式編程類庫-Vavr


  對於無法在工作中使用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

=========================================


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM