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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

驳斥5条普通流Tropes

發(fā)布時(shí)間:2023/12/3 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 驳斥5条普通流Tropes 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

我剛讀完“ JDK 8收集器的強(qiáng)大功能的一種例外” ,我不得不說我很失望。 Java冠軍 Simon Ritter是Oracle的前Java推廣者,現(xiàn)在是Oracle的Java傳播者,現(xiàn)在是Azul Systems的副CTO(使用JVM的人 )寫了它,因此我希望對流有一些有趣的見解。 相反,帖子歸結(jié)為:

  • 使用流減少行數(shù)
  • 你可以和收藏家一起做花哨的東西
  • 流中的異常很爛

這不僅是膚淺的,而且文章還采用了一些不合標(biāo)準(zhǔn)的開發(fā)實(shí)踐。 現(xiàn)在,西蒙(Simon)寫道,這只是一個小型演示項(xiàng)目,所以我想他并沒有將所有的專業(yè)知識投入其中。 盡管如此,它還是很草率的,而且,更糟的是,那里的許多人犯了同樣的錯誤并重復(fù)了相同的比喻。

看到它們被引用在許多不同的地方(即使各自的作者在按下時(shí)可能無法捍衛(wèi)這些觀點(diǎn)),也肯定不會幫助開發(fā)人員對如何使用流獲得良好的印象。 因此,我決定借此機(jī)會寫一篇反駁的文章-不僅是針對這篇文章,而且是對所有重復(fù)的文章的反駁。

(總是指出我的觀點(diǎn)是多余的(畢竟這是我的博客)并且很累人,所以我不會這樣做。但是請記住這一點(diǎn),因?yàn)槲艺f的某些東西即使它們是事實(shí)也是如此。僅是我的觀點(diǎn)。)

問題

關(guān)于發(fā)生了什么事情以及為什么發(fā)生的原因有很多解釋,但最終歸結(jié)為:我們有一個來自HTTP POST請求的查詢字符串,并且想要將參數(shù)解析為更方便的數(shù)據(jù)結(jié)構(gòu)。 例如,給定字符串a(chǎn) = foo&b = bar&a = fu,我們希望得到類似a?> {foo,fu} b?> {bar}的名稱。

我們也有一些在網(wǎng)上找到的已經(jīng)執(zhí)行此操作的代碼:

private void parseQuery(String query, Map parameters)throws UnsupportedEncodingException {if (query != null) {String pairs[] = query.split("[&]");for (String pair : pairs) {String param[] = pair.split("[=]");String key = null;String value = null;if (param.length > 0) {key = URLDecoder.decode(param[0],System.getProperty("file.encoding"));}if (param.length > 1) {value = URLDecoder.decode(param[1],System.getProperty("file.encoding"));}if (parameters.containsKey(key)) {Object obj = parameters.get(key);if(obj instanceof List) {List values = (List)obj;values.add(value);} else if(obj instanceof String) {List values = new ArrayList();values.add((String)obj);values.add(value);parameters.put(key, values);}} else {parameters.put(key, value);}}} }

我認(rèn)為沒有提及作者的名字是一種好意,因?yàn)榇舜a段在很多層面上都是錯誤的,因此我們甚至都不會討論。

我的牛肉

從這里開始,本文將說明如何向流重構(gòu)。 這就是我開始不同意的地方。

簡潔流

這就是重構(gòu)的動機(jī):

看完這個之后,我認(rèn)為我可以[...]使用流來使其更加簡潔。

當(dāng)人們把它作為使用流的第一個動機(jī)時(shí),我討厭它! 認(rèn)真地說,我們是Java開發(fā)人員,如果可以提高可讀性,我們習(xí)慣于編寫一些額外的代碼。

信息流與簡潔無關(guān)

因此,信息流與簡潔無關(guān)。 相反,我們習(xí)慣于循環(huán),因此我們經(jīng)常將大量操作塞入循環(huán)的主體行中。 當(dāng)向流重構(gòu)時(shí),我經(jīng)常將操作分開,從而導(dǎo)致更多的行。

相反,流的神奇之處在于它們?nèi)绾沃С炙季S模式匹配。 因?yàn)樗麄冎皇褂昧松贁?shù)幾個概念(主要是map / flatMap,filter,reduce / collect / find),所以我可以快速了解正在發(fā)生的事情并集中精力進(jìn)行操作,最好是一個接一個地進(jìn)行。

for (Customer customer : customers) {if (customer.getAccount().isOverdrawn()) {WarningMail mail = WarningMail.createFor(customer.getAccount());// do something with mail} }customers.stream().map(Customer::getAccount).filter(Account::isOverdrawn).map(WarningMail::createFor).forEach(/* do something with mail */ );

在代碼中,遵循通用的“客戶映射到帳戶,過濾透支的帳戶映射到警告郵件”,然后費(fèi)時(shí)費(fèi)力地“為從客戶那里獲得的帳戶創(chuàng)建警告郵件,但前提是透支,才容易得多”。

但是,為什么這是抱怨的理由? 每個人都有自己的喜好,對嗎? 是的,但是專注于簡潔性會導(dǎo)致錯誤的設(shè)計(jì)決策。

例如,我經(jīng)常決定通過為其創(chuàng)建一個方法并使用一個方法引用來總結(jié)一個或多個操作(如連續(xù)映射)。 這樣可以帶來不同的好處,例如將流管道中的所有操作都保持在相同的抽象級別上,或者簡單地命名本來就很難理解的操作(您知道,意圖顯示名稱和內(nèi)容)。 如果我專注于簡潔性,我可能不會這樣做。

減少代碼行數(shù)也可能導(dǎo)致將多個操作組合到一個lambda中,從而節(jié)省了兩個映射或過濾器。 再次,這打敗了背后的目的!

因此,當(dāng)您看到一些代碼并考慮將其重構(gòu)為流時(shí),不要數(shù)行來確定您的成功!

使用丑陋的力學(xué)

循環(huán)要做的第一件事也是啟動流的方法:我們將查詢字符串與&符分開,然后對結(jié)果鍵值對進(jìn)行操作。 該文章如下

Arrays.stream(query.split("[&]"))

看起來不錯? 老實(shí)說,沒有。 我知道,這是創(chuàng)建流的最佳方式,但只是因?yàn)槲覀儽仨氝@樣做 ,這樣并不意味著我們來看看它。 而且我們在這里所做的(沿著正則表達(dá)式分割字符串)似乎也很普通。 那么為什么不將其推入實(shí)用程序功能呢?

public static Stream<String> splitIntoStream(String s, String regex) {return Arrays.stream(s.split(regex)); }

然后,我們使用splitIntoStream(query,“ [&]”)啟動流。 一種簡單的“提取方法”重構(gòu),但效果更好。

次優(yōu)數(shù)據(jù)結(jié)構(gòu)

還記得我們想做什么? 將類似a = foo&b = bar&a = fu的內(nèi)容解析為a?> {foo,fu} b?> {bar}。 現(xiàn)在,我們怎么可能代表結(jié)果呢? 看起來我們正在將單個字符串映射到許多字符串,所以也許我們應(yīng)該嘗試Map <String,List <String >>?

那絕對是個不錯的初衷……但這絕不是我們能做的最好的! 首先,為什么要列出清單? 訂單真的很重要嗎? 我們需要重復(fù)的值嗎? 我猜這兩項(xiàng)都不對,所以也許我們應(yīng)該嘗試一套?

無論如何,如果您曾經(jīng)創(chuàng)建過一個以值為集合的地圖,那么您就會知道這有些不愉快。 總是存在這樣的極端情況:“這是第一個要素嗎?” 考慮。 盡管Java 8減輕了麻煩……

public void addPair(String key, String value) {// `map` is a `Map<String, Set<String>>`map.computeIfAbsent(key, k -> new HashSet<>()).add(value); }

…從API的角度來看,還遠(yuǎn)遠(yuǎn)不夠完美。 例如,迭代或流式傳輸所有值是一個兩步過程:

private <T> Stream<T> streamValues() {// `map` could be a `Map<?, Collection<T>>`return map.values().stream().flatMap(Collection::stream); }

長話短說,我們正在將需要的東西(從鍵到多個值的映射)變成我們想到的第一件事(從鍵到單個值的映射)。 那不是一個好的設(shè)計(jì)!

尤其是因?yàn)槲覀兊男枨蠓浅Fヅ?#xff1a; Guava的Multimap 。 也許有充分的理由不使用它,但在這種情況下,至少應(yīng)該提及它。 畢竟,本文的目的是找到一種處理和表示輸入的好方法,因此它應(yīng)該在為輸出選擇數(shù)據(jù)結(jié)構(gòu)方面做得很好。

(雖然一般來說,這是一個反復(fù)出現(xiàn)的主題,但它并不是非常特定于流的。我沒有將其歸類為5個常見的對立部分,但仍然想提及它,因?yàn)檫@樣可以使最終結(jié)果更好。)

康妮插圖

說到常見的比喻...一種是使用溪流的老照片為帖子添加一些顏色。 有了這個,我很樂意承擔(dān)!

由Dan Zen在CC-BY 2.0下發(fā)布

貧血管道

您是否曾經(jīng)看到幾乎什么都不做但突然將所有功能塞入單個操作的管道? 這篇文章對我們的小解析問題的解決方案是一個完美的例子(我刪除了一些空處理以提高可讀性):

private Map<String, List<String>> parseQuery(String query) {return Arrays.stream(query.split("[&]")).collect(groupingBy(s -> (s.split("[=]"))[0],mapping(s -> (s.split("[=]"))[1], toList()))); }

這是我在閱讀本文時(shí)的思考過程:“好吧,所以我們用&符分隔查詢字符串,然后,耶穌在他媽的棍子上,那是什么?!” 然后我冷靜下來,意識到這里隱藏著一個抽象-通常不追求它,而讓我們大膽地做到這一點(diǎn)。

在這種情況下,我們將請求參數(shù)a = foo拆分為[a,foo],然后分別處理這兩個部分。 因此,在流中包含該對的流水線中不應(yīng)該走一步嗎?

但這是一種罕見的情況。 流的元素通常是某種類型,我想用其他信息來豐富它。 也許我有大量的客戶,并希望將其與他們居住的城市配對。請注意,我不想用城市代替客戶-這是一個簡單的地圖-但需要同時(shí)使用這兩個功能,例如將城市映射到居住的客戶在其中。

正確表示中間結(jié)果是可讀性的福音。

兩種情況有什么共同點(diǎn)? 他們需要代表一對。 他們?yōu)槭裁床荒?#xff1f; 因?yàn)镴ava沒有慣用的方法。 當(dāng)然,您可以使用數(shù)組(適用于我們的請求參數(shù)), Map.Entry ,某些庫的元組類甚至特定于域的東西。 但很少有人做,這使得代碼做到的是通過一個有點(diǎn)令人驚訝脫穎而出。

不過,我還是喜歡這種方式。 正確表示中間結(jié)果是可讀性的福音。 使用Entry看起來像這樣:

private Map<String, List<String>> parseQuery(String query) {return splitIntoStream(query, "[&]").map(this::parseParameter).collect(groupingBy(Entry::getKey,mapping(Entry::getValue, toList()))); }private Entry<String, String> parseParameter(String parameterString) {String[] split = parameterString.split("[=]");// add all kinds of verifications herereturn new SimpleImmutableEntry<>(split[0], split[1]); }

我們?nèi)匀挥心g(shù)收藏家要處理,但那里發(fā)生的事情最少。

收藏魔術(shù)

Java 8附帶了一些瘋狂的收集器 (尤其是那些轉(zhuǎn)發(fā)給下游收集器的收集器),我們已經(jīng)看到如何濫用它們來創(chuàng)建不可讀的代碼。 如我所見,它們之所以存在是因?yàn)闆]有元組,就沒有辦法準(zhǔn)備復(fù)雜的約簡。 所以這是我的工作:

  • 我嘗試通過正確準(zhǔn)備流的元素來使收集器盡可能簡單(如有必要,我為此使用元組或特定于域的數(shù)據(jù)類型)。
  • 如果仍然需要做一些復(fù)雜的事情,可以將其放入實(shí)用程序方法中。

吃我自己的狗糧,這怎么辦?

private Map<String, List<String>> parseQuery(String query) {return splitIntoStream(query, "[&]").map(this::parseParameter).collect(toListMap(Entry::getKey, Entry::getValue)); }/** Beautiful JavaDoc comment explaining what the collector does. */ public static <T, K, V> Collector<T, ?, Map<K, List<V>>> toListMap(Function<T, K> keyMapper, Function<T, V> valueMapper) {return groupingBy(keyMapper, mapping(valueMapper, toList())); }

它仍然很丑陋-盡管不是那么可怕-但至少我不必一直都在看它。 如果我愿意,返回類型和合同注釋將使您更容易了解發(fā)生的情況。

或者,如果我們決定使用Multimap,我們會四處尋找匹配的收集器 :

private Multimap<String, String> parseQuery(String query) {return splitIntoStream(query, "[&]").map(this::parseParameter).collect(toMultimap(Entry::getKey, Entry::getValue)); }

在這兩種情況下,我們甚至可以更進(jìn)一步,對條目流進(jìn)行特殊處理。 我將其留給您練習(xí)。 :)

異常處理

本文在處理流時(shí)面臨的最大挑戰(zhàn)是異常處理。 它說:

不幸的是,如果您回頭查看原始代碼,將會發(fā)現(xiàn)我方便地省略了一個步驟:使用URLDecoder將參數(shù)字符串轉(zhuǎn)換為其原始格式。

問題是URLDecoder :: decode會引發(fā)檢查的UnsupportedEncodingException,因此無法將其簡單地添加到代碼中。 那么本文采用哪種方法解決這一相關(guān)問題? 鴕鳥之一 :

最后,我決定保留我的第一個超薄方法。 由于在這種情況下我的Web前端未進(jìn)行任何編碼,因此我的代碼仍然可以正常工作。

嗯...文章標(biāo)題沒有提到例外嗎? 因此,不應(yīng)該為此多花點(diǎn)時(shí)間嗎?

無論如何,錯誤處理總是很困難,流增加了一些約束和復(fù)雜性。 討論不同的方法需要花費(fèi)時(shí)間,而且具有諷刺意味的是,我并不熱衷于將其壓縮到帖子的最后部分。 因此,讓我們詳細(xì)討論如何使用運(yùn)行時(shí)異常,欺騙或monad來解決該問題,而不是考慮最簡單的解決方案。

一個操作要做的最簡單的事情就是篩選出引起麻煩的元素。 因此,該操作不是將每個元素映射到一個新元素,而是將一個元素映射到零或一個元素。 在我們的情況下:

private static Stream<Entry<String, String>> parseParameter(String parameterString) {try {return Stream.of(parseValidParameter(parameterString));} catch (IllegalArgumentException | UnsupportedEncodingException ex) {// we should probably log the exception herereturn Stream.empty();} }private static Entry<String, String> parseValidParameter(String parameterString)throws UnsupportedEncodingException {String[] split = parameterString.split("[=]");if (split.length != 2) {throw new IllegalArgumentException(/* explain what's going on */);}return new SimpleImmutableEntry<>(URLDecoder.decode(split[0], ENCODING),URLDecoder.decode(split[1], ENCODING)); }

然后,我們在flatMap而不是地圖中使用parseParameter,并獲取可以拆分和解碼的條目流(以及一堆日志消息,告訴我們在什么情況下出現(xiàn)問題)。

攤牌

這是文章的最終版本:

private Map<String, List> parseQuery(String query) {return (query == null) ? null : Arrays.stream(query.split("[&]")).collect(groupingBy(s -> (s.split("[=]"))[0],mapping(s -> (s.split("[=]"))[1], toList()))); }

摘要說:

由此得出的結(jié)論是,使用流和收集器的靈活性,可以大大減少復(fù)雜處理所需的代碼量。 缺點(diǎn)是,當(dāng)這些令人討厭的異常抬起頭來時(shí),這種方法就不能很好地工作了。

這是我的:

private Multimap<String, String> parseQuery(String query) {if (query == null)return ArrayListMultimap.create();return splitIntoStream(query, "[&]").flatMap(this::parseParameter).collect(toMultimap(Entry::getKey, Entry::getValue)); }// plus `parseParameter` and `parseValidParameter` as above// plus the reusable methods `splitIntoStream` and `toMultimap

行更多,是的,但是流管道具有更少的技術(shù)組合,通過URL解碼參數(shù)來設(shè)置完整的功能集,可接受(或至少存在)異常處理,適當(dāng)?shù)闹虚g結(jié)果,明智的收集器,以及良好的性能結(jié)果類型。 它帶有兩個通用實(shí)用程序功能,可以幫助其他開發(fā)人員改善其開發(fā)流程。 我認(rèn)為多余的幾行值得所有。

因此,我的收獲有所不同:使用流以簡單可預(yù)測的方式使用流的構(gòu)建塊來使代碼揭示其意圖。 抓住機(jī)會尋找可重用的操作(尤其是那些創(chuàng)建或收集流的操作),不要害羞地調(diào)用小方法以保持管道可讀。 最后但并非最不重要的一點(diǎn):忽略行數(shù)。

圣經(jīng)后

順便說一下,借助Java 9對流API的增強(qiáng) ,我們不必對空查詢字符串進(jìn)行特殊情況處理:

private Multimap<String, String> parseQuery(String query) {return Stream.ofNullable(query).flatMap(q -> splitIntoStream(q, "[&]")).flatMap(this::parseParameter).collect(toMultimap(Entry::getKey, Entry::getValue)); }

等不及了!

翻譯自: https://www.javacodegeeks.com/2016/09/rebutting-5-common-stream-tropes.html

總結(jié)

以上是生活随笔為你收集整理的驳斥5条普通流Tropes的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

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