日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

带有Javaslang的Java 8中的功能数据结构

發(fā)布時(shí)間:2023/12/3 java 49 豆豆
生活随笔 收集整理的這篇文章主要介紹了 带有Javaslang的Java 8中的功能数据结构 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

Java 8的lambda(λ)使我們能夠創(chuàng)建出色的API。 它們令人難以置信地提高了語言的表達(dá)能力。

Javaslang利用lambda來基于功能模式創(chuàng)建各種新功能。 其中之一是功能性集合庫,旨在替代Java的標(biāo)準(zhǔn)集合。

(這只是鳥瞰圖,您將在下面找到易于理解的版本。)

功能編程

在深入探討有關(guān)數(shù)據(jù)結(jié)構(gòu)的細(xì)節(jié)之前,我想談一些基本知識。 這將清楚說明為什么我創(chuàng)建Javaslang以及專門創(chuàng)建新的Java集合。

副作用

Java應(yīng)用程序通常有很多副作用 。 他們改變了某種狀態(tài),也許是外部世界。 常見的副作用是就地更改對象或變量,打印到控制臺,寫入日志文件或數(shù)據(jù)庫。 如果副作用以不希望的方式影響我們程序的語義,則認(rèn)為它們是有害的 。

例如,如果一個(gè)函數(shù)拋出一個(gè)異常并解釋了該異常,則它被認(rèn)為是影響我們程序的副作用。 此外, 異常類似于非本地goto語句 。 他們打破了正常的控制流程。 但是,實(shí)際應(yīng)用程序確實(shí)會(huì)產(chǎn)生副作用。

int divide(int dividend, int divisor) {// throws if divisor is zeroreturn dividend / divisor; }

在功能設(shè)置中,我們處于有利的情況下,可以在Try中封裝副作用:

// = Success(result) or Failure(exception) Try<Integer> divide(Integer dividend, Integer divisor) {return Try.of(() -> dividend / divisor); }

此版本的除法不再拋出。 通過使用嘗試類型,我們明確了可能的故障。

參照透明

如果可以用調(diào)用的值替換調(diào)用而不影響程序的行為,則該函數(shù)(或更一般的表達(dá)式)稱為引用透明 。 簡單地說,給定相同的輸入,輸出總是相同的。

// not referential transparent Math.random();// referential transparent Math.max(1, 2);

如果涉及的所有表達(dá)式都是引用透明的,則此函數(shù)稱為純函數(shù)。 由純函數(shù)組成的應(yīng)用程序在編譯后很可能就可以正常工作 。 我們能夠?qū)Υ诉M(jìn)行推理。 單元測試易于編寫,并且調(diào)試已成為過去。

價(jià)值觀思考

Clojure的創(chuàng)建者Rich Hickey就“價(jià)值的價(jià)值”發(fā)表了精彩的演講。 最有趣的值是不可變值。 主要原因是價(jià)值不變

  • 本質(zhì)上是線程安全的,因此不需要同步
  • 對于equals和hashCode穩(wěn)定,因此是可靠的哈希鍵
  • 不需要克隆
  • 在未經(jīng)檢查的協(xié)變強(qiáng)制轉(zhuǎn)換中使用時(shí),表現(xiàn)類型安全(特定于Java)

改進(jìn)Java的關(guān)鍵是使用不可變的值與引用透明函數(shù)配對。

Javaslang提供了必要的控件和集合,以實(shí)現(xiàn)日常Java編程中的這一目標(biāo)。

簡而言之,數(shù)據(jù)結(jié)構(gòu)

Javaslang的集合庫由建立在lambda之上的一組豐富的功能數(shù)據(jù)結(jié)構(gòu)組成。 他們與Java原始集合共享的唯一接口是Iterable。 主要原因是Java的collection接口的mutator方法不返回基礎(chǔ)collection類型的對象。

通過查看不同類型的數(shù)據(jù)結(jié)構(gòu),我們將了解為什么如此重要。

可變數(shù)據(jù)結(jié)構(gòu)

Java是一種面向?qū)ο蟮木幊陶Z言。 我們將狀態(tài)封裝在對象中以實(shí)現(xiàn)數(shù)據(jù)隱藏,并提供更改器方法來控制狀態(tài)。 Java集合框架(JCF??)就是基于這個(gè)想法而建立的。

interface Collection<E> {// removes all elements from this collectionvoid clear(); }

今天,我領(lǐng)會(huì)到一種無效的返回類型是一種氣味。 有證據(jù)表明發(fā)生了副作用 ,狀態(tài)發(fā)生了變化。 共享的可變狀態(tài)不僅是并發(fā)設(shè)置,而且是失敗的重要原因。

不變的數(shù)據(jù)結(jié)構(gòu)

不變的數(shù)據(jù)結(jié)構(gòu)在創(chuàng)建后無法修改。 在Java上下文中,它們以集合包裝器的形式廣泛使用。

List<String> list = Collections.unmodifiableList(otherList);// Boom! list.add("why not?");

有許多庫為我們提供了類似的實(shí)用程序方法。 結(jié)果始終是特定集合的不可修改視圖。 通常,當(dāng)我們調(diào)用mutator方法時(shí),它將在運(yùn)行時(shí)拋出。

持久數(shù)據(jù)結(jié)構(gòu)

持久數(shù)據(jù)結(jié)構(gòu)在被修改時(shí)會(huì)保留其自身的先前版本,因此實(shí)際上是不可變的。 完全持久的數(shù)據(jù)結(jié)構(gòu)允許在任何版本上進(jìn)行更新和查詢。

許多操作僅執(zhí)行很小的更改。 僅復(fù)制以前的版本并不會(huì)有效。 為了節(jié)省時(shí)間和內(nèi)存,確定兩個(gè)版本之間的相似性并共享盡可能多的數(shù)據(jù)至關(guān)重要。

該模型沒有施加任何實(shí)現(xiàn)細(xì)節(jié)。 功能數(shù)據(jù)結(jié)構(gòu)在這里發(fā)揮作用。

功能數(shù)據(jù)結(jié)構(gòu)

也被稱為純功能數(shù)據(jù)結(jié)構(gòu) ,它們是不可變的和持久的 。 功能數(shù)據(jù)結(jié)構(gòu)的方法是參照透明的 。

Javaslang具有各種最常用的功能數(shù)據(jù)結(jié)構(gòu)。 以下示例將進(jìn)行深入說明。

鏈表

最受歡迎的也是最簡單的功能數(shù)據(jù)結(jié)構(gòu)之一是(單)鏈接List 。 它具有頭元素和尾元素列表。 鏈接列表的行為類似于遵循后進(jìn)先出(LIFO)方法的堆棧。

在Javaslang中,我們實(shí)例化一個(gè)List像這樣:

// = List(1, 2, 3) List<Integer> list1 = List.of(1, 2, 3);

每個(gè)List元素形成一個(gè)單獨(dú)的List節(jié)點(diǎn)。 最后一個(gè)元素的尾部為Nil,即空列表。

這使我們能夠在列表的不同版本之間共享元素。

// = List(0, 2, 3) List<Integer> list2 = list1.tail().prepend(0);

新的head元素0 鏈接到原始List的尾部。 原始列表保持不變。

這些操作在恒定的時(shí)間內(nèi)發(fā)生,換句話說,它們與列表大小無關(guān)。 其他大多數(shù)操作都需要線性時(shí)間。 在Javaslang中,這是通過接口LinearSeq表示的,我們可能已經(jīng)從Scala知道了。

如果我們需要可在恒定時(shí)間內(nèi)查詢的數(shù)據(jù)結(jié)構(gòu),則Javaslang提供了Array和Vector。 兩者都具有隨機(jī)訪問功能。

數(shù)組類型由對象的Java數(shù)組支持。 插入和刪除操作花費(fèi)線性時(shí)間。 向量在數(shù)組和列表之間。 它在隨機(jī)訪問和修改這兩個(gè)方面都表現(xiàn)出色。

實(shí)際上,鏈接列表也可以用于實(shí)現(xiàn)Queue數(shù)據(jù)結(jié)構(gòu)。

隊(duì)列

可以基于兩個(gè)鏈接列表來實(shí)現(xiàn)非常高效的功能隊(duì)列。 前一個(gè) List包含已出隊(duì)的元素, 后一個(gè) List包含已入 隊(duì)的元素。 入隊(duì)和出隊(duì)這兩個(gè)操作均在O(1)中執(zhí)行。

Queue<Integer> queue = Queue.of(1, 2, 3).enqueue(4).enqueue(5);

初始隊(duì)列由三個(gè)元素創(chuàng)建。 后面的列表中有兩個(gè)元素。

如果在出隊(duì)時(shí)前面的List用完了元素,那么后面的List將被反轉(zhuǎn)并成為新的前面的List。

使一個(gè)元素出隊(duì)時(shí),我們得到一對第一個(gè)元素和剩余的Queue。 因?yàn)楣δ軘?shù)據(jù)結(jié)構(gòu)是不可變的且持久的,所以有必要返回新版本的Queue。 原始隊(duì)列不受影響。

Queue<Integer> queue = Queue.of(1, 2, 3);// = (1, Queue(2, 3)) Tuple2<Integer, Queue<Integer>> dequeued =queue.dequeue();

隊(duì)列為空時(shí)會(huì)發(fā)生什么? 然后dequeue()將拋出NoSuchElementException。 要以功能性方式做到這一點(diǎn),我們寧愿期望一個(gè)可選結(jié)果。

// = Some((1, Queue())) Queue.of(1).dequeueOption();// = None Queue.empty().dequeueOption();

不管是否為空,都可以進(jìn)一步處理可選結(jié)果。

// = 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);

排序集

排序集是比隊(duì)列更常用的數(shù)據(jù)結(jié)構(gòu)。 我們使用二分搜索樹以功能方式對它們進(jìn)行建模。 這些樹由最多具有兩個(gè)子節(jié)點(diǎn)的節(jié)點(diǎn)組成,每個(gè)節(jié)點(diǎn)處都有值。

我們在有序的情況下(由元素Comparator表示)構(gòu)建二進(jìn)制搜索樹。 任何給定節(jié)點(diǎn)的左子樹的所有值都嚴(yán)格小于給定節(jié)點(diǎn)的值。 正確的子樹的所有值都嚴(yán)格大于。

// = TreeSet(1, 2, 3, 4, 6, 7, 8) SortedSet<Integer> xs =TreeSet.of(6, 1, 3, 2, 4, 7, 8);

對此類樹的搜索以O(shè)(log n)時(shí)間運(yùn)行。 我們從根開始搜索,并確定是否找到了元素。 由于值的總順序,我們知道下一步要在當(dāng)前樹的左側(cè)或右側(cè)分支中搜索的位置。

// = 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);

大多數(shù)樹操作本質(zhì)上都是遞歸的 。 插入功能的行為類似于搜索功能。 當(dāng)?shù)竭_(dá)搜索路徑的末尾時(shí),將創(chuàng)建一個(gè)新節(jié)點(diǎn),并將整個(gè)路徑重建到根。 盡可能引用現(xiàn)有的子節(jié)點(diǎn)。 因此,插入操作需要O(log n)的時(shí)間和空間。

// = TreeSet(1, 2, 3, 4, 5, 6, 7, 8) SortedSet<Integer> ys = xs.add(5);

為了維持二叉搜索樹的性能特征,需要保持平衡。 從根到葉子的所有路徑都必須具有大致相同的長度。

在Javaslang中,我們基于Red / Black Tree實(shí)現(xiàn)了二叉搜索樹 。 它使用特定的著色策略來使樹在插入和刪除時(shí)保持平衡。 要了解有關(guān)此主題的更多信息,請參閱Chris Okasaki的《 Purely Functional Data Structures 》一書。

收藏狀態(tài)

通常,我們正在觀察編程語言的融合。 好的功能使它消失,其他消失。 但是Java是不同的,它將永遠(yuǎn)向后兼容。 這是一種優(yōu)勢,但也會(huì)減緩發(fā)展。

Lambda使Java和Scala更加緊密地聯(lián)系在一起,但是它們?nèi)匀蝗绱瞬煌?Scala的創(chuàng)建者M(jìn)artin Odersky最近在他的BDSBTB 2015主題演講中提到了Java 8集合的狀態(tài)。

他將Java的Stream描述為迭代器的一種奇特形式。 Java 8 Stream API是提升集合的示例。 它的作用是定義一個(gè)計(jì)算并將其鏈接到另一個(gè)專有步驟中的特定集合。

// i + 1 i.prepareForAddition().add(1).mapBackToInteger(Mappers.toInteger())

這就是新的Java 8 Stream API的工作方式。 它是眾所周知的Java集合之上的計(jì)算層。

// = ["1", "2", "3"] in Java 8 Arrays.asList(1, 2, 3).stream().map(Object::toString).collect(Collectors.toList())

Javaslang受到Scala的極大啟發(fā)。 這就是上面的示例在Java 8中的樣子。

// = Stream("1", "2", "3") in Javaslang Stream.of(1, 2, 3).map(Object::toString)

在過去的一年中,我們在實(shí)現(xiàn)Javaslang集合庫上投入了大量精力。 它包含使用最廣泛的收集類型。

順序

我們通過實(shí)現(xiàn)順序類型開始了自己的旅程。 我們已經(jīng)在上面描述了鏈接列表。 流,然后是一個(gè)懶惰的鏈表。 它使我們可以處理可能無限長的元素序列。

所有集合都是可迭代的,因此可以在增強(qiáng)的for語句中使用。

for (String s : List.of("Java", "Advent")) {// side effects and mutation }

我們可以通過內(nèi)部化循環(huán)并使用lambda注入行為來實(shí)現(xiàn)相同目的。

List.of("Java", "Advent").forEach(s -> {// side effects and mutation });

無論如何,正如我們之前所看到的,我們更喜歡返回值的表達(dá)式而不是什么都不返回的語句。 通過看一個(gè)簡單的例子,很快我們將認(rèn)識到語句增加了噪音,并將屬于的東西分開。

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集合為我們提供了許多對底層元素進(jìn)行操作的功能。 這使我們可以非常簡潔地表達(dá)事物。

String join(String... words) {return List.of(words).intersperse(", ").fold("", String::concat); }

大多數(shù)目標(biāo)可以使用Javaslang以各種方式實(shí)現(xiàn)。 在這里,我們將整個(gè)方法主體簡化為List實(shí)例上的流利函數(shù)調(diào)用。 我們甚至可以刪除整個(gè)方法,然后直接使用List獲取計(jì)算結(jié)果。

List.of(words).mkString(", ");

在現(xiàn)實(shí)世界的應(yīng)用程序中,我們現(xiàn)在能夠大幅度減少代碼行數(shù),從而降低錯(cuò)誤的風(fēng)險(xiǎn)。

設(shè)置并映射

順序很棒。 但是,為了完整起見,集合庫還需要不同類型的“集合”和“地圖”。

我們描述了如何使用二叉樹結(jié)構(gòu)對排序集進(jìn)行建模。 排序的Map就是包含鍵值對并具有鍵順序的排序Set。

HashMap實(shí)現(xiàn)由哈希數(shù)組映射樹(HAMT)支持 。 因此,HashSet由包含密鑰對的HAMT支持。

我們的地圖不具有特殊的條目類型來表示鍵值對。 相反,我們使用已經(jīng)是Javaslang一部分的Tuple2。 元組的字段被枚舉。

// = (1, "A") Tuple2<Integer, String> entry = Tuple.of(1, "A");Integer key = entry._1; String value = entry._2;

Maps和Tuples在整個(gè)Javaslang中使用。 元組不可避免地會(huì)以一般方式處理多值返回類型。

// = 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,我們通過實(shí)現(xiàn)99歐拉問題探索和測試我們的庫。 這是一個(gè)很好的概念證明。 請不要猶豫,發(fā)送請求請求。

動(dòng)手!

我真的希望本文能激發(fā)您對Javaslang的興趣。 即使您像我一樣在工作中使用Java 7(或更低版本),也可以遵循函數(shù)式編程的思想。 這將是非常好的!

請確保Javaslang在2016年成為工具帶的一部分。

駭客入侵!

PS:有問題嗎? @_Javaslang或Gitter聊天

翻譯自: https://www.javacodegeeks.com/2015/12/functional-data-structures-java-8-javaslang.html

總結(jié)

以上是生活随笔為你收集整理的带有Javaslang的Java 8中的功能数据结构的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。