Java流與Vavr流
1.簡介
在本文中,我們將研究Java和Vavr中Stream
實現的不同之處。
本文假定您熟悉Java Stream API和Vavr庫的基礎。
2.比較
兩種實現都表示相同的惰性序列概念,但細節不同。
Java Streams
的構建考慮到了強大的並行性,為並行化提供了輕鬆的支持。另一方面,Vavr實現有利於方便處理數據序列,並且不提供對並行性的本地支持(但是可以通過將實例轉換為Java實現來實現)。
這就是為什麼Java Streams由Spliterator
實例支持的Spliterator
–較舊的Iterator
的升級和Vavr的實現由上述的Iterator
(至少在最新的實現之一中)支持。
兩種實現都鬆散地與其支持的數據結構聯繫在一起,本質上是流所遍歷的數據源之上的立面,但是由於Vavr的實現是基於Iterator-
的,
因此它不能容忍對源集合的並發修改。
Java對流源的處理使得可以在執行終端流操作之前修改行為良好的流源。
儘管存在根本的設計差異,但Vavr提供了非常強大的API,可將其流(和其他數據結構)轉換為Java實現。
3.附加功能
處理流及其元素的方法導致我們在Java和Vavr中使用它們的方式產生有趣的差異
3.1。隨機元素訪問
提供方便的API和對元素的訪問方法是Vavr真正超越Java API的領域之一。例如,Vavr有一些提供隨機元素訪問的方法:
-
[get()](https://static.javadoc.io/io.vavr/vavr/0.9.2/io/vavr/collection/Stream.html#get-int-)
提供對流元素的基於索引的訪問。 -
indexOf()
提供與標準JavaList.
相同的索引定位功能List.
-
insert()
提供了將元素添加到流中指定位置的功能。 -
intersperse()
將在流的所有元素之間插入提供的參數。 -
find()
將在流中找到並返回一個項目。 Java提供了noneMatched
,noneMatched
檢查元素的存在。 -
[update()](https://static.javadoc.io/io.vavr/vavr/0.9.2/io/vavr/collection/Stream.html#update-int-java.util.function.Function-)
將替換給定索引處的元素。這也接受一個計算替換的函數。 -
search
()
將找到排序的項目 流(未排序的流將產生不確定的結果)
重要的是,我們要記住,此功能仍由具有線性搜索性能的數據結構支持。
3.2。並行和並發修改
儘管Vavr的Streams本身並不像Java的parallel()
方法那樣支持並行性,但是[toJavaParallelStream](https://static.javadoc.io/io.vavr/vavr/0.9.0/io/vavr/Value.html#toJavaParallelStream--)
方法提供了源Vavr流的基於Java的並行化副本。
Vavr流中相對較弱的區域是基於**Non-Interference**
原理。
簡單的說, Java流允許我們直接修改基礎數據源,直到調用終端操作為止。只要尚未在給定的Java流上調用終端操作,該流就可以對基礎數據源進行任何更改:
List<Integer> intList = new ArrayList<>();
intList.add(1);
intList.add(2);
intList.add(3);
Stream<Integer> intStream = intList.stream(); //form the stream
intList.add(5); //modify underlying list
intStream.forEach(i -> System.out.println("In a Java stream: " + i));
我們將發現最後的添加反映在流的輸出中。無論修改是在流管道內部還是外部,此行為都是一致的:
in a Java stream: 1
in a Java stream: 2
in a Java stream: 3
in a Java stream: 5
我們發現Vavr流無法容忍此情況:
Stream<Integer> vavrStream = Stream.ofAll(intList);
intList.add(5)
vavrStream.forEach(i -> System.out.println("in a Vavr Stream: " + i));
我們得到的是:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at io.vavr.collection.StreamModule$StreamFactory.create(Stream.java:2078)
根據Java標準,Vavr流不是“行為良好”的。 Vavr具有更好的原始備份數據結構:
int[] aStream = new int[]{1, 2, 4};
Stream<Integer> wrapped = Stream.ofAll(aStream);
aStream[2] = 5;
wrapped.forEach(i -> System.out.println("Vavr looped " + i));
給我們:
Vavr looped 1
Vavr looped 2
Vavr looped 5
3.3。短路操作和flatMap()
與map
操作一樣, flatMap,
是流處理中的中間操作-兩種實現都遵循中間流操作的約定-在調用終端操作之前,不應進行來自基礎數據結構的處理。
然而,JDK 8和9具有一個錯誤,當與短路的中間操作(如findFirst
或limit
結合使用時,該錯誤會導致flatMap
實現破壞此約定並進行急切的評估。
一個簡單的例子:
Stream.of(42)
.flatMap(i -> Stream.generate(() -> {
System.out.println("nested call");
return 42;
}))
.findAny();
在上面的代碼段中,我們將永遠不會從findAny
獲得結果,因為flatMap
會被急切地求值,而不是簡單地從嵌套Stream.
獲取單個元素Stream.
Java 10中提供了對此錯誤的修復。
Vavr的flatMap
沒有相同的問題,並且在O(1)中完成了功能相似的操作:
Stream.of(42)
.flatMap(i -> Stream.continually(() -> {
System.out.println("nested call");
return 42;
}))
.get(0);
3.4。核心Vavr功能
在某些方面,Java和Vavr之間沒有一對一的比較;只有在Java和Vavr之間才能進行比較。 Vavr通過Java中無法比擬的功能(或至少需要大量的手動工作)增強了流媒體體驗:
-
zip()
將流中的項目與提供的Iterable.
中的項目配對Iterable.
JDK-8曾經支持此操作,但是在build-93之後已將其刪除。 - 給定一個謂詞,
[partition()](https://static.javadoc.io/io.vavr/vavr/0.9.2/io/vavr/collection/Stream.html#partition-java.util.function.Predicate-)
將流的內容分為兩個流。 - 命名為
[permutation()](https://static.javadoc.io/io.vavr/vavr/0.9.2/io/vavr/collection/Stream.html#permutations--)
,將計算流元素的排列(所有可能的唯一順序)。 -
[combinations()](https://static.javadoc.io/io.vavr/vavr/0.9.2/io/vavr/collection/Stream.html#combinations-int-)
給出流的組合(即,可能的項目選擇)。 -
groupBy
將返回一個流Map
,其中包含來自原始流的元素(由提供的分類器分類)。 - Vavr中的
distinct
方法通過提供一個接受compareTo
表達式的變體在Java版本上進行了改進。
儘管Java SE流中對高級功能的支持似乎沒有靈感,但Expression Language 3.0奇怪地提供了比標準JDK流更多的功能支持。
4.流操作
Vavr允許直接操縱流的內容:
- 插入到現有的Vavr流中
Stream<String> vavredStream = Stream.of("foo", "bar", "baz");
vavredStream.forEach(item -> System.out.println("List items: " + item));
Stream<String> vavredStream2 = vavredStream.insert(2, "buzz");
vavredStream2.forEach(item -> System.out.println("List items: " + item));
- 從信息流中刪除項目
Stream<String> removed = inserted.remove("buzz");
- 基於隊列的操作
通過由隊列支持的Vavr流,它提供了恆定時間的prepend
和append
操作。
但是,對Vavr流所做的更改不會傳播回創建該流的數據源。
5.結論
Vavr和Java都有各自的長處,我們已經展示了每個庫對其設計目標的承諾– Java代表廉價的並行性,而Vavr代表便利的流操作。
通過Vavr支持在其自己的流和Java之間來迴轉換,可以在同一項目中獲得這兩個庫的好處,而又不會產生很多開銷。
本教程的源代碼可在Github上獲得。