1、為什么要使用高階函數(shù)?
先來(lái)看看兩段代碼,在Andriod自定義View中的一個(gè)小例子,分別用Java和Kotlin來(lái)實(shí)現(xiàn)
Java
public class DemoView {
interface OnClickListener {
void onClick();
}
interface OnItemClickListener {
void onItemClick(int position);
}
private OnClickListener onClickListener;
private OnItemClickListener onItemClickListener;
public void setOnClickListener(OnClickListener onClickListener) {
this.onClickListener = onClickListener;
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
public void test(){
onClickListener.onClick();
onItemClickListener.onItemClick(100);
}
public static void main(String[] args) {
DemoView demoView = new DemoView();
demoView.setOnClickListener(new OnClickListener() {
@Override
public void onClick() {
System.out.println("onClickListener");
}
});
demoView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(int position) {
System.out.println("onItemClickListener:"+position);
}
});
demoView.test();
}
}
Kotlin
class TestView {
//用函數(shù)類型替代接口
private var onClickListener: (() -> Unit)? = null
private var onItemClickListener: ((Int) -> Unit)? = null
fun test() {
onClickListener?.invoke()
onItemClickListener?.invoke(100)
}
/**
* 用lambda表達(dá)式作為函數(shù)參數(shù)
*/
fun setOnClickListener(onClickListener: () -> Unit) {
this.onClickListener = onClickListener
}
fun setOnItemClickListener(onItemClickListener: (Int) -> Unit) {
this.onItemClickListener = onItemClickListener
}
}
fun main() {
val testView = TestView()
testView.setOnClickListener {
println("onClickListener")
}
testView.setOnItemClickListener {
println("onItemClickListener${it}")
}
testView.test()
}
最終實(shí)現(xiàn)的效果一樣
onClick
onItemClickListener:100
可以看到Kotlin的代碼比Java少很多,Kotlin的設(shè)計(jì)者怎么實(shí)現(xiàn)的呢?實(shí)際分為兩部分:
- 用函數(shù)類型替代接口
/**
* 用lambda表達(dá)式作為函數(shù)參數(shù)
*/
fun setOnClickListener(onClickListener: () -> Unit) {
this.onClickListener = onClickListener
}
- 用Lambda表達(dá)式作為函數(shù)入?yún)?/li>
//用函數(shù)類型替代接口
private var onClickListener: (() -> Unit)? = null
以上我們可以小結(jié):
Kotlin引入高階函數(shù),省了兩個(gè)接口的定義,對(duì)于調(diào)用者來(lái)說(shuō),代碼更加簡(jiǎn)潔
2、高階函數(shù)中一些名稱的含義
什么是函數(shù)類型?
函數(shù)類型,顧名思義是函數(shù)的類型,我們知道一個(gè)變量有類型,那么函數(shù)也有類型。例如以下函數(shù):
fun add(a: Int, b: Int): Int {
return a + b
}
它的類型是(Int,Int)->Int,也就是一個(gè)函數(shù)的類型包含了函數(shù)的入?yún)⒑头祷仡愋徒Y(jié)合在一起,就是函數(shù)的類型。
那么我們就可以類似定義變量的方式定義一個(gè)函數(shù)
//函數(shù)名稱 函數(shù)類型
var function: ((Int, Int) -> Int)? = null
當(dāng)然我們也可以直接對(duì)函數(shù)初始化,并執(zhí)行函數(shù)。
fun main() {
//函數(shù)名稱 函數(shù)類型
var function: ((Int, Int) -> Int)? = { a, b ->
a + b
}
val result = function?.invoke(1, 2)
println(result)
}
什么是函數(shù)的引用
我們定義一個(gè)add函數(shù)
fun add(a: Int, b: Int): Int {
return a + b
}
將我們的add函數(shù)通過(guò)引用的方式,賦給我們定義的函數(shù)類型,其中::add就是函數(shù)的引用
fun main() {
//函數(shù)名稱 函數(shù)類型 函數(shù)的引用
var function: ((Int, Int) -> Int) = ::add
}
fun add(a: Int, b: Int): Int {
return a + b
}
什么是高階函數(shù)?
- 函數(shù)的參數(shù)中包含了函數(shù)的類型
- 函數(shù)的返回值是函數(shù)的類型
滿足以上某一個(gè)條件的函數(shù),稱之為高階函數(shù),如以下的例子:
fun main() {
add(1, 2) {
println(it)
}
println(get().invoke(2, 1))
}
/**
* 函數(shù)中的參數(shù)包含了函數(shù)類型
*/
fun add(a: Int, b: Int, f: (Int) -> Unit) {
f.invoke(a + b)
}
fun del(a: Int, b: Int): Int {
return a - b
}
/**
* 返回值是函數(shù)類型
*/
fun get(): ((Int, Int) -> Int) {
return ::del
}
什么是Lambda表達(dá)式
Lambda表達(dá)式我們可以理解位函數(shù)的簡(jiǎn)寫,分為兩種用途
- 使用Lambda表達(dá)式聲明創(chuàng)建一個(gè)函數(shù)
- 使用Lambda表達(dá)式作為函數(shù)類型的入?yún)?br> 如下面的例子:
fun main() {
//1、使用lambda表達(dá)聲明創(chuàng)建一個(gè)函數(shù)
val add: (Int, Int) -> Int = { a, b ->
a + b
}
val result = add.invoke(1, 2)
println(result)
//2、使用lambda表示作為函數(shù)的入?yún)? val result2 = add { a, b ->
a + b
}
println(result2)
}
/**
*使用lambda表示作為函數(shù)的入?yún)? */
fun add(f: (Int, Int) -> Int): Int {
return f.invoke(3, 2)
}
什么是SAM轉(zhuǎn)換?
SAM表示是Single Abstract Method (簡(jiǎn)單的抽象方法的類或者接口)但是在Kotlin和Java8里,SAM只代表只有一個(gè)抽象方法的接口。因此只要滿足接口中只有一個(gè)方法,我們就可以使用SAM轉(zhuǎn)換,也就是我們可以使用Lambda表達(dá)式來(lái)簡(jiǎn)寫接口類的參數(shù)。如:
轉(zhuǎn)換前
interface OnClickListener {
void onClick();
}
public void setOnClickListener(OnClickListener onClickListener) {
this.onClickListener = onClickListener;
}
demoView.setOnClickListener(new OnClickListener() {
@Override
public void onClick() {
System.out.println("onClickListener");
}
});
轉(zhuǎn)換后
/**
* 用lambda表達(dá)式作為函數(shù)參數(shù)
*/
fun setOnClickListener(onClickListener: () -> Unit) {
this.onClickListener = onClickListener
}
//使用SAM轉(zhuǎn)換
TestView().setOnClickListener {
}
我們聲明一個(gè)函數(shù)類型變量,并通過(guò)函數(shù)的引用賦值給此變量
fun main() {
//函數(shù)的引用add賦值給 addFun函數(shù)變量
val addFun: (Int, Int) -> Int = ::add
println(addFun.invoke(1, 2))
}
/**
* 普通的函數(shù)
*/
fun add(a: Int, b: Int): Int {
return a + b
}
這樣一來(lái)我們發(fā)現(xiàn)比較麻煩,遍可以通過(guò)SAM轉(zhuǎn)換的方式創(chuàng)建此函數(shù)
fun main() {
//使用SAM轉(zhuǎn)換
val addFun: (Int, Int) -> Int = {
a,b->
a+b
}
println(addFun.invoke(1, 2))
}
我小結(jié)就是
當(dāng)我們的接口中只有一個(gè)實(shí)現(xiàn)函數(shù)的時(shí)候,我們可以通過(guò)Kotlin中的函數(shù)類型替代。而在Kotlin中我們又可以通過(guò)Lambda表達(dá)式來(lái)簡(jiǎn)寫聲明一個(gè)函數(shù),因此我們就可以通過(guò)此方式替代接口。
因此對(duì)于兩種情況都是可以使用SAM轉(zhuǎn)換:
(1)接口中只有一個(gè)實(shí)現(xiàn)函數(shù)
(2)聲明創(chuàng)建一個(gè)函數(shù)類型的實(shí)現(xiàn)
在Kotlin中我們引入了函數(shù)的類型,也就是從此之后不僅僅一個(gè)普通的變量有類型,函數(shù)我們也可以當(dāng)成一個(gè)變量,也擁有類型,稱之為函數(shù)的類型。
這樣一來(lái)函數(shù)就可以擁有了普通變量等同的功能,函數(shù)類型變量的聲明,創(chuàng)建賦值。函數(shù)類型的傳參,函數(shù)類中的返回值。函數(shù)類型變量的使用。
- 將函數(shù)的參數(shù)類型和返回值類中抽出來(lái),就代表了這個(gè)函數(shù)的類型
- 如果一個(gè)函數(shù)的參數(shù)或者返回值的類型是一個(gè)函數(shù)類型,那這個(gè)函數(shù)就是高階函數(shù)
- Lambda表達(dá)式是函數(shù)一種簡(jiǎn)寫
3、分析高階函數(shù)在Kotlin源碼中下實(shí)現(xiàn)
let
fun main() {
var a = "a"
a.let {
val result = "$it bcd"
println(result)
}
}
源碼分析
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
- let函數(shù)是對(duì)泛型T擴(kuò)展的一個(gè)函數(shù),因此所有類型的變量都可以調(diào)用此函數(shù)。
- let函數(shù)的入?yún)lock是一個(gè)函數(shù)函數(shù)類型,因此let是一個(gè)高階函數(shù),block的入?yún)⑹荰,就是被擴(kuò)展的對(duì)象,返回值是R。并且整個(gè)let函數(shù)的返回值由block函數(shù)的返回值決定。
apply
fun main() {
var a = "a"
a.apply {
println(this)
}
}
源碼分析
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
apply和let一樣都是對(duì)T進(jìn)行擴(kuò)展,并且入?yún)⒍际呛瘮?shù)類型,因此都是高階函數(shù)。不同的地方在于apply的入?yún)⒗ㄌ?hào)旁邊多了一個(gè)T.
block: T.() -> Unit
這種函數(shù)類型稱為帶接收者的函數(shù)類型,并且接收者是T,也就是被擴(kuò)展的對(duì)象。因此在block函數(shù)中我們便可以使用該接收者T。因此在以上的代碼中,我們直接可以通過(guò)this使用接收者。
var a = "a"
a.apply {
println(this)
}
4、使用高階函數(shù)改版抽象模板的單例
abstract class BaseSingleInstance<T> {
@Volatile
private var instance: T? = null
protected abstract val creat: () -> T
fun getInstance(): T {
return instance ?: synchronized(this) {
instance ?: creat.invoke().also { instance = it }
}
}
}
class UserManager private constructor() {
companion object : BaseSingleInstance<UserManager>() {
override val creat: () -> UserManager = ::UserManager
}
}
fun main() {
println(UserManager.getInstance().hashCode())
println(UserManager.getInstance().hashCode())
println(UserManager.getInstance().hashCode())
}
5、劇終
為什么要使用高階函數(shù)?
- 為了簡(jiǎn)潔、代碼更少
`