Daniel Dietrich, Robert Winkler - Version 0.9.2,2018-10-01html
Vavr是Java 8 的對象函數式擴展,目標是減小代碼行數,提升代碼質量,提供了持久化集合、錯誤處理函數式抽象、模式匹配等等。java
Vavr 融合了面向對象編程的強大功能,具備功能編程的優雅性和堅固性。 最有趣的部分是擁有功能豐富且持久的集合庫,能夠與 Java 的標準集合順利集成。node
Vavr (formerly called Javaslang) is a functional library for Java 8+ that provides persistent data types and functional control structures.
Vavr(之前稱爲Javaslang)是Java 8+的函數庫,它提供持久數據類型和函數式控制結構。web
Java 8’s lambdas (λ) empower us to create wonderful API’s. They incredibly increase the expressiveness of the language.
Java 8的lambdas (λ)使咱們可以建立出色的API。 他們使人難以置信地增長了語言的表現力。shell
Vavr leveraged lambdas to create various new features based on functional patterns. One of them is a functional collection library that is intended to be a replacement for Java’s standard collections.
Vavr 利用lambdas基於函數式模式建立各類新功能。 其中之一是函數式集合庫,旨在替代Java的標準集合。數據庫
(This is just a bird’s view, you will find a human-readable version below.) (這只是一個鳥覽視圖,你會在下面找到一個可讀的版本。)express
Before we deep-dive into the details about the data structures I want to talk about some basics. This will make it clear why I created Vavr and specifically new Java collections.
在咱們深刻研究有關數據結構的細節以前,我想談談一些基礎知識。 這將清楚地代表我爲何建立Vavr以及特別是新的Java集合。apache
Java applications are typically plentiful of side-effects. They mutate some sort of state, maybe the outer world. Common side effects are changing objects or variables in place, printing to the console, writing to a log file or to a database. Side-effects are considered harmful if they affect the semantics of our program in an undesirable way.
Java應用程序一般有不少反作用。 他們改變某種狀態,也許是外部世界。 常見的反作用是更改對象或變量,打印到控制檯,寫入日誌文件或數據庫。 若是反作用以不合須要的方式影響咱們程序的語義,則認爲它們是有害的。編程
For example, if a function throws an exception and this exception is interpreted, it is considered as side-effect that affects our program. Furthermore exceptions are like non-local goto-statements. They break the normal control-flow. However, real-world applications do perform side-effects.
例如,若是函數拋出異常而且此異常被解釋,則將其視爲影響咱們的程序的反作用。 此外異常就像非本地goto語句。 它們打破了正常的控制流程。 可是,實際應用程序確實會產生反作用。api
int divide(int dividend, int divisor) { // throws if divisor is zero return dividend / divisor; }
In a functional setting we are in the favorable situation to encapsulate the side-effect in a Try:
在函數設設置中,咱們處於有利的狀況下將反作用封裝在Try中:
// = Success(result) or Failure(exception) Try<Integer> divide(Integer dividend, Integer divisor) { return Try.of(() -> dividend / divisor); }
This version of divide does not throw any exception anymore. We made the possible failure explicit by using the type Try.
這個版本的除法再也不拋出任何異常。 咱們使用Try類型明確了可能的失敗。
A function, or more generally an expression, is called referentially transparent if a call can be replaced by its value without affecting the behavior of the program. Simply spoken, given the same input the output is always the same.
若是能夠用其值替換調用而不影響程序的行爲,則函數或更通常地稱爲表達式稱爲引用透明。 簡單地說,給定相同的輸入,輸出老是相同的。
// not referentially transparent Math.random(); // referentially transparent Math.max(1, 2);
A function is called pure if all expressions involved are referentially transparent. An application composed of pure functions will most probably just work if it compiles. We are able to reason about it. Unit tests are easy to write and debugging becomes a relict of the past.
若是涉及的全部表達式都是引用透明的,則函數稱爲pure。 若是編譯,由純函數組成的應用程序極可能只是工做。 咱們可以解釋它。 單元測試很容易編寫,調試成爲過去的遺留問題。
Rich Hickey, the creator of Clojure, gave a great talk about The Value of Values. The most interesting values are immutablevalues. The main reason is that immutable values
Clojure的建立者Rich Hickey對價值的價值進行了精彩的討論。 最有趣的值是immutable值。 主要緣由是不可變的價值觀
Vavr provides the necessary controls and collections to accomplish this goal in every-day Java programming.
Vavr提供必要的控制和[collections](https:// static .javadoc.io / io.vavr / vavr / 0.9.2 / io / vavr / collection / package-summary.html)在平常Java編程中實現這一目標.
Vavr’s collection library comprises of a rich set of functional data structures built on top of lambdas. The only interface they share with Java’s original collections is Iterable. The main reason is that the mutator methods of Java’s collection interfaces do not return an object of the underlying collection type.
Vavr的集合庫包含一組構建在lambdas之上的豐富的函數式數據結構。 他們與Java的原始集合共享的惟一接口是Iterable。 主要緣由是Java集合接口的mutator方法不返回底層集合類型的對象。
We will see why this is so essential by taking a look at the different types of data structures.
咱們將經過研究不一樣類型的數據結構來了解爲何這是如此重要。
Java is an object-oriented programming language. We encapsulate state in objects to achieve data hiding and provide mutator methods to control the state. The Java collections framework (JCF) is built upon this idea.
Java是一種面向對象的編程語言. 咱們將狀態封裝在對象中以實現數據隱藏,並提供mutator方法來控制狀態. Java集合框架(JCF)創建在這個想法之上.
interface Collection<E> { // removes all elements from this collection void clear(); }
Today I comprehend a void return type as a smell. It is evidence that side-effects take place, state is mutated. Sharedmutable state is an important source of failure, not only in a concurrent setting.
今天我理解一個* void *返回類型做爲氣味. 有證據代表反作用發生了,狀態發生了變異. 共享可變狀態是一個重要的失敗源,不只僅是在併發設置中.
Immutable data structures cannot be modified after their creation. In the context of Java they are widely used in the form of collection wrappers.
Immutable數據結構在建立後沒法修改。 在Java的上下文中,它們以集合包裝器的形式被普遍使用。
List<String> list = Collections.unmodifiableList(otherList); // Boom! list.add("why not?");
There are various libraries that provide us with similar utility methods. The result is always an unmodifiable view of the specific collection. Typically it will throw at runtime when we call a mutator method.
有各類庫爲咱們提供相似的實用方法。 結果始終是特定集合的不可修改的視圖。 一般,當咱們調用mutator方法時,它將在運行時拋出。
A persistent data structure does preserve the previous version of itself when being modified and is therefore effectivelyimmutable. Fully persistent data structures allow both updates and queries on any version.
持久數據結構在修改時會保留其自身的先前版本,所以有效不可變. 徹底持久的數據結構容許對任何版本進行更新和查詢.
Many operations perform only small changes. Just copying the previous version wouldn’t be efficient. To save time and memory, it is crucial to identify similarities between two versions and share as much data as possible.
許多操做只執行很小的更改。 只是複製之前的版本效率不高。 爲了節省時間和內存,肯定兩個版本之間的類似性並儘量多地共享數據相當重要。
This model does not impose any implementation details. Here come functional data structures into play.
此模型不會強加任何實現細節。 這裏有函數式數據結構發揮做用。
Also known as purely functional data structures, these are immutable and persistent. The methods of functional data structures are referentially transparent.
也稱爲純函數式數據結構,這些是不可變和持久的. 函數式數據結構的方法是引用透明的.
Vavr features a wide range of the most-commonly used functional data structures. The following examples are explained in-depth.
Vavr具備普遍的最經常使用函數式數據結構。 如下示例將進行深刻解釋。
One of the most popular and also simplest functional data structures is the (singly) linked List. It has a head element and a tail List. A linked List behaves like a Stack which follows the last in, first out (LIFO) method.
最受歡迎且最簡單的函數式數據結構之一是(單連接)列表. 它有一個* head 元素和一個 tail * List. 連接列表的行爲相似於後進先出(LIFO)方法的堆棧.
In Vavr we instantiate a List like this:
在Vavr中,咱們實例化一個像這樣的List:
// = List(1, 2, 3) List<Integer> list1 = List.of(1, 2, 3);
Each of the List elements forms a separate List node. The tail of the last element is Nil, the empty List.
每一個List元素造成一個單獨的List節點。 最後一個元素的尾部是Nil,即空列表。
This enables us to share elements across different versions of the List.
這使咱們可以跨List的不一樣版本共享元素。
// = List(0, 2, 3) List<Integer> list2 = list1.tail().prepend(0);
The new head element 0 is linked to the tail of the original List. The original List remains unmodified.
新的head元素0 連接到原始List的尾部。 原始清單保持不變。
These operations take place in constant time, in other words they are independent of the List size. Most of the other operations take linear time. In Vavr this is expressed by the interface LinearSeq, which we may already know from Scala.
這些操做以恆定時間進行,換句話說,它們與列表大小無關。 大多數其餘操做須要線性時間。 在Vavr中,這由LinearSeq接口表示,咱們可能已經從Scala中知道了。
If we need data structures that are queryable in constant time, Vavr offers Array and Vector. Both have random accesscapabilities.
若是咱們須要在恆定時間內可查詢的數據結構,Vavr會提供Array和Vector。 二者都具備隨機訪問功能。
The Array type is backed by a Java array of objects. Insert and remove operations take linear time. Vector is in-between Array and List. It performs well in both areas, random access and modification.
Array類型由Java對象數組支持。 插入和刪除操做須要線性時間。 矢量是介於數組和列表之間。 它在兩個領域都表現良好,隨機訪問和修改。
In fact the linked List can also be used to implement a Queue data structure.
實際上,連接List也可用於實現Queue數據結構。
A very efficient functional Queue can be implemented based on two linked Lists. The front List holds the elements that are dequeued, the rear List holds the elements that are enqueued. Both operations enqueue and dequeue perform in O(1).
能夠基於兩個連接列表實現很是有效的函數式隊列。 * front * List包含出列的元素,* rear * List包含入隊的元素。 兩個操做在O(1)中排隊和出列。
Queue<Integer> queue = Queue.of(1, 2, 3) .enqueue(4) .enqueue(5);
The initial Queue is created of three elements. Two elements are enqueued on the rear List.
初始隊列由三個元素組成。 rear的List中有兩個元素。
If the front List runs out of elements when dequeueing, the rear List is reversed and becomes the new front List.
若是front List在出列時用完了元素,則rear List將反轉併成爲新的前列表。
When dequeueing an element we get a pair of the first element and the remaining Queue. It is necessary to return the new version of the Queue because functional data structures are immutable and persistent. The original Queue is not affected.
當一個元素出列時,咱們獲得一對第一個元素和剩餘的Queue。 有必要返回新版本的Queue,由於函數式數據結構是不可變的和持久的。 原始隊列不受影響。
Queue<Integer> queue = Queue.of(1, 2, 3); // = (1, Queue(2, 3)) Tuple2<Integer, Queue<Integer>> dequeued = queue.dequeue();
What happens when the Queue is empty? Then dequeue() will throw a NoSuchElementException. To do it the functional way we would rather expect an optional result.
當隊列爲空時會發生什麼? 而後dequeue()將拋出NoSuchElementException。 要作到函數式方式咱們寧願期待一個可選的結果。
// = Some((1, Queue())) Queue.of(1).dequeueOption(); // = None Queue.empty().dequeueOption();
An optional result may be further processed, regardless if it is empty or not.
能夠進一步處理可選結果,不管它是否爲空。
// = Queue(1) Queue<Integer> queue = Queue.of(1); // = Some((1, Queue())) Option<Tuple2<Integer, Queue<Integer>>> dequeued = queue.dequeueOption(); // = Some(1) Option<Integer> element = dequeued.map(Tuple2::_1); // = Some(Queue()) Option<Queue<Integer>> remaining = dequeued.map(Tuple2::_2);
Sorted Sets are data structures that are more frequently used than Queues. We use binary search trees to model them in a functional way. These trees consist of nodes with up to two children and values at each node.
排序Set是比隊列更頻繁使用的數據結構。 咱們使用二叉搜索樹以函數式方式對它們進行建模。 這些樹由最多兩個子節點和每一個節點的值組成。
We build binary search trees in the presence of an ordering, represented by an element Comparator. All values of the left subtree of any given node are strictly less than the value of the given node. All values of the right subtree are strictly greater.
咱們在排序的狀況下構建二元搜索樹,由元素Comparator表示。 任何給定節點的左子樹的全部值都嚴格小於給定節點的值。 右子樹的全部值都嚴格更大。
// = TreeSet(1, 2, 3, 4, 6, 7, 8) SortedSet<Integer> xs = TreeSet.of(6, 1, 3, 2, 4, 7, 8);
Searches on such trees run in O(log n) time. We start the search at the root and decide if we found the element. Because of the total ordering of the values we know where to search next, in the left or in the right branch of the current tree.
對這些樹的搜索在O(log n)時間內運行。 咱們從根開始搜索並決定是否找到了元素。 因爲值的總排序,咱們知道接下來要搜索的位置,在當前樹的左側或右側分支中。
// = TreeSet(1, 2, 3); SortedSet<Integer> set = TreeSet.of(2, 3, 1, 2); // = TreeSet(3, 2, 1); Comparator<Integer> c = (a, b) -> b - a; SortedSet<Integer> reversed = TreeSet.of(c, 2, 3, 1, 2);
Most tree operations are inherently recursive. The insert function behaves similarly to the search function. When the end of a search path is reached, a new node is created and the whole path is reconstructed up to the root. Existing child nodes are referenced whenever possible. Hence the insert operation takes O(log n) time and space.
大多數樹操做本質上是遞歸。 插入函數的行爲與搜索功能相似。 到達搜索路徑的末尾時,將建立一個新節點,並將整個路徑重建到根目錄。 儘量引用現有子節點。 所以,插入操做須要O(log n)時間和空間。
// = TreeSet(1, 2, 3, 4, 5, 6, 7, 8) SortedSet<Integer> ys = xs.add(5);
In order to maintain the performance characteristics of a binary search tree it needs to be kept balanced. All paths from the root to a leaf need to have roughly the same length.
爲了保持二叉搜索樹的性能特徵,須要保持平衡。 從根到葉子的全部路徑都須要具備大體相同的長度。
In Vavr we implemented a binary search tree based on a Red/Black Tree. It uses a specific coloring strategy to keep the tree balanced on inserts and deletes. To read more about this topic please refer to the book Purely Functional Data Structures by Chris Okasaki.
在Vavr中,咱們基於紅/黑樹實現了二叉搜索樹. 它使用特定的着色策略來保持樹在插入和刪除時保持平衡. 要閱讀有關此主題的更多信息,請參閱Chris Okasaki的書Purely Functional Data Structures.
Generally we are observing a convergence of programming languages. Good features make it, other disappear. But Java is different, it is bound forever to be backward compatible. That is a strength but also slows down evolution.
一般,咱們正在觀察編程語言的融合。 好的功能使用它,其餘消失。 可是Java是不一樣的,它永遠是向後兼容的。 這是一種力量,但也減緩了進化。
Lambda brought Java and Scala closer together, yet they are still so different. Martin Odersky, the creator of Scala, recently mentioned in his BDSBTB 2015 keynote the state of the Java 8 collections.
Lambda使Java和Scala更加緊密,但它們仍然如此不一樣. Scala的建立者Martin Odersky最近在他的BDSBTB 2015主題演講中提到了Java 8集合的狀態.
He described Java’s Stream as a fancy form of an Iterator. The Java 8 Stream API is an example of a lifted collection. What it does is to define a computation and link it to a specific collection in another excplicit step.
他將Java的Stream描述爲Iterator的一種奇特形式。 Java 8 Stream API是* lifted 集合的一個示例。 它的做用是定義一個計算並將其連接*到另外一個重複步驟中的特定集合。
// i + 1 i.prepareForAddition() .add(1) .mapBackToInteger(Mappers.toInteger())
This is how the new Java 8 Stream API works. It is a computational layer above the well known Java collections.
這就是新的Java 8 Stream API的工做方式。 它是衆所周知的Java集合之上的計算層。
// = ["1", "2", "3"] in Java 8 Arrays.asList(1, 2, 3) .stream() .map(Object::toString) .collect(Collectors.toList())
Vavr is greatly inspired by Scala. This is how the above example should have been in Java 8.
Vavr受到了Scala的極大啓發。 這就是上面的例子應該如何在Java 8中。
// = Stream("1", "2", "3") in Vavr Stream.of(1, 2, 3).map(Object::toString)
Within the last year we put much effort into implementing the Vavr collection library. It comprises the most widely used collection types.
在過去的一年裏,咱們付出了不少努力來實現Vavr集合庫。 它包含最普遍使用的集合類型。
We started our journey by implementing sequential types. We already described the linked List above. Stream, a lazy linked List, followed. It allows us to process possibly infinite long sequences of elements.
咱們經過實現順序類型開始了咱們的旅程 咱們已經描述了上面的鏈表。 流,一個懶惰的連接列表,緊隨其後。 它容許咱們處理可能無限長的元素序列。
All collections are Iterable and hence could be used in enhanced for-statements.
全部集合都是可迭代的,所以能夠用於加強的for語句。
for (String s : List.of("Java", "Advent")) { // side effects and mutation }
We could accomplish the same by internalizing the loop and injecting the behavior using a lambda.
咱們能夠經過內化循環並使用lambda注入行爲來完成相同的操做。
List.of("Java", "Advent").forEach(s -> { // side effects and mutation });
Anyway, as we previously saw we prefer expressions that return a value over statements that return nothing. By looking at a simple example, soon we will recognize that statements add noise and divide what belongs together.
不管如何,正如咱們以前看到的那樣,咱們更喜歡錶達式返回值而不返回任何語句。 經過一個簡單的例子,咱們很快就會認識到語句會增長噪音並將全部屬性分開。
String join(String... words) { StringBuilder builder = new StringBuilder(); for(String s : words) { if (builder.length() > 0) { builder.append(", "); } builder.append(s); } return builder.toString(); }
The Vavr collections provide us with many functions to operate on the underlying elements. This allows us to express things in a very concise way.
Vavr集合爲咱們提供了許多功能來操做底層元素。 這使咱們可以以很是簡潔的方式表達事物。
String join(String... words) { return List.of(words) .intersperse(", ") .foldLeft(new StringBuilder(), StringBuilder::append) .toString(); }
Most goals can be accomplished in various ways using Vavr. Here we reduced the whole method body to fluent function calls on a List instance. We could even remove the whole method and directly use our List to obtain the computation result.
大多數目標均可以使用Vavr以各類方式完成。 這裏咱們將整個方法體簡化爲List實例上的流暢函數調用。 咱們甚至能夠刪除整個方法並直接使用List來得到計算結果。
List.of(words).mkString(", ");
In a real world application we are now able to drastically reduce the number of lines of code and hence lower the risk of bugs.
在現實世界的應用程序中,咱們如今可以大幅減小代碼行數,從而下降錯誤風險。
Sequences are great. But to be complete, a collection library also needs different types of Sets and Maps.
序列很棒。 但要完成,集合庫還須要不一樣類型的集合和映射。
We described how to model sorted Sets with binary tree structures. A sorted Map is nothing else than a sorted Set containing key-value pairs and having an ordering for the keys.
咱們描述瞭如何使用二叉樹結構對排序集進行建模。 有序映射只不過是包含鍵值對的排序集合,而且具備鍵的排序。
The HashMap implementation is backed by a Hash Array Mapped Trie (HAMT). Accordingly the HashSet is backed by a HAMT containing key-key pairs.
HashMap實現由Hash Array Mapped Trie(HAMT)支持。 所以,HashSet由包含密鑰對的HAMT支持。
Our Map does not have a special Entry type to represent key-value pairs. Instead we use Tuple2 which is already part of Vavr. The fields of a Tuple are enumerated.
咱們的Map不具備特殊的條目類型來表示鍵值對。 相反,咱們使用已是Vavr一部分的Tuple2。 列舉了元組的字段。
// = (1, "A") Tuple2<Integer, String> entry = Tuple.of(1, "A"); Integer key = entry._1; String value = entry._2;
Maps and Tuples are used throughout Vavr. Tuples are inevitable to handle multi-valued return types in a general way.
Vavr中使用了Map和元組。 元組不可避免地以通常方式處理多值返回類型。
// = HashMap((0, List(2, 4)), (1, List(1, 3))) List.of(1, 2, 3, 4).groupBy(i -> i % 2); // = List((a, 0), (b, 1), (c, 2)) List.of('a', 'b', 'c').zipWithIndex();
At Vavr, we explore and test our library by implementing the 99 Euler Problems. It is a great proof of concept. Please don’t hesitate to send pull requests.
在Vavr,咱們經過實現99 Euler Problems來探索和測試咱們的庫。 這是一個很好的概念證實。 請不要猶豫,發送拉請求。
Projects that include Vavr need to target Java 1.8 at minimum.
包含Vavr的項目須要至少以Java 1.8爲目標。
The .jar is available at Maven Central.
.jar可在t Maven Central得到。
dependencies { compile "io.vavr:vavr:0.9.2" }
<dependencies> <dependency> <groupId>io.vavr</groupId> <artifactId>vavr</artifactId> <version>0.9.2</version> </dependency> </dependencies>
Because Vavr does not depend on any libraries (other than the JVM) you can easily add it as standalone .jar to your classpath.
由於Vavr不依賴於任何庫(JVM除外),因此能夠輕鬆地將它做爲獨立的.jar添加到類路徑中。
Developer versions can be found here.
能夠在此處找到開發人員版本。
Add the additional snapshot repository to your build.gradle
:
將快照存儲庫添加到build.gradle
:
repositories { (...) maven { url "https://oss.sonatype.org/content/repositories/snapshots" } }
Ensure that your ~/.m2/settings.xml
contains the following:
確保您的〜/ .m2 / settings.xml
包含如下內容:
<profiles> <profile> <id>allow-snapshots</id> <activation> <activeByDefault>true</activeByDefault> </activation> <repositories> <repository> <id>snapshots-repo</id> <url>https://oss.sonatype.org/content/repositories/snapshots</url> <releases> <enabled>false</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories> </profile> </profiles>
Vavr comes along with well-designed representations of some of the most basic types which apparently are missing or rudimentary in Java: Tuple
, Value
and λ
. In Vavr, everything is built upon these three basic building blocks:
Vavr伴隨着一些最基本類型的精心設計,這些類型在Java中顯然是缺失或低級的:Tuple
, Value
and λ
。 在Vavr中,一切都創建在這三個基本構建塊之上:
Java is missing a general notion of tuples. A Tuple combines a fixed number of elements together so that they can be passed around as a whole. Unlike an array or list, a tuple can hold objects with different types, but they are also immutable.
Java缺乏元組的通常概念。 元組將固定數量的元素組合在一塊兒,以便它們能夠做爲一個總體傳遞。 與數組或列表不一樣,元組能夠保存具備不一樣類型的對象,但它們也是不可變的。 Tuples are of type Tuple1, Tuple2, Tuple3 and so on. There currently is an upper limit of 8 elements. To access elements of a tuple t
, you can use method t._1
to access the first element, t._2
to access the second, and so on.
元組是Tuple1,Tuple2,Tuple3等類型。 目前有8個元素的上限。 要訪問元組t
的元素,可使用方法t._1
來訪問第一個元素,t._2
來訪問第二個元素,依此類推。
Here is an example of how to create a tuple holding a String and an Integer:
如下是如何建立包含String和Integer的元組的示例:
// (Java, 8) Tuple2<String, Integer> java8 = Tuple.of("Java", 8); //(1) // "Java" String s = java8._1; //(2) // 8 Integer i = java8._2; //(3)
- (1) A tuple is created via the static factory method
Tuple.of()
經過靜態工廠方法Tuple.of()
建立一個元組- (2) Get the 1st element of this tuple.
得到這個元組的第一個元素。- (3) Get the 2nd element of this tuple.
得到這個元組的第二個元素。
The component-wise map evaluates a function per element in the tuple, returning another tuple.
份量方式的Map計算元組中每一個元素的函數,返回另外一個元組。
// (vavr, 1) Tuple2<String, Integer> that = java8.map( s -> s.substring(2) + "vr", i -> i / 8 );
It is also possible to map a tuple using one mapping function.
還可使用一個映射函數映射元組。
// (vavr, 1) Tuple2<String, Integer> that = java8.map( (s, i) -> Tuple.of(s.substring(2) + "vr", i / 8) );
Transform creates a new type based on the tuple’s contents.
Transform根據元組的內容建立一個新類型。
// "vavr 1" String that = java8.apply( (s, i) -> s.substring(2) + "vr " + i / 8 );
Functional programming is all about values and transformation of values using functions. Java 8 just provides a Function
which accepts one parameter and a BiFunction
which accepts two parameters. Vavr provides functions up to a limit of 8 parameters. The functional interfaces are of called Function0, Function1, Function2, Function3
and so on. If you need a function which throws a checked exception you can use CheckedFunction1, CheckedFunction2
and so on.
函數式編程是關於使用函數的值和值的轉換。 Java 8只提供了一個接受一個參數的Function
和一個接受兩個參數的BiFunction
。 Vavr提供的功能最多可達8個參數。 功能接口稱爲「Function0,Function1,Function2,Function3」等。 若是須要一個拋出已檢查異常的函數,可使用CheckedFunction1,CheckedFunction2
等。 The following lambda expression creates a function to sum two integers:
如下lambda表達式建立一個函數來對兩個整數求和:
// sum.apply(1, 2) = 3 Function2<Integer, Integer, Integer> sum = (a, b) -> a + b;
This is a shorthand for the following anonymous class definition:
這是如下匿名類定義的簡寫:
Function2<Integer, Integer, Integer> sum = new Function2<Integer, Integer, Integer>() { @Override public Integer apply(Integer a, Integer b) { return a + b; } };
You can also use the static factory method Function3.of(…)
to a create a function from any method reference.
您還可使用靜態工廠方法Function3.of(...)
從任何方法引用建立函數。
Function3<String, String, String, String> function3 = Function3.of(this::methodWhichAccepts3Parameters);
In fact Vavr functional interfaces are Java 8 functional interfaces on steroids. They also provide features like:
事實上,Vavr函數式接口是建造在Java 8的函數式接口上。 它們還提供如下功能:
You can compose functions. In mathematics, function composition is the application of one function to the result of another to produce a third function. For instance, the functions f : X → Y and g : Y → Z can be composed to yield a function h: g(f(x))
which maps X → Z. You can use either andThen
:
你能夠組合函數。 在數學中,函數組合是將一個函數應用於另外一個函數以產生第三個函數。 例如,函數f:X→Y和g:Y→Z能夠被組合以產生映射X→Z的函數h:g(f(x))。你可使用
andThen`
Function1<Integer, Integer> plusOne = a -> a + 1; Function1<Integer, Integer> multiplyByTwo = a -> a * 2; Function1<Integer, Integer> add1AndMultiplyBy2 = plusOne.andThen(multiplyByTwo); then(add1AndMultiplyBy2.apply(2)).isEqualTo(6);
or compose
:
Function1<Integer, Integer> add1AndMultiplyBy2 = multiplyByTwo.compose(plusOne); then(add1AndMultiplyBy2.apply(2)).isEqualTo(6);
You can lift a partial function into a total function that returns an Option
result. The term partial function comes from mathematics. A partial function from X to Y is a function f: X′ → Y, for some subset X′ of X. It generalizes the concept of a function f: X → Y by not forcing f to map every element of X to an element of Y. That means a partial function works properly only for some input values. If the function is called with a disallowed input value, it will typically throw an exception.
您能夠將部分函數提高爲返回「Option」結果的total函數。 術語部分函數來自數學。 從X到Y的部分函數是函數f:X'→Y,對於X的某個子集X'。它經過不強制f將X的每一個元素映射到元素Y來歸納函數f:X→Y的概念。 這意味着部分功能僅適用於某些輸入值。 若是使用不容許的輸入值調用該函數,則一般會拋出異常。
The following method divide
is a partial function that only accepts non-zero divisors.
如下方法divide
是一個只接受非零除數的部分函數。
Function2<Integer, Integer, Integer> divide = (a, b) -> a / b;
We use lift
to turn divide
into a total function that is defined for all inputs.
咱們使用lift
將divide
轉換爲爲全部輸入定義的總函數。
Function2<Integer, Integer, Option<Integer>> safeDivide = Function2.lift(divide); // = None Option<Integer> i1 = safeDivide.apply(1, 0); //(1) // = Some(2) Option<Integer> i2 = safeDivide.apply(4, 2); //(2)
- (1) A lifted function returns
None
instead of throwing an exception, if the function is invoked with disallowed input values. 若是使用不容許的輸入值調用函數,則提高函數返回「無」而不是拋出異常。- (2) A lifted function returns
Some
, if the function is invoked with allowed input values. 若是使用容許的輸入值調用函數,則提高函數將返回「Some」。
The following method sum
is a partial function that only accepts positive input values.
如下方法sum
是僅接受正輸入值的部分函數。
int sum(int first, int second) { if (first < 0 || second < 0) { throw new IllegalArgumentException("Only positive integers are allowed"); //(1) } return first + second; }
- (1) The function
sum
throws anIllegalArgumentException
for negative input values. 函數sum
爲負輸入值拋出IllegalArgumentException
。
We may lift the sum
method by providing the methods reference.
咱們能夠經過提供方法參考來解除sum
方法。
Function2<Integer, Integer, Option<Integer>> sum = Function2.lift(this::sum); // = None Option<Integer> optionalResult = sum.apply(-1, 2); //(1)
- (1) The lifted function catches the
IllegalArgumentException
and maps it toNone
. 提高函數捕獲IllegalArgumentException
並將其映射到None
。
Partial application allows you to derive a new function from an existing one by fixing some values. You can fix one or more parameters, and the number of fixed parameters defines the arity of the new function such that new arity = (original arity - fixed parameters)
. The parameters are bound from left to right.
部分申請容許您經過固定某些值從現有函數派生新函數。 你能夠固定一個或多個參數,固定參數的數量定義新函數的參數數量,使得new arity =(original arity - fixed parameters)
。 參數從左到右綁定。
Function2<Integer, Integer, Integer> sum = (a, b) -> a + b; Function1<Integer, Integer> add2 = sum.apply(2); //(1) then(add2.apply(4)).isEqualTo(6);
- (1) The first parameter
a
is fixed to the value 2.
第一個參數a
固定爲值2。
This can be demonstrated by fixing the first three parameters of a Function5
, resulting in a Function2
.
這能夠經過固定Function5
的前三個參數來演示,從而產生Function2
。
Function5<Integer, Integer, Integer, Integer, Integer, Integer> sum = (a, b, c, d, e) -> a + b + c + d + e; Function2<Integer, Integer, Integer> add6 = sum.apply(2, 3, 1); //(1) then(add6.apply(4, 3)).isEqualTo(13);
(1) The
a
,b
andc
parameters are fixed to the values 2, 3 and 1 respectively.
a
,b
和c
參數分別固定爲值2,3和1。
Partial application differs from Currying, as will be explored in the relevant section.
部分申請與Currying不一樣,將在相關章節中進行探討。
Currying is a technique to partially apply a function by fixing a value for one of the parameters, resulting in a Function1
function that returns a Function1
.
Currying是一種經過固定其中一個參數的值來部分應用函數的技術,從而產生一個返回Function1
的Function1
函數。
When a Function2
is curried, the result is indistinguishable from the partial application of a Function2
because both result in a 1-arity function.
當Function2
被curried時,結果與Function2
的partial application沒法區分,由於二者都會產生1-arity函數。
Function2<Integer, Integer, Integer> sum = (a, b) -> a + b; Function1<Integer, Integer> add2 = sum.curried().apply(2); //(1) then(add2.apply(4)).isEqualTo(6);
- (1) The first parameter
a
is fixed to the value 2.
第一個參數a
固定爲值2。
You might notice that, apart from the use of .curried()
, this code is identical to the 2-arity given example in Partial application. With higher-arity functions, the difference becomes clear.
您可能會注意到,除了使用.curried()
以外,此代碼與Partial application中的2-arity給出示例相同。只是 具備更高的功能,差別變得清晰。
Function3<Integer, Integer, Integer, Integer> sum = (a, b, c) -> a + b + c; final Function1<Integer, Function1<Integer, Integer>> add2 = sum.curried().apply(2); //(1) then(add2.apply(4).apply(3)).isEqualTo(9); //(2)
- (1) Note the presence of additional functions in the parameters.
請注意參數中是否存在其餘功能。- (2) Further calls to
apply
returns anotherFunction1
, apart from the final call.
除了最終調用以外,對apply
的進一步調用返回另外一個Function1
。
Memoization is a form of caching. A memoized function executes only once and then returns the result from a cache.
記憶化是一種緩存形式。 memoized函數只執行一次,而後從緩存中返回結果。 The following example calculates a random number on the first invocation and returns the cached number on the second invocation.
如下示例在第一次調用時計算隨機數,並在第二次調用時返回緩存的數字。
Function0<Double> hashCache = Function0.of(Math::random).memoized(); double randomValue1 = hashCache.apply(); double randomValue2 = hashCache.apply(); then(randomValue1).isEqualTo(randomValue2);
In a functional setting we see a value as a kind of normal form, an expression which cannot be further evaluated. In Java we express this by making the state of an object final and call it immutable.
在函數式設置中,咱們將value
視爲一種普通形式,這是一個沒法進一步計算的表達式。 在Java中,咱們經過使對象的狀態爲final
並將其稱爲immutable來表達這一點。
Vavr’s functional Value abstracts over immutable objects. Efficient write operations are added by sharing immutable memory between instances. What we get is thread-safety for free!
Vavr的函數式Value
對不可變對象進行抽象。 經過在實例之間共享不可變內存來添加高效的寫入操做。 咱們免費獲得的是線程安全的!
Option is a monadic container type which represents an optional value. Instances of Option are either an instance of Some
or the None
.
Option是一個monadic(一元的)容器類型,表示可選值。 Option的實例是Some
或None
的實例。
// optional *value*, no more nulls Option<T> option = Option.of(...);
If you’re coming to Vavr after using Java’s Optional
class, there is a crucial difference. In Optional
, a call to .map
that results in a null will result in an empty Optional
. In Vavr, it would result in a Some(null)
that can then lead to a NullPointerException
.
若是你在使用Java的Optional
類以後來到Vavr,那就有一個相當重要的區別。 在Optional
中,調用.map
致使null將致使空Optional
。 在Vavr中,它會致使Some(null)
而後致使NullPointerException
。
Using Optional
, this scenario is valid.
使用Optional
,這種狀況是有效的。
Optional<String> maybeFoo = Optional.of("foo"); //(1) then(maybeFoo.get()).isEqualTo("foo"); Optional<String> maybeFooBar = maybeFoo.map(s -> (String)null) //(2) .map(s -> s.toUpperCase() + "bar"); then(maybeFooBar.isPresent()).isFalse();
- (1) The option is
Some("foo")
Option的值是Some(「foo」)
- (2) The resulting option becomes empty here
結果Option在此處變爲空
Using Vavr’s Option
, the same scenario will result in a NullPointerException
.
使用Vavr的Option
,相同的場景將致使NullPointerException
。
Option<String> maybeFoo = Option.of("foo"); //(1) then(maybeFoo.get()).isEqualTo("foo"); try { maybeFoo.map(s -> (String)null) (2) .map(s -> s.toUpperCase() + "bar"); (3) Assert.fail(); } catch (NullPointerException e) { // this is clearly not the correct approach }
- (1) The option is
Some("foo")
Option的值是Some(「foo」)
- (2) The resulting option is
Some(null)
Option的值是Some(「null」)
- (3) The call to
s.toUpperCase()
is invoked on anull
對s.toUpperCase()
的調用是在null
上調用的
This seems like Vavr’s implementation is broken, but in fact it’s not - rather, it adheres to the requirement of a monad to maintain computational context when calling .map
. In terms of an Option
, this means that calling .map
on a Some
will result in a Some
, and calling .map
on a None
will result in a None
. In the Java Optional
example above, that context changed from a Some
to a None
.
這看起來像Vavr的實現被破壞了,但事實上並不是如此 - 相反,它堅持monad在調用.map
時維護計算上下文的要求。 就Option
來講,這意味着在Some
上調用.map
將致使Some
,在None
上調用.map
將致使None
。 在上面的JavaOptional
示例中,該上下文從Some
變爲None
。
This may seem to make Option
useless, but it actually forces you to pay attention to possible occurrences of null
and deal with them accordingly instead of unknowingly accepting them. The correct way to deal with occurrences of null
is to use flatMap
.
這彷佛使Option
無用,但它實際上迫使你注意可能出現的null
並相應地處理它們而不是在不知不覺中接受它們。 處理null
出現的正確方法是使用flatMap
。
Option<String> maybeFoo = Option.of("foo"); //(1) then(maybeFoo.get()).isEqualTo("foo"); Option<String> maybeFooBar = maybeFoo.map(s -> (String)null) //(2) .flatMap(s -> Option.of(s) //(3) .map(t -> t.toUpperCase() + "bar")); then(maybeFooBar.isEmpty()).isTrue();
- (1) The option is
Some("foo")
- (2) The resulting option is
Some(null)
- (3)
s
, which isnull
, becomesNone
Alternatively, move the .flatMap
to be co-located with the the possibly null
value.
或者,將.flatMap
移動到與可能的'null`值位於同一位置。
Option<String> maybeFoo = Option.of("foo"); //(1) then(maybeFoo.get()).isEqualTo("foo"); Option<String> maybeFooBar = maybeFoo.flatMap(s -> Option.of((String)null)) (2) .map(s -> s.toUpperCase() + "bar"); then(maybeFooBar.isEmpty()).isTrue();
- (1) The option is
Some("foo")
- (2) The resulting option is
None
This is explored in more detail on the Vavr blog.
這在Vavr博客上有更詳細的探討。
Try is a monadic container type which represents a computation that may either result in an exception, or return a successfully computed value. It’s similar to, but semantically different from Either
. Instances of Try, are either an instance of Success
or Failure
.
Try是一個monadic(一元的)容器類型,表示可能致使異常或返回成功計算值的計算。 它與「Either」相似,但在語義上不一樣。 Try的實例是「成功」或「失敗」的實例。
// no need to handle exceptions Try.of(() -> bunchOfWork()).getOrElse(other); import static io.vavr.API.*; // $, Case, Match import static io.vavr.Predicates.*; // instanceOf A result = Try.of(this::bunchOfWork) .recover(x -> Match(x).of( Case($(instanceOf(Exception_1.class)), t -> somethingWithException(t)), Case($(instanceOf(Exception_2.class)), t -> somethingWithException(t)), Case($(instanceOf(Exception_n.class)), t -> somethingWithException(t)) )) .getOrElse(other);
Lazy is a monadic container type which represents a lazy evaluated value. Compared to a Supplier, Lazy is memoizing, i.e. it evaluates only once and therefore is referentially transparent.
Lazy是一個monadic(一元的)容器類型,表示惰性求值。 與Supplier相比,Lazy正在進行記憶,即它僅評估一次,所以是引用透明的。
Lazy<Double> lazy = Lazy.of(Math::random); lazy.isEvaluated(); // = false lazy.get(); // = 0.123 (random generated) lazy.isEvaluated(); // = true lazy.get(); // = 0.123 (memoized)
Since version 2.0.0 you may also create a real lazy value (works only with interfaces):
從版本2.0.0開始,您還能夠建立一個真正的延遲值(僅適用於接口):
CharSequence chars = Lazy.val(() -> "Yay!", CharSequence.class);
Either represents a value of two possible types. An Either is either a Left or a Right. If the given Either is a Right and projected to a Left, the Left operations have no effect on the Right value. If the given Either is a Left and projected to a Right, the Right operations have no effect on the Left value. If a Left is projected to a Left or a Right is projected to a Right, the operations have an effect.
Eitjer表明兩種可能類型的值。 一個要麼是Left,要麼是Right。 若是給定的Either是Right而且投影到Left,則Left操做對Right值沒有影響。 若是給定的Either是Left而且投影到Right,則Right操做對Left值沒有影響。 若是將Left投影到Left或Right投影到Right,則操做會生效。
Example: A compute() function, which results either in an Integer value (in the case of success) or in an error message of type String (in the case of failure). By convention the success case is Right and the failure is Left.
示例:compute()函數,其結果爲Integer值(在成功的狀況下)或在String類型的錯誤消息中(在失敗的狀況下)。 按照慣例,成功案例是正確的,失敗是左派。
Either<String,Integer> value = compute().right().map(i -> i * 2).toEither();
If the result of compute() is Right(1), the value is Right(2).
若是compute()的結果爲Right(1),則值爲Right(2)。
If the result of compute() is Left("error"), the value is Left("error").
若是compute()的結果爲Left(「error」),則值爲Left(「error」)。
A Future is a computation result that becomes available at some point. All operations provided are non-blocking. The underlying ExecutorService is used to execute asynchronous handlers, e.g. via onComplete(…).
Future是在某些時候可用的計算結果。 提供的全部操做都是非阻塞的。 底層的ExecutorService用於執行異步處理程序,例如 經過onComplete(...)。
A Future has two states: pending and completed.
Future有兩種狀態:待定和完成。
Pending: The computation is ongoing. Only a pending future may be completed or cancelled.
Pending: 計算正在進行中。 只有未決定的future可能會完成或取消。
Completed: The computation finished successfully with a result, failed with an exception or was cancelled.
**Completed:**計算結果成功完成,異常失敗或被取消。
Callbacks may be registered on a Future at each point of time. These actions are performed as soon as the Future is completed. An action which is registered on a completed Future is immediately performed. The action may run on a separate Thread, depending on the underlying ExecutorService. Actions which are registered on a cancelled Future are performed with the failed result.
能夠在每一個時間點在Future上註冊回調。 一旦Future完成,就會執行這些操做。 當即執行在已完成的Future上註冊的動做。 該操做能夠在單獨的Thread上運行,具體取決於底層的ExecutorService。 在取消的Future上註冊的操做將使用失敗的結果執行。
// future *value*, result of an async calculation Future<T> future = Future.of(...);
The Validation control is an applicative functor and facilitates accumulating errors. When trying to compose Monads, the combination process will short circuit at the first encountered error. But 'Validation' will continue processing the combining functions, accumulating all errors. This is especially useful when doing validation of multiple fields, say a web form, and you want to know all errors encountered, instead of one at a time.
Validation控制是一個applicative functor,有助於累積錯誤。 當嘗試組合Monads時,組合過程將在第一次遇到錯誤時短路。 但'Validation'將繼續處理組合功能,累積全部錯誤。 這在對多個字段(例如Web表單)進行驗證時很是有用,而且您但願知道遇到的全部錯誤,而不是一次一個。
Example: We get the fields 'name' and 'age' from a web form and want to create either a valid Person instance, or return the list of validation errors.
示例:咱們從Web表單中獲取字段'name'和'age',並但願建立有效的Person實例,或者返回驗證錯誤列表。
PersonValidator personValidator = new PersonValidator(); // Valid(Person(John Doe, 30)) Validation<Seq<String>, Person> valid = personValidator.validatePerson("John Doe", 30); // Invalid(List(Name contains invalid characters: '!4?', Age must be greater than 0)) Validation<Seq<String>, Person> invalid = personValidator.validatePerson("John? Doe!4", -1);
A valid value is contained in a Validation.Valid
instance, a list of validation errors is contained in a Validation.Invalid
instance.
有效值包含在Validation.Valid
實例中,驗證錯誤列表包含在Validation.Invalid
實例中。
The following validator is used to combine different validation results to one Validation
instance.
如下validator用於將不一樣的驗證結果組合到一個「Validation」實例。
class PersonValidator { private static final String VALID_NAME_CHARS = "[a-zA-Z ]"; private static final int MIN_AGE = 0; public Validation<Seq<String>, Person> validatePerson(String name, int age) { return Validation.combine(validateName(name), validateAge(age)).ap(Person::new); } private Validation<String, String> validateName(String name) { return CharSeq.of(name).replaceAll(VALID_NAME_CHARS, "").transform(seq -> seq.isEmpty() ? Validation.valid(name) : Validation.invalid("Name contains invalid characters: '" + seq.distinct().sorted() + "'")); } private Validation<String, Integer> validateAge(int age) { return age < MIN_AGE ? Validation.invalid("Age must be at least " + MIN_AGE) : Validation.valid(age); } }
If the validation succeeds, i.e. the input data is valid, then an instance of Person
is created of the given fields name
and age
.
若是驗證成功,即輸入數據有效,則建立給定字段'name'和'age'的'Person'實例。
class Person { public final String name; public final int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Person(" + name + ", " + age + ")"; } }
Much effort has been put into designing an all-new collection library for Java which meets the requirements of functional programming, namely immutability.
爲設計一個全新的Java集合庫已經付出了不少努力,它知足了函數式編程的要求,即不變性。
Java’s Stream lifts a computation to a different layer and links to a specific collection in another explicit step. With Vavr we don’t need all this additional boilerplate.
Java Stream將計算提高到不一樣的層,並在另外一個顯式步驟中連接到特定的集合。 使用Vavr,咱們不須要全部這些額外的樣板。
The new collections are based on java.lang.Iterable, so they leverage the sugared iteration style.
新集合基於java.lang.Iterable,所以它們利用了加糖的迭代樣式。
// 1000 random numbers for (double random : Stream.continually(Math::random).take(1000)) { ... }
TraversableOnce
has a huge amount of useful functions to operate on the collection. Its API is similar to java.util.stream.Stream but more mature.
TraversableOnce
具備大量有用的功能來操做集合。 它的API相似於java.util.stream.Stream,但更成熟。
Vavr’s List
is an immutable linked list. Mutations create new instances. Most operations are performed in linear time. Consequent operations are executed one by one.
Vavr的List
是一個不可變的鏈表。 突變建立新實例。 大多數操做都是以線性時間執行的。 後續操做逐個執行。
Arrays.asList(1, 2, 3).stream().reduce((i, j) -> i + j); IntStream.of(1, 2, 3).sum();
// io.vavr.collection.List List.of(1, 2, 3).sum();
The io.vavr.collection.Stream
implementation is a lazy linked list. Values are computed only when needed. Because of its laziness, most operations are performed in constant time. Operations are intermediate in general and executed in a single pass.
io.vavr.collection.Stream
實現是一個惰性鏈表。 僅在須要時計算值。 因爲它的懶惰,大多數操做是在恆定的時間內執行的。 操做一般是中間的,而且一次性執行。
The stunning thing about streams is that we can use them to represent sequences that are (theoretically) infinitely long.
關於Streams的驚人之處在於咱們可使用它們來表示(理論上)無限長的序列。
// 2, 4, 6, ... Stream.from(1).filter(i -> i % 2 == 0);
head() | tail() | get(int) | update(int, T) | prepend(T) | append(T) | |
---|---|---|---|---|---|---|
Array | const | linear | const | const | linear | linear |
CharSeq | const | linear | const | linear | linear | linear |
Iterator | const | const | — | — | — | — |
List | const | const | linear | linear | const | linear |
Queue | const | consta | linear | linear | const | const |
PriorityQueue | log | log | — | — | log | log |
Stream | const | const | linear | linear | constlazy | constlazy |
Vector | consteff | consteff | const eff | const eff | const eff | const eff |
contains/Key | add/put | remove | min | |
---|---|---|---|---|
HashMap | consteff | consteff | consteff | linear |
HashSet | consteff | consteff | consteff | linear |
LinkedHashMap | consteff | consteff | linear | linear |
LinkedHashSet | consteff | consteff | linear | linear |
Tree | log | log | log | log |
TreeMap | log | log | log | log |
TreeSet | log | log | log | log |
Legend(說明):
Property checking (also known as property testing) is a truly powerful way to test properties of our code in a functional way. It is based on generated random data, which is passed to a user defined check function.
屬性檢查(也稱爲屬性測試)是一種以功能方式測試代碼屬性的真正強大的方法。 它基於生成的隨機數據,傳遞給用戶定義的檢查功能。
Vavr has property testing support in its io.vavr:vavr-test
module, so make sure to include that in order to use it in your tests.
Vavr在其io.vavr:vavr-test
模塊中具備屬性測試支持,所以請確保包含它以便在測試中使用它。
Arbitrary<Integer> ints = Arbitrary.integer(); // square(int) >= 0: OK, passed 1000 tests. Property.def("square(int) >= 0") .forAll(ints) .suchThat(i -> i * i >= 0) .check() .assertIsSatisfied();
Generators of complex data structures are composed of simple generators.
複雜數據結構的生成器由簡單的生成器組成。
Scala has native pattern matching, one of the advantages over plain Java. The basic syntax is close to Java’s switch:
Scala具備本機模式匹配,是plain Java的優點之一。 基本語法接近Java的開關:
val s = i match { case 1 => "one" case 2 => "two" case _ => "?" }
Notably match is an expression, it yields a result. Furthermore it offers
值得注意的是* match *是一個表達式,它產生一個結果。 此外,它提供
case i: Int ⇒ "Int " + i
命名參數 case i: Int ⇒ "Int " + i
case Some(i) ⇒ i
對象解構 case Some(i) ⇒ i
case Some(i) if i > 0 ⇒ "positive " + i
守護 case Some(i) if i > 0 ⇒ "positive " + i
case "-h" | "--help" ⇒ displayHelp
多個條件case"-h"| " - help"⇒displayHelp
Pattern matching is a great feature that saves us from writing stacks of if-then-else branches. It reduces the amount of code while focusing on the relevant parts.
模式匹配是一個很好的功能,可使咱們免於編寫if-then-else分支的堆棧。 它在關注相關部分的同時減小了代碼量。
Vavr provides a match API that is close to Scala’s match. It is enabled by adding the following import to our application:
Vavr提供了一個接近Scala的Match
API。 經過將如下導入添加到咱們的應用程序來啓用它:
import static io.vavr.API.*;
Having the static methods Match, Case and the atomic patterns * 擁有靜態方法Match*,Case和atomic patterns
$()
- wildcard pattern 通配符模式$(value)
- equals pattern 等於模式$(predicate)
- conditional pattern 條件模式in scope, the initial Scala example can be expressed like this:
在範圍內,最初的Scala示例能夠表示以下:
String s = Match(i).of( Case($(1), "one"), Case($(2), "two"), Case($(), "?") );
We use uniform upper-case method names because 'case' is a keyword in Java. This makes the API special. 咱們使用統一的大寫方法名稱,由於'case'是Java中的關鍵字。 這使API變得特別。
The last wildcard pattern $()
saves us from a MatchError which is thrown if no case matches.
最後一個通配符模式$()
將咱們從MatchError(若是沒有大小寫匹配則拋出它)中拯救出來。
Because we can’t perform exhaustiveness checks like the Scala compiler, we provide the possibility to return an optional result:
由於咱們不能像Scala編譯器那樣執行窮舉檢查,因此咱們提供了返回可選結果的可能性:
Option<String> s = Match(i).option( Case($(0), "zero") );
As already shown, Case
allows to match conditional patterns.
如圖所示,Case
容許匹配條件模式。
Case($(predicate), ...)
Vavr offers a set of default predicates.
Vavr提供了一組默認謂詞。
import static io.vavr.Predicates.*;
These can be used to express the initial Scala example as follows:
這些可用於表示初始Scala示例,以下所示:
String s = Match(i).of( Case($(is(1)), "one"), Case($(is(2)), "two"), Case($(), "?") );
Multiple Conditions(多個條件)
We use the isIn
predicate to check multiple conditions:
咱們使用isIn
謂詞來檢查多個條件:
Case($(isIn("-h", "--help")), ...)
Performing Side-Effects(執行反作用)
Match acts like an expression, it results in a value. In order to perform side-effects we need to use the helper function run
which returns Void
:
匹配就像一個表達式,它會產生一個值。 爲了執行反作用,咱們須要使用輔助函數run
which返回Void
:
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); })) );
run
is used to get around ambiguities and becausevoid
isn’t a valid return value in Java.
run
用於解決歧義,由於void
不是Java中的有效返回值。
Caution: run
must not be used as direct return value, i.e. outside of a lambda body:
警告: run
不能用做直接返回值,即在lambda體外:
// Wrong! Case($(isIn("-h", "--help")), run(this::displayHelp))
Otherwise the Cases will be eagerly evaluated before the patterns are matched, which breaks the whole Match expression. Instead we use it within a lambda body:
不然,在模式匹配以前,將會先執行run
,這會打破整個匹配表達式。 相反,咱們在lambda體內使用它:
// Ok Case($(isIn("-h", "--help")), o -> run(this::displayHelp))
As we can see, run
is error prone if not used right. Be careful. We consider deprecating it in a future release and maybe we will also provide a better API for performing side-effects.
咱們能夠看到,若是沒有正確使用,run
很容易出錯。 當心。 咱們考慮在未來的版本中棄用它,也許咱們還會爲執行反作用提供更好的API。
Vavr leverages lambdas to provide named parameters for matched values.
Vavr利用lambdas爲匹配值提供命名參數。
Number plusOne = Match(obj).of( Case($(instanceOf(Integer.class)), i -> i + 1), Case($(instanceOf(Double.class)), d -> d + 1), Case($(), o -> { throw new NumberFormatException(); }) );
So far we directly matched values using atomic patterns. If an atomic pattern matches, the right type of the matched object is inferred from the context of the pattern.
到目前爲止,咱們使用原子模式直接匹配值。 若是原子模式能匹配,則從模式的上下文推斷出匹配對象的正確類型。
Next, we will take a look at recursive patterns that are able to match object graphs of (theoretically) arbitrary depth.
接下來,咱們將看一下可以匹配(理論上)任意深度的對象圖的遞歸模式。
In Java we use constructors to instantiate classes. We understand object decomposition as destruction of objects into their parts.
在Java中,咱們使用構造函數來實例化類。 咱們理解object decomposition做爲對象的破壞到它們的部分。
While a constructor is a function which is applied to arguments and returns a new instance, a deconstructor is a function which takes an instance and returns the parts. We say an object is unapplied.
雖然構造函數是一個function,它applied參數並返回一個新實例,但解構函數是一個接受實例並返回零件的函數。 咱們說對象是unapplied。
Object destruction is not necessarily a unique operation. For example, a LocalDate can be decomposed to
對象破壞不必定是惟一的操做。 例如,能夠將LocalDate分解爲
In Vavr we use patterns to define how an instance of a specific type is deconstructed. These patterns can be used in conjunction with the Match API.
在Vavr中,咱們使用模式來定義如何解構特定類型的實例。 這些模式能夠與Match API結合使用。
For many Vavr types there already exist match patterns. They are imported via
對於許多Vavr類型,已經存在匹配模式。 它們是經過引進
import static io.vavr.Patterns.*;
For example we are now able to match the result of a Try:
例如,咱們如今可以匹配Try的結果:
Match(_try).of( Case($Success($()), value -> ...), Case($Failure($()), x -> ...) );
A first prototype of Vavr’s Match API allowed to extract a user-defined selection of objects from a match pattern. Without proper compiler support this isn’t practicable because the number of generated methods exploded exponentially. The current API makes the compromise that all patterns are matched but only the root patterns are decomposed.
Vavr的Match API的第一個原型容許從匹配模式中提取用戶定義的對象選擇。 若是沒有適當的編譯器支持,這是不切實際的,由於生成的方法的數量呈指數級增加。 當前的API作出了妥協,即全部模式都匹配,但只有根模式被分解。
Match(_try).of( Case($Success(Tuple2($("a"), $())), tuple2 -> ...), Case($Failure($(instanceOf(Error.class))), error -> ...) );
Here the root patterns are Success and Failure. They are decomposed to Tuple2 and Error, having the correct generic types.
根模式是成功和失敗。 它們被分解爲Tuple2和Error,具備正確的泛型類型。
Deeply nested types are inferred according to the Match argument and not according to the matched patterns.
深度嵌套類型根據Match參數推斷,not根據匹配模式推斷。
It is essential to be able to unapply arbitrary objects, including instances of final classes. Vavr does this in a declarative style by providing the compile time annotations @Patterns
and @Unapply
.
必須可以取消應用任意對象,包括最終類的實例。 Vavr經過提供編譯時註釋@ Patterns
和@Unapply
來以聲明式方式執行此操做。
To enable the annotation processor the artifact vavr-match needs to be added as project dependency. 要啓用註釋處理器,須要將工件vavr-match添加爲項目依賴項。
Note: Of course the patterns can be implemented directly without using the code generator. For more information take a look at the generated source.
注意:固然能夠直接實現模式而無需使用代碼生成器。 有關更多信息,請查看生成的源。
import io.vavr.match.annotation.*; @Patterns class My { @Unapply static <T> Tuple1<T> Optional(java.util.Optional<T> optional) { return Tuple.of(optional.orElse(null)); } }
The annotation processor places a file MyPatterns in the same package (by default in target/generated-sources). Inner classes are also supported. Special case: if the class name is $
, the generated class name is just Patterns, without prefix.
註釋處理器將文件MyPatterns放在同一個包中(默認狀況下,在target/generated-sources中)。 內部類也受支持。 特殊狀況:若是類名是$
,則生成的類名稱只是Patterns,沒有前綴。
Now we are able to match Optionals using guards.
如今咱們可使用guards匹配Optionals。
Match(optional).of( Case($Optional($(v -> v != null)), "defined"), Case($Optional($(v -> v == null)), "empty") );
The predicates could be simplified by implementing isNull
and isNotNull
.
經過實現isNull
和isNotNull
能夠簡化謂詞。
And yes, extracting null is weird. Instead of using Java’s Optional give Vavr’s Option a try!
是的,提取null是很奇怪的。 不要使用Java的Optional來試試Vavr的選項!
Match(option).of( Case($Some($()), "defined"), Case($None(), "empty") );
Copyright 2014-2018 Vavr, http://vavr.io
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.