流与装饰器
幾年前, Streams API隨lambda表達式一起在Java 8中引入。 作為一個熟練的Java專家,我嘗試在我的一些項目中使用此新功能,例如here和here 。 我不是很喜歡它,然后又回到了好的老房子里。 此外,我創建了裝飾庫Cactoos來取代Guava ,而Guava在很多地方都不是很好。
這是一個原始示例。 假設我們有一些來自某些數據源的測量值,它們都是零到一之間的數字:
Iterable<Double> probes;現在,我們只需要顯示其中的前10個,忽略零和一,然后將它們重新縮放為(0..100) 。 聽起來很容易,對吧? 有三種方法可以實現:過程式,面向對象和Java 8方法。 讓我們從過程的方式開始:
int pos = 0; for (Double probe : probes) {if (probe == 0.0d || probe == 1.0d) {continue;}if (++pos > 10) {break;}System.out.printf("Probe #%d: %f", pos, probe * 100.0d); }為什么這是一種程序方式? 因為這勢在必行。 為什么勢在必行? 因為它是程序性的。 不,我在開玩笑。
這是當務之急,因為我們正在向計算機發出有關將哪些數據放入何處以及如何對其進行迭代的指令。 我們不是在聲明結果,而是必須構建它。 它可以工作,但不是真正可擴展的。 我們無法參與該算法并將其應用于另一個用例。 我們不能真正輕松地對其進行修改,例如從兩個來源而不是一個來源獲取數字,等等。這是程序性的。 說夠了。 不要這樣
現在,Java 8為我們提供了Streams API ,該API應該提供一種實現此目的的功能方法。 讓我們嘗試使用它。
首先,我們需要創建一個Stream的實例, Iterable 不允許我們直接獲取它。 然后,我們使用流API來完成這項工作:
StreamSupport.stream(probes.spliterator(), false).filter(p -> p == 0.0d || p == 1.0d).limit(10L).forEach(probe -> System.out.printf("Probe #%d: %f", 0, probe * 100.0d));這將起作用,但是將對所有探針說出Probe #0 ,因為forEach()不適用于索引。 目前是沒有這樣的事forEachWithIndex()在Stream界面的Java 8(和Java 9的太 )。 這是使用原子計數器的解決方法 :
AtomicInteger index = new AtomicInteger(); StreamSupport.stream(probes.spliterator(), false).filter(probe -> probe == 0.0d || probe == 1.0d).limit(10L).forEach(probe -> System.out.printf("Probe #%d: %f",index.getAndIncrement(),probe * 100.0d));“那是怎么了?” 你可能會問。 首先,看看在Stream接口中找不到正確的方法時,我們遇到麻煩的Stream 。 我們立即擺脫了“流式”范式,回到了良好的舊程序全局變量(計數器)。 其次,我們真的看不到那些filter() , limit()和forEach()方法內部發生了什么。 它們如何工作? 該文檔說,這種方法是“聲明性的”,并且Stream接口中的每個方法都返回某個類的實例。 他們是什么班? 只看這段代碼,我們一無所知。
此流API的最大問題是接口Stream,它非常龐大!
這兩個問題是聯系在一起的。 此流API的最大問題是接口Stream –很大。 在撰寫本文時,有43種方法。 在單個界面中四十三! 從SOLID到后來的更嚴格的原則 ,這都違反了面向對象編程的每一個原則 。
實現相同算法的面向對象方式是什么? 這就是我如何用Cactoos做到的 ,這只是一個集合 原始 簡單的Java類:
new And(new Mapped<Double, Scalar<Boolean>>(new Limited<Double>(new Filtered<Double>(probes,probe -> probe == 0.0d || probe == 1.0d),10),probe -> () -> {System.out.printf("Probe #%d: %f", 0, probe * 100.0d);return true;}), ).value();讓我們看看這里發生了什么。 首先, Filtered裝飾了我們的可迭代probes以從中取出某些項。 注意, Filtered實現了Iterable 。 然后, Limited (也是一個Iterable )僅取出前十個項目。 然后, Mapped將每個探針轉換為Scalar<Boolean>的實例,該實例執行行打印。
最后, And的實例遍歷“標量”列表,并要求每個標量返回boolean 。 他們打印行并返回true 。 由于它是true , And使用下一個標量進行下一次嘗試。 最后,其方法value()返回true 。
但是,等等,沒有索引。 讓我們添加它們。 為了做到這一點,我們僅使用另一個名為AndWithIndex類:
new AndWithIndex(new Mapped<Double, Func<Integer, Boolean>>(new Limited<Double>(new Filtered<Double>(probes,probe -> probe == 0.0d || probe == 1.0d),10),probe -> index -> {System.out.printf("Probe #%d: %f", index, probe * 100.0d);return true;}), ).value();現在,我們將探針映射到Func<Integer, Boolean> ,而不是Scalar<Boolean> Func<Integer, Boolean>以使其接受索引。
這種方法的優點在于所有類和接口都很小,這就是為什么它們很容易組合的原因。 為了限制探針的迭代,我們用Limited裝飾它; 為了使它過濾,我們用Filtered裝飾它; 為了做其他事情,我們創建一個新的裝飾器并使用它。 我們并沒有像Stream這樣的單一接口。
最重要的是,裝飾器是一種用于修改集合行為的面向對象的工具,而流是我什至找不到名稱的其他東西。
PS順便說一下,這就是在Guava的Iterables的幫助下可以實現相同算法的方式:
Iterable<Double> ready = Iterables.limit(Iterables.filter(probes,probe -> probe == 0.0d || probe == 1.0d),10 ); int pos = 0; for (Double probe : probes) {System.out.printf("Probe #%d: %f", pos++, probe * 100.0d); }這是一些面向對象和功能樣式的怪異組合。
翻譯自: https://www.javacodegeeks.com/2017/10/streams-vs-decorators.html
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
- 上一篇: 爱迪尔门锁设置(爱迪尔门锁设置顺序酒店)
- 下一篇: 笔记本换电脑cpu教程(笔记本换电脑cp