您好,登錄后才能下訂單哦!
Java 8的新特性以及改進(jìn)有哪些,相信很多沒有經(jīng)驗(yàn)的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個(gè)問題。
這篇文章是對Java 8中即將到來的改進(jìn)做一個(gè)面向開發(fā)者的綜合性的總結(jié),JDK的這一特性將會(huì)在2013年9月份發(fā)布。
在寫這篇文章的時(shí)候,Java 8的開發(fā)工作仍然在緊張有序的進(jìn)行中,語言特新和API仍然有可能改變,我會(huì)盡我最大的努力保持這份文檔跟得到Java 8的改動(dòng)。
Java 8的預(yù)覽版,也就是 “Project Lambda”,現(xiàn)在可以從java.net下載到。
我使用了IntelliJ的預(yù)覽版做我的IDE,在我看來他是目前支持java 8特性最好的一個(gè)IDE,你可以從這里下載到.
由于我沒有找到Oracle發(fā)布的Java 8的官方文檔,所以目前Java 8的文檔還只有本地版本,等Oracle公開文檔的時(shí)候,我將會(huì)重新鏈接到官方文檔。
接口改善
現(xiàn)在接口里已經(jīng)完全可以定義靜態(tài)方法了. 舉一個(gè)比較普遍的例子就是在java類庫中, 對于一些接口如Foo, 都會(huì)有一個(gè)有靜態(tài)方法的工具類Foos 來生成或者配合Foo對象實(shí)例來使用. 既然靜態(tài)方法可以存在于接口當(dāng)中, 那么大多數(shù)情況下 Foos工具類完全可以使用接口中的公共方法來代理 (或者將Foos置成package-private).
除此之外更重要的就是, Java 8中接口可以定義默認(rèn)的方法了.舉個(gè)例子,一個(gè)for-each循環(huán)的方法就可以加入到j(luò)ava.lang.Iterable中:
public default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } }
在過去,java類庫的接口中添加方法基本上是不可能的. 在接口中添加方法意味著破壞了實(shí)現(xiàn)了這個(gè)接口的代碼. 但是現(xiàn)在, 只要能夠提供一個(gè)正確明智的默認(rèn)的方法的實(shí)現(xiàn), java類庫的維護(hù)者就可以在接口中添加方法.
Java 8中, 大量的默認(rèn)方法已經(jīng)被添加到核心的JDK接口中了. 稍候我會(huì)詳細(xì)介紹它們.
為什么不能用默認(rèn)方法來重載equals,hashCode和toString?
接口不能提供對Object類的任何方法的默認(rèn)實(shí)現(xiàn)。特別是,這意味著從接口里不能提供對equals,hashCode或toString的默認(rèn)實(shí)現(xiàn)。
這剛看起來挺奇怪的,但考慮到一些接口實(shí)際上是在文檔里定義他們的equals行為的。List接口就是一個(gè)例子了。因此,為什么不允許這樣呢?
Brian Goetz在這個(gè)問題上的冗長的回復(fù)里給出了4個(gè)原因。我這里只說其中一個(gè),因?yàn)槟莻€(gè)已經(jīng)足夠說服我了:
它會(huì)變得更困難來推導(dǎo)什么時(shí)候該調(diào)用默認(rèn)的方法?,F(xiàn)在它變得很簡單了:如果一個(gè)類實(shí)現(xiàn)了一個(gè)方法,那總是優(yōu)先于默認(rèn)的實(shí)現(xiàn)的。一旦所有接口的實(shí)例都是Object的子類,所有接口實(shí)例都已經(jīng)有對equals/hashCode/toString的非默認(rèn)實(shí)現(xiàn)。因此,一個(gè)在接口上這些的默認(rèn)版本都是沒用的,它也不會(huì)被編譯。
要看更多的話,看下由Brian Goetz寫的解釋: 對“允許默認(rèn)方法來重載Object的方法”的回復(fù)
函數(shù)式接口
Java 8 引入的一個(gè)核心概念是函數(shù)式接口。如果一個(gè)接口定義個(gè)唯一一個(gè)抽象方法,那么這個(gè)接口就成為函數(shù)式接口。比如,java.lang.Runnable就是一個(gè)函數(shù)式接口,因?yàn)樗豁斠粋€(gè)一個(gè)抽象方法:
public abstract void run();
留意到“abstract”修飾詞在這里是隱含的,因?yàn)檫@個(gè)方法缺少方法體。為了表示一個(gè)函數(shù)式接口,并非想這段代碼一樣一定需要“abstract”關(guān)鍵字。
默認(rèn)方法不是abstract的,所以一個(gè)函數(shù)式接口里可以定義任意多的默認(rèn)方法,這取決于你。
同時(shí),引入了一個(gè)新的Annotation:@FunctionalInterface??梢园阉旁谝粋€(gè)接口前,表示這個(gè)接口是一個(gè)函數(shù)式接口。加上它的接口不會(huì)被編譯,除非你設(shè)法把它變成一個(gè)函數(shù)式接口。它有點(diǎn)像@Override,都是聲明了一種使用意圖,避免你把它用錯(cuò)。
Lambdas
一個(gè)函數(shù)式接口非常有價(jià)值的屬性就是他們能夠用lambdas來實(shí)例化。這里有一些lambdas的例子:
左邊是指定類型的逗號分割的輸入列表,右邊是帶有return的代碼塊:
(int x, int y) -> { return x + y; }
左邊是推導(dǎo)類型的逗號分割的輸入列表,右邊是返回值:
(x, y) -> x + y
左邊是推導(dǎo)類型的單一參數(shù),右邊是一個(gè)返回值:
x -> x * x
左邊沒有輸入 (官方名稱: “burger arrow”),在右邊返回一個(gè)值:
() -> x
左邊是推導(dǎo)類型的單一參數(shù),右邊是沒返回值的代碼塊(返回void):
x -> { System.out.println(x); }
靜態(tài)方法引用:
String::valueOf
非靜態(tài)方法引用:
Object::toString
繼承的函數(shù)引用:
x::toString
構(gòu)造函數(shù)引用:
ArrayList::new
你可以想出一些函數(shù)引用格式作為其他lambda格式的簡寫。
方法引用 | 等價(jià)的lambda表達(dá)式 | |
---|---|---|
String::valueOf | x -> String.valueOf(x) | |
Object::toString | x -> x.toString() | |
x::toString | () -> x.toString() | |
ArrayList::new | () -> new ArrayList<>() |
當(dāng)然,在Java里方法能被重載。類可以有多個(gè)同名但不同參數(shù)的方法。這同樣對構(gòu)造方法有效。ArrayList::new能夠指向它的3個(gè)構(gòu)造方法中任何一個(gè)。決定使用哪個(gè)方法是根據(jù)在使用的函數(shù)式接口。
一個(gè)lambda和給定的函數(shù)式接口在“外型”匹配的時(shí)候兼容。通過“外型”,我指向輸入、輸出的類型和聲明檢查異常。
給出兩個(gè)具體有效的例子:
Comparator<String> c = (a, b) -> Integer.compare(a.length(), b.length());
一個(gè)Comparator<String>的compare方法需要輸入兩個(gè)闡述,然后返回一個(gè)int。這和lambda右側(cè)的一致,因此這個(gè)任務(wù)是有效的。
Runnable r = () -> { System.out.println("Running!"); }
一個(gè)Runnable的run方法不需要參數(shù)也不會(huì)返回值。這和lambda右側(cè)一致,所以任務(wù)有效。
在抽象方法的簽名里的受檢查異常(如果存在)也很重要。如果函數(shù)式接口在它的簽名里聲明了異常,lambda只能拋出受檢查異常。
捕獲和非捕獲的Lambda表達(dá)式
當(dāng)Lambda表達(dá)式訪問一個(gè)定義在Lambda表達(dá)式體外的非靜態(tài)變量或者對象時(shí),這個(gè)Lambda表達(dá)式稱為“捕獲的”。比如,下面這個(gè)lambda表達(dá)式捕捉了變量x:
int x = 5; return y -> x + y;
為了保證這個(gè)lambda表達(dá)式聲明是正確的,被它捕獲的變量必須是“有效final”的。所以要么它們需要用final修飾符號標(biāo)記,要么保證它們在賦值后不能被改變。
Lambda表達(dá)式是否是捕獲的和性能悄然相關(guān)。一個(gè)非不捕獲的lambda通常比捕獲的更高效,雖然這一點(diǎn)沒有書面的規(guī)范說明(據(jù)我所知),而且也不能為了程序的正確性指望它做什么,非捕獲的lambda只需要計(jì)算一次. 然后每次使用到它都會(huì)返回一個(gè)唯一的實(shí)例。而捕獲的lambda表達(dá)式每次使用時(shí)都需要重新計(jì)算一次,而且從目前實(shí)現(xiàn)來看,它很像實(shí)例化一個(gè)匿名內(nèi)部類的實(shí)例。
lambdas不做的事
你應(yīng)該記住,有一些lambdas不提供的特性。為了Java 8它們被考慮到了,但是沒有被包括進(jìn)去,由于簡化以及時(shí)間限制的原因。
Non-final* 變量捕獲 - 如果一個(gè)變量被賦予新的數(shù)值,它將不能被用于lambda之中?!眆inal”關(guān)鍵字不是必需的,但變量必須是“有效final”的(前面討論過)。這個(gè)代碼不會(huì)被編譯:
int count = 0; List<String> strings = Arrays.asList("a", "b", "c"); strings.forEach(s -> { count++; // error: can't modify the value of count });
例外的透明度 - 如果一個(gè)已檢測的例外可能從lambda內(nèi)部拋出,功能性的接口也必須聲明已檢測例外可以被拋出。這種例外不會(huì)散布到其包含的方法。這個(gè)代碼不會(huì)被編譯:
void appendAll(Iterable<String> values, Appendable out) throws IOException { // doesn't help with the error values.forEach(s -> { out.append(s); // error: can't throw IOException here // Consumer.accept(T) doesn't allow it }); }
有繞過這個(gè)的辦法,你能定義自己的功能性接口,擴(kuò)展Consumer的同時(shí)通過像RuntimeException之類拋出 IOException。我試圖用代碼寫出來,但發(fā)現(xiàn)它令人困惑是否值得。
控制流程 (break, early return) -在上面的 forEach例子中,傳統(tǒng)的繼續(xù)方式有可能通過在lambda之內(nèi)放置 ”return;”來實(shí)現(xiàn)。但是,沒有辦法中斷循環(huán)或者從lambda中通過包含方法的結(jié)果返回一個(gè)數(shù)值。例如:
final String secret = "foo"; boolean containsSecret(Iterable<String> values) { values.forEach(s -> { if (secret.equals(s)) { ??? // want to end the loop and return true, but can't } }); }
進(jìn)一步閱讀關(guān)于這些問題的資料,看看這篇Brian Goetz寫的說明:在 Block<T>中響應(yīng)“已驗(yàn)證例外”
為什么抽象類不能通過利用lambda實(shí)例化
抽象類,哪怕只聲明了一個(gè)抽象方法,也不能使用lambda來實(shí)例化。
下面有兩個(gè)類 Ordering 和 CacheLoader的例子,都帶有一個(gè)抽象方法,摘自于Guava 庫。那豈不是很高興能夠聲明它們的實(shí)例,像這樣使用lambda表達(dá)式?
Ordering<String> order = (a, b) -> ...; CacheLoader<String, String> loader = (key) -> ...;
這樣做引發(fā)的最常見的爭論就是會(huì)增加閱讀lambda的難度。以這種方式實(shí)例化一段抽象類將導(dǎo)致隱藏代碼的執(zhí)行:抽象類的構(gòu)造方法。
另一個(gè)原因是,它拋出了lambda表達(dá)式可能的優(yōu)化。在未來,它可能是這種情況,lambda表達(dá)式都不會(huì)計(jì)算到對象實(shí)例。放任用戶用lambda來聲明抽象類將妨礙像這樣的優(yōu)化。
此外,有一個(gè)簡單地解決方法。事實(shí)上,上述兩個(gè)摘自Guava 庫的實(shí)例類已經(jīng)證明了這種方法。增加工廠方法將lambda轉(zhuǎn)換成實(shí)例。
Ordering<String> order = Ordering.from((a, b) -> ...); CacheLoader<String, String> loader = CacheLoader.from((key) -> ...);
要深入閱讀,請參看由 Brian Goetz所做的說明: response to “Allow lambdas to implement abstract classes”。
java.util.function
包概要:java.util.function
作為Comparator 和Runnable早期的證明,在JDK中已經(jīng)定義的接口恰巧作為函數(shù)接口而與lambdas表達(dá)式兼容。同樣方式可以在你自己的代碼中定義任何函數(shù)接口或第三方庫。
但有特定形式的函數(shù)接口,且廣泛的,通用的,在之前的JD卡中并不存在。大量的接口被添加到新的java.util.function 包中。下面是其中的一些:
Function<T, R> -T作為輸入,返回的R作為輸出
Predicate<T> -T作為輸入,返回的boolean值作為輸出
Consumer<T> - T作為輸入,執(zhí)行某種動(dòng)作但沒有返回值
Supplier<T> - 沒有任何輸入,返回T
BinaryOperator<T> -兩個(gè)T作為輸入,返回一個(gè)T作為輸出,對于“reduce”操作很有用
這些最原始的特征同樣存在。他們以int,long和double的方式提供。例如:
IntConsumer -以int作為輸入,執(zhí)行某種動(dòng)作,沒有返回值
這里存在性能上的一些原因,主要釋在輸入或輸出的時(shí)候避免裝箱和拆箱操作。
java.util.stream
包匯總: java.util.stream
新的java.util.stream包提供了“支持在流上的函數(shù)式風(fēng)格的值操作”(引用javadoc)的工具。可能活動(dòng)一個(gè)流的最常見方法是從一個(gè)collection獲?。?/p>
Stream<T> stream = collection.stream();
一個(gè)流就像一個(gè)地帶器。這些值“流過”(模擬水流)然后他們離開。一個(gè)流可以只被遍歷一次,然后被丟棄。流也可以無限使用。
流能夠是 串行的 或者 并行的。 它們可以使用其中一種方式開始,然后切換到另外的一種方式,使用stream.sequential()或stream.parallel()來達(dá)到這種切換。串行流在一個(gè)線程上連續(xù)操作。而并行流就可能一次出現(xiàn)在多個(gè)線程上。
所以,你想用一個(gè)流來干什么?這里是在javadoc包里給出的例子:
int sumOfWeights = blocks.stream().filter(b -> b.getColor() == RED) .mapToInt(b -> b.getWeight()) .sum();
注意:上面的代碼使用了一個(gè)原始的流,以及一個(gè)只能用在原始流上的sum()方法。下面馬上就會(huì)有更多關(guān)于原始流的細(xì)節(jié)。
流提供了流暢的API,可以進(jìn)行數(shù)據(jù)轉(zhuǎn)換和對結(jié)果執(zhí)行某些操作。流操作既可以是“中間的”也可以是“末端的”。
中間的 -中間的操作保持流打開狀態(tài),并允許后續(xù)的操作。上面例子中的filter和map方法就是中間的操作。這些操作的返回?cái)?shù)據(jù)類型是流;它們返回當(dāng)前的流以便串聯(lián)更多的操作。
末端的 - 末端的操作必須是對流的最終操作。當(dāng)一個(gè)末端操作被調(diào)用,流被“消耗”并且不再可用。上面例子中的sum方法就是一個(gè)末端的操作。
通常,處理一個(gè)流涉及了這些步驟:
從某個(gè)源頭獲得一個(gè)流。
執(zhí)行一個(gè)或更多的中間的操作。
執(zhí)行一個(gè)末端的操作。
可能你想在一個(gè)方法中執(zhí)行所有那些步驟。那樣的話,你就要知道源頭和流的屬性,而且要可以保證它被正確的使用。你可能不想接受任意的Stream<T>實(shí)例作為你的方法的輸入,因?yàn)樗鼈兛赡芫哂心汶y以處理的特性,比如并行的或無限的。
有幾個(gè)更普通的關(guān)于流操作的特性需要考慮:
有狀態(tài)的 - 有狀態(tài)的操作給流增加了一些新的屬性,比如元素的唯一性,或者元素的最大數(shù)量,或者保證元素以排序的方式被處理。這些典型的要比無狀態(tài)的中間操作代價(jià)大。
短路 - 短路操作潛在的允許對流的操作盡早停止,而不去檢查所有的元素。這是對無限流的一個(gè)特殊設(shè)計(jì)的屬性;如果對流的操作沒有短路,那么代碼可能永遠(yuǎn)也不會(huì)終止。
對每個(gè)Sttream方法這里有一些簡短的,一般的描述。查閱javadoc獲取更詳盡的解釋。下面給出了每個(gè)操作的重載形式的鏈接。
中間的操作:
filter 1 - 排除所有與斷言不匹配的元素。
map 1 2 3 4 - 通過Function對元素執(zhí)行一對一的轉(zhuǎn)換。
flatMap 1 2 3 4 5 - 通過FlatMapper將每個(gè)元素轉(zhuǎn)變?yōu)闊o或更多的元素。
peek 1 - 對每個(gè)遇到的元素執(zhí)行一些操作。主要對調(diào)試很有用。
distinct 1 - 根據(jù).equals行為排除所有重復(fù)的元素。這是一個(gè)有狀態(tài)的操作。
sorted 1 2 - 確保流中的元素在后續(xù)的操作中,按照比較器(Comparator)決定的順序訪問。這是一個(gè)有狀態(tài)的操作。
limit 1 - 保證后續(xù)的操作所能看到的最大數(shù)量的元素。這是一個(gè)有狀態(tài)的短路的操作。
substream 1 2 - 確保后續(xù)的操作只能看到一個(gè)范圍的(根據(jù)index)元素。像不能用于流的String.substring一樣。也有兩種形式,一種有一個(gè)開始索引,一種有一個(gè)結(jié)束索引。二者都是有狀態(tài)的操作,有一個(gè)結(jié)束索引的形式也是一個(gè)短路的操作。
末端的操作:
forEach 1 - 對流中的每個(gè)元素執(zhí)行一些操作。
toArray 1 2 - 將流中的元素傾倒入一個(gè)數(shù)組。
reduce 1 2 3 - 通過一個(gè)二進(jìn)制操作將流中的元素合并到一起。
collect 1 2 - 將流中的元素傾倒入某些容器,例如一個(gè)Collection或Map.
min 1 - 根據(jù)一個(gè)比較器找到流中元素的最小值。
max 1 -根據(jù)一個(gè)比較器找到流中元素的最大值。
count 1 - 計(jì)算流中元素的數(shù)量。
anyMatch 1 - 判斷流中是否至少有一個(gè)元素匹配斷言。這是一個(gè)短路的操作。
allMatch 1 - 判斷流中是否每一個(gè)元素都匹配斷言。這是一個(gè)短路的操作。
noneMatch 1 - 判斷流中是否沒有一個(gè)元素匹配斷言。這是一個(gè)短路的操作。
findFirst 1 - 查找流中的第一個(gè)元素。這是一個(gè)短路的操作。
findAny 1 - 查找流中的任意元素,可能對某些流要比findFirst代價(jià)低。這是一個(gè)短路的操作。
如 javadocs中提到的 , 中間的操作是延遲的(lazy)。只有末端的操作會(huì)立即開始流中元素的處理。在那個(gè)時(shí)刻,不管包含了多少中間的操作,元素會(huì)在一個(gè)傳遞中處理(通常,但并不總是)。(有狀態(tài)的操作如sorted() 和distinct()可能需要對元素的二次傳送。)
流試圖盡可能做很少的工作。有一些細(xì)微優(yōu)化,如當(dāng)可以判定元素已經(jīng)有序的時(shí)候,省略一個(gè)sorted()操作。在包含limit(x) 或 substream(x,y)的操作中,有些時(shí)候?qū)σ恍┎粫?huì)決定結(jié)果的元素,流可以避免執(zhí)行中間的map操作。在這里我不準(zhǔn)備實(shí)現(xiàn)公平判斷;它通過許多細(xì)微的但卻很重要的方法表現(xiàn)得很聰明,而且它仍在進(jìn)步。
回到并行流的概念,重要的是要意識到并行不是毫無代價(jià)的。從性能的立場它不是無代價(jià)的,你不能簡單的將順序流替換為并行流,且不做進(jìn)一步思考就期望得到相同的結(jié)果。在你能(或者應(yīng)該)并行化一個(gè)流以前,需要考慮很多特性,關(guān)于流、它的操作以及數(shù)據(jù)的目標(biāo)方面。例如:訪問順序確實(shí)對我有影響嗎?我的函數(shù)是無狀態(tài)的嗎?我的流有足夠大,并且我的操作有足夠復(fù)雜,這些能使得并行化是值得的嗎?
有針對int,long和double的專業(yè)原始的Stream版本:
IntStream
LongStream
DoubleStream
可以在眾多函數(shù)中,通過專業(yè)原始的map和flatMap函數(shù),在一個(gè)stream對象與一個(gè)原始stream對象之間來回轉(zhuǎn)換。給幾個(gè)虛設(shè)例子:
List<String> strings = Arrays.asList("a", "b", "c"); strings.stream() // Stream<String> .mapToInt(String::length) // IntStream .longs() // LongStream .mapToDouble(x -> x / 10.0) // DoubleStream .boxed() // Stream<Double> .mapToLong(x -> 1L) // LongStream .mapToObj(x -> "") // Stream<String> ...
原始的stream也為獲得關(guān)于stream的基礎(chǔ)數(shù)據(jù)統(tǒng)計(jì)提供方法,那些stream是指作為數(shù)據(jù)結(jié)構(gòu)的。你可以發(fā)現(xiàn)count, sum, min, max, 以及元素平均值全部是來自于一個(gè)終端的操作。
原始類型的剩余部分沒有原始版本,因?yàn)檫@需要一個(gè)不可接受的JDK數(shù)量的膨脹。IntStream, LongStream, 和 DoubleStream被認(rèn)為非常有用應(yīng)當(dāng)被包含進(jìn)去,其他的數(shù)字型原始stream可以由這三個(gè)通過擴(kuò)展的原始轉(zhuǎn)換來表示。
在flatMap操作中使用的 FlatMapper 接口是具有一個(gè)抽象方法的功能性接口:
void flattenInto(T element, Consumer<U> sink);
在一個(gè)flatMap操作的上下文中,stream為你提供element和 sink,然后你定義該用element 和 sink做什么。element是指在stream中的當(dāng)前元素,而sink代表當(dāng)flatMap操作結(jié)束之后在stream中應(yīng)該顯示些什么。例如:
Set<Color> colors = ...; List<Person> people = ...; Stream<Color> stream = people.stream().flatMap( (Person person, Consumer<Color> sink) -> { // Map each person to the colors they like. for (Color color : colors) { if (person.likesColor(color)) { sink.accept(color); } } });
注意上面lambda中的參數(shù)類型是指定的。在大多數(shù)其它上下文中,你可以不需要指定類型,但這里由于FlatMapper的自然特性,編譯器需要你幫助判定類型。如果你在使用flatMap又迷惑于它為什么不編譯,可能是因?yàn)槟銢]有指定類型。
最令人感到困惑,復(fù)雜而且有用的終端stream操作之一是collect。它引入了一個(gè)稱為Collector的新的非功能性接口。這個(gè)接口有些難理解,但幸運(yùn)的是有一個(gè)Collectors工具類可用來產(chǎn)生所有類型的有用的Collectors。例如:
List<String> strings = values.stream() .filter(...) .map(...) .collect(Collectors.toList());
如果你想將你的stream元素放進(jìn)一個(gè)Collection,Map或String,那么Collectors可能具有你需要的。在javadoc中瀏覽那個(gè)類絕對是值得的。
泛型接口改進(jìn)
建議摘要:JEP 101: 通用化目標(biāo)-Type 接口
這是一個(gè)以前不能做到的,對編譯器判定泛型能力的努力改進(jìn)。在以前版本的Java中有許多情形編譯器不能給某個(gè)方法計(jì)算出泛型,當(dāng)方法處于嵌套的或串聯(lián)方法調(diào)用這樣的上下文的時(shí)候,即使有時(shí)候?qū)Τ绦騿T來說它看起來“很明顯”。那些情況需要程序員明確的指定一個(gè)“類型見證”(type witness)。它是一種通用的特性,但吃驚的是很少有Java程序員知道(我這么說是基于私下的交流并且閱讀了一些StackOverflow的問題)。它看起來像這樣:
// In Java 7: foo(Utility.<Type>bar()); Utility.<Type>foo().bar();
如果沒有類型見證,編譯器可能會(huì)將<Object>替代為泛型,而且如果需要的是一個(gè)更具體的類型,代碼將編譯失敗。
Java 8 極大的改進(jìn)了這個(gè)狀況。在更多的案例中,它可以基于上下文計(jì)算出更多的特定的泛型類型。
// In Java 8: foo(Utility.bar()); Utility.foo().bar();
這項(xiàng)工作仍在發(fā)展中,所以我不確定建議中列出的例子有多少能真正包含進(jìn)Java 8。希望是它們?nèi)俊?/p>
java.time
包概要: java.time
在Java8中新的 date/timeAPI存在于 java.time包中。如果你熟悉Joda Time,它將很容易掌握。事實(shí)上,我認(rèn)為如此好的設(shè)計(jì),以至于從未聽說過 Joda Time的人也能很容易的掌握。
幾乎在API中的任何東西都是永恒的,包括值類型和格式化 。對于Date域或者處理或處理本地線程日期格式化不必太過擔(dān)心。
與傳統(tǒng)的date/timeAPI的交叉是最小的。有一個(gè)清晰的分段:
Date.toInstant()
Date.from(Instant)
Calendar.toInstant()
新API對于像月份和每周的天數(shù),喜歡枚舉類型更勝過整形常量。
那么,那是什么呢?包級別的javadocs 對額外類型的做出了非常好的闡述。我將對一些值得注意的部分做一些簡短的綱要。
非常有用的值類型:
Instant - 與java.util.Date相似
ZonedDateTime, ZoneId -時(shí)區(qū)很重要的時(shí)候使用
OffsetDateTime, OffsetTime, ZoneOffset -對UTC的偏移處理
Duration, Period - 但如果你想找到兩個(gè)日期之間的時(shí)間量,你可能會(huì)尋找ChronoUnit代替(見下文)
其他有用的類型:
DateTimeFormatter - 將日期類型轉(zhuǎn)換成字符串類型
ChronoUnit - 計(jì)算出兩點(diǎn)之間的時(shí)間量,例如ChronoUnit.DAYS.between(t1, t2)
TemporalAdjuster - 例如date.with(TemporalAdjuster.firstDayOfMonth())
大多數(shù)情況下,新的值類型由JDBC提供支持。有一小部分異常,如ZonedDateTime在SQL中沒有對應(yīng)的(類型)。
集合API附件
實(shí)際上接口能夠定義默認(rèn)方法允許了JDK作者加入大量的附件到集合API接口中。默認(rèn)實(shí)現(xiàn)在核心接口里提供,而其他更有效或更好的重載實(shí)現(xiàn)被加入到可適用的具體類中。
這里是新方法的列表:
Iterable.forEach(Consumer)
Iterator.forEach(Consumer)
Collection.removeAll(Predicate)
Collection.spliterator()
Collection.stream()
Collection.parallelStream()
List.sort(Comparator)
Map.forEach(BiConsumer)
Map.replaceAll(BiFunction)
Map.putIfAbsent(K, V)
Map.remove(Object, Object)
Map.replace(K, V, V)
Map.replace(K, V)
Map.computeIfAbsent(K, Function)
Map.computeIfPresent(K, BiFunction)
Map.compute(K, BiFunction)
Map.merge(K, V, BiFunction)
Map.getOrDefault(Object, V)
同樣, Iterator.remove() 現(xiàn)在有一個(gè)默認(rèn)的, 會(huì)拋出異常的實(shí)現(xiàn),使得它稍微容易地去定義不可修改的迭代器。
Collection.stream()和Collection.parallelStream()是流API的主要門戶。有其他方法去生成流,但這些在以后會(huì)更為長用。
List.sort(Comparator)的附件有點(diǎn)怪異。以前排序一個(gè)ArrayList的方法是:
Collections.sort(list, comparator);
這代碼是你在Java7里唯一可選的,非常低效。它會(huì)復(fù)制list到一個(gè)數(shù)組里,排序這個(gè)數(shù)組,然后使用ListIterator來把數(shù)組插入到新list的新位置上。
List.sort(比較器)的默認(rèn)實(shí)現(xiàn)仍然會(huì)做這個(gè),但是具體的實(shí)現(xiàn)類可以自由的優(yōu)化。例如,ArrayList.sort在ArrayList內(nèi)部數(shù)組上調(diào)用了Arrays.sort。CopyOnWriteArrayList做了同樣的事情。
從這些新方法中獲得的不僅僅是性能。它們也具有更多的令人滿意的語義。例如, 對Collections.synchronizedList()排序是一個(gè)使用了list.sort的原子操作。你可以使用list.forEach對它的所有元素進(jìn)行迭代,使之成為原子操作。
Map.computeIfAbsent使得操作類似多重映射的結(jié)構(gòu)變得容易了:
// Index strings by length: Map<Integer, List<String>> map = new HashMap<>(); for (String s : strings) { map.computeIfAbsent(s.length(), key -> new ArrayList<String>()) .add(s); } // Although in this case the stream API may be a better choice: Map<Integer, List<String>> map = strings.stream() .collect(Collectors.groupingBy(String::length));
增加并發(fā)API
ForkJoinPool.commonPool()
ConcurrentHashMap(v8)
下面的形式有并行,順序,對象,整型,長整型和double型。
有太多的鏈接可以點(diǎn)擊,因此參看ConcurrentHashMap javadocs文檔以獲得更多信息。
ConcurrentHashMap.reduce…
ConcurrentHashMap.search…
ConcurrentHashMap.forEach…
ConcurrentHashMap.newKeySet()
ConcurrentHashMap.newKeySet(int)
CompletableFuture
StampedLock
LongAdder
LongAccumulator
DoubleAdder
DoubleAccumulator
CountedCompleter
Executors.newWorkStealingPool()
Executors.newWorkStealingPool(int)
下面的形式有AtomicReference, AtomicInteger, AtomicLong, 和每一個(gè)原子數(shù)組的版本。
AtomicReference.getAndUpdate(UnaryOperator)
AtomicReference.updateAndGet(UnaryOperator)
AtomicReference.getAndAccumulate(V, UnaryOperator)
AtomicReference.accumulateAndGet(V, UnaryOperator)
ForkJoinPool.commonPool()是處理所有并行流操作的結(jié)構(gòu)。當(dāng)你 需要的時(shí)候,它是一個(gè)好而簡單的方式去獲得一個(gè) ForkJoinPool/ExecutorService/Executor對象。ConcurrentHashMap<K, V>完全重寫。內(nèi)部看起來它一點(diǎn)不像是Java7版本。從外部來看幾乎相同,除了它有大量批量操作方法:多種形式的減少搜索和forEach。
ConcurrentHashMap.newKeySet()提供了一個(gè)并發(fā)的java.util.Set實(shí)現(xiàn)。它基本上是 Collections.newSetFromMap(new ConcurrentHashMap<T, Boolean>())的另一種方式的重寫。
StampedLock是一種新型鎖的實(shí)現(xiàn),很可能在大多數(shù)場景都可以替代ReentrantReadWriteLock。當(dāng)作為一個(gè)簡單的讀寫鎖的時(shí)候,它比RRWL的性能要好。它也為“讀優(yōu)化”提供了API,通過它你獲得了一個(gè)功能有點(diǎn)弱,但代價(jià)很小的讀操作鎖的版本,執(zhí)行讀操作,然后檢查鎖是否被一個(gè)寫操作設(shè)定為無效。在Heinz Kabutz匯總的一系列幻燈片中,有更多關(guān)于這個(gè)類及其性能的細(xì)節(jié)(在這個(gè)系列幻燈片大約一半的地方開始的):“移相器和StampedLock演示“
CompletableFuture<T>是Future接口的一個(gè)非常棒的實(shí)現(xiàn),它提供了無數(shù)執(zhí)行(和串接)異步任務(wù)的方法。它特別依賴功能性的接口;lambdas是值得增加這個(gè)類的一個(gè)重要原因。如果你正在使用Guava的 Future工具,例如Futures, ListenableFuture, 和 SettableFuture,那么你可能會(huì)希望校驗(yàn)CompletableFuture能否作為潛在的替代選擇。
IO/NIO API的新增內(nèi)容
BufferedReader.lines()
Files.list(Path)
Files.walk(Path, int, FileVisitOption…)
Files.walk(Path, FileVisitOption…)
Files.find(Path, int, BiPredicate, FileVisitOption…)
Files.lines(Path, Charset)
DirectoryStream.entries()
簡單的說,這些API用于從文件和InputStreams獲取java.util.stream.Stream對象。不過它們與直接從常規(guī)的collection得到的流有些不同,它們引入了兩個(gè)概念:
UncheckedIOException - 當(dāng)有IO錯(cuò)誤時(shí)拋出這個(gè)異常,不過由于Iterator/Stream的簽名中不允許有IOException,所以它只能借助于unchecked異常。
CloseableStream - 可以(并且應(yīng)該)定義在 try-with-resources 語句里面的流。
反射和annotation的改動(dòng)
類型annotation (JSR 308)
AnnotatedType
Repeatable
Method.getAnnotatedReturnType()
Field.getAnnotationsByType(Class)
Field.getAnnotatedType()
Constructor.getAnnotatedReturnType()
Parameter - 支持 parameter.getName(),等等。
Annotation允許在更多的地方被使用,例如List<@Nullable String>。受此影響最大的可能是那些靜態(tài)分析工具,如Sonar和FindBugs。
JSR 308的網(wǎng)站解釋了增加這些改動(dòng)的動(dòng)機(jī),介紹的不錯(cuò): “類型Annotation (JSR 308) 和 Checker框架”
Nashorn JavaScript 引擎
提案的摘要: JEP 174: Nashorn JavaScript 引擎
我對Nashorn沒什么經(jīng)驗(yàn),因而我對上面提案所描述的所知甚少。簡單的說,它是 Rhino 的接替者。Rhino 顯得有些老了,并且有點(diǎn)慢,開發(fā)者決定最好還是從頭做一個(gè)。
其他新增,涉及java.lang,java.util,和java.sql
ThreadLocal.withInitial(Supplier)
String.join(CharSequence, Charsequence…)
String.join(CharSequence, Iterable)
下面的方法適用于所有數(shù)字的原語類型,并且作為這些類型的包裝(wrapper)類的三個(gè)方法。hashCode方法除外,它們的作用是作為BinaryOperatorin的reduce操作的參數(shù)。關(guān)于這些方法還有很多的鏈接,更多的內(nèi)容,參考 Integer, Long, Double, Float, Byte, Short, 和 Character 的javadoc。
Primitive.min(primitive, primitive);
Primitive.max(primitive, primitive);
Primitive.sum(primitive, primitive);
Primitive.hashCode(primitive)
同樣,下面新增的 Boolean 的方法可用于BinaryOperator<Boolean>:
Boolean.logicalAnd(boolean, boolean)
Boolean.logicalOr(boolean, boolean)
Boolean.logicalXor(boolean, boolean)
Optional
OptionalInt
OptionalLong
OptionalDouble
Base64
StringJoiner
Spliterator
Spliterators
Comparator.reverseOrder()
Comparator.thenComparing(Comparator)
Comparator.thenComparing(Function, Comparator)
Comparator.thenComparing(Function)
Comparator.thenComparing(ToIntFunction)
Comparator.thenComparing(ToLongFunction)
Comparator.thenComparing(ToDoubleFunction)
Comparators
下面的方法適用于數(shù)組,支持T[], int[], long[], double[]。關(guān)于這些方法有很多鏈接,更多信息參考 Arrays 的javadoc。
Arrays.spliterator(array)
Arrays.spliterator(array, int, int)
Arrays.stream(array)
Arrays.stream(array, int, int);
Arrays.parallelStream(array)
Arrays.parallelStream(array, int, int);
Arrays.setAll(array, IntFunction)
Arrays.parallelSetAll(array, IntFunction)
Math.toIntExact(long)
Math.addExact(int, int)
Math.subtractExact(int, int)
Math.multiplyExact(int, int)
Math.floorDiv(int, int)
Math.floorMod(int, int)
Math.addExact(long, long)
Math.subtractExact(long, long)
Math.multiplyExact(long, long)
Math.floorDiv(long, long)
Math.floorMod(long, long)
Integer.toUnsignedLong(int)
Integer.toUnsignedString(int)
Integer.toUnsignedString(int, int)
Integer.parseUnsignedInt(String)
Integer.parseUnsignedInt(String, int)
Integer.compareUnsigned(int, int)
Long.toUnsignedString(long, int)
Long.toUnsignedString(long)
Long.parseUnsignedLong(String, int)
Long.parseUnsignedLong(String)
Long.compareUnsigned(long, long)
Long.divideUnsigned(long, long)
Long.remainderUnsigned(long, long)
BigInteger.longValueExact()
BigInteger.intValueExact()
BigInteger.shortValueExact()
BigInteger.byteValueExact()
Objects.isNull(Object) - 可用作謂詞,例如stream.anyMatch(Objects::isNull)
Objects.nonNull(Object) - 可用作謂詞,stream.filter(Objects::nonNull)
Random.ints()
Random.longs()
Random.doubles()
Random.gaussians()
BitSet.stream()
IntSummaryStatistics
LongSummaryStatistics
DoubleSummaryStatistics
Logger的雜項(xiàng)新增
Locale的雜項(xiàng)新增
ResultSet的雜項(xiàng)新增
這里可以介紹的太多了,只能挑一些最需要注意的項(xiàng)目。
ThreadLocal.withInitial(Supplier<T>) 可以在定義thread-local變量時(shí)更好的進(jìn)行初始化。之前你初始化變量時(shí)是這樣的:
ThreadLocal<List<String>> strings = new ThreadLocal<List<String>>() { @Override protected List<String> initialValue() { return new ArrayList<>(); } };
現(xiàn)在則是這樣:
ThreadLocal<List<String>> strings = ThreadLocal.withInital(ArrayList::new);
stream的API的返回值有一個(gè)可選的<T>,就像min/max, findFirst/Any, 以及reduce的某些形式。這樣做是因?yàn)閟tream中可能沒有任何元素,但是它要提供一個(gè)一致的API,既可以處理“一些結(jié)果”,也可以處理“沒有結(jié)果”。你可以提供默認(rèn)值,拋異常,或者只在有結(jié)果的時(shí)候執(zhí)行一些動(dòng)作。
它與Guava’s Optional類非常非常的相似。它一點(diǎn)都不像是在Scala里的操作,也不會(huì)試圖成為之一,有相似的地方純屬巧合。
旁白:Java 8′s Optional和Guava’s Optional最終如此的相似是很有趣的事,盡管荒謬的辯論發(fā)生在這兩個(gè)庫。
“FYI…. Optional was the cause of possibly the single greatest conflagration on the internal Java libraries discussion lists ever.”
Kevin Bourrillion在 response to “Some new Guava classes targeted for release 10″如實(shí)寫到:
“On a purely practical note, the discussions surrounding Optional have exceeded its design budget by several orders of magnitude.”
Brian Goetz 在 response to “Optional require(s) NonNull”寫到。
StringJoinerandString.join(…)來得太晚了。他們來得如此之晚以至于大多數(shù)Java開發(fā)者已經(jīng)為字符串聯(lián)合編寫或發(fā)現(xiàn)了有用的工具,但這對JDK本身來說很每秒,因?yàn)樽罱K自己實(shí)現(xiàn)這一點(diǎn)。每個(gè)人都會(huì)遇到要求字符串連接的情形,我們現(xiàn)在能夠通過每個(gè)Java開發(fā)者(事實(shí)上的)即將知道的標(biāo)準(zhǔn)的API來闡明,這也算是一件好事。
ComparatorsandComparator.thenComparing(…)提供了非常優(yōu)秀的工具,基于鏈的比較和基于域的比較。像這樣:
people.sort(Comparators.comparing(Person::getLastName) .thenComparing(Person::getFirstName));
這些新增功能提供了良好的,復(fù)雜的各種可讀的簡寫。大多數(shù)用例由JDK里增加的 ComparisonChain和Ordering工具類來提供服務(wù)。對于什么是有價(jià)值的,我認(rèn)為JDK版本比在Guava-ese里功能等效的版本的可讀性好了很多。
其實(shí),還有很多的小問題的修正和一些性能的改進(jìn)在這篇文章中沒有提及,但是那些也是非常的重要的。
主要是想全面的介紹到Java 8的每個(gè)語言層面和API層面的改進(jìn)。
看完上述內(nèi)容,你們掌握J(rèn)ava 8的新特性以及改進(jìn)有哪些的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。