Lambda表達式的前世今生(來歷與概述)
Lambda表達式的前世------匿名類
以往,使用單一抽象方法的接口被用作函數類型。 它們的實例表示函數(functions)或行動(actions)。 自從 JDK 1.1 于 1997 年發布以來,創建函數對象的主要手段就是匿名類。匿名類,通俗地講,就是沒有類名,直接通過new關鍵字創建這個類的實例。下面是匿名類的一個例子:
java.util包中的Comparator接口
public interface Comparator<T> {int compare(T o1, T o2);
}
使用匿名類創建排序的比較方法(強制排序順序):
Collections.sort(words, new Comparator<String>() {public int compare(String s1, String s2) {return Integer.compare(s1.length(), s2.length());}
});
匿名類適用于需要函數對象的經典面向對象設計模式,特別是策略模式,上面的匿名類是排序字符串的具體策略。 然而,匿名類確實過于冗長。
Lambda表達式的今生
在 Java 8 中,語言形式化了這樣的概念,即使用單個抽象方法的接口是特別的,應該得到特別的對待。 這些接口現在稱為函數式接口,并且該語言允許你使用lambda 表達式或簡稱 lambda 來創建這些接口的實例。 Lambdas 在功能上與匿名類相似,但更為簡潔。 下面的代碼使用 lambdas 替換上面的匿名類。清晰明了:
Collections.sort(words,(s1, s2) -> Integer.compare(s1.length(), s2.length()));
Lambda表達式語法
JDK1.8之后引入的一種語法,他的寫法是使用一個->符號,箭頭將Lambda表達式分為左右兩部分,左邊寫的是實現的這個接口中的抽象方法中的形參列表,右邊就是對抽象方法的處理;
(實現的這個接口中的抽象方法中的形參列表) -> 抽象方法的處理
具體寫法
因為Lambda表達式的核心就是(實現的這個接口中的抽象方法中的形參列表) -> 抽象方法的處理
,因此根據形參列表與返回值的不同,Lambda表達式的具體寫法也不相同
無返回值有形參的抽象方法
public class MyTest1 {public static void main(String[] args) {MyInterface myInterface = new MyInterface() {@Overridepublic void show(int a, int b) {System.out.println(a + b);}};myInterface.show(20, 30);//50//簡寫1:方法名可以自己推斷出來MyInterface myInterface1 = (int a, int b) -> {System.out.println(a + b);};myInterface1.show(20, 40);//60//簡寫2:可以省略形參列表中的形參類型MyInterface myInterface2 = (a, b) -> {System.out.println(a + b);//70};myInterface2.show(20, 50);//簡寫3:如果抽象方法中只有一行代碼,可以省略方法體的大括號,當然,如果不止一行,就不能省略MyInterface myInterface3 = (a, b) -> System.out.println(a + b);myInterface3.show(20, 60);//80}
}
public interface MyInterface {public abstract void show(int a,int b);
}
主要特點:
- 可以省略方法名,IDEA會幫你自動檢測方法名;
- 可以省略方法中的形參類型;
- 如果對抽象方法的實現邏輯只有一行,可以省略方法體的大括號,當然如果不止一行,就不能省略了;
有返回值的抽象方法
public class MyTest2 {public static void main(String[] args) {MyInterface1 test1 = new MyInterface1() {@Overridepublic int test(int a, int b) {return a - b;}};System.out.println(test1.test(90, 8));//82//簡寫1:MyInterface1 test2 = (int a, int b) -> {return a - b;};System.out.println(test2.test(20, 10));//10//簡寫2:MyInterface1 test3 = (a, b) -> {return a - b;};System.out.println(test3.test(30, 10));//20//簡寫3:這個有返回值的方法,不能直接去掉大括號,還需要去掉return關鍵字MyInterface1 test4 = (a, b) -> a - b;System.out.println(test4.test(40, 10));//30}
}
public interface MyInterface1 {public abstract int test(int a,int b);
}
- 有返回值的方法,如果要去掉大括號,還需要去掉return關鍵字;
有一個形參的抽象方法
public class MyTest3 {public static void main(String[] args) {MyInterface2 myInterface = a -> a-20;myInterface.show(20);}
}
public interface MyInterface2 {public abstract int show(int a);
}
- 形參列表中只有一個參數,可以去掉形參的括號
Lambda表達式作為參數傳遞
參數的類型為函數式接口
import java.util.Arrays;
public class MyTest4 {public static void main(String[] args) {Integer[] ints = {89, 67, 23};Arrays.sort(ints, (o1, o2) -> o1-o2);System.out.println(Arrays.toString(ints));//[23, 67, 89]}
}
什么情況下使用
Lambda表達式不是萬能的,他需要函數式接口的支持,只有當某個方法需要傳入的參數類型為函數式接口或者作為方法返回值時,才能使用。
什么是函數式接口:
函數式接口的定義是: 只包含一個抽象方法的接口,稱為函數式接口;
其實我們的Lambda表達式就是對函數式接口的一種簡寫方式,所以只有是函數式接口,我們才能用Lambda表達式;再換句話說,Lambda表達式需要函數式接口的支持,那函數式接口我們可以自己定義,當然JDK1.8也給我們提供了一些現成的函數式接口;
//自定義一個函數式接口
@FunctionalInterface//此注解用來表明這是一個函數式接口
public interface MyInterFace<T> {
//函數式接口只有一個抽象方法
void getValue(T t);
}
//自定義的函數式接口的Lambda表達式
MyInterFace<String> face=(x)->System.out.println(x);
- 可以通過 Lambda 表達式來創建該接口的對象,我們可以在任意函數式接口上使用@FunctionalInterface 注解,這樣做可以檢查它是否是一個函數式接口;
- 為什么只能有一個抽象方法,如果有多個抽象方法,這個接口不是函數式接口,簡寫的時候省略了方法名,IDEA不能知道到底重寫的是哪一個方法,不能推斷出來;
- 注解寫在接口聲明上面,如果不報錯,就不是函數式接口;
- JDK1.8之后,提供了很多函數式接口,作為參數傳遞;
常用的函數式接口
Java 8在java.util.function包下預定義了大量的函數式接口供我們使用
我們重點學習下面4個接口
- Supplier接口
- Consumer接口
- Predicate接口
- Function接口
Supplier接口
Supplier< T >:包含一個無參的方法
- T get():獲得結果
- 該方法不需要參數,他會按照某種實現邏輯(由Lambda表達式實現)返回一個數據
- Supplier< T >接口也被稱為生產型接口,如果我們指定了接口的泛型是什么類型,那么接口中的get方法就會產生什么類型的數據供我們使用
import java.util.function.Supplier;public class SupplierDemo {public static void main(String[] args) {String s = getString(()->"張三");int i = getInteger(()->18);System.out.println(s+","+i);}//定義一個方法,返回一個字符串數據private static String getString(Supplier<String> sup){return sup.get();}//定義一個方法,返回一個整數數據private static Integer getInteger(Supplier<Integer> sup){return sup.get();}
}
Consumer接口
Consumer< T >:包含兩個方法
- void accept(T t):對給定的參數執行此操作
- default Consumer < T > andThen(Consumer after):返回一個組合的Consumer,依次執行此操作,然后執行after操作
- Consumer< T >接口也被稱為消費型接口,它消費的數據類型由泛型指定
import java.util.function.Consumer;public class ConsumerDemo {public static void main(String[] args) {operatorString("張三", (s) -> System.out.println(s));operatorString("張三", (s) -> System.out.println(s), (s)-> System.out.println(new StringBuilder(s).reverse().toString()));}//定義一個方法,消費一個字符串數據private static void operatorString(String name, Consumer<String> con) {con.accept(name);}//定義一個方法,用不同的方式消費同一個字符串兩次private static void operatorString(String name, Consumer<String> con1,Consumer<String> con2) {
// con1.accept(name);
// con2.accept(name);//返回一個組合的Consumercon1.andThen(con2).accept(name);}
}
Predicate接口
Predicate< T >:常用的四個方法
- boolean test(T t):對給定的參數進行判斷(判斷邏輯由Lambda表達式實現),返回一個布爾值
- default Predicate< T > negate():返回一個邏輯的否定,對應邏輯非
- default Predicate< T > and():返回一個組合判斷,對應短路與
- default Predicate< T > or():返回一個組合判斷,對應短路或
- isEqual():測試兩個參數是否相等
- Predicate< T >:接口通常用于判斷參數是否滿足指定的條件
import java.util.function.Predicate;public class ConsumerTest {public static void main(String[] args) {boolean string = chenkString("張三", s -> s.equals("張三"));System.out.println(string);boolean hello = chenkString("hello", s -> s.length() > 8, s -> s.length() < 18);System.out.println(hello);}//判定給定的字符串是否滿足要求
// private static boolean chenkString(String s, Predicate<String> pre){
// return pre.test(s);
// }private static boolean chenkString(String s, Predicate<String> pre){return pre.negate().test(s);}// private static boolean chenkString(String s, Predicate<String> pre, Predicate<String> pre1){
// return pre.and(pre1).test(s);
// }private static boolean chenkString(String s, Predicate<String> pre, Predicate<String> pre1){return pre.or(pre1).test(s);}
}
Function接口
Runction<T,R>:常用的兩個方法
- R apply(T t):將此函數應用于給定的參數
- default< V >:Function andThen(Function after):返回一個組合函數,首先將該函數應用于輸入,然后將after函數應用于結果
- Function<T,R>:接口通常用于對參數進行處理,轉換(處理邏輯由Lambda表達式實現),然后返回一個新值
import java.util.function.Function;public class ConsumerTest {public static void main(String[] args) {convert("18", s -> Integer.parseInt(s));convert(20, integer -> String.valueOf(integer + 18));convert("245", s -> Integer.parseInt(s), integer -> String.valueOf(integer + 18));} //定義一個方法,把一個字符串轉換成int類型,在控制臺輸出private static void convert(String s, Function<String, Integer> fun) {int i = fun.apply(s);System.out.println(i);} //定義一個方法,把int類型數據加上一個整數之后,轉換為字符串在控制臺輸出private static void convert(int i, Function<Integer, String> fun) {String s = fun.apply(i);System.out.println(s);} //定義一個方法,把一個字符串轉換int類型,把int類型的數據加上一個整數后,轉換成字符串在控制臺輸出private static void convert(String s, Function<String, Integer> fun1, Function<Integer, String> fun2) {String s1 = fun2.apply(fun1.apply(s));System.out.println(s1);}
}
方法引用
先來看一下什么是方法引用:
方法引用其實是Lambda表達式的另一種寫法,當要傳遞給Lambda體的操作,已經有實現的方法了,可以使用方法引用;
注意: 實現抽象方法的參數列表,必須與方法引用方法的參數列表保持一致!
方法引用:使用操作符::將方法名和對象或類的名字分隔開來,三種主要使用情況為:
對象::實例方法
類::靜態方法
類::實例方法
對象::實例方法
import java.util.function.Consumer;public class MyTest {public static void main(String[] args) {Consumer<String> consumer = new Consumer<String>() {@Overridepublic void accept(String s) {System.out.println(s);}};consumer.accept("aaaaaaaaaaaaaa");//aaaaaaaaaaaaaa//簡寫1:Consumer<String> consumer1 = (String s) -> {System.out.println(s);};consumer1.accept("abc");//abc//簡寫2:Consumer<String> consumer2 = (s) -> System.out.println(s);consumer2.accept("bcd");//bcd//簡寫3:Consumer<String> consumer3 = System.out::println;consumer3.accept("abc");//abc}
}
為什么可以寫成上述方式?
因為:System.out.println(s);與void accept(String s)一樣,都是使用s作為參數,返回值是void,因此就可以簡寫為簡寫3;
類::靜態方法
import java.util.function.BinaryOperator;public class MyTest1 {public static void main(String[] args) {BinaryOperator<Double> operator = new BinaryOperator<Double>(){@Overridepublic Double apply(Double o, Double o2) {return Math.max(o,o2);}};System.out.println(operator.apply(2.13, 3.12));//3.12BinaryOperator<Double> operator2 = (o, o2) -> Math.max(o,o2);System.out.println(operator2.apply(2.13, 3.12));//3.12BinaryOperator<Double> operator3 = Math::max;Double max = operator3.apply(5.0, 20.0);System.out.println(max);//20.0}
}
因為Math.max()所需要的參數以及返回值與重寫的accpet()一樣,因此可以簡寫為類::靜態方法
;
下面再舉一個例子
import java.util.Comparator;
public class MyTest2 {public static void main(String[] args) {Comparator<Integer> comparator = new Comparator<Integer>() {@Overridepublic int compare(Integer o1, Integer o2) {return Integer.compare(o1,o2);}};System.out.println(comparator.compare(20, 12));//1Comparator<Integer> comparator1 = Integer::compareTo;System.out.println(comparator1.compare(20, 12));//1}
}
類::實例方法
import java.util.Comparator;
public class MyTest2 {public static void main(String[] args) {Comparator<String> comparator = new Comparator<String>() {@Overridepublic int compare(String o1, String o2) {return o1.compareTo(o2);}};System.out.println(comparator.compare("20", "12"));//1Comparator<String> comparator1 = String::compareTo;System.out.println(comparator1.compare("20", "12"));//1}
}
為什么可以這樣寫?、
傳遞過來的兩個參數,一個作為調用者,一個作為參數,這時候,使用類::實例方法
簡寫;
構造引用
格式:ClassName::new
與函數式接口相結合,自動與函數式接口中方法兼容。可以把構造器引用賦值給定義的方法,與構造器參數列表要與接口中抽象方法的參數列表一致!
public class MyTest4 {public static void main(String[] args) {Student student = new Student("張三", 23);BiFunction<String, Integer, Student> function = new BiFunction<String, Integer, Student>() {@Overridepublic Student apply(String s, Integer integer) {return student;}};BiFunction<String, Integer, Student> function1 = (s, integer) -> student;BiFunction<String, Integer, Student> function2 = Student::new;}
}