Java 8於2014年3月18日發布,在閱讀相關文章后,在本教程中,我們將通過示例研究 Java 8功能。
一、Java 8 的新特性
- Iterable 接口中的 forEach()方法
- 接口中允許有默認方法和靜態方法
- 函數式接口與 Lambda 表達式
- 用於集合上批量數據操作的 Java Stream API
- 新的時間 API
- 集合 API 的改進
- 並發 API 的改進
- Java IO 的改進
- 其他核心 API 的改進
以上是 Java 8 的新特性的列舉,接下來展示一些代碼片段,來更好理解這些新特性
二、新特性應用
2.1、Iterable 接口中的 forEach()方法
package com.taotao.springboot;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;
public class IterableForEachExample {
public static void main(String[] args) {
List<Integer> ints = new ArrayList<>();
for (int i = 0; i < 10; i++) ints.add(i);
//迭代器遍歷
Iterator<Integer> iterator = ints.iterator();
while (iterator.hasNext()) {
Integer i = iterator.next();
System.out.println("Iterator Value::" + i);
}
//使用forEach方法遍歷,這里使用了匿名內部類
ints.forEach(new Consumer<Integer>() {
public void accept(Integer t) {
System.out.println("forEach anonymous class Value::" + t);
}
});
//通過實現Consumer接口的子類遍歷,業務處理從遍歷邏輯中抽取出來
MyConsumer action = new MyConsumer();
ints.forEach(action);
}
}
//實現Conumer接口
class MyConsumer implements Consumer<Integer> {
public void accept(Integer t) {
System.out.println("Consumer impl Value::" + t);
}
}
通過上面這個例子,看出使用 forEach()方法代碼行數會增加,但是它將遍歷邏輯與業務處理邏輯分離,有助於我們將關注點放在業務邏輯的處理上。
2.2、接口中的默認方法和靜態方法
在上面的例子中,如果有點進去看 forEach()方法,你會發現它是在接口 Iterable 中定義的。但是我們知道接口不能有方法體。從 Java 8開始,接口已改進,允許有默認的實現方法。我們可以在接口中使用關 default
和 static
關鍵字來創建方法實現。以下是 forEach()的定義。
我們知道在 Java 中不能同時繼承多個類,因為這樣做會引發 Diamond 問題 ,接口允許有默認方法實現后,接口變得與抽象類非常類似,現在可以利用接口解決這個問題。我們定義兩個接口 FunctionInterface1 和FunctionInterface2,以及一個它兩的實現類 FunctionInterfaceImpl
package com.taotao.springboot;
@FunctionalInterface
public interface FunctionInterface1 {
void hello1(String str);
default void eat(String str){
System.out.println("I1 eatting::"+str);
}
static void print(String str){
System.out.println("Printing "+str);
}
//如果默認方法重寫了 Object 類中方法,編譯不能通過
// default String toString(){
// return "";
// }
}
在上面代碼中,我們知道接口中的默認實現方法不能與Object類中的方法簽名相同
package com.taotao.springboot;
@FunctionalInterface
public interface FunctionInterface2 {
void hello2(String str);
default void eat(String str){
System.out.println("I2 eatting::"+str);
}
}
注意,FunctionInterface1 與 FunctionInterface2 都有一個默認的實現方法 eat()。在這種情況下,我們看看它兩的實現類 FunctionInterfaceImpl 會不會有要注意的點。
package com.taotao.springboot;
public class FunctionInterfaceImpl implements FunctionInterface1, FunctionInterface2 {
@Override
public void hello1(String str) {
System.out.println("hello1 " + str);
}
//如果不實現 eat() 方法,編譯不通過
@Override
public void eat(String str) {
System.out.println("functionInterfaceImpl eatting " + str);
}
@Override
public void hello2(String str) {
System.out.println("hello2 " + str);
}
public static void main(String[] args) {
FunctionInterface1 interface1 = new FunctionInterfaceImpl();
interface1.hello1("world");
interface1.eat("apple");
FunctionInterface2 interface2 = new FunctionInterfaceImpl();
interface2.eat("banana");
interface2.hello2("world");
}
}
注意,FunctionInterfaceImpl 必須實現 eat()方法。運行 main 方法,打印輸出如下:
hello1 world
functionInterfaceImpl eatting apple
functionInterfaceImpl eatting banana
hello2 world
可見,Java 8 在 Collection API 中大量使用默認和靜態方法,以便我們的代碼保持向后兼容。
同時從 functionInterfaceImpl 的例子可以看出,如果一個類同時實現了兩個及以上接口,而這些接口中都有一個簽名相同的方法,那么,這個類必須實現這個方法,否則將不能通過編譯。此時,接口中的默認實現方法已經不起作用了。如果類只是實現一個接口,則沒有這個問題
這也就解釋了之前提到的 接口中的默認實現方法不能與Object類中的方法簽名相同 。因為Object是所有類的超類(父類),因此如果接口中具有equals(),hashCode()默認方法,那這些默認方法也將不起作用。
2.3、函數式接口與 Lambda 表達式
如果您仔細觀察上述接口代碼,則會注意到 @FunctionalInterface 注解。函數式接口是 Java 8中引入的新概念。有且只有一個抽象方法的接口就是函數式接口。@FunctionalInterface 注解不是函數式接口定義的必要條件,它是一個避免在函數式接口中意外添加其他抽象方法的注解。java.lang.Runnable
具有單個抽象方法run(),是函數式接口一個很好的例子。
函數式接口的主要優點之一是可以使用 lambda 表達式實例化它們。我們可以用匿名內部類實例化一個接口,但是代碼看起來很龐大。
//匿名內部類實現Runnable
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("My Runnable");
}
};
//lambda 表達式實現Runnable
Runnable runnable = () -> {
System.out.println("My Runnable");
};
如果方法實現中只有一條語句,我們也不需要花括號。如下所示:
Runnable runnable = () -> System.out.println("My Runnable");
因此,lambda表達式是輕松創建函數式接口的匿名類的一種方法。使用lambda表達式代碼可讀性感覺更差,因此要謹慎使用它,不介意編寫一些額外的代碼行。
java.util.function
是添加了帶有函數式接口的新程序包,以提供lambda表達式和方法引用的目標類型。
2.4、用於集合上批量數據操作的 Java Stream API
java.util.stream
是 Java 8中添加的新程序包,以便對集合執行類似過濾/映射/歸約的操作。Stream API 將允許順序執行和並行執,是最好的功能之一,如果經常處理Collections,而且集合元素很多,我們可以根據某些條件過濾掉它們。
Collection接口已使用 stream()和 parallelStream()默認方法進行了擴展,以獲取用於順序執行和並行執行的 Stream。讓我們用一個簡單的例子看看它們的用法。
package com.taotao.springboot;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class StreamList {
public static void main(String[] args) {
List<Integer> myList = new ArrayList<>();
for (int i = 0; i < 100; i++) myList.add(i);
//串行流
Stream<Integer> sequentialStream = myList.stream();
//並行流
Stream<Integer> parallelStream = myList.parallelStream();
//過濾
Stream<Integer> highNums = parallelStream.filter(p -> p > 90);
//使用 forEach 遍歷
highNums.forEach(p -> System.out.println("High Nums parallel=" + p));
Stream<Integer> highNumsSeq = sequentialStream.filter(p -> p > 90);
highNumsSeq.forEach(p -> System.out.println("High Nums sequential=" + p));
}
}
運行上述代碼的 main 方法,你將看到如下輸出:
High Nums parallel=91
High Nums parallel=93
High Nums parallel=96
High Nums parallel=94
High Nums parallel=95
High Nums parallel=92
High Nums parallel=97
High Nums parallel=98
High Nums parallel=99
High Nums sequential=91
High Nums sequential=92
High Nums sequential=93
High Nums sequential=94
High Nums sequential=95
High Nums sequential=96
High Nums sequential=97
High Nums sequential=98
High Nums sequential=99
請注意,並行流不按集合元素排列順序處理,但在處理大量元素的集合時將非常有用。
2.5、新的時間 API
在 Java中 使用日期,時間和時區一直很困難。Java中沒有用於日期和時間的標准方法或API。java.time
程序包是 Java 8一個不錯的附加功能,它將簡化Java中使用時間的過程。
僅查看 Java Time API軟件包,我就可以感覺到它非常易於使用。它具有一些子包java.time.format
,這些子包提供用於打印和解析日期和時間的類,並java.time.zone
提供對時區及其規則的支持。
新的Time API在整月的幾個月和一周中的幾天中都更喜歡枚舉而不是整數常量。將DateTime對象轉換為字符串的類是DateTimeFormatter
。
2.6、集合API的改進
我們已經看到了 forEach()方法和用於集合的 Stream API。Collection API還有一些新方法是:
Iterator
forEachRemaining(Consumer action)
在所有元素都已處理完畢或該動作引發異常之前,對每個剩余元素執行給定操作的默認方法。Collection
removeIf(Predicate filter)
刪除此集合中所有滿足特定條件的元素的默認方法。Collection
spliterator()
該方法返回Spliterator實例,該實例可用於順序或並行遍歷元素。map
replaceAll()
,compute()
,merge()
方法。- 具有鍵沖突的HashMap類的性能改進
2.7、並發 API 的改進
一些重要的並發API增強功能包括:
ConcurrentHashMap
compute(),forEach(),forEachEntry(),forEachKey(),forEachValue(),merge(),reduce()和search()方法。CompletableFuture
可以明確完成(設置其值和狀態)。Executors
newWorkStealingPool()
使用所有可用處理器作為目標並行度級別創建竊取線程池的方法。
2.8、Java IO改進
一些IO改進包括:
Files.list(Path dir)
返回延遲填充的Stream,其元素是目錄中的條目。Files.lines(Path path)
從文件中讀取所有行作為流。Files.find()
通過在以給定起始文件為根的文件樹中搜索文件,返回通過路徑延遲填充的Stream。BufferedReader.lines()
返回一個Stream,其元素是從此BufferedReader中讀取的行。
2.9、其他核心API改進
一些其他API改進:
- ThreadLocal 靜態方法可以使用 withInitial(Supplier supplier) 輕松創建實例。
- Comparator 接口已擴展了許多默認和靜態方法,用於自然排序,反向排序等。
- Integer,Long 和 Double 包裝器類中的 min(),max()和sum()方法。
- 布爾類中的 logicalAnd(),logicalOr()和 logicalXor()方法。
- ZipFile.stream()方法獲取ZIP文件條目上的有序 Stream。條目以在 ZIP 文件的中央目錄中出現的順序出現在 Stream 中。
- Math類中的幾種實用方法。
jjs
添加命令以調用 Nashorn Engine。jdeps
添加命令以分析類文件- JDBC-ODBC 橋已被刪除。
- PermGen 內存空間已被刪除
總結
這就是帶有代碼片段的 Java 8 全部新特性。