負一、知道啥是匿名內部類不?
要使用lambda,我覺得你至少得明白匿名內部類是個啥。“o -> o.getName”是lambda表達式,"Book::getName"也是一個lambda表達式,表達式表達式,表達的是什麼呢?當你在看到這個式子的時候不懵逼嗎?你好像知道要取個什麼東西的名字,但是是怎麼取名字的,取出來的名字怎麼處理,你真的知道嘛?懵逼不,不懂匿名內部類的時候,擱我我也懵。
所以我在準備之前還要給你介紹一下lambda爲何而存在,這樣你才能知道在什麼時候能夠用它,再然後,你才能開始學習它怎麼使用。
Let's start:
舉個栗子,數組排序見過吧?
List<Long> num = Lists.newArrayList(1L,2L,5L,3L,4L,30L,15L,0L,8L,2L);
num.sort(new Comparator<Long>() {
@Override
public int compare(Long o1, Long o2) {
return (int)(o1-o2);
}
});
Comparator本來是一個接口,它有一個唯一的未實現的抽象方法,那就是compare(Long o1, Long o2)這個方法。
但是你是不是發現了我既然知道我要的是這個接口(Comparator),我也不用選我要實現的哪個抽象方法(就一個compare沒實現了)
——那爲啥還要我特地說一聲我實現的是這個接口(Comparator)的這個方法(compare(o1,o2))?嗯?
於是這幫搞語言的懶鬼決定整一個更方便寫的東西。
“Lambda”
怎麼個方便法呢?
我們先來整理一下我們寫的那一堆垃圾中,有哪些是有價值的可回收垃圾
...
是不是隻有一個入參、一個方法體內容,最多再有個泛型確定方法體可以調用哪些方法處理入參。
於是懶鬼們爲了方便更廣大的懶鬼們,說,那你們給個入參給個處理方法,剩下的我們幫你寫唄。
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
(o1, o2) -> o1-o2
↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
你讓我這麼給我就這麼給唄,其他的想都不要想,分號都沒得(* ̄︶ ̄)
附上最後它的模樣
List<Long> num = Lists.newArrayList(1L,2L,5L,3L,4L,30L,15L,0L,8L,2L);
num.sort((o1, o2) -> (int) (o1-o2));
真·一行更比五行強👍!
(以上的場景都是我yy的)
P.S.:順便說一句,只有一個抽象方法的接口,就叫函數式接口,你要是不能判斷,可以在接口前加上@FunctionalInterface註解來校驗,這個註解在程序中並不會有什麼具體的作用,只是會告訴編譯器更嚴格地檢查這個接口的代碼,保證此接口只有一個抽象的方法。
零、嘮前準備:
用到哪些實體和接口呢?
1.沒事兒找事兒幹類:
public class JustDoSomething {
//非靜態方法
public int justDoSomething(Object o1, Object o2){
System.out.println(-1);
return -1;
}
//靜態方法
public static int justDoSomeOtherThing(Object o1, Object o2){
System.out.println(-2);
return -2;
}
}
沒了
一、lambda介紹:
1.lambda的本質是?
一個可以代替特定匿名內部類(有且只有一個未實現的抽象方法)的表達式。可以用下面的代碼來說明一下
/**
* 這是剛剛的排序方法
*/
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
可以看到,在lambda出現的位置上,本來應該用一個實現了比較器接口的實現類來當作參數的,所以現在的情況就是lambda能夠替代這個類。
我們再來看一個方法
Thread t = new Thread(() -> System.out.println(1));
這個線程的構造方法是
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
那麼問題來了,在sort中我lambda可以表示Comparator,在new Thread的時候我又表示Runnable了?
莫非lambda這個狗東西見人說人話,見鬼說鬼話?
沒錯!
lambda的類型就是所謂的“目標類型”!
通俗的說,就是,只要你符合函數式接口的標準,你要啥我給啥,啥姿勢都行。
2.lambda可以表示成哪些形式?
前面已經展示過lambda表達式長什麼樣子了,總結來說,大部分lambda表達式可以歸納爲下面的形狀
(參數們) -> {對參數的操作}
這種極簡的表達形式,也會導致理解上的偏差,我們可以分開來進行理解:
① "(參數)"部分:參數部分的標準形式應該是(a, b, .......),如果參數只有一個,a -> {方法體}也是可以的,其他時候括號均不可省略,包括無參與複數個參數的時候,另外,如果你想要加上參數的種類也是被允許的(int a, String b, ......),但咱本來不就是學lambda來偷懶的嘛!
② "->"箭頭部分:這個只要英文的就行了沒啥好說的。記得指向的是方法體啊!
③ "{對參數的操作}"方法體部分:這一部分只要記住兩點,第一點,只有複數條語句才需要加"{}",第二點,如果只有一條語句並且是形如"return a"這樣的語句,那麼連"return "都可以省略掉。(究極懶惰)
注:這裏的參數爲什麼能這樣子省略呢?是因爲lambda表示的對應匿名內部類中的對應抽象方法只有一個,而編譯器能夠推導出來各個參數對應的類型,所以你不寫也沒問題的啦。
但是也不一定所有的lambda都是那個形狀的啦。
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓看👀↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
3.lambda的特殊形式:
如果你的表達式的方法體部分的返回值只需要調用另一個類的一個方法的返回值,同時,抽象方法的參數可以原封不動地傳入調用方法中,那麼你可以把lambda寫成
List<Long> num = Lists.newArrayList(1L,2L,5L,3L,4L,30L,15L,0L,8L,2L);
num.sort(JustDoSomething::justDoSomeOtherThing);
這裏,justDoSomeOtherThing是靜態方法,所以可以直接通過類類引用,如果不是靜態方法,則要先實例對象,
List<Long> num = Lists.newArrayList(1L,2L,5L,3L,4L,30L,15L,0L,8L,2L);
JustDoSomething jds = new JustDoSomething();
num.sort(jds::justDoSomething);
如果你的抽象方法是要求第一個參數傳入處理對象的實例,比如這樣
@FunctionalInterface
public interface Job {
void doSomeJobThing(String s, int a, int b);
}
期望的實現類是這樣的(調用第一個參數的方法,根據後面的參數做一些操作):
public class JobImpl implements Job{
@Override
public String doSomeJobThing(String s, int a, int b) {
return s.substring(a,b);
}
}
那麼,你完全可以這麼寫:
Job job = String::substring;
是不是快很多!不過一定要注意哦,這是對參數有要求的。↓↓↓↓
接口的抽象方法的第一個參數是提供處理方法的對象。
最後一種情況,如果你的返回值只需要一個某個類的新的實例對象:
@FunctionalInterface
public interface Job {
JobImpl getInstance();
}
我懶得改名字了↓
public class JobImpl{
}
這時候就可以:
Job job = JobImpl::new;
OK,懶人助手lambda工具的使用就基本講完了~
下面整理一下lambda和匿名內部類的區別吧!
二、lambda和匿名內部類的區別:
你是誰 | lambda | 匿名內部類 |
---|---|---|
異 | ||
工作量 | 少的一批 | 比左邊多 |
this關鍵詞 | this指向外部類 |
this指向內部類自己 |
super關鍵詞 | 同上 | 同上 |
可實現的接口與類 | 只能是函數式接口 | 不限於函數式接口,可以實現有多個抽象方法的接口與抽象類 |
是否能使用接口的的默認方法 | 不能使用(因爲沒法用this指向接口類) | 可以使用(使用this關鍵詞調用自身的方法) |
同 | ||
對外部類成員變量的引用 | 引用則會給外部類的成員變量加上隱性的final關鍵字,對該變量再賦值則會讓程序報錯。(此處涉及到了閉包的概念,我會在別的筆記裏說) |
在@山鬼謠me的這篇文章(原文鏈接見文末)中指出了使用lambda可能會遇到這樣的問題
假設有這麼一個函數式接口:
interface Task{ public void execute(); }
和這兩個同名方法
public static void doSomething(Runnable r){ r.run(); } public static void doSomething(Task a){ a.execute(); }
如果用匿名類實現Task:// 沒有歧義 doSomething(new Task() { public void execute() { System.out.println("Danger danger!!"); } });
但如果用lambda表達式:// 存在歧義,到底調用的是Runnable重載方法還是Task重載方法 doSomething(() -> System.out.println("Danger danger!!"));
解決辦法:可以通過強制轉換來解決。
doSomething((Task)() -> System.out.println("Danger danger!!"));
————————————————
版權聲明:本文爲CSDN博主「山鬼謠me」的原創文章,遵循CC 4.0 by-sa版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/u013066244/article/details/90644711
三、寫在文末(怎麼三就文末了):
本文的參考學習資料大部分來自瘋狂java講義(第4版),部分來源於
http://blog.oneapm.com/apm-tech/226.html#comment
這篇文章。
順便,diss一下這個unfriendly網站