Android進階之Kotlin高階函數和Lambda表達式詳細講解
前言
Lambda語法在Java中已經被廣泛的運用,我們在開發Android中幾乎上每一個項目也會在項目中接入Lambda插件,因為Lambda確實能簡少很多的代碼量。
無獨有偶,在Kotlin中也是Lambda語法的,在這篇文章中就詳細的為大家講解Lambda語法的編寫與使用
一、kotin高階函數詳解
1、高階函數是將函數用作參數或返回值的函數。這種函數的一個很好的例子是 lock(),它接受一個鎖對象和一個函數,獲取鎖,運行函數并釋放鎖:
- fun <T> lock(lock: Lock, body: () -> T): T {
- lock.lock()
- try {
- return body()
- }
- finally {
- lock.unlock()
- }
- }
body 擁有函數類型:() -> T,所以它應該是一個不帶參數并且返回 T 類型值的函數。它在 try{: .keyword }-代碼塊內部調用、被 lock 保護,其結果由lock()函數返回。如果我們想調用lock()函數,我們可以把另一個函數傳給它作為參數(參見函數引用)
2、如果一個函數接收另一個函數作為參數,或者返回值的類型是另一個函數,那么該函數就稱為高階函數
函數類型,基本規則如下:
(String,Int) -> Unit
現在將上述函數類型添加到某個函數的參數聲明或者返回值聲明上,那么這個函數就是一個高階函數了,例如
fun example(func: (String, Int) -> Unit) {
func("hello", 123)
}
可以看到這里的 example() 函數接收到了一個函數類型參數,因此 example() 函數就是一個高階函數;
這里我準備定義一個叫作 num1AndNum2() 的高階函數,讓它接收兩個整形和一個函數類型的參數。我們會在 num1AndNum2() 函數中對傳入的兩個整型參數進行某種運算,并返回運行結果。但具體進行什么運算是由傳入的函數類型參數決定的
新建一個名為 Test1.kt 文件
- fun num1AndNum2(num1:Int,num2:Int,operation:(Int,Int)->Int):Int{
- val result = operation(num1,num2)
- return result
- }
- fun plus(num1: Int, num2: Int): Int {
- return num1 + num2
- }
- fun minus(num1: Int, num2: Int): Int {
- return num1 - num2
- }
- main() 函數
- fun main(){
- val num1 = 100
- val num2 = 80
- val result1 = num1AndNum2(num1,num2, ::plus)
- val result2 = num1AndNum2(num1,num2,::minus)
- println("result1:"+result1)
- println("result2:"+result2)
- }
- result1:180
- result1:20
::plus 和 ::minus 的寫法,這是一種函數引用方式的寫法,表示將 plus() 和 minus() 函數作為參數傳遞給 num1AndNum2() 函數
如果每次調用任何高階函數時都還得先定義一個與其函數類型參數相匹配的函數,是不是太復雜了?沒錯,因此 Kotlin 還支持其他多種方式來調用高階函數,比如 Lambda 表達式、匿名函數、成員引用等。其中 Lambda 表達式是最常見也是最普遍的高階函數調用方式,剛才的代碼使用 Lambda 表達式來實現(Lambda 表達式最后一行自動作為返回值),plus() 和 minus() 函數可以刪掉了
- fun main() {
- val num1 = 100
- val num2 = 80
- val result1 = num1AndNum2(num1, num2) { n1, n2 ->
- n1 + n2
- }
- val result2 = num1AndNum2(num1, num2) { n1, n2 ->
- n1 - n2
- }
- println("result1:" + result1)
- println("result2:" + result2)
- }
- fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {
- val result = operation(num1, num2)
- return result
- }
3、閉包函數
閉包函數 一個函數的返回值是函數,函數的內部包含另一個函數,可以是有參無參的匿名函數
- fun main(args: Array<String>) {
- val mm = aaa()
- println(mm())
- println(mm())
- println(mm())
- println(mm())
- println(mm())
- val kk = bbb()
- println(kk("shadow")) //shadow --- 1
- println(kk("shadow")) //shadow --- 2
- println(kk("shadow")) //shadow --- 3
- println(kk("shadow")) //shadow --- 4
- println(kk("shadow")) //shadow --- 5
- }
- //閉包函數 就是函數作為返回參數
- fun aaa(): () -> (Int) {
- var current = 10
- return fun(): Int {
- return current++
- }
- }
- fun bbb(): (String) -> (String) {
- var current = 0;
- return fun(str: String): String {
- current++;
- return "$str --- $current";
- }
- }
4、kotin中高階函數案例
map 變換
- fun main(args: Array<String>) {
- val list = listOf(1, 2, 3, 4, 5, 6)
- val newList = list.map {
- //對集合中的數據進行操作,然后賦值給新的集合
- (it * 2).toString()
- }.forEach(::println) //2 4 6 8 10 12
- val doubleList = list.map {
- it.toDouble()
- }.forEach(::print) //1.0 2.0 3.0 4.0 5.0 6.0
- //函數作為參數的第二種方式調用 類名::方法名
- val doubleList2 = list.map(Int::toDouble).forEach(::print) ////1.0 2.0 3.0 4.0 5.0 6.0
- }
flatMap 對集合的集合進行變換
- fun main(args: Array<String>) {
- val list = arrayOf(
- 1..5,
- 50..55
- )
- //把多個數組集合變成一個數組,并且對數據進行變換
- val mergeList = list.flatMap { intRange -> //集合內的集合 1..5 , 50..55
- intRange.map { intElement -> //集合內集合遍歷 1,2,3,4,5
- "No.$intElement"
- }
- }
- //No.1 , No.2 , No.3 , No.4 , No.5 , No.50 , No.51 , No.52 , No.53 , No.54 , No.55 ,
- mergeList.forEach { print("$it , ") }
- println()
- //直接多個數組集合變換成一個結集合
- val newList = list.flatMap {
- it
- }
- //1 , 2 , 3 , 4 , 5 , 50 , 51 , 52 , 53 , 54 , 55 ,
- newList.forEach { print("$it , ") }
- }
filter 篩選
- fun main(args: Array<String>) {
- val list = arrayOf(
- 1..5,
- 2..3
- )
- val newList = list.flatMap {
- it
- }
- //篩選 集合中數據 > 2的item
- val filterList = newList.filter { it > 2 }
- filterList.forEach(::print) //3453
- //篩選 集合中下標是奇數item
- val filterIndexList = newList.filterIndexed { index, i -> index % 2 == 0; }
- filterIndexList.forEach { print(it) } //1 3 5 3
- }
forEach
- fun main(args: Array<String>) {
- var list = listOf(1, 2, 3, 4, 5, 6)
- list.forEach(::println)
- val newList = arrayListOf<String>() --->1,2,3,4,5,6
- list.forEach {
- newList.add((it * 2).toString()) --->2,4,6,8,10,12
- }
- newList.forEach(::println)
- }
下面我們就來介紹Lambda
二、Lambda表達式詳解
1、Lambda表達式是什么?
- Lambda表達式是JDK8推出一個重要的新特性,雖然看著很高大上,其實Lambda表達式的本質只是一個”語法糖”,習慣了面向對象編程的思想,一開始看起來會有點不習慣這種語法形式,但如果你學過C#,你就會發現語法和C#中的“委托”很像;
- 大家都知道,在Java中萬物皆對象,Java 一直都致力維護其對象至上的特征,函數對 Java 而言雖然重要,但在 Java 的世界里,函數無法獨立存在,只能依賴于對象來調用。在函數式編程語言中,函數是一等公民,它們可以獨立存在,你可以將其賦值給一個變量,或將他們當做參數傳給其他函數。JavaScript 就是函數式編程語言最典型的代表;
- 函數式語言提供了一種強大的功能——閉包,相比于傳統的編程方法有很多優勢,閉包是一個可調用的對象,它記錄了一些信息,這些信息來自于創建它的作用域。因此Java 現在提供的最接近閉包的概念便是 Lambda 表達式,雖然閉包與 Lambda 表達式之間存在顯著差別,但至少 Lambda 表達式是閉包很好的替代者;
- 使用Lambda表達式的目的就是取代大部分的匿名內部類,讓我們能寫出更簡潔優雅的 Java 代碼,尤其在集合的遍歷和其他集合操作中,可以極大地優化代碼結構。JDK 也提供了大量的內置函數式接口供我們使用,使得 Lambda 表達式的運用更加方便、高效;
- 如果在你沒有熟練掌握Lambda表達式時,不建議亂用,因為不使用Lambda表達式,你同樣可以實現相應功能,只把它當做一種錦上添花的工具就可以了;
2、Lambda表達式語法結構
Lambda表達式基礎語法結構如下:
- (parameters) -> expression
- 或
- (parameters) ->{ statements; }
其中 () 用來描述參數列表,{} 用來描述方法體,-> 為 lambda運算符 ,讀作(goes to),parameters表示參數,expression表示表達式,statements表示代碼塊。
結構說明如下:
一個 Lambda 表達式可以有零個或多個參數
參數的類型既可以明確聲明,也可以根據上下文來推斷。
例如:
(int a)與(a)效果相同
所有參數需包含在圓括號內,參數之間用逗號相隔。
例如:
- (a, b) 或 (int a, int b) 或 (String a, int b, float c)
空圓括號代表參數集為空。例如:() -> 42
當只有一個參數,且其類型可推導時,圓括號()可省略。
例如:a -> return a*a
Lambda 表達式的主體可包含零條或多條語句
如果 Lambda 表達式的主體只有一條語句,花括號{}可省略。匿名函數的返回類型與該主體表達式一致;
如果 Lambda 表達式的主體包含一條以上語句,則表達式必須包含在花括號{}中(形成代碼塊)。匿名函數的返回類型與代碼塊的返回類型一致,若沒有返回則為空;
3、Lambda表達式重要特征
- 可選類型聲明:不需要聲明參數類型,編譯器可以統一識別參數值。
- 可選的參數圓括號:一個參數無需定義圓括號,但多個參數需要定義圓括號。
- 可選的大括號:如果主體包含了一個語句,就不需要使用大括號。
- 可選的返回關鍵字:如果主體只有一個表達式返回值則編譯器會自動返回值,大括號需要指定明表達式返回了一個數值。
4、函數式接口詳解
①什么是函數式接口
函數式接口在java中是指只有一個抽象方法的接口。
函數式接口,就是適用于函數式編程場景的接口。在java中函數式編程就體現在Lambda,因此函數式接口就是能夠適用于lambda使用的接口。只有確保接口中有且僅有一個抽象方法,lambda才能進行順利的推導;
②語法
@FunctionalInterface注解
注解和@override注解的作用類似。該住處應用于函數式接口的定義上。
一旦使用了該注解來定義函數式接口,編譯器就會檢查該接口是否是有且僅有一個抽象方法
- @FunctionalInterface
- public interface MyFunctionalInterface {
- void myMethod();
- }
- 將函數式接口作為方法的參數
- public class Demo {
- private static void dos(FunctionInterface fi){
- fi.method();
- }
- public static void main(String[] args) {
- Demo.dos(()->{System.out.println("lambda表達式");});
- }
5、Lambda表達式基本使用案例
表達式基本使用案例
- // 1. 不需要參數,返回值為 5
- () -> 5
- // 2. 接收一個參數(數字類型),返回其2倍的值
- x -> 2 * x
- // 3. 接受2個參數(數字),并返回他們的差值
- (x, y) -> x – y
- // 4. 接收2個int型整數,返回他們的和
- (int x, int y) -> x + y
- // 5. 接受一個 string 對象,并在控制臺打印,不返回任何值(看起來像是返回void)
- (String s) -> System.out.print(s)
- 定義6個接口,后面我們都會基于這6個接口來演示案例
- /**多參數無返回*/
- @FunctionalInterface
- public interface NoReturnMultiParam {
- void method(int a, int b);
- }
- /**無參無返回值*/
- @FunctionalInterface
- public interface NoReturnNoParam {
- void method();
- }
- /**一個參數無返回*/
- @FunctionalInterface
- public interface NoReturnOneParam {
- void method(int a);
- }
- /**多個參數有返回值*/
- @FunctionalInterface
- public interface ReturnMultiParam {
- int method(int a, int b);
- }
- /** 無參有返回*/
- @FunctionalInterface
- public interface ReturnNoParam {
- int method();
- }
- /**一個參數有返回值*/
- @FunctionalInterface
- public interface ReturnOneParam {
- int method(int a);
- }
- 案例代碼:
- public class Test2 {
- public static void main(String[] args) {
- //1.簡化參數類型,可以不寫參數類型,但是必須所有參數都不寫
- NoReturnMultiParam lamdba1 = (a, b) -> {
- System.out.println("簡化參數類型");
- };
- lamdba1.method(1, 2);
- //2.簡化參數小括號,如果只有一個參數則可以省略參數小括號
- NoReturnOneParam lambda2 = a -> {
- System.out.println("簡化參數小括號");
- };
- lambda2.method(1);
- //3.簡化方法體大括號,如果方法條只有一條語句,則可以省略方法體大括號,類似if或for
- NoReturnNoParam lambda3 = () -> System.out.println("簡化方法體大括號");
- lambda3.method();
- //4.如果方法體只有一條語句,并且是 return 語句,則可以省略方法體大括號
- ReturnOneParam lambda4 = a -> a+3;
- System.out.println(lambda4.method(5));
- ReturnMultiParam lambda5 = (a, b) -> a+b;
- System.out.println(lambda5.method(1, 1));
- }
- }
6、使用lambda 表達式去引用方法
①引用方法的語法為:方法歸屬者::方法名
注意:靜態方法的歸屬者為類名,普通方法歸屬者為對象。該代碼案例結合最上面的自定義函數式接口:
- public class Exe1 {
- public static void main(String[] args) {
- ReturnOneParam lambda1 = a -> doubleNum(a);
- System.out.println(lambda1.method(3));
- //lambda2 引用了已經實現的 doubleNum 方法
- ReturnOneParam lambda2 = Exe1::doubleNum;
- System.out.println(lambda2.method(3));
- Exe1 exe = new Exe1();
- //lambda4 引用了已經實現的 addTwo 方法
- ReturnOneParam lambda4 = exe::addTwo;
- System.out.println(lambda4.method(2));
- }
- /**
- * 要求
- * 1.參數數量和類型要與接口中定義的一致
- * 2.返回值類型要與接口中定義的一致
- */
- public static int doubleNum(int a) {
- return a * 2;
- }
- public int addTwo(int a) {
- return a + 2;
- }
- }
②構造方法的引用
一般我們需要聲明接口,該接口作為對象的生成器,通過 類名::new 的方式來實例化對象,然后調用方法返回對象。
- interface ItemCreatorBlankConstruct {
- Item getItem();
- }
- interface ItemCreatorParamContruct {
- Item getItem(int id, String name, double price);
- }
- public class Exe2 {
- public static void main(String[] args) {
- ItemCreatorBlankConstruct creator = () -> new Item();
- Item item = creator.getItem();
- ItemCreatorBlankConstruct creator2 = Item::new;
- Item item2 = creator2.getItem();
- ItemCreatorParamContruct creator3 = Item::new;
- Item item3 = creator3.getItem(119, "電腦", 5888.88);
- }
- }
使用匿名類與 Lambda 表達式的一大區別在于關鍵詞的使用。
對于匿名類,關鍵詞 this 解讀為匿名類,而對于 Lambda 表達式,關鍵詞 this 解讀為寫就 Lambda 的外部類
7、Lambda再次總結
- 無參、無返回值的函數類型(Unit 返回類型不可省略):() -> Unit;
- 接收T類型參數、無返回值的函數類型:(T) -> Unit;
- 接收T類型和A類型參數、無返回值的函數類型(多個參數同理):(T,A) -> Unit;
- 接收T類型參數,并且返回R類型值的函數類型:(T) -> R;
- 接收T類型和A類型參數、并且返回R類型值的函數類型(多個參數同理):(T,A) -> R;
- 在大括號{}的方法實現里,如果只有一個參數,可以用it指定而不需要再寫x->...這種代碼;
- 如果方法實現里有多個參數,則需要x:Int,y:Int->...這種方式指明->后面的自定義參數名;
總結
1、Lambda和高階函數理解起來有點繞,需要大量的練習和實驗才能慢慢的理解
2、Lambda很深,我們一起來學習進步