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

相關文章
相關標籤/搜索