带示例的 Java 8 特性

Java 8於2014年3月18日發布。雖然已經很久了,但仍然有許多專案在使用Java 8。這是因為它是一個具有許多新功能的重大發布版本。讓我們通過示例代碼來看看Java 8的所有令人興奮和重大功能。

Java 8功能快速概述

一些重要的Java 8功能包括:

  1. Iterable接口中的forEach()方法
  2. 接口中的默認和靜態方法
  3. 功能接口和Lambda表達式
  4. 用於集合的批量數據操作的Java Stream API
  5. Java Time API
  6. 集合API改進
  7. 並行API改進
  8. Java IO改進

讓我們簡要介紹一下這些Java 8功能。我將提供一些代碼片段,以便更好地理解這些功能。

1. Iterable接口中的forEach()方法

每當我們需要遍歷一個集合時,我們需要創建一個迭代器,其整個目的是遍歷,然後我們在集合中的每個元素上都有一個循環的業務邏輯。如果迭代器未正確使用,我們可能會收到ConcurrentModificationException。

Java 8引入了java.lang.Iterable接口中的forEach方法,以便在編寫代碼時我們專注於業務邏輯。forEach方法將java.util.function.Consumer對象作為參數,因此它有助於將我們的業務邏輯放在一個可以重用的獨立位置。讓我們通過一個簡單的例子來看看forEach的使用情況。

package com.journaldev.java8.foreach;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;
import java.lang.Integer;

public class Java8ForEachExample {

	public static void main(String[] args) {
		
		//創建樣本集合
		List<Integer> myList = new ArrayList<Integer>();
		for(int i=0; i<10; i++) myList.add(i);
		
		//使用迭代器遍歷
		Iterator<Integer> it = myList.iterator();
		while(it.hasNext()){
			Integer i = it.next();
			System.out.println("Iterator Value::"+i);
		}
		
		//通過Iterable的forEach方法和匿名類別遍歷
		myList.forEach(new Consumer<Integer>() {

			public void accept(Integer t) {
				System.out.println("forEach anonymous class Value::"+t);
			}

		});
		
		//使用Consumer接口實現遍歷
		MyConsumer action = new MyConsumer();
		myList.forEach(action);
		
	}

}

//可以重用的Consumer實現
class MyConsumer implements Consumer<Integer>{

	public void accept(Integer t) {
		System.out.println("Consumer impl Value::"+t);
	}
}

行數可能會增加,但forEach方法有助於將迭代邏輯和業務邏輯分開,使得關注點分離更高,代碼更清晰。

2. 接口中的默认方法和静态方法

如果您仔细阅读forEach方法的详细信息,您会注意到它是在Iterable接口中定义的,但我们知道接口不能有方法体。从Java 8开始,接口被增强为具有带有实现的方法。我们可以使用defaultstatic关键字来创建具有方法实现的接口。Iterable接口中的forEach方法实现为:

default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

我们知道Java不提供在类中的多重继承,因为这会导致菱形问题。那么现在接口与抽象类类似,接口又如何处理呢?

解决方案是编译器将在这种情况下引发异常,并且我们必须在实现接口的类中提供实现逻辑。

package com.journaldev.java8.defaultmethod;

@FunctionalInterface
public interface Interface1 {

	void method1(String str);
	
	default void log(String str){
		System.out.println("I1 logging::"+str);
	}
	
	static void print(String str){
		System.out.println("Printing "+str);
	}
	
	//尝试重写Object方法会在编译时出错,因为
	//“默认方法无法覆盖java.lang.Object中的方法”
	
//	default String toString(){
//		return "i1";
//	}
	
}
package com.journaldev.java8.defaultmethod;

@FunctionalInterface
public interface Interface2 {

	void method2();
	
	default void log(String str){
		System.out.println("I2 logging::"+str);
	}

}

請注意,這兩個介面都有一個共同的方法log(),並且有實現邏輯。

package com.journaldev.java8.defaultmethod;

public class MyClass implements Interface1, Interface2 {

	@Override
	public void method2() {
	}

	@Override
	public void method1(String str) {
	}

	//如果MyClass沒有自己的log()實現,則無法編譯
	@Override
	public void log(String str){
		System.out.println("MyClass logging::"+str);
		Interface1.print("abc");
	}
	
}

正如你所看到的,Interface1有一個靜態方法實現,該方法在MyClass.log()方法實現中使用。Java 8在Collection API中大量使用默認靜態方法,並添加了默認方法以保持代碼的向後兼容性。

如果層次結構中的任何類具有相同簽名的方法,則默認方法將變得無關緊要。Object是基類,因此如果介面中有equals()、hashCode()默認方法,它將變得無關緊要。這就是為什麼為了更好地清晰性,不允許介面有Object默認方法。

有關Java 8介面更改的詳細信息,請閱讀Java 8介面更改

3. 功能接口和Lambda表達式

如果你注意到上面的接口代碼,你會注意到@FunctionalInterface注釋。 函数接口是Java 8中引入的一個新概念。 接口中只有一個抽象方法的接口稱為函数接口。 我們不需要使用@FunctionalInterface注釋來將接口標記為函数接口。

@FunctionalInterface注釋是一種防止在函数接口中意外添加抽象方法的設施。 您可以將其視為@Override注釋,最好使用它。 具有單個抽象方法run()的java.lang.Runnable是函数接口的一個很好的示例。

函数接口的一個主要好處是可以使用lambda表達式來實例化它們。 我們可以用匿名類實例化一個接口,但代碼看起來很笨重。

Runnable r = new Runnable(){
			@Override
			public void run() {
				System.out.println("My Runnable");
			}};

由於函数接口只有一個方法,lambda表達式可以很容易地提供方法實現。 我們只需要提供方法參數和業務邏輯。 例如,我們可以使用lambda表達式將上述實現編寫如下:

Runnable r1 = () -> {
			System.out.println("My Runnable");
		};

如果方法實現中只有一個語句,我們也不需要花括號。 例如,上面的Interface1匿名類可以使用lambda實例化如下:

Interface1 i1 = (s) -> System.out.println(s);
		
i1.method1("abc");

所以lambda表达式是一种轻松创建功能接口的匿名类的方法。使用lambda表达式没有运行时的好处,因此我会谨慎使用,因为我不介意多写几行代码。

A new package java.util.function has been added with bunch of functional interfaces to provide target types for lambda expressions and method references. Lambda expressions are a huge topic, I will write a separate article on that in the future.

您可以在 Java 8 Lambda 表达式教程 中阅读完整的教程。

4. Java 流 API 用于集合的批量数据操作

A new java.util.stream has been added in Java 8 to perform filter/map/reduce like operations with the collection. Stream API will allow sequential as well as parallel execution. This is one of the best features for me because I work a lot with Collections and usually with Big Data, we need to filter out them based on some conditions.

Collection 接口已经通过 stream() 和 parallelStream() 默认方法进行了扩展,以获取用于顺序和并行执行的流。让我们通过一个简单的示例来看看它们的用法。

package com.journaldev.java8.stream;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class StreamExample {

	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 API 中使用 lambda,过滤示例
		Stream<Integer> highNums = parallelStream.filter(p -> p > 90);
		//在 forEach 中使用 lambda
		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));

	}

}

如果您运行上面的示例代码,您将会得到如下输出:

High Nums parallel=91
High Nums parallel=96
High Nums parallel=93
High Nums parallel=98
High Nums parallel=94
High Nums parallel=95
High Nums parallel=97
High Nums parallel=92
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

请注意,并行处理的值没有顺序,因此在处理大型集合时并行处理将非常有帮助。

這篇文章無法涵蓋有關 Stream API 的所有內容,您可以在Java 8 Stream API 示例教程中閱讀有關 Stream API 的所有內容。

5. Java Time API

在 Java 中處理日期、時間和時區一直是困難的。在 Java 中,沒有標準的方法或 API 來處理日期和時間。Java 8 中的一個很好的新增功能是 java.time 套件,它將簡化在 Java 中處理時間的過程。

僅僅查看 Java Time API 套件,我就能感覺到它們將非常容易使用。它具有一些子套件, java.time.format 提供了用於打印和解析日期和時間的類,而 java.time.zone 則提供了對時區及其規則的支持。

新的 Time API 偏好使用枚舉而不是整數常量來表示月份和星期幾。其中一個有用的類是 DateTimeFormatter,用於將 DateTime 對象轉換為字符串。欲了解完整教程,請前往 Java 日期時間 API 示例教程

6. 集合 API 改進

我們已經看到了forEach()方法和集合的Stream API。在Collection API中新增的一些方法包括:

  • Iterator默認方法forEachRemaining(Consumer action),對每個剩餘元素執行給定的操作,直到所有元素都被處理完畢或操作引發異常為止。
  • Collection默認方法removeIf(Predicate filter),用於刪除滿足給定預測條件的此集合的所有元素。
  • Collection spliterator()方法返回Spliterator實例,可用於按順序或並行遍歷元素。
  • Map replaceAll()compute()merge()方法。
  • HashMap類的性能改進,處理鍵碰撞

7. 並發API改進

一些重要的並發API增強功能包括:

  • ConcurrentHashMap compute()、forEach()、forEachEntry()、forEachKey()、forEachValue()、merge()、reduce()和search()方法。
  • CompletableFuture可以明確完成(設置其值和狀態)。
  • Executors newWorkStealingPool()方法,用於使用所有可用處理器作為其目標並行級別來創建工作窮竭線程池。

8. Java IO 改進

我知道的一些 IO 改進有:

  • Files.list(Path dir) 返回一個懶加載的 Stream,其中的元素是目錄中的條目。
  • Files.lines(Path path) 讀取文件中的所有行並作為 Stream 返回。
  • Files.find() 返回一個 Stream,該流通過搜索以給定起始文件為根的文件樹來懶加載 Path。
  • BufferedReader.lines() 返回一個 Stream,其中的元素是從該 BufferedReader 讀取的行。

其他 Java 8 核心 API 改進

一些可能有用的其他 API 改進包括:

  1. ThreadLocal 靜態方法 withInitial(Supplier supplier) 以便輕鬆創建實例。
  2. Comparator 接口已擴展為具有大量自帶的默認和靜態方法,用於自然排序、反向排序等。
  3. 在 Integer、Long 和 Double 封裝類中的 min()、max() 和 sum() 方法。
  4. Boolean类中的logicalAnd()、logicalOr()和logicalXor()方法。
  5. ZipFile.stream()方法用于获取ZIP文件条目的有序流。条目按照它们在ZIP文件的中央目录中出现的顺序出现在流中。
  6. Math类中的多个实用方法。
  7. jjs命令已添加以调用Nashorn引擎。
  8. jdeps命令已添加以分析类文件
  9. JDBC-ODBC桥已被移除。
  10. PermGen内存空间已被移除

这就是Java 8的特性以及示例程序。如果我错过了Java 8的一些重要特性,请通过评论让我知道。

Source:
https://www.digitalocean.com/community/tutorials/java-8-features-with-examples