溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Java 8的新特性以及改進(jìn)有哪些

發(fā)布時(shí)間:2021-10-29 17:01:50 來源:億速云 閱讀:127 作者:柒染 欄目:編程語言

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è)流涉及了這些步驟:

  1. 從某個(gè)源頭獲得一個(gè)流。

  2. 執(zhí)行一個(gè)或更多的中間的操作。

  3. 執(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&hellip;

  • ConcurrentHashMap.search&hellip;

  • ConcurrentHashMap.forEach&hellip;

  • 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&hellip;)

  • Files.walk(Path, FileVisitOption&hellip;)

  • Files.find(Path, int, BiPredicate, FileVisitOption&hellip;)

  • 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&hellip;)

  • 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&rsquo;s Optional類非常非常的相似。它一點(diǎn)都不像是在Scala里的操作,也不會(huì)試圖成為之一,有相似的地方純屬巧合。

旁白:Java 8&prime;s Optional和Guava&rsquo;s Optional最終如此的相似是很有趣的事,盡管荒謬的辯論發(fā)生在這兩個(gè)庫。

“FYI&hellip;. 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&Prime;如實(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(&hellip;)來得太晚了。他們來得如此之晚以至于大多數(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(&hellip;)提供了非常優(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è)資訊頻道,感謝各位的閱讀!

向AI問一下細(xì)節(jié)

免責(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)容。

AI