vavr:让你像写Scala同样写Java

Hystrix是Netflix开源的限流、熔断降级组件,去年发现Hystrix已经再也不更新了,而在github主页上将我引导到了另外一个替代项目——resilience4j,这个项目是基于Java 8开发的,而且只使用了vavr库,也就是咱们今天要介绍的主角。
image.pngjava

Lambda表达式

既然要谈vavr,那么先要谈为何要使用vavr,vavr是为了加强Java的函数式编程体验的,那么这里先介绍下Java中的函数式编程。git

Java 8引入了函数式编程范式,思路是:将函数做为其余函数的参数传递,其实在Java 8以前,Java也支持相似的功能,可是须要使用接口实现多态,或者使用匿名类实现。不论是接口仍是匿名类,都有不少模板代码,所以Java 8引入了Lambda表达式,正式支持函数式编程。github

比方说,咱们要实现一个比较器来比较两个对象的大小,在Java 8以前,只能使用下面的代码:面试

Compartor<Apple> byWeight = new Comparator<Apple>() {
  public int compare(Apple a1, Apple a2) {
    return a1.getWeight().compareTo(a2.getWeight());
  }
}

上面的代码使用Lambda表达式能够写成下面这样(IDEA会提示你作代码的简化):编程

Comparator<Apple> byWeight = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());

关于Lambda表达式,你须要掌握的知识点有:后端

  • Lambda表达式能够理解为是一种匿名函数:它没有名称,可是又参数列表、函数主体、返回类型,可能还有一个能够抛出的异常列表;
  • 函数式接口就是仅仅声明了一个抽象方法的接口;
  • @FunctionalInterface注解对于函数式接口的做用,相似于@Override对于被重写的方法——不是必须的,可是用了有助于提高代码的可读性,所以若是你在开发中本身定义函数式接口,最好也使用这个注解修饰;
  • Java 8自带一些经常使用的函数式接口,放在java.util.function包里,包括Predicate<T>、Function<T,R>、Supplier<T>、Consumer<T>和BinaryOperator<T>等等;

vavr

受限于 Java 标准库的通用性要求和二进制文件大小,Java 标准库对函数式编程的 API 支持相对比较有限。函数的声明只提供了 Function 和 BiFunction 两种,流上所支持的操做的数量也较少。基于这些缘由,你也许须要vavr
来帮助你更好得使用Java 8进行函数式开发。数组

vavr是在尝试让Java拥有跟Scala相似的语法。vavr提供了不可变的集合框架;更好的函数式编程特性;元组。
image.png缓存

集合

Vavr实现了一套新的Java集合框架来匹配函数式编程范式,vavr提供的集合都是不可变的。在Java中使用Stream,须要显示得将集合转成steam的步骤,而在vavr中则免去了这样的步骤。app

  1. vavr的List是不可变的链表,在该链表对象上的操做都会生成一个新的链表对象。

使用Java 8的代码:框架

Arrays.asList(1, 2, 3).stream().reduce((i, j) -> i + j);

IntStream.of(1, 2, 3).sum();

使用vavr实现相同的功能,则更加直接:

//io.vavr.collection.List
List.of(1, 2, 3).sum();
  1. vavr的Stream是惰性链表,元素只有在必要的时候才会参与计算,所以大部分操做均可以在常量时间内完成。

函数(Functions)

Java 8提供了接受一个参数的函数式接口Function和接受两个参数的函数式接口BiFunction,vavr则提供了最多能够接受8个参数的函数式接口:Function0、Function一、Function二、Function三、Function4……Function8。
image.png

vavr还提供了更多函数式编程的特性:

  • 组合(Composition)

在数学上,函数组合能够用两个函数造成第三个函数,例如函数f:X->Y和函数g:Y->Z能够组合成h:g(f(x)),表示X->Z。这里看个组合的例子:

public class VavrFunctionExample {

    @Test
    public void testCompose() {
        //使用andThen
        Function1<Integer, Integer> plusOne = a -> a + 1;
        Function1<Integer, Integer> multiplyByTwo = a -> a * 2;
        Function1<Integer, Integer> add1AndMultiplyBy2 = plusOne.andThen(multiplyByTwo);
        Assert.assertEquals(6, add1AndMultiplyBy2.apply(2).intValue());

        //使用compose
        Function1<Integer, Integer> add1AndMultiplyBy2WithCompose = multiplyByTwo.compose(plusOne);
        Assert.assertEquals(6, add1AndMultiplyBy2WithCompose.apply(2).intValue());
    }
}
  • Lifting

你是否是经常写这种代码:调用一个函数,判断它的返回值是否符合需求,或者须要catch全部异常以防异常状况,甚至是catch(Throwable t)。Lifting特性就是为了解决这个问题而存在的,能够在内部处理异常状况,并将异常转换成一个特殊的结果None,这样函数外部就能够用统一的模式去处理函数结果。举个例子:

public class VavrFunctionExample {
    @Test
    public void testLifting() {
        Function2<Integer, Integer, Integer> divide = (a, b) -> a / b;
        Function2<Integer, Integer, Option<Integer>> safeDivide = Function2.lift(divide);

        // = None
        Option<Integer> i1 = safeDivide.apply(1, 0);
        Assert.assertEquals("None", i1.toString());

        // = Some(2)
        Option<Integer> i2 = safeDivide.apply(4, 2);
        Assert.assertEquals(2, i2.get().intValue());
    }
}
  • 柯里化方法(Curring)

柯里化(Currying)指的是将原来接受多个参数的函数变成新的接受一个参数的函数的过程。对于Java来讲,能够方便得提供默认值方法,这里看个例子:

public class VavrFunctionExample {
    @Test
    public void testCurried() {
        Function2<Integer, Integer, Integer> sum = (a, b) -> a + b;
        Function1<Integer, Integer> add2 = sum.curried().apply(2);

        Assert.assertEquals(6, add2.apply(4).intValue());
    }
}
  • 记忆化方法(Memorization)

这是一种缓存,某个方法只须要执行一次,后面都会返回第一次的结果;可是在实际应用中用到的地方应该很少。

public class VavrFunctionExample {
    @Test
    public void testMemorize() {
        Function0<Double> hashCache =
            Function0.of(Math::random).memoized();

        double randomValue1 = hashCache.apply();
        double randomValue2 = hashCache.apply();

        Assert.assertTrue(randomValue1 == randomValue1);
    }
}

模式匹配

模式匹配是函数式编程语言中的概念,目前Java中还不支持这个特性,使用vavr能够用Java写模式匹配的代码。Java中的switch...case语句只能针对常量起做用,而使用模式匹配则能够对另外一个函数的返回结果起做用,功能很是抢到。下面的例子分别给出了使用if、switch...case、模式匹配三个语法实现一样功能的例子,能够看出,模式匹配有助于减小代码行数。

import org.junit.Test;

import static io.vavr.API.$;
import static io.vavr.API.Case;
import static io.vavr.API.Match;
import static org.junit.Assert.assertEquals;

public class VavrPatternExample {

    @Test
    public void whenIfWorksAsMatcher_thenCorrect() {
        int input = 3;
        String output;
        if (input == 0) {
            output = "zero";
        }
        if (input == 1) {
            output = "one";
        }
        if (input == 2) {
            output = "two";
        }
        if (input == 3) {
            output = "three";
        } else {
            output = "unknown";
        }

        assertEquals("three", output);
    }

    @Test
    public void whenSwitchWorksAsMatcher_thenCorrect() {
        int input = 2;
        String output;
        switch (input) {
            case 0:
                output = "zero";
                break;
            case 1:
                output = "one";
                break;
            case 2:
                output = "two";
                break;
            case 3:
                output = "three";
                break;
            default:
                output = "unknown";
                break;
        }

        assertEquals("two", output);
    }

    @Test
    public void whenMatchworks_thenCorrect() {
        int input = 2;
        String output = Match(input).of(
            Case($(1), "one"),
            Case($(2), "two"),
            Case($(3), "three"),
            Case($(), "?"));

        assertEquals("two", output);
    }
}

参考资料

  1. 《Java 8实战》
  2. https://github.com/resilience4j/resilience4j
  3. https://www.baeldung.com/vavr
  4. https://www.vavr.io/vavr-docs/

本号专一于后端技术、JVM问题排查和优化、Java面试题、我的成长和自我管理等主题,为读者提供一线开发者的工做和成长经验,期待你能在这里有所收获。
javaadu

相关文章
相关标签/搜索