?前幾天同事跑過來說有行代碼看不懂窥翩,問我懂不懂。代碼是android 8.0原生代碼氓仲,路徑是frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java水慨,
代碼如下:
44 mTransitionsController = new LightBarTransitionsController(context,
45 this::setIconTintInternal);
public LightBarTransitionsController(Context context, DarkIntensityApplier applier)
?this::setIconTintInternal是什么鬼?沒見過熬纯浮晰洒!LightBarTransitionsController的構(gòu)造方法需要傳一個(gè)DarkIntensityApplier類,但是實(shí)參卻是this::setIconTintInternal啥箭,看著像是一個(gè)方法谍珊。再看DarkIntensityApplier的定義,它其實(shí)是一個(gè)接口:
202 public interface DarkIntensityApplier {
203 void applyDarkIntensity(float darkIntensity);
204 }
?通過搜索發(fā)現(xiàn)this::setIconTintInternal的用法叫方法引用急侥,是java 8的新特性砌滞。下面我們就來探究一下它的用法和原理。在開始之前坏怪,我們先列一下對(duì)上面代碼的疑問:
- 為什么this::setIconTintInternal可以被轉(zhuǎn)換成一個(gè)接口贝润?
- setIconTintInternal 跟 DarkIntensityApplier接口是什么關(guān)系?
- setIconTintInternal 什么時(shí)候會(huì)被調(diào)用铝宵?
方法引用入門
?方法引用和lambda表達(dá)式一樣打掘,都是java 8為了增強(qiáng)java語言表現(xiàn)力而引入的华畏。我們舉最常用的click listener這個(gè)例子看一下它們的用法。
?java 8之前我們的寫法是這樣的:
View.OnClickListener listener = new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "click");
}
};
button.setOnClickListener(listener);
?使用lambda表達(dá)式的寫法是這樣的:
button.setOnClickListener(v -> Log.d(TAG, "click"));
?lambda表達(dá)式的形式是尊蚁, 參數(shù) -> 執(zhí)行語句; 如果有多個(gè)參數(shù)和多條執(zhí)行語句時(shí)可以這樣寫: (參數(shù)1, 參數(shù)2) -> {執(zhí)行語句; 執(zhí)行語句2;}
?使用方法引用的寫法是這樣的:
button.setOnClickListener(this::onButtonClick);
private void onButtonlick(View v) {
Log.d(TAG, "click");
}
?我們發(fā)現(xiàn)lambda表達(dá)式最簡(jiǎn)練亡笑,方法引用次之。而方法引用有一個(gè)lambda表達(dá)式做不到的地方横朋,就是把實(shí)現(xiàn)和調(diào)用分離÷匚冢現(xiàn)在我們就可以回答第一個(gè)問題了,為什么this::setIconTintInternal可以被轉(zhuǎn)換成一個(gè)接口琴锭?因?yàn)閠his::setIconTintInternal是一個(gè)方法引用晰甚,在編譯階段,就會(huì)把它編譯成對(duì)應(yīng)的接口决帖。當(dāng)然編譯通過有以下條件:
- 這個(gè)接口只有一個(gè)抽象方法压汪;也就是這個(gè)接口是一個(gè)函數(shù)式接口(FunctionalInterface);
- 被引用的方法參數(shù)必須和接口的抽象方法的參數(shù)一致古瓤,方法名和返回類型不要求一致;
如果方法參數(shù)不一致會(huì)報(bào)下面的編譯錯(cuò)誤:
error: incompatible types: invalid method reference
incompatible types: View cannot be converted to int
?我們可以使用類靜態(tài)方法腺阳、對(duì)象的實(shí)例方法落君、構(gòu)造方法作為方法引用;引用構(gòu)造方法的寫法比較特殊亭引,形式如下绎速,ClassName::new。
一行代碼實(shí)現(xiàn)列表排序
?從上面的介紹中我們知道lambda表達(dá)式和方法引用只適用于函數(shù)式接口焙蚓,除了上面的 Click listener纹冤,還有哪些常用的函數(shù)式接口呢?用于排序的Comparator是一個(gè)购公。下面我們來看如何使用方法引用用一行代碼實(shí)現(xiàn)列表排序萌京。我們定義一個(gè)Person類,根據(jù)age進(jìn)行排序宏浩。
public class Person {
private int age;
public int getAge() {return age;}
public void setAge(int age) {this.age = age;}
}
List<Person> personList = new ArrayList<>();
personList.add(...);
?在java 8之前知残,我們會(huì)定義一個(gè)Comparator,在compare方法里實(shí)現(xiàn)排序條件:
class MyComparator implements Comparator<Person>{
@Override
public int compare(Person o1, Person o2) {
return o1.age - o2.age;
}
}
Collections.sort(personList, new MyComparator());
?使用lambda表達(dá)式比庄,我們可以不定義MyComparator求妹,
Collections.sort(personList, (p1, p2) -> p1.age - p2.age);
?使用方法引用如何實(shí)現(xiàn)呢?
Collections.sort(personList, comparing(Person::getAge()));
?這里借助了Comparator 里的 comparing 方法實(shí)現(xiàn)比較操作佳窑,不再需要人工做比較制恍。另外,利用List接口里的sort方法神凑,可以做到更簡(jiǎn)化:
personList.sort(comparing(Person::getAge));
?如果我們?yōu)?Comparator 接口增加一個(gè)默認(rèn)方法 reversed()(產(chǎn)生一個(gè)逆序比較器)净神,我們就可以非常容易的在前面代碼的基礎(chǔ)上實(shí)現(xiàn)降序排序。
people.sort(comparing(Person::getAge).reversed());
總結(jié)
?上面我們通過列表排序的例子,使用lambda表達(dá)式和方法引極大的簡(jiǎn)化了代碼强挫。也基本解決了文章開始的疑問岔霸。但是很難說做到了融會(huì)貫通、舉一反三俯渤。后面將繼續(xù)探究java對(duì)函數(shù)式接口的支持呆细。敬請(qǐng)期待~
參考
深入理解Java 8 Lambda(語言篇——lambda,方法引用八匠,目標(biāo)類型和默認(rèn)方法)
30分鐘入門Java8之lambda表達(dá)式
30分鐘入門Java8之方法引用