本篇內(nèi)容介紹了“Java8有哪些特性”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)公司!專注于網(wǎng)頁設(shè)計、網(wǎng)站建設(shè)、微信開發(fā)、微信小程序、集團企業(yè)網(wǎng)站建設(shè)等服務(wù)項目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了哈巴河免費建站歡迎大家使用!
Java 8 新特性:
Stream API
向方法傳遞代碼的技巧
接口中的默認(rèn)方法
助記:
//Java8主要有哪些新特性? //1.Stream API 2.接口的默認(rèn)實現(xiàn) 3.方法晉升一級公民 inventory.stream().filter((Apple a) -> a.getWeight() > 150) .collect(Collectors.toList());
在Java 8之前:
//對inventory中的蘋果按照重量進行排序 Collections.sort(inventory, new Comparator() { public int compare(Apple a1, Apple a2){ return a1.getWeight().compareTo(a2.getWeight()); } });
在Java 8之后:
//給庫存排序,比較蘋果重量,讀起來言簡意賅 inventory.sort(Collectors.comparing(Apple::getWeight));
Java 8提供了一個新的API(稱為“流”,Stream),它支持許多處理數(shù)據(jù)的并行操作,其思路和在數(shù)據(jù)庫查詢語言中的思路類似——用更高級的方式表達(dá)想要的東西,而由“實現(xiàn)”(在這里是Streams庫)來選擇最佳低級執(zhí)行機制。這樣就可以避免用synchronized編寫代碼,這一代碼不僅容易出錯,而且在多核CPU上執(zhí)行所需的成本也比你想象的要高。(更高效利用多核CPU)
從有點修正主義的角度來看,在Java 8中加入Streams可以看作把另外兩項擴充加入Java 8的直接原因:把代碼傳遞給方法的簡潔方式(方法引用、Lambda)和接口中的默認(rèn)方法。
如果僅僅“把代碼傳遞給方法”看作Streams的一個結(jié)果,那就低估了它在Java 8中的應(yīng)用范圍。它提供了一種新的方式,這種方式簡潔地表達(dá)了行為參數(shù)化。比方說,你想要寫兩個只有幾行代碼不同的方法,那現(xiàn)在你只需要把不同的那部分代碼作為參數(shù)傳遞進去就可以了。采用這種編程技巧,代碼會更短、更清晰,也比常用的復(fù)制粘貼更不容易出錯。
Java 8里面將代碼傳遞給方法的功能(同時也能夠返回代碼并將其包含在數(shù)據(jù)結(jié)構(gòu)中)還讓我們能夠使用一整套新技巧,通常稱為函數(shù)式編程。一言以蔽之,這種被函數(shù)式編程界稱為函數(shù)的代碼,可以被來回傳遞并加以組合,以產(chǎn)生強大的編程語匯。
略
流是一系列數(shù)據(jù)項,一次只生成一項。程序可以從輸入流中一個一個讀取數(shù)據(jù)項,然后以同樣的方式將數(shù)據(jù)項寫入輸出流。一個程序的輸出流很可能是另一個程序的輸入流。
舉一個例子:Unix的cat命令會把兩個文件連接起來創(chuàng)建一個流,tr會轉(zhuǎn)換流中的字符,sort會對流中的行進行排序,而tail -3則給出流的最后三行。Unix命令行允許這些程序通過管道(|)連接在一起,命令如下
cat file1 file2 | tr "[A-Z]" "[a-z]" | sort | tail -3
基于這一思想,Java 8在java.util.stream中添加了一個Stream API;Stream
推動這種做法的關(guān)鍵在于,現(xiàn)在你可以在一個更高的抽象層次上寫Java 8程序了:思路變成了把這樣的流變成那樣的流(就像寫數(shù)據(jù)庫查詢語句時的那種思路),而不是一次只處理一個項目。另一個好處是,Java 8可以透明地把輸入的不相關(guān)部分拿到幾個CPU內(nèi)核上去分別執(zhí)行你的Stream操作流水線——這是幾乎簡單易行的并行,用不著去費勁搞Thread了
將方法當(dāng)作參數(shù)傳入方法
Java8前只能傳基本類型,對象類型,不能單純存入方法
一般情況下這就意味著,寫代碼時不能訪問共享的可變數(shù)據(jù)。這些函數(shù)有時被稱為“純函數(shù)”或“無副作用函數(shù)”或“無狀態(tài)函數(shù)”,
并行只有在假定你的代碼的多個副本可以獨立工作時才能進行。但如果要寫入的是一個共享變量或?qū)ο?,這就行不通了:如果兩個進程需要同時修改這個共享變量怎么辦?
Java 8的流實現(xiàn)并行比Java現(xiàn)有的線程API更容易,因此,盡管可以使用synchronized來打破“不能有共享的可變數(shù)據(jù)”這一規(guī)則,但這相當(dāng)于是在和整個體系作對,因為它使所有圍繞這一規(guī)則做出的優(yōu)化都失去意義了。在多個處理器內(nèi)核之間使用synchronized,其代價往往比你預(yù)期的要大得多,因為同步迫使代碼按照順序執(zhí)行,而這與并行處理的宗旨相悖。
這兩個要點(沒有共享的可變數(shù)據(jù),將方法和函數(shù)即代碼傳遞給其他方法的能力)是函數(shù)式編程范式的基石。與此相反,在命令式編程范式中,你寫的程序則是一系列改變狀態(tài)的指令。
“不能有共享的可變數(shù)據(jù)”的要求意味著,一個方法是可以通過它將參數(shù)值轉(zhuǎn)換為結(jié)果的方式完全描述的;換句話說,它的行為就像一個數(shù)學(xué)函數(shù),沒有可見的副作用。
你之前已經(jīng)見過了Java的演變。例如,引入泛型,使用List
其他改變讓普通的東西更容易表達(dá),比如,使用for-each循環(huán)而不用暴露Iterator里面的套路寫法。
Java 8中的主要變化反映了它開始遠(yuǎn)離常側(cè)重改變現(xiàn)有值的經(jīng)典面向?qū)ο笏枷?/strong>,而向函數(shù)式編程領(lǐng)域轉(zhuǎn)變,在大面上考慮做什么(例如,創(chuàng)建一個值代表所有從A到B低于給定價格的交通線路)被認(rèn)為是頭等大事,并和如何實現(xiàn)(例如,掃描一個數(shù)據(jù)結(jié)構(gòu)并修改某些元素)區(qū)分開來。
請注意,如果極端點兒來說,傳統(tǒng)的面向?qū)ο缶幊毯秃瘮?shù)式可能看起來是沖突的。但是我們的理念是獲得兩種編程范式中最好的東西,這樣你就有更大的機會為任務(wù)找到理想的工具了。(取長補短)
語言需要不斷改進以跟進硬件的更新或滿足程序員的期待。要堅持下去,Java必須通過增加新功能來改進,而且只有新功能被人使用,變化才有意義。所以,使用Java 8,你就是在保護你作為Java程序員的職業(yè)生涯
編程語言中的函數(shù)一詞通常是指方法,尤其是靜態(tài)方法;這是在數(shù)學(xué)函數(shù),也就是沒有副作用的函數(shù)之外的新含義。
Java 8中新增了函數(shù)——值的一種新形式。有了它,Java 8可以進行多核處理器上的并行編程
想想Java程序可能操作的值吧。首先有原始值,比如42(int類型)和3.14(double類型)。其次,值可以是對象(更嚴(yán)格地說是對象的引用)。獲得對象的唯一途徑是利用new,也許是通過工廠方法或庫函數(shù)實現(xiàn)的;對象引用指向類的一個實例。例子包括"abc"(String類型),new Integer(1111)(Integer類型),以及new HashMap
編程語言的整個目的就在于操作值,要是按照歷史上編程語言的傳統(tǒng),這些值因此被稱為一等值(或一等公民,這個術(shù)語是從20世紀(jì)60年代美國民權(quán)運動中借用來的)
編程語言中的其他結(jié)構(gòu)也許有助于我們表示值的結(jié)構(gòu),但在程序執(zhí)行期間不能傳遞,因而是二等公民(Java中如方法和類等)。
用方法來定義類很不錯,類還可以實例化來產(chǎn)生值,但方法和類本身都不是值。這又有什么關(guān)系呢?
人們發(fā)現(xiàn),在運行時傳遞方法能將方法變成一等公民。這在編程中非常有用,因此Java 8的設(shè)計者把這個功能加入到了Java中。
Scala和Groovy等語言的實踐已經(jīng)證明,讓方法等概念作為一等值可以擴充程序員的工具庫,從而讓編程變得更容易。
Java 8的第一個新功能是方法引用
MethodArgument
Java 8 之前:
File[] hiddenFiles = new File(".").listFiles(new FileFilter() { public boolean accept(File file) { return file.isHidden(); } });
Java 8 之后:
File[] hiddenFiles = new File(".").listFiles(File::isHidden);
Java 8的方法引用::語法(即“把這個方法作為值”)將其傳給listFiles方法,也開始用函數(shù)代表方法了。一個好處是,你的代碼現(xiàn)在讀起來更接近問題的陳述了。方法不再是二等值了。
與用對象引用傳遞對象類似(對象引用是用new創(chuàng)建的),在Java 8里寫下File::isHidden的時候,創(chuàng)建了一個方法引用,同樣可以傳遞它
Lambda——匿名函數(shù)
除了允許(命名)函數(shù)成為一等值外,Java 8還體現(xiàn)了更廣義的將函數(shù)作為值的思想,包括Lambda(或匿名函數(shù))。比如,你現(xiàn)在可以寫(int x) -> x + 1,表示“調(diào)用時給定參數(shù)x,就返回x + 1值的函數(shù)”。
你可能會想這有什么必要呢?因為你可以在MyMathsUtils類里面定義一個add1方法,然后寫MyMathsUtils::add1嘛!確實是可以,但要是你沒有方便的方法和類可用,新的Lambda語法更簡潔。
FilteringApples
需求:
假設(shè)你有一個Apple類,它有一個getColor方法,還有一個變量inventory保存著一個Apples的列表。你可能想要選出所有的綠蘋果,并返回一個列表。
Java 8之前的寫法:
public static ListfilterGreenApples(List inventory) { List result = new ArrayList<>(); for (Apple apple : inventory) { if ("green".equals(apple.getColor())) { result.add(apple); } } return result; }
另一個新需求
可能想要選出重量超過150克的蘋果
public static ListfilterHeavyApples(List inventory) { List result = new ArrayList<>(); for (Apple apple : inventory) { if (apple.getWeight() > 150) { result.add(apple); } } return result; }
上面有代碼重復(fù),重構(gòu)的氣味出現(xiàn)
Java 8會把條件代碼作為參數(shù)傳遞進去,這樣可以避免filter方法出現(xiàn)重復(fù)的代碼
public static boolean isGreenApple(Apple apple) { return "green".equals(apple.getColor()); } public static boolean isHeavyApple(Apple apple) { return apple.getWeight() > 150; } public static ListfilterApples(List inventory, java.util.function.Predicate p) { List result = new ArrayList<>(); for (Apple apple : inventory) { if (p.test(apple)) { result.add(apple); } } return result; }
要用它的話,可以寫成
filterApples(inventory, FilteringApples::isGreenApple); filterApples(inventory, FilteringApples::isHeavyApple);
什么是謂詞Predicate?
在數(shù)學(xué)上常常用來代表一個類似函數(shù)的東西,它接受一個參數(shù)值,并返回true或false
把方法作為值來傳遞顯然很有用,但要是為類似于isHeavyApple和isGreenApple這種可能只用一兩次的短方法寫一堆定義很煩人。
filterApples(inventory, (Apple a) -> "green".equals(a.getColor())); //or filterApples(inventory, (Apple a) -> a.getWeight() > 150 ); //or filterApples(inventory, (Apple a) -> a.getWeight() < 80 || "brown".equals(a.getColor()) );
都不需要為只用一次的方法寫定義;代碼更干凈、更清晰,因為你用不著去找自己到底傳遞了什么代碼。
但要是Lambda的長度多于幾行(它的行為也不是一目了然)的話,那你還是應(yīng)該用方法引用來指向一個有描述性名稱的方法,而不是使用匿名的Lambda。你應(yīng)該以代碼的清晰度為準(zhǔn)繩。
Java 8的設(shè)計師幾乎可以就此打住了,要是沒有多核CPU,可能他們真的就到此為止了。
我們迄今為止談到的函數(shù)式編程竟然如此強大,在后面你更會體會到這一點。本來,Java加上filter和幾個相關(guān)的東西作為通用庫方法就足以讓人滿意了,比如
staticCollection filter(Collection c, Predicate p);
從
filterApples(inventory, (Apple a) -> a.getWeight() > 150);
就可以直接調(diào)用庫方法filter
inventory.stream().filter((Apple a) -> a.getWeight() > 150) .collect(Collectors.toList())
幾乎每個Java應(yīng)用都會制造和處理集合。但集合用起來并不總是那么理想。
需求
Java 8前
比方說,你需要從一個列表中篩選金額較高的交易,然后按貨幣分組。你需要寫一大堆套路化的代碼來實現(xiàn)這個數(shù)據(jù)處理命令。
Map> transactionsByCurrencies = new HashMap<>(); for (Transaction transaction : transactions) { if(transaction.getPrice() > 1000){ Currency currency = transaction.getCurrency(); List transactionsForCurrency = transactionsByCurrencies.get(currency); if (transactionsForCurrency == null) { transactionsForCurrency = new ArrayList<>(); transactionsByCurrencies.put(currency, transactionsForCurrency); } transactionsForCurrency.add(transaction); } }
Java 8后
import static java.util.stream.Collectors.toList; Map> transactionsByCurrencies = transactions.stream() .filter((Transaction t) -> t.getPrice() > 1000) .collect(groupingBy(Transaction::getCurrency));
和Collection API相比,Stream API處理數(shù)據(jù)的方式非常不同。用集合的話,你得自己去做迭代的過程。你得用for-each循環(huán)一個個去迭代元素,然后再處理元素。我們把這種數(shù)據(jù)迭代的方法稱為外部迭代。
相反,有了Stream API,根本用不著操心循環(huán)的事情。數(shù)據(jù)處理完全是在庫內(nèi)部進行的。我們把這種思想叫作內(nèi)部迭代。
使用流的好處——更高效利用多核CPU
使用集合的另一個頭疼的地方是,想想看,要是你的交易量非常龐大,你要怎么處理這個巨大的列表呢?單個CPU根本搞不定這么大量的數(shù)據(jù),但你很可能已經(jīng)有了一臺多核電腦。理想的情況下,你可能想讓這些CPU內(nèi)核共同分擔(dān)處理工作,以縮短處理時間。理論上來說,要是你有八個核,那并行起來,處理數(shù)據(jù)的速度應(yīng)該是單核的八倍。
傳統(tǒng)上是利用synchronized關(guān)鍵字,但是要是用錯了地方,就可能出現(xiàn)很多難以察覺的錯誤。Java 8基于Stream的并行提倡很少使用synchronized的函數(shù)式編程風(fēng)格,它關(guān)注數(shù)據(jù)分塊而不是協(xié)調(diào)訪問。
問題在于,通過多線程代碼來利用并行(使用先前Java版本中的Thread API)并非易事。
譬如:線程可能會同時訪問并更新共享變量。
因此,如果沒有協(xié)調(diào)好,數(shù)據(jù)可能會被意外改變。相比一步步執(zhí)行的順序模型,這個模型不太好理解。
下圖就展示了如果沒有同步好,兩個線程同時向共享變量sum加上一個數(shù)時,可能出現(xiàn)的問題。
Java 8也用Stream API(java.util.stream)解決了這兩個問題:集合處理時的套路和晦澀,以及難以利用多核。
這樣設(shè)計的第一個原因是,有許多反復(fù)出現(xiàn)的數(shù)據(jù)處理模式,類似于前一節(jié)所說的filterApples或SQL等數(shù)據(jù)庫查詢語言里熟悉的操作,如果在庫中有這些就會很方便:根據(jù)標(biāo)準(zhǔn)篩選數(shù)據(jù)(比如較重的蘋果),提取數(shù)據(jù)(例如抽取列表中每個蘋果的重量字段),或給數(shù)據(jù)分組(例如,將一個數(shù)字列表分組,奇數(shù)和偶數(shù)分別列表)等。
第二個原因是,這類操作常??梢圆⑿谢?/strong>。
例如,如下圖所示,在兩個CPU上篩選列表,可以讓一個CPU處理列表的前一半,第二個CPU處理后一半,這稱為分支步驟(1)。CPU隨后對各自的半個列表做篩選(2)。最后(3),一個CPU會把兩個結(jié)果合并起來
現(xiàn)在最好記得,Collection主要是為了存儲和訪問數(shù)據(jù),而Stream則主要用于描述對數(shù)據(jù)的計算
// 順序處理 ListheavyApples3 = inventory.stream().filter((Apple a) -> a.getWeight() > 150) .collect(Collectors.toList()); System.out.println(heavyApples3); // 并行處理 List heavyApples4 = inventory.parallelStream().filter((Apple a) -> a.getWeight() > 150) .collect(Collectors.toList()); System.out.println(heavyApples4);
Java中的并行與無共享可變狀態(tài)
大家都說Java里面并行很難,而且和synchronized相關(guān)的玩意兒都容易出問題。那Java 8里面有什么“靈丹妙藥”呢?事實上有兩個。
首先,庫會負(fù)責(zé)分塊,即把大的流分成幾個小的流,以便并行處理。
其次,流提供的這個幾乎免費的并行,只有在傳遞給filter之類的庫方法的方法不會互動(比方說有可變的共享對象)時才能工作。
但是其實這個限制對于程序員來說挺自然的,舉個例子,我們的Apple::isGreenApple就是這樣。確實,雖然函數(shù)式編程中的函數(shù)的主要意思是“把函數(shù)作為一等值”,不過它也常常隱含著第二層意思,即“執(zhí)行時在元素之間無互動”。
Java 8中加入默認(rèn)方法主要是為了支持庫設(shè)計師,讓他們能夠?qū)懗龈菀赘倪M的接口。
這一方法很重要,因為你會在接口中遇到越來越多的默認(rèn)方法,但由于真正需要編寫默認(rèn)方法的程序員相對較少,而且它們只是有助于程序改進,而不是用于編寫任何具體的程序
譬如
ListheavyApples1 = inventory.stream().filter((Apple a) -> a.getWeight() > 150).collect(Collectors.toList()); List heavyApples2 = inventory.parallelStream().filter((Apple a) -> a.a.getWeight() > 150).collect(Collectors.toList());
但這里有個問題:在Java 8之前,List
換作你自己的接口的話,最簡單的解決方案就是讓Java 8的設(shè)計者把stream方法加入Collection接口,并加入ArrayList類的實現(xiàn)。
可要是這樣做,對用戶來說就是噩夢了。有很多的替代集合框架都用Collection API實現(xiàn)了接口。但給接口加入一個新方法,意味著所有的實體類都必須為其提供一個實現(xiàn)。語言設(shè)計者沒法控制Collections所有現(xiàn)有的實現(xiàn),這下你就進退兩難了:你如何改變已發(fā)布的接口而不破壞已有的實現(xiàn)呢?
Java 8的解決方法就是——接口如今可以包含實現(xiàn)類沒有提供實現(xiàn)的方法簽名 了!那誰來實現(xiàn)它呢?缺失的方法主體隨接口提供了(因此就有了默認(rèn)實現(xiàn)),而不是由實現(xiàn)類提供。
這就給接口設(shè)計者提供了一個擴充接口的方式,而不會破壞現(xiàn)有的代碼。Java 8在接口聲明中使用新的default關(guān)鍵字來表示這一點。
例如,在Java 8里,你現(xiàn)在可以直接對List調(diào)用sort方法。它是用Java 8 List接口中如下所示的默認(rèn)方法實現(xiàn)的,它會調(diào)用Collections.sort靜態(tài)方法:
default void sort(Comparator super E> c) { Collections.sort(this, c); }
這意味著List的任何實體類都不需要顯式實現(xiàn)sort,而在以前的Java版本中,除非提供了sort的實現(xiàn),否則這些實體類在重新編譯時都會失敗。
眾所周知,一個類可以實現(xiàn)多個接口,那么,如果在好幾個接口里有多個默認(rèn)實現(xiàn),是否意味著Java中有了某種形式的多重繼承?Java 8用一些限制來避免出現(xiàn)類似于C++中臭名昭著的菱形繼承問題。
“Java8有哪些特性”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!