您好,登錄后才能下訂單哦!
Java 8的新特性以及改進有哪些,相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。
這篇文章是對Java 8中即將到來的改進做一個面向開發者的綜合性的總結,JDK的這一特性將會在2013年9月份發布。
在寫這篇文章的時候,Java 8的開發工作仍然在緊張有序的進行中,語言特新和API仍然有可能改變,我會盡我最大的努力保持這份文檔跟得到Java 8的改動。
Java 8的預覽版,也就是 “Project Lambda”,現在可以從java.net下載到。
我使用了IntelliJ的預覽版做我的IDE,在我看來他是目前支持java 8特性最好的一個IDE,你可以從這里下載到.
由于我沒有找到Oracle發布的Java 8的官方文檔,所以目前Java 8的文檔還只有本地版本,等Oracle公開文檔的時候,我將會重新鏈接到官方文檔。
接口改善
現在接口里已經完全可以定義靜態方法了. 舉一個比較普遍的例子就是在java類庫中, 對于一些接口如Foo, 都會有一個有靜態方法的工具類Foos 來生成或者配合Foo對象實例來使用. 既然靜態方法可以存在于接口當中, 那么大多數情況下 Foos工具類完全可以使用接口中的公共方法來代理 (或者將Foos置成package-private).
除此之外更重要的就是, Java 8中接口可以定義默認的方法了.舉個例子,一個for-each循環的方法就可以加入到java.lang.Iterable中:
public default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } }
在過去,java類庫的接口中添加方法基本上是不可能的. 在接口中添加方法意味著破壞了實現了這個接口的代碼. 但是現在, 只要能夠提供一個正確明智的默認的方法的實現, java類庫的維護者就可以在接口中添加方法.
Java 8中, 大量的默認方法已經被添加到核心的JDK接口中了. 稍候我會詳細介紹它們.
為什么不能用默認方法來重載equals,hashCode和toString?
接口不能提供對Object類的任何方法的默認實現。特別是,這意味著從接口里不能提供對equals,hashCode或toString的默認實現。
這剛看起來挺奇怪的,但考慮到一些接口實際上是在文檔里定義他們的equals行為的。List接口就是一個例子了。因此,為什么不允許這樣呢?
Brian Goetz在這個問題上的冗長的回復里給出了4個原因。我這里只說其中一個,因為那個已經足夠說服我了:
它會變得更困難來推導什么時候該調用默認的方法。現在它變得很簡單了:如果一個類實現了一個方法,那總是優先于默認的實現的。一旦所有接口的實例都是Object的子類,所有接口實例都已經有對equals/hashCode/toString的非默認實現。因此,一個在接口上這些的默認版本都是沒用的,它也不會被編譯。
要看更多的話,看下由Brian Goetz寫的解釋: 對“允許默認方法來重載Object的方法”的回復
函數式接口
Java 8 引入的一個核心概念是函數式接口。如果一個接口定義個唯一一個抽象方法,那么這個接口就成為函數式接口。比如,java.lang.Runnable就是一個函數式接口,因為它只頂一個一個抽象方法:
public abstract void run();
留意到“abstract”修飾詞在這里是隱含的,因為這個方法缺少方法體。為了表示一個函數式接口,并非想這段代碼一樣一定需要“abstract”關鍵字。
默認方法不是abstract的,所以一個函數式接口里可以定義任意多的默認方法,這取決于你。
同時,引入了一個新的Annotation:@FunctionalInterface。可以把他它放在一個接口前,表示這個接口是一個函數式接口。加上它的接口不會被編譯,除非你設法把它變成一個函數式接口。它有點像@Override,都是聲明了一種使用意圖,避免你把它用錯。
Lambdas
一個函數式接口非常有價值的屬性就是他們能夠用lambdas來實例化。這里有一些lambdas的例子:
左邊是指定類型的逗號分割的輸入列表,右邊是帶有return的代碼塊:
(int x, int y) -> { return x + y; }
左邊是推導類型的逗號分割的輸入列表,右邊是返回值:
(x, y) -> x + y
左邊是推導類型的單一參數,右邊是一個返回值:
x -> x * x
左邊沒有輸入 (官方名稱: “burger arrow”),在右邊返回一個值:
() -> x
左邊是推導類型的單一參數,右邊是沒返回值的代碼塊(返回void):
x -> { System.out.println(x); }
靜態方法引用:
String::valueOf
非靜態方法引用:
Object::toString
繼承的函數引用:
x::toString
構造函數引用:
ArrayList::new
你可以想出一些函數引用格式作為其他lambda格式的簡寫。
方法引用 | 等價的lambda表達式 | |
---|---|---|
String::valueOf | x -> String.valueOf(x) | |
Object::toString | x -> x.toString() | |
x::toString | () -> x.toString() | |
ArrayList::new | () -> new ArrayList<>() |
當然,在Java里方法能被重載。類可以有多個同名但不同參數的方法。這同樣對構造方法有效。ArrayList::new能夠指向它的3個構造方法中任何一個。決定使用哪個方法是根據在使用的函數式接口。
一個lambda和給定的函數式接口在“外型”匹配的時候兼容。通過“外型”,我指向輸入、輸出的類型和聲明檢查異常。
給出兩個具體有效的例子:
Comparator<String> c = (a, b) -> Integer.compare(a.length(), b.length());
一個Comparator<String>的compare方法需要輸入兩個闡述,然后返回一個int。這和lambda右側的一致,因此這個任務是有效的。
Runnable r = () -> { System.out.println("Running!"); }
一個Runnable的run方法不需要參數也不會返回值。這和lambda右側一致,所以任務有效。
在抽象方法的簽名里的受檢查異常(如果存在)也很重要。如果函數式接口在它的簽名里聲明了異常,lambda只能拋出受檢查異常。
捕獲和非捕獲的Lambda表達式
當Lambda表達式訪問一個定義在Lambda表達式體外的非靜態變量或者對象時,這個Lambda表達式稱為“捕獲的”。比如,下面這個lambda表達式捕捉了變量x:
int x = 5; return y -> x + y;
為了保證這個lambda表達式聲明是正確的,被它捕獲的變量必須是“有效final”的。所以要么它們需要用final修飾符號標記,要么保證它們在賦值后不能被改變。
Lambda表達式是否是捕獲的和性能悄然相關。一個非不捕獲的lambda通常比捕獲的更高效,雖然這一點沒有書面的規范說明(據我所知),而且也不能為了程序的正確性指望它做什么,非捕獲的lambda只需要計算一次. 然后每次使用到它都會返回一個唯一的實例。而捕獲的lambda表達式每次使用時都需要重新計算一次,而且從目前實現來看,它很像實例化一個匿名內部類的實例。
lambdas不做的事
你應該記住,有一些lambdas不提供的特性。為了Java 8它們被考慮到了,但是沒有被包括進去,由于簡化以及時間限制的原因。
Non-final* 變量捕獲 - 如果一個變量被賦予新的數值,它將不能被用于lambda之中。”final”關鍵字不是必需的,但變量必須是“有效final”的(前面討論過)。這個代碼不會被編譯:
int count = 0; List<String> strings = Arrays.asList("a", "b", "c"); strings.forEach(s -> { count++; // error: can't modify the value of count });
例外的透明度 - 如果一個已檢測的例外可能從lambda內部拋出,功能性的接口也必須聲明已檢測例外可以被拋出。這種例外不會散布到其包含的方法。這個代碼不會被編譯:
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 }); }
有繞過這個的辦法,你能定義自己的功能性接口,擴展Consumer的同時通過像RuntimeException之類拋出 IOException。我試圖用代碼寫出來,但發現它令人困惑是否值得。
控制流程 (break, early return) -在上面的 forEach例子中,傳統的繼續方式有可能通過在lambda之內放置 ”return;”來實現。但是,沒有辦法中斷循環或者從lambda中通過包含方法的結果返回一個數值。例如:
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 } }); }
進一步閱讀關于這些問題的資料,看看這篇Brian Goetz寫的說明:在 Block<T>中響應“已驗證例外”
為什么抽象類不能通過利用lambda實例化
抽象類,哪怕只聲明了一個抽象方法,也不能使用lambda來實例化。
下面有兩個類 Ordering 和 CacheLoader的例子,都帶有一個抽象方法,摘自于Guava 庫。那豈不是很高興能夠聲明它們的實例,像這樣使用lambda表達式?
Ordering<String> order = (a, b) -> ...; CacheLoader<String, String> loader = (key) -> ...;
這樣做引發的最常見的爭論就是會增加閱讀lambda的難度。以這種方式實例化一段抽象類將導致隱藏代碼的執行:抽象類的構造方法。
另一個原因是,它拋出了lambda表達式可能的優化。在未來,它可能是這種情況,lambda表達式都不會計算到對象實例。放任用戶用lambda來聲明抽象類將妨礙像這樣的優化。
此外,有一個簡單地解決方法。事實上,上述兩個摘自Guava 庫的實例類已經證明了這種方法。增加工廠方法將lambda轉換成實例。
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中已經定義的接口恰巧作為函數接口而與lambdas表達式兼容。同樣方式可以在你自己的代碼中定義任何函數接口或第三方庫。
但有特定形式的函數接口,且廣泛的,通用的,在之前的JD卡中并不存在。大量的接口被添加到新的java.util.function 包中。下面是其中的一些:
Function<T, R> -T作為輸入,返回的R作為輸出
Predicate<T> -T作為輸入,返回的boolean值作為輸出
Consumer<T> - T作為輸入,執行某種動作但沒有返回值
Supplier<T> - 沒有任何輸入,返回T
BinaryOperator<T> -兩個T作為輸入,返回一個T作為輸出,對于“reduce”操作很有用
這些最原始的特征同樣存在。他們以int,long和double的方式提供。例如:
IntConsumer -以int作為輸入,執行某種動作,沒有返回值
這里存在性能上的一些原因,主要釋在輸入或輸出的時候避免裝箱和拆箱操作。
java.util.stream
包匯總: java.util.stream
新的java.util.stream包提供了“支持在流上的函數式風格的值操作”(引用javadoc)的工具。可能活動一個流的最常見方法是從一個collection獲取:
Stream<T> stream = collection.stream();
一個流就像一個地帶器。這些值“流過”(模擬水流)然后他們離開。一個流可以只被遍歷一次,然后被丟棄。流也可以無限使用。
流能夠是 串行的 或者 并行的。 它們可以使用其中一種方式開始,然后切換到另外的一種方式,使用stream.sequential()或stream.parallel()來達到這種切換。串行流在一個線程上連續操作。而并行流就可能一次出現在多個線程上。
所以,你想用一個流來干什么?這里是在javadoc包里給出的例子:
int sumOfWeights = blocks.stream().filter(b -> b.getColor() == RED) .mapToInt(b -> b.getWeight()) .sum();
注意:上面的代碼使用了一個原始的流,以及一個只能用在原始流上的sum()方法。下面馬上就會有更多關于原始流的細節。
流提供了流暢的API,可以進行數據轉換和對結果執行某些操作。流操作既可以是“中間的”也可以是“末端的”。
中間的 -中間的操作保持流打開狀態,并允許后續的操作。上面例子中的filter和map方法就是中間的操作。這些操作的返回數據類型是流;它們返回當前的流以便串聯更多的操作。
末端的 - 末端的操作必須是對流的最終操作。當一個末端操作被調用,流被“消耗”并且不再可用。上面例子中的sum方法就是一個末端的操作。
通常,處理一個流涉及了這些步驟:
從某個源頭獲得一個流。
執行一個或更多的中間的操作。
執行一個末端的操作。
可能你想在一個方法中執行所有那些步驟。那樣的話,你就要知道源頭和流的屬性,而且要可以保證它被正確的使用。你可能不想接受任意的Stream<T>實例作為你的方法的輸入,因為它們可能具有你難以處理的特性,比如并行的或無限的。
有幾個更普通的關于流操作的特性需要考慮:
有狀態的 - 有狀態的操作給流增加了一些新的屬性,比如元素的唯一性,或者元素的最大數量,或者保證元素以排序的方式被處理。這些典型的要比無狀態的中間操作代價大。
短路 - 短路操作潛在的允許對流的操作盡早停止,而不去檢查所有的元素。這是對無限流的一個特殊設計的屬性;如果對流的操作沒有短路,那么代碼可能永遠也不會終止。
對每個Sttream方法這里有一些簡短的,一般的描述。查閱javadoc獲取更詳盡的解釋。下面給出了每個操作的重載形式的鏈接。
中間的操作:
filter 1 - 排除所有與斷言不匹配的元素。
map 1 2 3 4 - 通過Function對元素執行一對一的轉換。
flatMap 1 2 3 4 5 - 通過FlatMapper將每個元素轉變為無或更多的元素。
peek 1 - 對每個遇到的元素執行一些操作。主要對調試很有用。
distinct 1 - 根據.equals行為排除所有重復的元素。這是一個有狀態的操作。
sorted 1 2 - 確保流中的元素在后續的操作中,按照比較器(Comparator)決定的順序訪問。這是一個有狀態的操作。
limit 1 - 保證后續的操作所能看到的最大數量的元素。這是一個有狀態的短路的操作。
substream 1 2 - 確保后續的操作只能看到一個范圍的(根據index)元素。像不能用于流的String.substring一樣。也有兩種形式,一種有一個開始索引,一種有一個結束索引。二者都是有狀態的操作,有一個結束索引的形式也是一個短路的操作。
末端的操作:
forEach 1 - 對流中的每個元素執行一些操作。
toArray 1 2 - 將流中的元素傾倒入一個數組。
reduce 1 2 3 - 通過一個二進制操作將流中的元素合并到一起。
collect 1 2 - 將流中的元素傾倒入某些容器,例如一個Collection或Map.
min 1 - 根據一個比較器找到流中元素的最小值。
max 1 -根據一個比較器找到流中元素的最大值。
count 1 - 計算流中元素的數量。
anyMatch 1 - 判斷流中是否至少有一個元素匹配斷言。這是一個短路的操作。
allMatch 1 - 判斷流中是否每一個元素都匹配斷言。這是一個短路的操作。
noneMatch 1 - 判斷流中是否沒有一個元素匹配斷言。這是一個短路的操作。
findFirst 1 - 查找流中的第一個元素。這是一個短路的操作。
findAny 1 - 查找流中的任意元素,可能對某些流要比findFirst代價低。這是一個短路的操作。
如 javadocs中提到的 , 中間的操作是延遲的(lazy)。只有末端的操作會立即開始流中元素的處理。在那個時刻,不管包含了多少中間的操作,元素會在一個傳遞中處理(通常,但并不總是)。(有狀態的操作如sorted() 和distinct()可能需要對元素的二次傳送。)
流試圖盡可能做很少的工作。有一些細微優化,如當可以判定元素已經有序的時候,省略一個sorted()操作。在包含limit(x) 或 substream(x,y)的操作中,有些時候對一些不會決定結果的元素,流可以避免執行中間的map操作。在這里我不準備實現公平判斷;它通過許多細微的但卻很重要的方法表現得很聰明,而且它仍在進步。
回到并行流的概念,重要的是要意識到并行不是毫無代價的。從性能的立場它不是無代價的,你不能簡單的將順序流替換為并行流,且不做進一步思考就期望得到相同的結果。在你能(或者應該)并行化一個流以前,需要考慮很多特性,關于流、它的操作以及數據的目標方面。例如:訪問順序確實對我有影響嗎?我的函數是無狀態的嗎?我的流有足夠大,并且我的操作有足夠復雜,這些能使得并行化是值得的嗎?
有針對int,long和double的專業原始的Stream版本:
IntStream
LongStream
DoubleStream
可以在眾多函數中,通過專業原始的map和flatMap函數,在一個stream對象與一個原始stream對象之間來回轉換。給幾個虛設例子:
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也為獲得關于stream的基礎數據統計提供方法,那些stream是指作為數據結構的。你可以發現count, sum, min, max, 以及元素平均值全部是來自于一個終端的操作。
原始類型的剩余部分沒有原始版本,因為這需要一個不可接受的JDK數量的膨脹。IntStream, LongStream, 和 DoubleStream被認為非常有用應當被包含進去,其他的數字型原始stream可以由這三個通過擴展的原始轉換來表示。
在flatMap操作中使用的 FlatMapper 接口是具有一個抽象方法的功能性接口:
void flattenInto(T element, Consumer<U> sink);
在一個flatMap操作的上下文中,stream為你提供element和 sink,然后你定義該用element 和 sink做什么。element是指在stream中的當前元素,而sink代表當flatMap操作結束之后在stream中應該顯示些什么。例如:
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中的參數類型是指定的。在大多數其它上下文中,你可以不需要指定類型,但這里由于FlatMapper的自然特性,編譯器需要你幫助判定類型。如果你在使用flatMap又迷惑于它為什么不編譯,可能是因為你沒有指定類型。
最令人感到困惑,復雜而且有用的終端stream操作之一是collect。它引入了一個稱為Collector的新的非功能性接口。這個接口有些難理解,但幸運的是有一個Collectors工具類可用來產生所有類型的有用的Collectors。例如:
List<String> strings = values.stream() .filter(...) .map(...) .collect(Collectors.toList());
如果你想將你的stream元素放進一個Collection,Map或String,那么Collectors可能具有你需要的。在javadoc中瀏覽那個類絕對是值得的。
泛型接口改進
建議摘要:JEP 101: 通用化目標-Type 接口
這是一個以前不能做到的,對編譯器判定泛型能力的努力改進。在以前版本的Java中有許多情形編譯器不能給某個方法計算出泛型,當方法處于嵌套的或串聯方法調用這樣的上下文的時候,即使有時候對程序員來說它看起來“很明顯”。那些情況需要程序員明確的指定一個“類型見證”(type witness)。它是一種通用的特性,但吃驚的是很少有Java程序員知道(我這么說是基于私下的交流并且閱讀了一些StackOverflow的問題)。它看起來像這樣:
// In Java 7: foo(Utility.<Type>bar()); Utility.<Type>foo().bar();
如果沒有類型見證,編譯器可能會將<Object>替代為泛型,而且如果需要的是一個更具體的類型,代碼將編譯失敗。
Java 8 極大的改進了這個狀況。在更多的案例中,它可以基于上下文計算出更多的特定的泛型類型。
// In Java 8: foo(Utility.bar()); Utility.foo().bar();
這項工作仍在發展中,所以我不確定建議中列出的例子有多少能真正包含進Java 8。希望是它們全部。
java.time
包概要: java.time
在Java8中新的 date/timeAPI存在于 java.time包中。如果你熟悉Joda Time,它將很容易掌握。事實上,我認為如此好的設計,以至于從未聽說過 Joda Time的人也能很容易的掌握。
幾乎在API中的任何東西都是永恒的,包括值類型和格式化 。對于Date域或者處理或處理本地線程日期格式化不必太過擔心。
與傳統的date/timeAPI的交叉是最小的。有一個清晰的分段:
Date.toInstant()
Date.from(Instant)
Calendar.toInstant()
新API對于像月份和每周的天數,喜歡枚舉類型更勝過整形常量。
那么,那是什么呢?包級別的javadocs 對額外類型的做出了非常好的闡述。我將對一些值得注意的部分做一些簡短的綱要。
非常有用的值類型:
Instant - 與java.util.Date相似
ZonedDateTime, ZoneId -時區很重要的時候使用
OffsetDateTime, OffsetTime, ZoneOffset -對UTC的偏移處理
Duration, Period - 但如果你想找到兩個日期之間的時間量,你可能會尋找ChronoUnit代替(見下文)
其他有用的類型:
DateTimeFormatter - 將日期類型轉換成字符串類型
ChronoUnit - 計算出兩點之間的時間量,例如ChronoUnit.DAYS.between(t1, t2)
TemporalAdjuster - 例如date.with(TemporalAdjuster.firstDayOfMonth())
大多數情況下,新的值類型由JDBC提供支持。有一小部分異常,如ZonedDateTime在SQL中沒有對應的(類型)。
集合API附件
實際上接口能夠定義默認方法允許了JDK作者加入大量的附件到集合API接口中。默認實現在核心接口里提供,而其他更有效或更好的重載實現被加入到可適用的具體類中。
這里是新方法的列表:
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() 現在有一個默認的, 會拋出異常的實現,使得它稍微容易地去定義不可修改的迭代器。
Collection.stream()和Collection.parallelStream()是流API的主要門戶。有其他方法去生成流,但這些在以后會更為長用。
List.sort(Comparator)的附件有點怪異。以前排序一個ArrayList的方法是:
Collections.sort(list, comparator);
這代碼是你在Java7里唯一可選的,非常低效。它會復制list到一個數組里,排序這個數組,然后使用ListIterator來把數組插入到新list的新位置上。
List.sort(比較器)的默認實現仍然會做這個,但是具體的實現類可以自由的優化。例如,ArrayList.sort在ArrayList內部數組上調用了Arrays.sort。CopyOnWriteArrayList做了同樣的事情。
從這些新方法中獲得的不僅僅是性能。它們也具有更多的令人滿意的語義。例如, 對Collections.synchronizedList()排序是一個使用了list.sort的原子操作。你可以使用list.forEach對它的所有元素進行迭代,使之成為原子操作。
Map.computeIfAbsent使得操作類似多重映射的結構變得容易了:
// 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));
增加并發API
ForkJoinPool.commonPool()
ConcurrentHashMap(v8)
下面的形式有并行,順序,對象,整型,長整型和double型。
有太多的鏈接可以點擊,因此參看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, 和每一個原子數組的版本。
AtomicReference.getAndUpdate(UnaryOperator)
AtomicReference.updateAndGet(UnaryOperator)
AtomicReference.getAndAccumulate(V, UnaryOperator)
AtomicReference.accumulateAndGet(V, UnaryOperator)
ForkJoinPool.commonPool()是處理所有并行流操作的結構。當你 需要的時候,它是一個好而簡單的方式去獲得一個 ForkJoinPool/ExecutorService/Executor對象。ConcurrentHashMap<K, V>完全重寫。內部看起來它一點不像是Java7版本。從外部來看幾乎相同,除了它有大量批量操作方法:多種形式的減少搜索和forEach。
ConcurrentHashMap.newKeySet()提供了一個并發的java.util.Set實現。它基本上是 Collections.newSetFromMap(new ConcurrentHashMap<T, Boolean>())的另一種方式的重寫。
StampedLock是一種新型鎖的實現,很可能在大多數場景都可以替代ReentrantReadWriteLock。當作為一個簡單的讀寫鎖的時候,它比RRWL的性能要好。它也為“讀優化”提供了API,通過它你獲得了一個功能有點弱,但代價很小的讀操作鎖的版本,執行讀操作,然后檢查鎖是否被一個寫操作設定為無效。在Heinz Kabutz匯總的一系列幻燈片中,有更多關于這個類及其性能的細節(在這個系列幻燈片大約一半的地方開始的):“移相器和StampedLock演示“
CompletableFuture<T>是Future接口的一個非常棒的實現,它提供了無數執行(和串接)異步任務的方法。它特別依賴功能性的接口;lambdas是值得增加這個類的一個重要原因。如果你正在使用Guava的 Future工具,例如Futures, ListenableFuture, 和 SettableFuture,那么你可能會希望校驗CompletableFuture能否作為潛在的替代選擇。
IO/NIO API的新增內容
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對象。不過它們與直接從常規的collection得到的流有些不同,它們引入了兩個概念:
UncheckedIOException - 當有IO錯誤時拋出這個異常,不過由于Iterator/Stream的簽名中不允許有IOException,所以它只能借助于unchecked異常。
CloseableStream - 可以(并且應該)定義在 try-with-resources 語句里面的流。
反射和annotation的改動
類型annotation (JSR 308)
AnnotatedType
Repeatable
Method.getAnnotatedReturnType()
Field.getAnnotationsByType(Class)
Field.getAnnotatedType()
Constructor.getAnnotatedReturnType()
Parameter - 支持 parameter.getName(),等等。
Annotation允許在更多的地方被使用,例如List<@Nullable String>。受此影響最大的可能是那些靜態分析工具,如Sonar和FindBugs。
JSR 308的網站解釋了增加這些改動的動機,介紹的不錯: “類型Annotation (JSR 308) 和 Checker框架”
Nashorn JavaScript 引擎
提案的摘要: JEP 174: Nashorn JavaScript 引擎
我對Nashorn沒什么經驗,因而我對上面提案所描述的所知甚少。簡單的說,它是 Rhino 的接替者。Rhino 顯得有些老了,并且有點慢,開發者決定最好還是從頭做一個。
其他新增,涉及java.lang,java.util,和java.sql
ThreadLocal.withInitial(Supplier)
String.join(CharSequence, Charsequence…)
String.join(CharSequence, Iterable)
下面的方法適用于所有數字的原語類型,并且作為這些類型的包裝(wrapper)類的三個方法。hashCode方法除外,它們的作用是作為BinaryOperatorin的reduce操作的參數。關于這些方法還有很多的鏈接,更多的內容,參考 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
下面的方法適用于數組,支持T[], int[], long[], double[]。關于這些方法有很多鏈接,更多信息參考 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的雜項新增
Locale的雜項新增
ResultSet的雜項新增
這里可以介紹的太多了,只能挑一些最需要注意的項目。
ThreadLocal.withInitial(Supplier<T>) 可以在定義thread-local變量時更好的進行初始化。之前你初始化變量時是這樣的:
ThreadLocal<List<String>> strings = new ThreadLocal<List<String>>() { @Override protected List<String> initialValue() { return new ArrayList<>(); } };
現在則是這樣:
ThreadLocal<List<String>> strings = ThreadLocal.withInital(ArrayList::new);
stream的API的返回值有一個可選的<T>,就像min/max, findFirst/Any, 以及reduce的某些形式。這樣做是因為stream中可能沒有任何元素,但是它要提供一個一致的API,既可以處理“一些結果”,也可以處理“沒有結果”。你可以提供默認值,拋異常,或者只在有結果的時候執行一些動作。
它與Guava’s Optional類非常非常的相似。它一點都不像是在Scala里的操作,也不會試圖成為之一,有相似的地方純屬巧合。
旁白:Java 8′s Optional和Guava’s Optional最終如此的相似是很有趣的事,盡管荒謬的辯論發生在這兩個庫。
“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″如實寫到:
“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(…)來得太晚了。他們來得如此之晚以至于大多數Java開發者已經為字符串聯合編寫或發現了有用的工具,但這對JDK本身來說很每秒,因為最終自己實現這一點。每個人都會遇到要求字符串連接的情形,我們現在能夠通過每個Java開發者(事實上的)即將知道的標準的API來闡明,這也算是一件好事。
ComparatorsandComparator.thenComparing(…)提供了非常優秀的工具,基于鏈的比較和基于域的比較。像這樣:
people.sort(Comparators.comparing(Person::getLastName) .thenComparing(Person::getFirstName));
這些新增功能提供了良好的,復雜的各種可讀的簡寫。大多數用例由JDK里增加的 ComparisonChain和Ordering工具類來提供服務。對于什么是有價值的,我認為JDK版本比在Guava-ese里功能等效的版本的可讀性好了很多。
其實,還有很多的小問題的修正和一些性能的改進在這篇文章中沒有提及,但是那些也是非常的重要的。
主要是想全面的介紹到Java 8的每個語言層面和API層面的改進。
看完上述內容,你們掌握Java 8的新特性以及改進有哪些的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。