JDK1.8新特性——接口改動和Lambda表達式
摘要:本文主要學習了JDK1.8的新特性中有關接口和Lambda表達式的部分。
部分內容來自以下博客:
https://www.cnblogs.com/onetwo/p/8526374.html
https://www.cnblogs.com/xxez-d/p/5989944.html
https://www.cnblogs.com/runningTurtle/p/7092632.html
https://www.cnblogs.com/jiangwz/p/9197238.html
https://www.cnblogs.com/hujingnb/p/10181630.html
接口增強
在JDK1.8以前的版本中,定義一個接口時,所有的方法必須是抽象方法,不能有具體實現,這是Java語法規定的。但是在JDK1.8中定義一個接口時,在滿足特定的前提下,可以有方法的具體實現。這樣一個接口中可以有屬性,可以有抽象方法,也可以有具體的方法,這跟JDK1.8以前的接口比,明顯接口的功能變得強大了。
使用接口增強
接口中定義具體的方法實現是有限制的,它不能像我們在一個普通類那樣隨便定義方法實現,它只能定義default和static類型的方法。
在調用的時候,被default修飾的默認方法需要通過實現了該接口的類去調用,被static修飾的靜態方法可以通過接口名直接調用。
default和static不能用來修飾Object中的public方法,但是可以修飾Object中其他訪問修飾符修飾的方法。
代碼如下:
1 public class Demo { 2 public static void main(String[] args) { 3 NewInterface.testStatic(); 4 new NewInterfaceImpl().testDefault(); 5 } 6 } 7 8 interface NewInterface { 9 public String name = ""; 10 11 default void testDefault() { 12 System.out.println("測試新增的default方法"); 13 } 14 15 static void testStatic() { 16 System.out.println("測試新增的static方法"); 17 } 18 } 19 20 class NewInterfaceImpl implements NewInterface { 21 @Override 22 public void testDefault() { 23 NewInterface.super.testDefault(); 24 System.out.println("測試子類重寫的新增的static方法"); 25 } 26 }
運行結果如下:
1 測試新增的static方法 2 測試新增的default方法 3 測試子類重寫的新增的static方法
好處
想象這樣一中情況,當多個類實現一個接口的某個方法時,方法的具體實現代碼相同,這樣就會造成代碼重復問題。接口增強就相當於把公共的代碼抽離出來,放入接口定義中,這樣實現類對於該方法就不用重新定義,直接調用即可,這很好的解決了實現該接口的子類代碼重復的問題。
函數式接口
所謂的函數式接口,當然首先是一個接口,然后就是在這個接口里面只能有一個抽象方法,這種類型的接口也稱為SAM(Single Abstract Method)接口。
下面的代碼展示了一個簡單的函數式接口:
1 interface NewInterface { 2 void test(); 3 }
關於@FunctionalInterface注解
JDK1.8為函數式接口引入了一個新注解@FunctionalInterface,主要用於編譯級錯誤檢查,加上該注解,當你寫的接口不符合函數式接口定義的時候,編譯器會報錯。
加不加@FunctionalInterface對於接口是不是函數式接口沒有影響,該注解只是提醒編譯器去檢查該接口是否僅包含一個抽象方法。
下面的代碼展示了一個加了注解的函數式接口:
1 @FunctionalInterface 2 interface NewInterface { 3 void test(); 4 }
函數式接口里允許定義默認方法
函數式接口里是可以包含默認方法,因為默認方法不是抽象方法,其有一個默認實現,所以是符合函數式接口的定義的。
下面的代碼展示了一個帶有默認方法的函數式接口:
1 @FunctionalInterface 2 interface NewInterface { 3 void test(); 4 5 default void defaultRead() { 6 System.out.println("defaultRead() ..."); 7 } 8 9 default void defaultWrite() { 10 System.out.println("defaultWrite() ..."); 11 } 12 }
函數式接口里允許定義靜態方法
函數式接口里是可以包含靜態方法,因為靜態方法不能是抽象方法,是一個已經實現了的方法,所以是符合函數式接口的定義的。
下面的代碼展示了一個帶有靜態方法的函數式接口:
1 @FunctionalInterface 2 interface NewInterface { 3 void test(); 4 5 static void staticRead() { 6 System.out.println("staticRead() ..."); 7 } 8 9 static void staticWrite() { 10 System.out.println("staticWrite() ..."); 11 } 12 }
函數式接口里允許定義java.lang.Object里的public方法
函數式接口里是可以包含Object里的public方法,這些方法對於函數式接口來說,不被當成是抽象方法(雖然它們是抽象方法)。因為此接口的實現類一定是Object的子類,會自動實現這個接口中定義的抽象方法。
但也只能是public方法,其他訪問修飾符修飾的方法是不允許在函數式接口里定義的。
下面的代碼展示了一個帶有Object類的public方法的函數式接口:
1 @FunctionalInterface 2 interface NewInterface { 3 void test(); 4 5 @Override 6 public String toString(); 7 8 @Override 9 public boolean equals(Object obj); 10 11 // @Override 12 // public Object clone(); 13 }
JDK1.8新增的函數式接口
JDK1.8內置的函數式接口放在包java.util.function下,默認在JDK安裝路徑下的src.zip中。
Supplier接口
Supplier接口沒有參數,只有返回值,作用是為了產生對象。
1 @FunctionalInterface 2 public interface Supplier<T> { 3 T get(); 4 }
Consumer接口
Consumer接口有參數,但是沒有返回值,作用是為了進行某種操作,比如打印操作。
1 @FunctionalInterface 2 public interface Consumer<T> { 3 void accept(T t); 4 5 default Consumer<T> andThen(Consumer<? super T> after) { 6 Objects.requireNonNull(after); 7 return (T t) -> { accept(t); after.accept(t); }; 8 } 9 }
Function接口
Function接口有參數,也有返回值,作用是對參數進行處理並返回。
1 @FunctionalInterface 2 public interface Function<T, R> { 3 R apply(T t); 4 5 default <V> Function<V, R> compose(Function<? super V, ? extends T> before) { 6 Objects.requireNonNull(before); 7 return (V v) -> apply(before.apply(v)); 8 } 9 10 default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { 11 Objects.requireNonNull(after); 12 return (T t) -> after.apply(apply(t)); 13 } 14 15 static <T> Function<T, T> identity() { 16 return t -> t; 17 } 18 }
Predicate接口
Predicate接口有參數,也有返回值,但返回值是Boolean類型的值,作用是對傳入的參數進行某種判斷。
1 @FunctionalInterface 2 public interface Predicate<T> { 3 boolean test(T t); 4 5 default Predicate<T> and(Predicate<? super T> other) { 6 Objects.requireNonNull(other); 7 return (t) -> test(t) && other.test(t); 8 } 9 10 default Predicate<T> negate() { 11 return (t) -> !test(t); 12 } 13 14 default Predicate<T> or(Predicate<? super T> other) { 15 Objects.requireNonNull(other); 16 return (t) -> test(t) || other.test(t); 17 } 18 19 static <T> Predicate<T> isEqual(Object targetRef) { 20 return (null == targetRef) 21 ? Objects::isNull 22 : object -> targetRef.equals(object); 23 } 24 }
Lambda表達式
什么是Lambda表達式
通過使用表達式來提供一種清晰簡潔的方式來表示方法接口。
語法
參數列表 -> 方法體
如果參數列表有多個值,可以使用小括號包含,如果只有一條,則可以省略小括號。
如果方法體有多條語句,可以使用大括號包含,如果只有一條,則可以省略大括號,如果是return語句,則可以省略return關鍵字。
使用舉例
在創建一個線程並調用的時候,需要傳入一個Runnable接口的實現類。
使用Lambda表達式之前:
1 new Thread(new Runnable() { 2 @Override 3 public void run() { 4 System.out.println("Thread1 run() ..."); 5 } 6 }).start();
使用Lambda表達式之后:
1 new Thread(() -> System.out.println("Thread2 run() ...")).start();
變量作用域
Lambda表達式只能引用標記了final的外層局部變量,這就是說不能在Lambda內部修改定義在域外的局部變量,否則會編譯錯誤。
1 final int num = 100; 2 NewInterface ni = i -> String.valueOf(i + num); 3 System.out.println(ni.test(num));
Lambda表達式的局部變量可以不用聲明為final,但是必須不可被后面的代碼修改(即隱性的具有final的語義)。
1 final int num = 100; 2 NewInterface ni = i -> String.valueOf(i + num); 3 System.out.println(ni.test(num)); 4 // num++;
在Lambda表達式當中不允許聲明一個與局部變量同名的參數或者局部變量。
1 final int num = 100; 2 // NewInterface ni = num -> String.valueOf(num); 3 NewInterface ni = i -> String.valueOf(i); 4 System.out.println(ni.test(num));
方法引用
什么是方法引用
方法引用是JDK1.8中提出的用來簡化Lambda表達式的一種手段。它通過類名和方法名來定位到一個靜態方法或者實例方法。
語法
類或對象名::方法名或new
靜態方法引用
如果是靜態方法引用,那么語法是 類名::靜態方法名 ,並且方法調用傳入的參數就是方法用到的參數。
代碼如下:
1 public class Demo { 2 public static void main(String[] args) { 3 // 使用匿名內部類 4 // NewInterface ni = new NewInterface() { 5 // @Override 6 // public String test(String name) { 7 // return String.valueOf(name); 8 // } 9 // }; 10 // 使用Lambda表達式 11 // NewInterface ni = name -> String.valueOf(name); 12 // 使用方法引用 13 NewInterface ni = String::valueOf; 14 System.out.println(ni.test("name")); 15 } 16 } 17 18 @FunctionalInterface 19 interface NewInterface { 20 String test(String name); 21 }
運行結果如下:
1 name
實例方法引用
如果是實例方法引用,那么語法是 對象實例::方法名 ,並且方法調用傳入的第一個參數是方法的調用者,后面的參數才是方法的參數。
代碼如下:
1 public class Demo { 2 public static void main(String[] args) { 3 // 使用匿名內部類 4 // NewInterface ni = new NewInterface() { 5 // @Override 6 // public int test(String str) { 7 // return str.length(); 8 // } 9 // }; 10 // 使用Lambda表達式 11 // NewInterface ni = str -> str.length(); 12 // 使用方法引用 13 NewInterface ni = String::length; 14 System.out.println(ni.test("lenth")); 15 } 16 } 17 18 @FunctionalInterface 19 interface NewInterface { 20 int test(String str); 21 }
運行結果如下:
1 5
構造方法引用
如果是構造方法引用,那么語法是 類名::new ,並且方法調用傳入的參數就是構造方法的參數。
代碼如下:
1 public class Demo { 2 public static void main(String[] args) { 3 // 使用匿名內部類 4 // NewInterface ni = new NewInterface() { 5 // @Override 6 // public String test(String str) { 7 // return new String(str); 8 // } 9 // }; 10 // 使用Lambda表達式 11 // NewInterface ni = str -> new String(str); 12 // 使用方法引用 13 NewInterface ni = String::new; 14 System.out.println(ni.test("str")); 15 } 16 } 17 18 @FunctionalInterface 19 interface NewInterface { 20 String test(String str); 21 }
運行結果如下:
1 str