相信很多人關注 Vavr 的原因,還是因為 Hystrix 庫。Hystrix 不更新了,并在 GitHub 主頁上推薦了 Resilience4j,而 Vavr 作為 Resilience4j 的唯一依賴被提及。對于 Resilience4j 這個以輕依賴作為特色之一的容錯庫,為什么還會引用 Vavr 呢?
以下是 Resilience4j 官方原文:
?Resilience4j is a lightweight fault tolerance library inspired by Netflix Hystrix, but designed for Java 8 and functional programming. Lightweight, because the library only uses Vavr, which does not have any other external library dependencies.
?
Resilience4j 除了輕量,另一特點是對 Java 8 函數式編程的支持,經過一番了解,Vavr 正是為了提升 Java 函數式編程體驗而開發的,通過它可以幫助我們編寫出更簡潔、高效的函數風格代碼。
c++匿名函數。限于篇幅,該系列分為上、下兩篇:上篇著重回顧函數式編程的一些基礎知識,以及 Vavr 總體介紹、Vavr 對元組、函數的支持,通過上篇的學習;下篇著重講述 Vavr 中對各種值類型、增強集合、參數檢查、模式匹配等特性。力求通過這兩篇文章,把 Vavr 的總體特性呈現給大家,讓大家對 Vavr 有個全面的認識。
Vavr是 Java 8+ 函數式編程的增強庫。提供了不可變數據類型和函數式控制結構,旨在讓 Java 函數編程更便捷高效。特別是功能豐富的集合庫,可以與Java的標準集合平滑集成。
Vavr 的讀音是 /?we?.v?/,早期版本叫 Javaslang,由于和 Java? 商標沖突(類似國內的 JavaEye 改名),所以把 Java 倒過來取名。
學習 Vavr 之前,我們先回顧下 Java 函數式編程及 Lambda (λ) ?表達式的一些相關內容。
Java 8 開始,在原有面向對象、命令式編程范式的基礎上,增加了函數式編程支持,其核心是行為參數化,把行為具體理解為一個程序函數(方法),即是將函數作為其它函數的參數傳遞,組成高階函數。
javascript匿名函數、舉個例子,人(People)這個類存在一個年齡(age)屬性,我們想根據每個人的年齡進行排序比較。
首先看下 Java 8 之前的做法:
Comparator comparator = new Comparator() { @Override public int compare(People p1, People p2) { return Integer.compare(p1.getAge(), p2.getAge()); }};
再看 Java 8 之后的做法:
Comparator comparator = (p1, p2) -> Integer.compare(p1.getAge(), p2.getAge());
利用 Lambda 表達式的語法,確實少了很多模板代碼,看起來更加簡潔了。關于 Java 的函數式編程及 Lambda 表達式語法,有以下需要掌握的知識點:
函數式接口 (Functional Interface) 就是一個有且僅有一個抽象方法,但是可以有多個非抽象方法的接口,通常會用 @FunctionalInterface
進行標注,但不是必須的。Java 8 自帶了常用的函數式接口,存放在 java.util.function
包下,包括 Function
、Supplier
、Consumer
、Predicate
等,此外在其它地方也用到很多函數式接口,比如前面演示的 Comparator
。
Java匿名對象,Lambda 表達式是一種匿名函數,在 Java 中,定義一個匿名函數的實質依然是函數式接口的匿名實現類,它沒有名稱,只有參數列表、函數主體、返回類型,可能還有一個異常列表聲明。Lambda 表達式有以下重要特征:
// 1. 不需要參數,返回值為 1() -> 1// 2. 接收一個參數(數字類型),返回值為 x + 1x -> x + 1// 3. 接受2個參數(數字),返回值為 x + y (x, y) -> x + y// 4. 接收2個int型整數,返回值為 x + y (int x, int y) -> x + y// 5. 接受一個 String 對象,并在控制臺打印,不返回任何值(返回 void) (String s) -> System.out.print(s)
?如果一個操作、函數或表達式在其本地環境之外修改了某個狀態變量值,則會產生副作用,也就是說,除了向操作的調用者返回一個值(主要效果)之外,還會產生可觀察到的效果。
?
例如,一個函數產生異常,并且這個異常向上傳遞,就是一種影響程序的副作用,此外,異常就像一種非本地的 goto 語句,打斷了正常的程序流程。具體看以下代碼:
int divide(int dividend, int divisor) { // 如果除數為0,會產生異常 return dividend / divisor;}
怎么處理這種副作用呢?在 Vavr 中,可以把它封裝到一個 Try
實例,具體實現:
// = Success(result) or Failure(exception)Try safeDivide(Integer dividend, Integer divisor) { return Try.of(() -> divide(dividend, divisor));}
這個版本的除法函數不再拋出異常,我們通過 Try
類型明確了可能的錯誤。
?Vm是狀態函數嗎?引用透明的概念與函數的副作用相關,且受其影響。如果程序中任意兩處具有相同輸入值的函數調用能夠互相置換,而不影響程序的動作,那么該程序就具有引用透明性。
?
從以下示例可以看出,第一種實現,隨機數是根據可變的外部狀態生成的,所以每次調用產生的結果都不同,無法做到引用透明;第二種實現,我們給隨機對象指定一個隨機種子,這樣保證不受外部狀態的影響,達到引用透明的效果:
// 非引用透明Math.random();// 引用透明new Random(1).nextDouble();
不可變對象是指其狀態在創建后不能修改的對象。它有以下好處:
受限于 Java 標準庫的通用性要求及體量大小考慮,JDK API 對函數式編程的支持比較有限,這時候可以引入 Vavr 來提供更便捷的安全集合類型、支持更多的 stream 流操作、豐富函數式接口類型……
在 Vavr 中,所有類型都是基于 Tuple, Value, λ 構建的:
函數vlookup?這里使用 Maven 項目構建,完整的 pom.xml
配置如下:
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 com.example demo-vavr 0.0.1 UTF-8 1.8 1.8 0.9.3 io.vavr vavr ${vavr.version}
Java 必須是 1.8 以上版本,這是使用 Java 函數式編程的前提,另外 Vavr 使用的是 0.9.3 版本。Vavr 本身沒有其它外部依賴,Jar 包大小僅有 800+K,相當輕量。
Java 自身并沒有元組的概念,元組是將固定數量的元素組合在一起,這樣它們就可以作為一個整體傳遞,但它與數組或集合的區別是,元組能包含不同類型的對象,且是不可變的。
Vavr 提供了 Tuple1
、Tuple2
到 Tuple8
等8個具體的元組類型,分別代表可存儲1~8個元素的元組,并可以通過 _1
、_2
..._8
等屬性訪問對應元素。
以下是創建并獲取元組的示例:
// 通過 Tuple.of() 靜態方法創建一個二元組Tuple2 people = Tuple.of("Bob", 18);// 獲取第一個元素,名稱:BobString name = people._1;// 獲取第二個元素,年齡:18Integer age = people._2;
V函數構建?元組也提供了對元素映射處理的能力,以下兩種寫法效果是相同的:
// ("Hello, Bob", 9)people.map( name -> "Hello, " + name, age-> age / 2);// ("Hello, Bob", 9) people.map( (name, age) -> Tuple.of("Hello, " + name, age / 2));
此外,元組還提供了基于元素內容轉換創建新的類型:
// 返回 name: Bob, age: 18String str = people.apply( (name, age) -> "name: " + name + ", age: " + age);
Java 8 僅提供了接受一個參數的函數式接口 Function
和接受兩個參數的函數式接口 BiFunction
,vavr 則提供了最多可以接受8個參數的函數式接口:Function0
、Function1
、Function2
...Function8
。如果需要拋出受檢異常的函數,可以使用 CheckedFunction{0...8}
版本。
以下是使用函數的示例:
// 聲明一個接收兩個參數的函數Function2 description = (name, age) -> "name: " + name + ", age: " + age;// 返回 "name: Bob, age: 18"String str = description.apply("Bob", 18);
Vavr 函數是 Java 8 函數的增強,它提供了以下特性:
V條件函數,組合是將一個函數 f(x)
的結果作為另一個函數 g(y)
的參數,產生新函數 h: g(f(x))
的操作,可以使用 andThen
或 compose
方法實現函數組合:
Function1 plusOne = a -> a + 1;Function1 multiplyByTwo = a -> a * 2;// 以下兩種寫法結果一致,都是 z -> (z + 1) * 2Function1 addOneAndMultiplyByTwo1 = plusOne.andThen(multiplyByTwo);Function1 addOneAndMultiplyByTwo2 = plusOne.andThen(multiplyByTwo);
提升是針對部分函數(partial function)的操作,如果一個函數 f(x) 的定義域是 x,另一個函數 g(y) 跟 f(x) 定義相同,只是定義域 y 是 x 的子集,就說 f(x) 是全函數(total function),g(y) 是部分函數。函數的提升會返回當前函數的全函數,返回類型為 Option
,以下是一個部分函數定義:
// 當除數 0 時,將導致程序異常Function2 divide = (a, b) -> a / b;
我們再利用lift
將divide
提升為可以接收所有輸入的全函數:
Function2> safeDivide = Function2.lift(divide);// = NoneOption i1 = safeDivide.apply(1, 0);// = Some(2)Option i2 = safeDivide.apply(4, 2);
通過以上示例可以看出,如果使用不允許的輸入值調用提升后的全函數,則返回None
而不是引發異常。
部分應用是通過固定函數的前n個參數值,產生一個新函數,該新函數參數為原函數總參數個數減n,具體示例如下:
Function2 sum = (a, b) -> a + b;Function1 add2 = sum.apply(2);
V區間函數。sum
函數通過部分應用,第一個參數被固定為2
,并產生新的add2
函數。
柯里化是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,并且返回接受余下的參數而且返回結果的新函數的技術。
當Function2
被柯里化時,結果與部分應用沒有區別,因為兩者都會產生單參數的函數。但函數參數多于2個,就能明顯看出柯里化的不同:
Function3 sum = (a, b, c) -> a + b + c;final Function1> add2 = sum.curried().apply(2);// = 9Integer i = add2.apply(4).apply(3);
函數記憶是利用緩存技術,第一次執行后,將結果緩存起來,后續從緩存返回結果。以下函數在第一次調用時,生成隨機數并進行緩存,第二次調用直接從緩存返回結果,所以多次返回的隨機數是同一個:
Function0 hashCache = Function0.of(Math::random).memoized();double randomValue1 = hashCache.apply();double randomValue2 = hashCache.apply();
今天對 Vavr 的介紹先到這里,下篇我們將會接著介紹另外一些特性:
java工作、歷史推薦
1、三分鐘快速記住 選擇排序算法!
2、1.5W 字搞懂 Spring Cloud,太牛了!
3、一文帶你全方位弄懂 Java8 流式操作!
4、三分鐘快速記住冒泡排序算法!
java程序?公眾號ID|javabaiwen
小編微信|619531440
每天分享技術干貨
視頻 | 電子書 | 面試題?|?開發經驗
版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。
工作时间:8:00-18:00
客服电话
电子邮件
admin@qq.com
扫码二维码
获取最新动态