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.unmodifiableListotherList);
        /**
         * 这里会抛出 UnsupportedOperationException
         *  Collections.unmodifiableListN 返回的是
         *  java.util.Collections.UnmodifiableList#UnmodifiableList
         */
        list.add"a");
        //通过 map实现对数据的修改
    List<String> listNew =  list.parallelStream).mapi -> i +"--").collectCollectors.toList));
    }

 Collections.unmodifiableList返回的是一个UnmodifiableList的子类,而该方法中所对元素进行增加/删除的操作进行了屏蔽.

list 的一些简单操作
    //指定元素数量
        List<Integer> list1 =List.of1,2,3,4);
        //循环10次填充1
        List list2 =List.fill10,1);
        //填充1-> 100 的数据
        List list3 = List.range1,100);
        //指定步长为20
        List list4 = List.rangeBy1,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.mapi -> i * 10)).flatMapi-> List.filli,i)).foldLeft0,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.of1);
        Tuple2<Integer, String> entry2 = Tuple.of1, "A");
        Tuple3<Integer, String, A> entry3 = Tuple.of1, "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.charAt0)));

  元组的类型为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.andThenmultiplyByTwo);
        Integer result  = add1AndMultiplyBy2.apply2); // result = 6
        Function1<Integer, Integer> add1AndMultiplyBy3 = multiplyByTwo.composeplusOne);
        add1AndMultiplyBy3.apply2);

  同样,对于柯里化,vavr也是支持的。通过柯里化,可以减少函数的入参。

        //三个入参
        Function3<Integer, Integer, Integer, Integer> sum = a, b, c) -> a + b * c;
        //转换为两个入参,a默认设置为2
        final Function1<Integer, Function1<Integer, Integer>> add2 = sum.curried).apply2);
        //转换为一个参数,a默认为2. 此时 mulit3 = (c) -> 2 + 3 * c
        final Function1<Integer, Integer> mulit3 = add2.curried).apply3);
        Integer result = mulit3.apply5); //17
        

 四、记忆化

 记忆化是缓存的一种形式。记忆功能仅执行一次,然后从缓存返回结果。
  下面的示例在第一次调用时计算一个随机数,并在第二次调用时返回缓存的数字。

     Function0<Double> hashCache = Function0.ofMath::random).memoized);
        double randomValue1 = hashCache.apply); //首次触发生成 hashCache
        double randomValue2 = hashCache.apply); //第二次调用时返回第一次生成的 hashCache
        System.out.printfString.valueOfrandomValue1 - randomValue2)); // 0.0

  五、对Option 进行支持

  Option使用的场景还是比较多的,为了避免NPE的情况,通过Option可以对null值进行包装,返回empty或者None对象,减少了在代码中通过 ifvalue == null)的判断逻辑。

        //java 的 Optional
        Optional<String> javaMaybeFoo = Optional.of"foo");
        Optional<String> javaMaybeFooBar = javaMaybeFoo.maps -> String)null)
                .maps -> s.toUpperCase) + "bar");
        System.out.printlnjavaMaybeFooBar); //Optional.empty

        //vavr 同样支持 Option,这里的Option和Scala的Option几乎一模一样
        Option<String> maybeFoo = Option.of"foo");
        Option<String> maybeFooBar = maybeFoo.flatMaps -> Option.ofString)null))
                .maps -> s.toUpperCase) + "bar");
        System.out.printlnmaybeFooBar); //None

 六、Try对异常的处理

  在Java中,如果发生异常且未进行捕获,则当前线程就会被中断,后续的调用就会停止。而Java中通过try{}catch){}对可能出现异常的代码块进行捕获,保证了发生异常之后能够进行有效的处理,且顺利的执行完余下的工作。但问题是通过大量的try{}catch代码块看起来较为臃肿。vavr中通过Try类型则很好的解决了try{}catch的臃肿问题。

        Try result = Try.of) -> 0)
                .mapa) -> 10 / a) //即使此处抛出异常,不会导致当前线程结束。这里无需使用 try{}catch)对代码进行捕获
                .andThen) -> System.out.printf"--抛出异常此处不会执行--")) //执行一个动作,不修改结果
                .mapi -> {
                    System.out.println"当前值:" + i);
                    return i + 10;
                })
                .onFailuree -> e.printStackTrace))//失败时会触发onFailure
                .recoverArithmeticException.class, 1000) //如果遇到 Exception类型的异常,则返回1000
                .mapa) -> a + 1);

        System.out.println"是否抛出异常:" + result.isFailure));
        System.out.println"执行结果:" + result.getOrElse100)); //如果有异常发生,则返回100

 七、延迟计算Lazy 

   Lazy 类型数据在不进行get前是不会触发计算的,只有在调用get方法时,才会触发整个流程的计算,起到延迟计算的作用。

        Lazy<Double> lazy = Lazy.ofMath::random)
                .mapi -> {
                    System.out.println"-----正在进行计算,此处只会执行一次------");
                    return i * 100;
                });
        System.out.printlnlazy.isEvaluated));
        System.out.printlnlazy.get)); //触发计算
        System.out.printlnlazy.isEvaluated));
        System.out.printlnlazy.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.sleep2000);
            return 100;
        })
                .mapi -> i * 10)
                .await3000, TimeUnit.MILLISECONDS) //等待线程执行3000毫秒
                .onFailuree -> e.printStackTrace))
                .getValue) //返回Optional<Try<Integer>>类型结果
                .getOrElseTry.of) -> 100)) //如果Option 为 empty时,则返回Try100)
                .get);
        System.out.printlnresult); // 1000

执行结果:

当前线程名称:main
future线程名称:ForkJoinPool.commonPool-worker-1
1000

 九、减少if else的模式匹配

  Java的switch case是具有很大的局限性的,仅仅对简单的基本类型进行了支持,而对于包装类型是无法使用的。而在Scala中是支持各种类型的模式匹配,不仅如此,其还具有对象解构、前置条件,守卫,而vavr支持了这些功能。

vavr中用$表达不同的匹配模式。

$) -通配符模式

$value) -等于模式

$predicate) -条件模式

 int i = 1;
        String s = Matchi).of
                Case$1), "one"), //等值匹配
                Case$2), "two"),
                Case$), "?")
        );
        String b = Matchi).of
                Case$is1)), "one"), //谓词表达式匹配
                Case$is2)), "two"),
                Case$), "?")
        );
        String arg = "-h";
        Matcharg).of
                Case$isIn"-h", "--help")), o -> runthis::displayHelp)), //支持在成功匹配后执行动作
                Case$isIn"-v", "--version")), o -> runthis::displayVersion)),
                Case$), o -> run) -> {
                    throw new IllegalArgumentExceptionarg);
                }))
        );
        A obj = new A);
        Number plusOne = Matchobj).of
                Case$instanceOfInteger.class)), i -> i + 1), //根据值类型进行匹配
                Case$instanceOfDouble.class)), d -> d + 1),
                Case$), o -> { throw new NumberFormatException); })
        );

        Tuple2 tuple2 = Tuple"a",2);
        Try<Tuple2<String,Integer>> _try = Try.successtuple2);
        Match_try).of
                Case$Success$Tuple2$"a"), $))), tuple22 ->{}),
                Case$Failure$instanceOfError.class))), error -> error.fillInStackTrace))
        );

        Option option = Option.some1);
        Matchoption).of
                Case$Some$)), "defined"),
                Case$None), "empty")
        );

  除了上面比较常用的数据类型,vavr还有其他各种便捷的数据结构,感兴趣的可以深入了解一下。

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

原文链接:Java函数式编程类库-Vavr

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

Published by

风君子

独自遨游何稽首 揭天掀地慰生平

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注