javaslang_使用Javaslang的Java 8中的功能数据结构
javaslang
Java 8的lambda(λ)使我們能夠創建出色的API。 它們極大地提高了語言的表達能力。
Javaslang利用lambda來基于功能模式創建各種新功能。 其中之一是功能性集合庫,旨在替代Java的標準集合。
(這只是鳥瞰圖,您將在下面找到易于理解的版本。)
功能編程
在深入探討有關數據結構的細節之前,我想談一些基本知識。 這將清楚說明為什么我創建Javaslang以及專門創建新的Java集合。
副作用
Java應用程序通常有很多副作用 。 他們改變了某種狀態,也許是外部世界。 常見的副作用是在適當位置更改對象或變量,打印到控制臺,寫入日志文件或數據庫。 如果副作用以不希望的方式影響我們程序的語義,則認為它們是有害的 。
例如,如果一個函數拋出一個異常并解釋了該異常,則它被認為是影響我們程序的副作用。 此外, 異常類似于非本地goto語句 。 他們打破了正常的控制流程。 但是,實際應用程序確實會產生副作用。
int divide(int dividend, int divisor) {// throws if divisor is zeroreturn dividend / divisor; }在功能設置中,我們處于有利的情況下,可以在Try中封裝副作用:
// = Success(result) or Failure(exception) Try<Integer> divide(Integer dividend, Integer divisor) {return Try.of(() -> dividend / divisor); }此版本的除法不再拋出。 通過使用嘗試類型,我們明確了可能的故障。
參照透明
如果某個函數可以用其值替換而不影響程序的行為,則該函數或更一般的表達式稱為“ 引用透明” 。 簡單地說,給定相同的輸入,輸出總是相同的。
// not referential transparent Math.random();// referential transparent Math.max(1, 2);如果涉及的所有表達式都是引用透明的,則該函數稱為純函數。 由純函數組成的應用程序在編譯后很可能就可以正常工作 。 我們能夠對此進行推理。 單元測試易于編寫,并且調試已成為過去。
價值觀思考
Clojure的創建者Rich Hickey就“價值的價值”進行了精彩的演講。 最有趣的值是不可變值。 主要原因是價值不變
- 本質上是線程安全的,因此不需要同步
- 對于equals和hashCode穩定,因此是可靠的哈希鍵
- 不需要克隆
- 在未經檢查的協變強制轉換中使用時,表現為類型安全(特定于Java)
改進Java的關鍵是使用不可變的值與引用透明函數配對。
Javaslang提供了必要的控件和集合,以實現日常Java編程中的這一目標。
簡而言之,數據結構
Javaslang的集合庫由建立在lambda之上的一組豐富的功能數據結構組成。 他們與Java原始集合共享的唯一接口是Iterable。 主要原因是Java的collection接口的mutator方法不返回基礎collection類型的對象。
通過查看不同類型的數據結構,我們將了解為什么如此重要。
可變數據結構
Java是一種面向對象的編程語言。 我們將狀態封裝在對象中以實現數據隱藏,并提供更改器方法來控制狀態。 Java集合框架(JCF??)就是基于這個想法而建立的。
interface Collection<E> {// removes all elements from this collectionvoid clear(); }今天,我領悟到一種無效的返回類型是一種氣味。 有證據表明發生了副作用 ,狀態發生了變化。 共享的可變狀態不僅是并發設置,而且是失敗的重要原因。
不變的數據結構
不變的數據結構在創建后無法修改。 在Java上下文中,它們以集合包裝的形式廣泛使用。
List<String> list = Collections.unmodifiableList(otherList);// Boom! list.add("why not?");有許多庫為我們提供了類似的實用程序方法。 結果始終是特定集合的不可修改視圖。 通常,當我們調用mutator方法時,它將在運行時拋出。
持久數據結構
持久數據結構在被修改時會保留其自身的先前版本,因此實際上是不可變的。 完全持久的數據結構允許在任何版本上進行更新和查詢。
許多操作僅執行很小的更改。 僅復制以前的版本是沒有效率的。 為了節省時間和內存,至關重要的是確定兩個版本之間的相似性并共享盡可能多的數據。
該模型沒有施加任何實現細節。 功能數據結構在這里發揮作用。
功能數據結構
也被稱為純功能數據結構 ,它們是不可變的和持久的 。 功能數據結構的方法是參照透明的 。
Javaslang具有各種最常用的功能數據結構。 以下示例將進行深入說明。
鏈表
最受歡迎的也是最簡單的功能數據結構之一是(單)鏈接List 。 它具有頭元素和尾元素列表。 鏈接列表的行為類似于遵循后進先出(LIFO)方法的堆棧。
在Javaslang中,我們實例化一個List像這樣:
// = List(1, 2, 3) List<Integer> list1 = List.of(1, 2, 3);每個List元素形成一個單獨的List節點。 最后一個元素的尾部為Nil,即空列表。
這使我們能夠在列表的不同版本之間共享元素。
// = List(0, 2, 3) List<Integer> list2 = list1.tail().prepend(0);新的head元素0 鏈接到原始List的尾部。 原始列表保持不變。
這些操作在恒定的時間內發生,換句話說,它們與List的大小無關。 其他大多數操作都需要線性時間。 在Javaslang中,這是由接口LinearSeq表示的,我們可能已經從Scala知道了。
如果我們需要可在恒定時間內查詢的數據結構,則Javaslang提供了Array和Vector。 兩者都具有隨機訪問功能。
數組類型由對象的Java數組支持。 插入和刪除操作花費線性時間。 向量在數組和列表之間。 它在隨機訪問和修改這兩個方面都表現出色。
實際上,鏈接列表也可以用于實現Queue數據結構。
隊列
可以基于兩個鏈接列表來實現非常有效的功能隊列。 前一個 List包含已出隊的元素, 后一個 List包含已入 隊的元素。 入隊和出隊兩個操作均在O(1)中執行。
Queue<Integer> queue = Queue.of(1, 2, 3).enqueue(4).enqueue(5);初始隊列由三個元素創建。 后面的列表上有兩個元素。
如果在出隊時前面的List用完了元素,那么后面的List將被反轉并成為新的前面的List。
使一個元素出隊時,我們得到一對第一個元素和剩余的Queue。 因為功能數據結構是不可變的且持久的,所以有必要返回新版本的Queue。 原始隊列不受影響。
Queue<Integer> queue = Queue.of(1, 2, 3);// = (1, Queue(2, 3)) Tuple2<Integer, Queue<Integer>> dequeued =queue.dequeue();隊列為空時會發生什么? 然后dequeue()將引發NoSuchElementException。 要以功能性的方式來實現它,我們寧愿期望一個可選結果。
// = Some((1, Queue())) Queue.of(1).dequeueOption();// = None Queue.empty().dequeueOption();不管是否為空,都可以進一步處理可選結果。
// = 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);排序集
排序集是比隊列更常用的數據結構。 我們使用二分搜索樹來對它們進行功能化建模。 這些樹由最多具有兩個子節點的節點組成,每個節點處都有值。
我們在有序的情況下(由元素Comparator表示)構建二進制搜索樹。 任何給定節點的左子樹的所有值都嚴格小于給定節點的值。 正確的子樹的所有值都嚴格大于。
// = TreeSet(1, 2, 3, 4, 6, 7, 8) SortedSet<Integer> xs =TreeSet.of(6, 1, 3, 2, 4, 7, 8);
對此類樹的搜索以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);大多數樹操作本質上都是遞歸的 。 插入功能的行為類似于搜索功能。 當到達搜索路徑的末尾時,將創建一個新節點,并將整個路徑重建到根。 盡可能引用現有的子節點。 因此,插入操作需要O(log n)的時間和空間。
// = TreeSet(1, 2, 3, 4, 5, 6, 7, 8) SortedSet<Integer> ys = xs.add(5);
為了維持二叉搜索樹的性能特征,需要保持平衡。 從根到葉的所有路徑都必須具有大致相同的長度。
在Javaslang中,我們基于Red / Black Tree實現了二叉搜索樹 。 它使用特定的著色策略來使樹在插入和刪除時保持平衡。 要了解有關此主題的更多信息,請參閱Chris Okasaki的《 Purely Functional Data Structures》 。
收藏狀態
通常,我們正在觀察編程語言的融合。 好的功能使它消失,其他消失。 但是Java是不同的,它永遠是向后兼容的。 這是一種優勢,但也會減緩發展。
Lambda使Java和Scala更加緊密地聯系在一起,但是它們仍然如此不同。 Scala的創建者Martin Odersky最近在他的BDSBTB 2015主題演講中提到了Java 8集合的狀態。
他將Java的Stream描述為迭代器的一種奇特形式。 Java 8 Stream API是提升集合的示例。 它的作用是定義一個計算并將其鏈接到另一個專有步驟中的特定集合。
// i + 1 i.prepareForAddition().add(1).mapBackToInteger(Mappers.toInteger())這就是新的Java 8 Stream API的工作方式。 它是眾所周知的Java集合之上的計算層。
// = ["1", "2", "3"] in Java 8 Arrays.asList(1, 2, 3).stream().map(Object::toString).collect(Collectors.toList())Javaslang受到Scala的極大啟發。 這就是上面的示例在Java 8中的樣子。
// = Stream("1", "2", "3") in Javaslang Stream.of(1, 2, 3).map(Object::toString)在過去的一年中,我們為實現Javaslang集合庫付出了很多努力。 它包含使用最廣泛的收集類型。
順序
我們通過實現順序類型開始了自己的旅程。 我們已經在上面描述了鏈接列表。 流,然后是一個懶惰的鏈表。 它使我們可以處理可能無限長的元素序列。
所有集合都是可迭代的,因此可以在增強的for語句中使用。
for (String s : List.of("Java", "Advent")) {// side effects and mutation }我們可以通過內部化循環并使用lambda注入行為來實現相同目的。
List.of("Java", "Advent").forEach(s -> {// side effects and mutation });無論如何,正如我們之前所看到的,我們更喜歡返回值的表達式而不是什么都不返回的語句。 通過看一個簡單的示例,很快我們將認識到語句增加了噪音,并將屬于的內容分開。
String join(String... words) {StringBuilder builder = new StringBuilder();for(String s : words) {if (builder.length() > 0) {builder.append(", ");}builder.append(s);}return builder.toString(); }Javaslang集合為我們提供了許多對底層元素進行操作的功能。 這使我們能夠以一種非常簡潔的方式表達事物。
String join(String... words) {return List.of(words).intersperse(", ").fold("", String::concat); }大多數目標可以使用Javaslang以各種方式實現。 在這里,我們將整個方法主體簡化為List實例上的流暢函數調用。 我們甚至可以刪除整個方法,然后直接使用List獲取計算結果。
List.of(words).mkString(", ");在現實世界的應用程序中,我們現在能夠大幅度減少代碼行數,從而降低錯誤的風險。
設置并映射
順序很棒。 但是,為了完整起見,集合庫還需要不同類型的“集合”和“地圖”。
我們描述了如何使用二叉樹結構對排序集進行建模。 排序的Map就是包含鍵值對并具有鍵順序的排序Set。
HashMap實現由哈希數組映射樹(HAMT)支持 。 因此,HashSet由包含密鑰對的HAMT支持。
我們的地圖不具有特殊的條目類型來表示鍵值對。 相反,我們使用已經是Javaslang一部分的Tuple2。 元組的字段被枚舉。
// = (1, "A") Tuple2<Integer, String> entry = Tuple.of(1, "A");Integer key = entry._1; String value = entry._2;Maps和Tuples在整個Javaslang中使用。 元組不可避免地會以一般方式處理多值返回類型。
// = 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();在Javaslang,我們通過實現99歐拉問題探索和測試我們的庫。 這是一個很好的概念證明。 請不要猶豫,發送請求請求。
動手!
我真的希望本文能引起您對Javaslang的興趣。 即使像我一樣在工作中使用Java 7(或更低版本),也可以遵循函數式編程的思想。 這將是非常好的!
請確保Javaslang在2016年成為工具帶的一部分。
駭客駭客!
PS:有問題嗎? @_Javaslang或Gitter聊天
翻譯自: https://www.javacodegeeks.com/2015/12/functional-data-structures-java-8-javaslang.html
javaslang
總結
以上是生活随笔為你收集整理的javaslang_使用Javaslang的Java 8中的功能数据结构的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: pc端安卓模拟器哪个好用(pc端安卓模拟
- 下一篇: 如何防止ddos攻击的危害(如何防止dd