Lambda从入门到精通(一篇搞懂)
Lambda表達式
- 范例:觀察傳統開發中的問題
- 范例:使用Lambda表達式實現與之前完全一樣的功能
- Lambda表達式的幾種格式
- 使用Lambda表達式(無參)
- 使用Lambda表達式(有參)
- 使用Lambda表達式簡化(再度簡化Lambda表達式,把return語句也省略)
- Lambda 表達式使用總結
- 練習
- 方法引用
- 方法引用的三種形式
- 類引用靜態方法
- 對象引用非靜態方法
- 類引用普通方法
- 構造引用
- JDK8自帶函數式接口
- 功能型函數式接口
- 消費型函數式接口
- 供給型函數式接口
- 斷言型函數式接口
- 總結
? 從JDK1.8開始為了簡化使用者進行代碼的開發,專門提供有Lambda表達式的支持,利用此操作可以實現函數式的編程,對于函數式編程比較著名的語言有:Haskell、Scala,利用函數式的編程可以避免掉面向對象編程之中一些繁瑣的處理問題。
范例:觀察傳統開發中的問題
interface IMessage {public void send(String str); }public class JavaDemo {public static void main(String[] args) {IMessage i = new IMessage() {public void send(String str) { System.out.println(str);}};i.send("www.baidu.com");} }在這樣的程序里,實際上核心的功能只有一行語句System.out.println(str),但是為了這一行的核心語句,我們仍然需要按照完整的面向對象給出的結構進行開發。于是這些問題隨著技術的發展也是越來越突出了。
范例:使用Lambda表達式實現與之前完全一樣的功能
lambda 表達式的形式為 () -> {}; () 里面是函數式接口中抽象方法的形參, {} 中是抽象方法的實現。
interface IMessage {public void send(String str); }public class JavaDemo {public static void main(String[] args) {IMessage i = (str) -> {System.out.println(str);};i.send("www.baidu.com");} }現在在代碼中就十分簡潔了,只寫了關鍵語句System.out.println(str) ,于是利用這種形式就避免了面向對象復雜的結構化要求。這便是Lambda表達式的基本處理形式。
Lambda表達式如果要想使用,那么必須要有一個重要的實現要求:SAM(Single Abstract Method),接口中只有一個抽象方法。以IMessage接口為例,這個接口里面只是提供有一個send()方法,除此之外沒有任何其他方法定義,所以這樣的接口就被稱為函數式接口,而只有函數式接口才能被Lambda表達式所使用。
范例:錯誤的定義
interface IMessage {public void send(String str);public void say(); }public class JavaDemo {public static void main(String[] args) {IMessage i = (str) -> {System.out.println(str);};i.send("www.baidu.com");} }所以很多時候為了明確的標注出你是一個函數式接口,往往會在接口上面增加一行注釋@functionalInterface。但是,默認方法和靜態方法不會破壞函數式接口的定義。
之所以在JDK1.8之后提供有默認和靜態方法,也都是為函數式開發做準備。
Lambda表達式的幾種格式
-
方法沒有參數: () -> {};
-
方法有參數::(參數,…,參數) -> {};
下面看幾個例子:
使用Lambda表達式(無參)
要創建接口 IMessage 的實現類,如果該類只是使用一次,我們可以使用匿名內部類的方式,但是匿名內部類寫起來很麻煩。而 IMessage 接口中,只有一個抽象方法,是一個函數式接口,那么我們就可以使用 lambda 來代替匿名內部類。lambda 體就是接口的實現。
@FunctionalInterface interface IMessage {public void send(); }public class JavaDemo {public static void main(String[] args) {IMessage i = () -> {System.out.println("www.baidu.com");};i.send();} }上述寫法還是有點麻煩,在 -> 右邊的方法體中,如果只有一行語句,那么 可以省略大括號,直接一行搞定。
@FunctionalInterface interface IMessage {public void send(); }public class JavaDemo {public static void main(String[] args) {IMessage i = () -> System.out.println("www.baidu.com");i.send();} }注:抽象方法如果沒有參數,則 lambda 表達式不能省略 ();
使用Lambda表達式(有參)
IMath 接口中只有一個抽象方法,該方法有返回值,且有兩個參數。可以使用 lambda 進行簡化。
@FunctionalInterface interface IMath {public int add(int x, int y); }public class JavaDemo {public static void main(String[] args) {// t1,t2是形參名,隨便取,但是個數必須匹配形參IMath math = (t1, t2) -> { return t1 + t2;};System.out.println(math.add(20, 30));} }以上的表達式之中你會發現只有一行語句“ return t1 + t2;”,這時候可以進一步簡化。
使用Lambda表達式簡化(再度簡化Lambda表達式,把return語句也省略)
@FunctionalInterface interface IMath{public int add(int x , int y); } public class Demo01 {IMath math = (n1,n2)-> n1 + n2;System.out.println(math.add(10,20));}}利用Lambda表達式確實可以使得代碼更加簡便。
Lambda 表達式使用總結
- lambda 表達式形式為 ()->{},-> 左邊是抽象方法的形參列表, -> 是抽象方法的實現體。
- lambda 方法如果沒有參數或有兩個及以上的參數,則 小括號不能省略。
- lambda 方法如果只有一個參數,則小括號可以省略。
- lambda 方法體如果只有一行語句,則 大括號和return都可省略。
- 省略了大括號,則必須省略 return,省略了 return ,則必須省略 {},這倆要么成對出現,要么都不出現。
lambda 的本質就是函數式接口的一個實現類。
練習
Java中Comparator 接口使得開發人員可以定制排序。在 Arrays 的 sort 方法中,就可以傳入一個 Comparator 接口,進行定制排序。那么我們就可以使用 lambda 接口來簡化 Comparator 的實現。
匿名內部類原始寫法如下
public void test5() {Comparator<Integer> com=new Comparator<Integer>(){@Overridepublic int compare(Integer o1, Integer o2) {return Integer.compare(o1,o2);}};}lambda 簡化
compare 方法需要兩個參數,返回一個 int 類型的值。那么 () 中兩個參數,return Integer重寫的比較方法即可。
還可以使用 方法引用 進一步簡化:
public void test5() {Comparator<Integer> com= Integer::compare;}方法引用
方法引用的三種形式
- 對象 :: 非靜態方法
- 類 :: 靜態方法
- 類 :: 非靜態方法
注:方法引用規定,對象不能調用靜態方法,這和面向對象的思想一致。但類可以調用非靜態方法,這是面向對象中不允許的。
類引用靜態方法
語法格式: 類名稱::static方法名稱
第一步,我們自定義一個接口,該接口中只有一個抽象方法,是一個函數式接口。
第二步,隨便建立一個類,創建一個方法。這里要注意,創建的方法返回值類型和形參列表必須和函數式接口中的抽象方法相同。
第三步,創建函數式接口的實現類,我們可以使用方法引用。相當于實現類里的重寫的方法,就是方法引用的方法。這樣才能方法引用。
@FunctionalInterface interface IMessage<T,P> {public T transfer(P p); }class Supplier{public static String getStr(Integer integer) {return String.valueOf(integer);} }public class JavaDemo {public static void main(String[] args) {IMessage<String, Integer> msg = Supplier::getStr;System.out.println(msg.transfer(31415926));} }對象引用非靜態方法
語法格式: 實例化對象::普通方法;
有了類引用靜態方法的基礎,相信大家已經有了一點感覺。
對象引用非靜態方法,和類引用靜態方法一致。要求我們對象引用的方法,返回值和形參列表要和函數式接口中的抽象方法相同。
@FunctionalInterface interface IMessage {public double get(); }class Supplier{private Double salary;public Supplier() {}public Supplier(Double salary){this.salary = salary;}public Double getSalary() {return this.salary;} }public class JavaDemo {public static void main(String[] args) {Supplier supplier = new Supplier(9999.9);IMessage msg = supplier::getSalary;System.out.println(msg.get());} }類引用普通方法
語法格式: 類::普通方法
類引用普通方法就有點難以理解了。
當抽象方法中有兩個參數,且第一個參數是調用者,第二個參數是形參,則可以使用類::實例方法。
@FunctionalInterface interface IMessage<T, P> {// 要看成 T res = p1.compare(p2);public T compare(P p1, P p2); }@Data class Person {public Person() {}public Person(String name, Integer age) {this.name = name;this.age = age;}private String name;private Integer age;public boolean equal(Person per) {return this.name.equals(per.getName()) && this.age.equals(per.getAge());} }public class JavaDemo {public static void main(String[] args) {Person person1 = new Person("張三", 22);Person person2 = new Person("張三", 23);// 符合T res = p1.compare(p2);IMessage<Boolean, Person> msg = Person::equal;System.out.println(msg.compare(person1, person2));} }再來一個例子:
@FunctionalInterface interface IMessage<T, P, V> {// 看成 T res = p1.compare(p2);// public int compareTo(String anotherString){} 符合抽象方法的格式// int res = str1.compare(str2);public T compare(P p1, V p2); }public class JavaDemo {public static void main(String[] args) {IMessage<Integer,String,String> stringCompare = String::compareTo;Integer compare = stringCompare.compare("adc", "abd");System.out.println(compare);} }剛開始可能不適應,需要慢慢體會。
構造引用
語法格式: 類名稱::new
@FunctionalInterface interface IMessage<T, P, V> {// public T create(P p1, V p2); 符合抽象方法的要求public T create(P p1, V p2); }@Data class Person {public Person() {}public Person(String name) {this.name = name;}// 符合 public T create(P p1, V p2); 要求,故可以構造引用public Person(String name, Integer age) {this.name = name;this.age = age;}private String name;private Integer age; }public class JavaDemo {public static void main(String[] args) {IMessage<Person,String,Integer> msg = Person::new;Person person = msg.create("張三", 20);System.out.println(person);} }再來舉個構造引用的例子,函數式接口我就不自己創建了,使用 JDK 自帶的函數式接口,這些接口都位于 java.util.function 包下。后面會說。
看下 IntFunction< R > 這個接口
這個接口顧名思義,就是一個 int 的功能函數接口,可以將 int 轉換成其他類型,也就是 R,這個 R 由我們指定。那么有 int,肯定還有 long 等其它基本類型,有興趣可以自己看看,效果都一樣。
那么我們可以創建一個 User 類,該 user 類里面有一個 age 屬性,并且有一個有參的構造參數。
此時我們就可以來使用構造引用來創建 User 對象。
@Testpublic void constructReference() {// 構造引用創建 User 對象就是 IntFunction 的一個實現類IntFunction<User> userIntFunction = User::new;// 調用有參構造器創建 User 對象User user = userIntFunction.apply(10);System.out.println(user);}也可以通過 IntFunction< R > 來創建數組對象,舉個例子。
@Testpublic void constructReference2() {// 構造引用創建 Long[] 對象就是 IntFunction 的一個實現類IntFunction<Long[]> userIntFunction = Long[]::new;// 通過有參構造器來給數組長度賦值 ==> new Long[10];Long[] longArr = userIntFunction.apply(10);System.out.println("數組長度: " +longArr.length);System.out.println(Arrays.toString(longArr));}JDK8自帶函數式接口
在JDK1.8之中,提供有Lambda表達式和方法引用,但是你會發現如果由開發者自己定義函數式的接口,往往都需要使用@FunctionalInterface來進行大量的聲明,于是很多的情況下如果為了方便則可以引用系統中提供的函數式接口。
在系統之中專門提供有一個java.util.functional的開發包,里面可以直接使用函數式接口,在這個包下面一共有如下幾個核心的接口供我們使用。
功能型函數式接口
| @FunctionalInterface public interface Function<T,R>{ public R apply(T t); } | 消費 T 類型參數,返回 R 類型結果 | 如下所示 |
function 相當于是給一個參數,然后返回一個結果。
如果是給兩個參數,返回一個結果,那么就是 BiFunction。Bi 前綴即使 binary 的縮寫。
消費型函數式接口
消費性函數式接口,只能進行數據的處理操作,而沒有返回值
? · 在進行系統輸出的時候使用的是:System.out.println();這個操作只是進行數據的輸出和消費,而不能返回,這就是消費性接口。
| @FunctionalInterface public interface Consumer{ public void accept(T t); } | 接收一個 T 類型參數,但是不返回任何東西,消費型接口 | 如下 |
其實最常見的消費型接口的實現,就是 System.out.println(xxx) 了。 我們只管往方法中輸入參數,但是并沒有返回任何值。
Consumer 相當于是有來無回,給一個參數,但是無返回。
而如果是兩個參數,無返回,那么就是 BiConsumer。
當然我們也可以自定義消費性接口
class StringCompare {// 接收 StringBuilder ,但是不返回任何數據。public void fun(StringBuilder sb) {sb.append("World!");} }public class JavaDemo {public static void main(String[] args) {StringBuilder sb = new StringBuilder();sb.append("Hello ");Consumer<StringBuilder> consumer = new StringCompare()::fun;consumer.accept(sb);System.out.println(sb.toString());} }供給型函數式接口
| @FunctionalInterface public interface Supplier{public T get();} | 啥也不接受,但是卻返回 T 類型數據,供給型接口 | 如下 |
Supplier 相當于是無中生有,什么也不傳,但是返回一個結果。
像 String 類里的 toUpperCase() 方法,也是不接受參數,但是返回 String 類型,就可以看成這個供給型函數式接口的一個實現。
斷言型函數式接口
| @FunctionalInterface public interface Predicate{ public boolean test(T t);} | 傳入 T 類型參數,返回布爾類型,常常用于對入參進行判斷 | 如下 |
如果JDK本身提供的函數式接口可以被我們所使用,那么就沒必要重新去定義了。
總結
在開發中,使用 lambda 表達式能提高開發效率,寫出更加 “高大上” 的代碼。但 lambda 可讀性較差,如果沒接觸過過的程序員不友好。
想要掌握 lambda 表達式,還是要多練。從手寫一個接口的實現類,到匿名內部類,到 lambda。一開始不能直接寫出 lambda ,可以先用匿名內部類的方式,然后一步步簡化,多敲多練,最終就能游刃有余。
總結
以上是生活随笔為你收集整理的Lambda从入门到精通(一篇搞懂)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: “笑脸”手机设计构思
- 下一篇: UART串行通信模式