1. 程式人生 > >JDK1.8新特性(一)

JDK1.8新特性(一)

本文主要介紹了JDK1.8的一些新特性,僅供參考;

前言:JDK1.8新特性知識點

  • 紅黑樹 – 速度更快
  • Lambda表示式 – 程式碼更少
  • 函式式介面
  • 方法引用和構造器呼叫
  • Stream API – 強大的Stream API
  • 介面中的預設方法和靜態方法
  • 新時間日期API

一、HashMap中的紅黑樹

HashMap碰撞:

HashMap中用的最多的方法就屬put() 和 get() 方法;HashMap的Key值是唯一的,那如何保證唯一性呢?
我們首先想到的是用equals比較,沒錯,這樣可以實現,但隨著內部元素的增多,put和get的效率將越來越低,
這裡的時間複雜度是O(n),假如有1000個元素,put時最差情況需要比較1000次。
實際上,HashMap很少會用到equals方法,因為其內通過一個雜湊表管理所有元素,雜湊是通過hash單詞音譯過來的,
也可以稱為散列表,雜湊演算法可以快速的存取元素,當我們呼叫put存值時,HashMap首先會呼叫Key的hash方法,
計算出雜湊碼,通過雜湊碼快速找到某個存放位置(桶),這個位置可以被稱之為bucketIndex,但可能會存在多
個元素找到了相同的bucketIndex,有個專業名詞叫碰撞,當碰撞發生時,這時會取到bucketIndex位置已儲存
的元素,最終通過equals來比較,equals方法就是碰撞時才會執行的方法,所以前面說HashMap很少會用到equals。
HashMap通過hashCode和equals最終判斷出Key是否已存在,如果已存在,則使用新Value值替換舊Value值,並返
回舊Value值,如果不存在,則存放新的鍵值對<K, V>到bucketIndex位置。

JDK1.8對Map結構優化:

在jdk1.8中對hashMap等map集合的資料結構進行了優化:
1.hashMap資料結構的優化 
原來的hashMap採用的資料結構是雜湊表(陣列+連結串列),hashMap預設大小是16,一個0-15索引的陣列;
如何往裡面儲存(put)元素,首先呼叫元素的hashcode方法,計算出雜湊碼值,經過雜湊演算法算出陣列的索引值,
如果對應的索引處沒有元素,直接存放,如果有物件在,那麼再呼叫它們的equals方法比較內容;
如果內容一樣,後一個value會將前一個value的值覆蓋,如果不一樣,在1.7的時候,後加的放在前面,形成一個連結串列,
形成了碰撞。
載入因子:0.75,陣列擴容,達到總容量的75%,就進行擴容,但是無法避免碰撞的情況發生。
在1.8之後,在陣列+連結串列+紅黑樹來實現hashmap,當碰撞的元素個數大於8時並且總容量大於64,會有紅黑樹的引入。 
1.8之後連結串列新進元素加到末尾,除此之外,效率都比連結串列高。

二、Lambda表示式

lambda表示式本質上是一段匿名內部類,也可以是一段可以傳遞的程式碼

1.基本語法

(parameters) -> expression
(parameters) -> { statements; }

前置 語法
無引數無返回值 () -> System.out.println(“Hello World”)
有一個引數無返回值 (x) -> System.out.println(x)
有且只有一個引數無返回值 x -> System.out.println(x)
有多個引數,有返回值,有多條lambda體語句 (x,y) -> {System.out.println(“xxx”);return xxxx;};
有多個引數,有返回值,只有一條lambda體語句 (x,y) -> xxxx

口訣:左右遇一省括號,左側推斷型別省

2.例子

2.1 集合的foreach迴圈

String[] atp = {"Rafael Nadal", "Novak Djokovic",  
       "Stanislas Wawrinka",  
       "David Ferrer","Roger Federer",  
       "Andy Murray","Tomas Berdych",  
       "Juan Martin Del Potro"};  
List<String> players =  Arrays.asList(atp);  
  
// jdk1.8 以前的迴圈方式  
for (String player : players) {  
     System.out.print(player + "; ");  
}  
  
// jdk1.8 使用lambda表示式以及函式操作(functional operation)  
players.forEach((player) -> System.out.print(player + "; "));  
   
// 在 Java 8 中使用雙冒號操作符(double colon operator)  
players.forEach(System.out::println);

2.2 匿名內部類

2.2.1 awt的監聽、動作

/**
 * awt程式設計,button的動作設定
 */
// 使用匿名內部類  
btn.setOnAction(new EventHandler<ActionEvent>() {  
      @Override  
      public void handle(ActionEvent event) {  
          System.out.println("Hello World!");   
      }  
});  
   
// 或者使用 lambda expression  
btn.setOnAction(event -> System.out.println("Hello World!"));  

2.2.2 執行緒

// 1.1使用匿名內部類  
new Thread(new Runnable() {  
    @Override  
    public void run() {  
        System.out.println("Hello world !");  
    }  
}).start();  
  
// 1.2使用 lambda expression  
new Thread(() -> System.out.println("Hello world !")).start();  
  

// 2.1使用匿名內部類  
Runnable race1 = new Runnable() {  
    @Override  
    public void run() {  
        System.out.println("Hello world !");  
    }  
};  
  
// 2.2使用 lambda expression  
Runnable race2 = () -> System.out.println("Hello world !");  
   
// 直接呼叫 run 方法(沒開新執行緒哦!)  
race1.run();  
race2.run();

2.2.3 集合排序

String[] players = {"Rafael Nadal", "Novak Djokovic",   
    "Stanislas Wawrinka", "David Ferrer",  
    "Roger Federer", "Andy Murray",  
    "Tomas Berdych", "Juan Martin Del Potro",  
    "Richard Gasquet", "John Isner"};  
   
// 1.1 使用匿名內部類根據 name 排序 players  
Arrays.sort(players, new Comparator<String>() {  
    @Override  
    public int compare(String s1, String s2) {  
        return (s1.compareTo(s2));  
    }  
}); 

// 1.2 使用 lambda expression 排序 players  
Comparator<String> sortByName = (String s1, String s2) -> (s1.compareTo(s2));  
Arrays.sort(players, sortByName);  
  
// 1.3 也可以採用如下形式:  
Arrays.sort(players, (String s1, String s2) -> (s1.compareTo(s2)));

二、函式式介面

Java 8允許我們給介面新增一個非抽象的方法實現,只需要使用 default關鍵字即可,這個特徵又叫做擴充套件方法,示例如下:

interface Formula {
    double calculate(int a);
    boolean isStr(String s);
    default double sqrt(int a) {
        return Math.sqrt(a);
    }
}

特點:

  • 函式式介面(Functional Interface)就是一個有且僅有一個抽象方法,但是可以有多個非抽象方法的介面。
  • 函式式介面可以被隱式轉換為lambda表示式。
  • 函式式介面現有的函式可以友好地支援 lambda。

解釋:

函式式介面其實本質上還是一個介面,但是它是一種特殊的介面:SAM型別的介面(Single Abstract Method)。
  定義了這種型別的介面,使得以其為引數的方法,在呼叫時,可以使用一個lambda表示式作為引數。從另一個方面說,一旦我們呼叫某方法,可以傳入lambda表示式作為引數,則這個方法的引數型別,必定是一個函式式的介面,這個型別必定會使用@FunctionalInterface進行修飾。

從SAM原則上講,這個介面中,只能有一個函式需要被實現,但是也可以有如下例外:

  1. 預設方法與靜態方法並不影響函式式介面的契約,可以任意使用,即函式式介面中可以有靜態方法,一個或者多個靜態方法不會影響SAM介面成為函式式介面,並且靜態方法可以提供方法實現可以由 default 修飾的預設方法方法,這個關鍵字是Java8中新增的,為的目的就是使得某一些介面,原則上只有一個方法被實現,但是由於歷史原因,不得不加入一些方法來相容整個JDK中的API,所以就需要使用default關鍵字來定義這樣的方法;
  2. 可以有 Object 中覆蓋的方法,也就是 equals,toString,hashcode等方法;可以有 Object 中覆蓋的方法,也就是 equals,toString,hashcode等方法;

JDK中以前所有的函式式介面都已經使用 @FunctionalInterface 定義,可以通過檢視JDK原始碼來確認

JDK 1.8之前已有的函式式介面:

java.lang.Runnable
java.util.concurrent.Callable
java.security.PrivilegedAction
java.util.Comparator
java.io.FileFilter
java.nio.file.PathMatcher
java.lang.reflect.InvocationHandler
java.beans.PropertyChangeListener
java.awt.event.ActionListener
javax.swing.event.ChangeListener

JDK 1.8 新增加的函式介面:

java.util.function

java.util.function 它包含了很多類,用來支援 Java的 函數語言程式設計,該包中的函式式介面有:

序號	介面 & 描述
1	BiConsumer<T,U>
代表了一個接受兩個輸入引數的操作,並且不返回任何結果

2	BiFunction<T,U,R>
代表了一個接受兩個輸入引數的方法,並且返回一個結果

3	BinaryOperator<T>
代表了一個作用於於兩個同類型操作符的操作,並且返回了操作符同類型的結果

4	BiPredicate<T,U>
代表了一個兩個引數的boolean值方法

5	BooleanSupplier
代表了boolean值結果的提供方

6	Consumer<T>
代表了接受一個輸入引數並且無返回的操作

7	DoubleBinaryOperator
代表了作用於兩個double值操作符的操作,並且返回了一個double值的結果。

8	DoubleConsumer
代表一個接受double值引數的操作,並且不返回結果。

9	DoubleFunction<R>
代表接受一個double值引數的方法,並且返回結果

10	DoublePredicate
代表一個擁有double值引數的boolean值方法

11	DoubleSupplier
代表一個double值結構的提供方

12	DoubleToIntFunction
接受一個double型別輸入,返回一個int型別結果。

13	DoubleToLongFunction
接受一個double型別輸入,返回一個long型別結果

14	DoubleUnaryOperator
接受一個引數同為型別double,返回值型別也為double 。

15	Function<T,R>
接受一個輸入引數,返回一個結果。

16	IntBinaryOperator
接受兩個引數同為型別int,返回值型別也為int 。

17	IntConsumer
接受一個int型別的輸入引數,無返回值 。

18	IntFunction<R>
接受一個int型別輸入引數,返回一個結果 。

19	IntPredicate
:接受一個int輸入引數,返回一個布林值的結果。

20	IntSupplier
無引數,返回一個int型別結果。

21	IntToDoubleFunction
接受一個int型別輸入,返回一個double型別結果 。

22	IntToLongFunction
接受一個int型別輸入,返回一個long型別結果。

23	IntUnaryOperator
接受一個引數同為型別int,返回值型別也為int 。

24	LongBinaryOperator
接受兩個引數同為型別long,返回值型別也為long。

25	LongConsumer
接受一個long型別的輸入引數,無返回值。

26	LongFunction<R>
接受一個long型別輸入引數,返回一個結果。

27	LongPredicate
R接受一個long輸入引數,返回一個布林值型別結果。

28	LongSupplier
無引數,返回一個結果long型別的值。

29	LongToDoubleFunction
接受一個long型別輸入,返回一個double型別結果。

30	LongToIntFunction
接受一個long型別輸入,返回一個int型別結果。

31	LongUnaryOperator
接受一個引數同為型別long,返回值型別也為long。

32	ObjDoubleConsumer<T>
接受一個object型別和一個double型別的輸入引數,無返回值。

33	ObjIntConsumer<T>
接受一個object型別和一個int型別的輸入引數,無返回值。

34	ObjLongConsumer<T>
接受一個object型別和一個long型別的輸入引數,無返回值。

35	Predicate<T>
接受一個輸入引數,返回一個布林值結果。

36	Supplier<T>
無引數,返回一個結果。

37	ToDoubleBiFunction<T,U>
接受兩個輸入引數,返回一個double型別結果

38	ToDoubleFunction<T>
接受一個輸入引數,返回一個double型別結果

39	ToIntBiFunction<T,U>
接受兩個輸入引數,返回一個int型別結果。

40	ToIntFunction<T>
接受一個輸入引數,返回一個int型別結果。

41	ToLongBiFunction<T,U>
接受兩個輸入引數,返回一個long型別結果。

42	ToLongFunction<T>
接受一個輸入引數,返回一個long型別結果。

43	UnaryOperator<T>
接受一個引數為型別T,返回值型別也為T。

函式式介面例項:

Predicate 介面是一個函式式介面,它接受一個輸入引數 T,返回一個布林值結果。
該介面包含多種預設方法來將Predicate組合成其他複雜的邏輯(比如:與,或,非)。
該介面用於測試物件是 true 或 false。
我們可以通過以下例項(Java8Tester.java)來了解函式式介面 Predicate 的使用:

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
 
public class Java8Tester {
   public static void main(String args[]){
      List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
        
      // Predicate<Integer> predicate = n -> true
      // n 是一個引數傳遞到 Predicate 介面的 test 方法
      // n 如果存在則 test 方法返回 true
        
      System.out.println("輸出所有資料:");
        
      // 傳遞引數 n
      eval(list, n->true);
        
      // Predicate<Integer> predicate1 = n -> n%2 == 0
      // n 是一個引數傳遞到 Predicate 介面的 test 方法
      // 如果 n%2 為 0 test 方法返回 true
        
      System.out.println("輸出所有偶數:");
      eval(list, n-> n%2 == 0 );
        
      // Predicate<Integer> predicate2 = n -> n > 3
      // n 是一個引數傳遞到 Predicate 介面的 test 方法
      // 如果 n 大於 3 test 方法返回 true
        
      System.out.println("輸出大於 3 的所有數字:");
      eval(list, n-> n > 3 );
   }
    
   public static void eval(List<Integer> list, Predicate<Integer> predicate) {
      for(Integer n: list) {
        
         if(predicate.test(n)) {
            System.out.println(n + " ");
         }
      }
   }
}

三、方法引用

三種表現形式:

型別 示例
某個物件的方法引用 Object : instanceMethodName
特定類的任意物件的方法引用 Type : methodName
類靜態方法引用 Class : staticMethodName
構造方法引用 className : new

使用形式:

  • 物件::例項方法名
  • 類::靜態方法名
  • 類::例項方法名

函式介面中抽象方法的引數列表,必須與方法引用方法的引數列表保持一致,方法引用中::後只是方法名,不能加();

例項方法引用

迴圈集合中元素,使用forEach方法
(s) -> System.out.println(s)的型別是Consumer型別
其accept接受引數和println一致

import java.util.ArrayList;
import java.util.List;

public class Demo01 {
	public static void main(String[] args) {
		// 建立集合
		List<String> list = new ArrayList<>();
	
		// 新增元素
		list.add("e");
		list.add("c");
		list.add("a");
		list.add("d");
		list.add("b");
	
		// 排序
		list.sort((s1, s2) -> s1.compareTo(s2));

		// 遍歷
		list.forEach((s) -> System.out.println(s));
		list.forEach(System.out::println);
	}
}

類靜態方法引用

求絕對值,使用Function實現:

import java.util.function.Function;

public class Demo01 {
	public static void main(String[] args) {
		// 呼叫
		long l1 = testAbs(-10L, (s) -> Math.abs(s));

		// 改進
		long l2 = testAbs(-10, Math::abs);
		System.out.println(l1);
		System.out.println(l2);
	}
	public static long testAbs(long s, Function<Long, Long> fun) {
		Long l = fun.apply(s);
		return l;
	}
}

構造方法引用

在引用構造器的時候,構造器引數列表要與介面中抽象方法的引數列表一致
格式為 類名::new

import java.util.function.Supplier;

public class Demo01 {
	public static void main(String[] args) {
		getString(String :: new);
	}
	
	public static void getString(Supplier<String> su) {
		String s = su.get();
		System.out.println(s == null);
	}
}

任意物件的例項方法

public void test() {
        /**
        *注意:
        *   1.lambda體中呼叫方法的引數列表與返回值型別,要與函式式介面中抽象方法的函式列表和返回值型別保持一致!
        *   2.若lambda引數列表中的第一個引數是例項方法的呼叫者,而第二個引數是例項方法的引數時,可以使用ClassName::method
        *
        */
        Consumer<Integer> con = (x) -> System.out.println(x);
        con.accept(100);

        // 方法引用-物件::例項方法
        Consumer<Integer> con2 = System.out::println;
        con2.accept(200);

        // 方法引用-類名::靜態方法名
        BiFunction<Integer, Integer, Integer> biFun = (x, y) -> Integer.compare(x, y);
        BiFunction<Integer, Integer, Integer> biFun2 = Integer::compare;
        Integer result = biFun2.apply(100, 200);

        // 方法引用-類名::例項方法名
        BiFunction<String, String, Boolean> fun1 = (str1, str2) -> str1.equals(str2);
        BiFunction<String, String, Boolean> fun2 = String::equals;
        Boolean result2 = fun2.apply("hello", "world");
        System.out.println(result2);
    }