學妹問我 JDK1.8 的新特性,我這樣幫她總結

0 引子

學妹:師兄師兄!我去面試被問到 JDK1.8 的新特性這個問題,不知道怎麼回答啊!哭了哭了,師兄能幫我總結一下 JDK1.8 的新特性嗎?
我:小意思!學妹你先坐下,且讓師兄爲你慢慢講解(嘻嘻)

1 前言

瞭解 JDK1.8 的新特性,無論是面試需要還是工作要求,對我們都是非常重要的,本文會介紹幾種 JDK1.8 的新特性,希望能夠對大家有所幫助。

2 JDK1.8 特性:Lambda 表達式

Lambda 表達式是一個匿名函數,Lambda 表達式沒有聲明的方法,也沒有訪問修飾符、返回值聲明和名字,用於幫助我們寫出更簡潔、更靈活的代碼。

Lambda 表達式建立在函數式接口之上,那什麼是函數式接口呢?只包含一個抽象方法的接口就被稱爲函數式接口,我們可以通過 Lambda 表達式來創建函數式接口的對象。

下面我們舉個栗子,相信大家經常使用排序功能吧,這裏我們使用 Comparator 接口對 Lambda 表達式的使用做一個測試。

我們先使用匿名內部類來實現 Comparator 接口。

public class Test {

	public static void main(String[] args){
		String[] array = {"apple","string","ss"};
		
		//匿名內部類的使用
		Arrays.sort(array,new Comparator<String>() {

			@Override
			public int compare(String o1, String o2) {
				return o1.length() - o2.length();
			}
			
		});  
		
	}
	
}

然後再使用 Lambda 表達式實現 Comparator 接口。

public class Test {

	public static void main(String[] args){
		String[] array = {"apple","string","ss"};
		//使用 Lambda 表達式
		Arrays.sort(array, (String s1, String s2) -> (s1.length() - s2.length())); 	
	}
	
}

可以發現使用 Lambda 表達式更爲簡潔。

我們之前提到過 Lambda 表達式建立在函數式接口之上,爲什麼呢?其實是爲了保證唯一,我們上面的 Comparator 接口只有一個抽象方法,使用 Lambda 表達式可以代表那個方法,如果接口有多個抽象方法,誰知道 Lambda 表達式代表的是哪一個抽象方法呢?

3 JDK1.8 特性:函數式接口

我們在介紹 Lambda 表達式時其實已經講得很清楚了,函數式接口是隻有一個抽象函數的接口,在 JDK1.8 中提供了@FunctionalInterface 註解來檢查函數式接口的合法性。

下面我們介紹幾種基礎的函數式接口:

Consumer

消費者接口,有參無返回值,用於消費數據。

Consumer 接口中提供了 accept 抽象方法,accept 方法會接受一個變量,即使用該函數式接口時會提供數據,只需接受使用即可。

public class Test {
	
	//使用消費型接口
	public static void change(Consumer<String> con,String str) {
		con.accept(str);
	}

	public static void main(String[] args){
		change((str) -> System.out.println(str),"Hello World!");
	}
	
}

Function

函數式接口,有參有返回值,提供轉換功能。

Function 接口中提供了 apply 抽象方法,apply 接受 T 類型數據並返回 R 類型數據。

public class Test {
	
	//使用函數式接口
	public static Integer change(Function<Integer, Integer> fun,Integer i) {
		return fun.apply(i);
	}

	public static void main(String[] args){
		Integer num = change((x) -> 2 * x,100);
		System.out.println(num);
	}
	
}

Predicate

斷言型接口,有參有返回值,其中會返回一個布爾類型的變量,提供斷言、判斷功能。

Predicate 接口中提供了 test 抽象方法,對傳入的數據進行判斷,返回 boolean 類型。

public class Test {
	
	//使用斷言型接口
	public static boolean change(Predicate<String> pre,String str) {
		return pre.test(str);
	}

	public static void main(String[] args){
		boolean res = change((str) -> str.equals("Hello"),"Hello");
		System.out.println(res);
	}
	
}

Supplier

供給型接口,無參有返回值,用於生產數據。

Supplier 接口中提供了 get 抽象方法,用於返回數據。

public class Test {
	
	//使用供給型接口
	public static String change(Supplier<String> sup) {
		return sup.get();
	}

	public static void main(String[] args){
		String str = change(() -> "Hello World!");
		System.out.println(str);
	}
	
}

函數式接口事實上是爲了更加方便地使用 Lambda 表達式,有了 JDK1.8 提供的函數式接口,我們不需要手動創建函數式接口,直接使用即可。

4 JDK1.8 特性:方法引用

我們經常使用 Lambda 表達式來創建匿名方法,但有時我們只是調用一個已經存在的方法而已。在 JDK1.8 中,可以通過方法引用來簡寫 Lambda 表達式中已經存在的方法。

方法引用是一種更簡潔易懂的 Lambda 表達式,其操作符爲雙冒號 :: 。方法引用是用來直接訪問類或者實例的已經存在的方法,它提供了一種引用而不執行方法的方式。

如果 Lambda 表達式僅僅調用一個已存在的方法而不做任何其它事,通過一個方法名字來引用這個已存在的方法也許會更加清晰,Java 8 的方法引用允許我們這樣操作。

下面我們舉個栗子,對一個 Integer 的封裝類進行排序。

Integer 的封裝類代碼如下:

class NewInteger{
	
	private Integer num;
	
	public NewInteger(Integer num) {
		this.num = num;
	}
	
	public int getNum() {
		return num;
	}
	
	public static int compare(NewInteger a,NewInteger b) {
		return a.getNum() - b.getNum();
	}
}

對其進行排序,我們可以使用匿名內部類寫法:

public class Test {

	public static void main(String[] args){
		NewInteger[] array = {new NewInteger(1),new NewInteger(12),new NewInteger(-1),new NewInteger(34)};
		//匿名內部類寫法
		Arrays.sort(array, new Comparator<NewInteger>() {
			@Override
			public int compare(NewInteger o1, NewInteger o2) {
				return o1.getNum() - o2.getNum();
			}
        });
	}
	
}

我們可以發現,Comparator 是一個函數式接口,故我們可以使用 Lambda 表達式,寫法如下:

public class Test {

	public static void main(String[] args){
		NewInteger[] array = {new NewInteger(1),new NewInteger(12),new NewInteger(-1),new NewInteger(34)};
		//Lambda表達式寫法
		Arrays.sort(array, (NewInteger a,NewInteger b) -> {
			return a.getNum() - b.getNum();
		});
	}
	
}

其實,我們之前在 Integer 的封裝類中已經定義了一個比較方法,因此我們可以直接使用該比較方法:

public class Test {

	public static void main(String[] args){
		NewInteger[] array = {new NewInteger(1),new NewInteger(12),new NewInteger(-1),new NewInteger(34)};
		//Lambda表達式寫法
		Arrays.sort(array, (a ,b) -> NewInteger.compare(a, b));
	}
	
}

由於 Lambda 表達式調用了一個已存在的方法,因此,我們可以使用方法引用來替代這個 Lambda 表達式。

public class Test {

	public static void main(String[] args){
		NewInteger[] array = {new NewInteger(1),new NewInteger(12),new NewInteger(-1),new NewInteger(34)};
		//方法引用寫法
		Arrays.sort(array, NewInteger::compare);
	}
	
}

方法引用 NewInteger::compare 與 Lambda 表達式 (a ,b) -> NewInteger.compare(a, b) 是等價的。

方法引用的標準形式是 類名::方法名,一共有四種形式的方法引用,分別爲引用靜態方法,引用某個對象的實例方法,引用某個類型的任意對象的實例方法,引用構造方法。我們之前所舉例子就是一個靜態方法引用。

5 JDK1.8 特性:Stream API

由於文章篇幅關係,本文不準備詳細介紹 Stream 的各類 API,只會介紹一下 Stream 的概念,並舉一個使用 Stream 的小例子,關於 Stream 的各類 API,會在我的下一篇文章爲大家一一介紹。

Stream 是一個處理集合的關鍵抽象概念,可以對集合進行各種操作,Stream API 爲我們操作集合提供了強大的功能,同時操作簡單,容易上手。

Stream 一般有如下三個操作步驟:

  1. 創建 Stream:從一個數據源(集合、數組)中獲取流
  2. 中間操作:一個操作的中間鏈,對數據源的數據進行操作
  3. 終止操作:一個終止操作,執行中間操作鏈,併產生結果

注意,對流的操作完成後需要對其進行關閉。

如何理解 Stream?我們可以這麼想,集合的要點在於數據,流的要點在於計算。Stream 既不會存儲元素,也不會改變源對象,且會返回一個持有結果的新的 Stream,另外,Stream 的操作是延遲執行的,只有在需要結果時,纔會執行。

我們現在實現一個小功能,計算集合中大於 10 的元素數量,在 JDK1.8 之前,我們一般這樣實現:

public class Test {

	public static void main(String[] args){
		List<Integer> list = new ArrayList();
		list.add(34);
		list.add(7);
		list.add(2);
		list.add(66);
		list.add(-3);
		list.add(71);
		
		int count = 0;
		for (Integer integer : list) {
			if(integer > 10) count++;
		}
		System.out.println("集合中大於10的元素數量:" + count );
	}
	
}

如何使用 Stream API 的話,我們可以這樣寫代碼:

public class Test {

	public static void main(String[] args){
		List<Integer> list = new ArrayList();
		list.add(34);
		list.add(7);
		list.add(2);
		list.add(66);
		list.add(-3);
		list.add(71);
		
		long count = list.stream().filter(i -> i > 10).count();
		System.out.println("集合中大於10的元素數量:" + count );
	}
	
}

使用 Stream API 一行代碼就解決了,是不是很簡單呢!

6 JDK1.8 特性:default 關鍵字

在 JDK1.8 之前,接口不能提供方法的實現,但在 JDK1.8 之後,我們可以爲方法提供默認實現方法和靜態方法,分別用關鍵字 default 和 static 修飾即可。

如果一個類既繼承父類又實現接口時,兩者方法名相同,類優先;如果實現兩個同名方法的接口,則實現類必須手動聲明默認實現哪個接口中的方法。

interface Car {
	
	void begin();
	
	//默認實現方法
	default void play() {
		System.out.println("正在開車");
	}
	
	//靜態方法
	static void end() {
		System.out.println("結束開車");
	}
	
}

class Porsche implements Car {

	@Override
	public void begin() {
		System.out.println("保時捷啓動");
	}
	
}

public class Test {

	public static void main(String[] args){
		Car porsche = new Porsche();
		porsche.begin();
		porsche.play();
		Car.end();
	}
	
}

7 總結

JDK1.8 的幾個主要新特性我都簡單介紹了一下,還有一些別的特性,例如 Optional,Date API 等,由於文章篇幅問題,只能一筆帶過。面試能夠講到上面幾點,通過應該是沒問題的,但在工作中運用卻是遠遠不夠。冰凍三尺,非一日之寒,共勉!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章