寧波網(wǎng)站建設(shè)在線智慧軟文網(wǎng)站
kotlin高階函數(shù)
函數(shù)式API:一個(gè)函數(shù)的入?yún)?shù)為Lambda表達(dá)式的函數(shù)就是函數(shù)式api
例子:
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {return filterTo(ArrayList<T>(), predicate)
}
上面這段函數(shù): 首先這個(gè)函數(shù)是一個(gè)泛型函數(shù)
泛型函數(shù)的定義:就是我們?cè)趯懲暌粋€(gè)函數(shù)后,只知道一個(gè)總的類型,而這個(gè)總的類型下有很多繼承了這個(gè)總類型的類,在返回時(shí)我們不知道這個(gè)函數(shù)具體返回哪個(gè)子類,這時(shí)我們就可以在這個(gè)函數(shù)前面加一個(gè)泛型,泛型中放這些子類的基類,上面的filter方法也可以看作 Iterable的擴(kuò)展方法
例子:
// A為基類
public class A{}
public class B extends A{}public class C extends A{}
// 我們不知道下面這個(gè)方法具體返回A,B,C到底哪一個(gè)類型時(shí),因?yàn)轭怋和C繼承自A,這時(shí)我們就可以將這個(gè)返回類型指定為基類型A
public static <A> test(){}
下面我寫kotlin的寫法
class ReportV2Controller : Controller(){
}
和上面java一樣有多個(gè)類繼承自Controller()這個(gè)類,kotlin中繼承都是以函數(shù)的形式,因?yàn)檫@樣寫直觀的表現(xiàn)了自類具體會(huì)調(diào)用基類的哪個(gè)構(gòu)造函數(shù)
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {return filterTo(ArrayList<T>(), predicate)
}
上面的filter方法是迭代集合Iterable,但是在kotlin中這個(gè)Iterable是一個(gè)被inline修飾的內(nèi)聯(lián)函數(shù)
內(nèi)聯(lián)函數(shù)的出現(xiàn)的原因:
1.首先在kotlin中有高階函數(shù)的原因,因?yàn)楦唠A函數(shù)的傳參可以是一個(gè)函數(shù),而且出參也可以為一個(gè)函數(shù)
// 下面這段代碼原作者為csdn: Mr YiRan
fun num1AndNum2(num1:Int,num2:Int,operator:(Int,Int)->Int):Int{val result=operator(num1,num2)return result
}
上面這段代碼高階函數(shù)num1AndNum2入?yún)閮蓚€(gè)Int類型與一個(gè)函數(shù)類型operator,這個(gè)operator又接收兩個(gè)int類型的參數(shù),這個(gè)operator返回類型為int,operator返回的類型int同時(shí)也是num1AndNum2高階函數(shù)的返回類型,同時(shí)num1AndNum2函數(shù)的入?yún)?shù),也可以當(dāng)作函數(shù)operator的入?yún)?/p>
fun num1AndNum2(num1:Int,num2:Int,operator:(Int,Int)->Int):Int{val result=operator(num1,num2)return result
}// 定義兩個(gè)具體的運(yùn)算函數(shù)
fun plus(num1: Int,num2: Int):Int{
return num1+num2
}
fun minus(num1: Int,num2: Int):Int{return num1-num2
}fun main(){val num1=100val num2=80// 這是一個(gè)函數(shù)引用方式的寫法,表示將plus()和minus()函數(shù)作為參數(shù)傳遞給num1AndNum2()函數(shù)val result1= num1AndNum2(num1,num2,::plus)val result2 = num1AndNum2(num1, num2, ::minus)println("result1 is $result1")println("result2 is $result2")
}fun main(){
val num1=100
val num2=80
// 上面的函數(shù)引用寫法的調(diào)用可以改為Lambda表達(dá)式的方式來調(diào)用高階函數(shù)
val result1=num1AndNum2(num1,num2){ n1,n2 ->
n1+n2
}
val result2=num1AndNum2(num1,num2){ n1,n2 ->
n1-n2
}
println("result1 is $result1")
println("result2 is $result2")
}
Lambda表達(dá)式的優(yōu)點(diǎn)與缺點(diǎn)
優(yōu)點(diǎn) :
如果一個(gè)抽象類或者接口中只有一個(gè)方法,我們又不想實(shí)例化這個(gè)類的對(duì)象就想調(diào)用這個(gè)方法,而且也不想將這個(gè)類中的方法標(biāo)記為靜態(tài)的方法,就可以用匿名內(nèi)部類的方式類寫,但是這樣寫代碼很不美觀,所以就簡(jiǎn)化成Lambda表達(dá)式的方式來寫
缺點(diǎn):
Lambda表達(dá)式的方式,系統(tǒng)會(huì)默認(rèn)實(shí)例化一個(gè)匿名內(nèi)部類的方式,就會(huì)造成額外的內(nèi)存開銷與cpu性能損耗
因?yàn)榫退愀唠A函數(shù)中的傳參寫成Lambda表達(dá)式的方式,其內(nèi)部運(yùn)行順序也為先實(shí)例化最外層類的對(duì)象,然后通過對(duì)象去引用當(dāng)前這個(gè)對(duì)象的方法,將當(dāng)前對(duì)象的方法存入方法區(qū)進(jìn)行壓棧然后再實(shí)例化一個(gè)Lambda表達(dá)式的匿名內(nèi)部類,再通過這個(gè)對(duì)象去引用這個(gè)匿名內(nèi)部類中的方法,再進(jìn)行壓榨
kotlin是如何解決這種因?yàn)閭魅隠ambda表達(dá)式寫法的性能開銷的
內(nèi)聯(lián)函數(shù)
在定義高階函數(shù)時(shí)加上inline關(guān)鍵字,就表示此高階函數(shù)是一個(gè)內(nèi)聯(lián)函數(shù)
inline fun num1AndNum2(num1:Int,num2:Int,operation:(Int,Int)->Int):Int{
val result=operation(num1,num2)
return result
}
如何消除 Lambda表達(dá)式額外開銷
簡(jiǎn)單來說就是:在調(diào)用傳參為 Lambda表達(dá)式的高階函數(shù)時(shí),將 Lambda表達(dá)式的函數(shù)體直接拷貝在調(diào)用參數(shù)為 Lambda表達(dá)式的高階函數(shù)后方
示例
1.首先將 Lambda表達(dá)式的入?yún)⒖截惖胶瘮?shù)類型參數(shù)的參數(shù)入?yún)⒘斜碇?br /> 2.其次再將內(nèi)聯(lián)函數(shù)的方法體拷貝到調(diào)用該高階函數(shù)的地方
3.總結(jié)為:將實(shí)例化匿名內(nèi)部類調(diào)用方法壓棧改為方法入?yún)?shù)拷貝和方法體拷貝的過程
下面給一個(gè)返回值是一個(gè)函數(shù)的高階函數(shù)
示例
/**
- 定義一個(gè)返回值是函數(shù)的高階函數(shù)
- @param name 入場(chǎng)
- @return 返回一個(gè)函數(shù)或者lambda
*/
fun highFunction2(name:String):(Int) -> Int{
if (name == “A”){
// 這里返回一個(gè)函數(shù)引用,意思調(diào)用returnFun函數(shù)
// 上面的highFunction2函數(shù)入?yún)閮蓚€(gè)一個(gè)String類型的name,還有一個(gè)入?yún)⑹且粋€(gè)參數(shù)為一個(gè)int類型的Lambda表達(dá)式
// 如果想調(diào)用returnFun函數(shù)那highFunction2函數(shù)傳入的Lambda表達(dá)式入?yún)㈩?數(shù)量與順序必須完全與函數(shù)returnFun一致
return ::returnFun
}
//返回lambda
return {a -> a + 10}
}
/**
- 作為高階函數(shù)的返回函數(shù)
*/
fun returnFun(a:Int):Int{
return a * 100
}
//使用高階函數(shù)
val res = highFunction2("A")
println(res(20)) //打印2000
val res2 = highFunction2("B")
println(res2(20)) //打印30
擴(kuò)展函數(shù)
fun main() {open class Shapeclass Rectangle: Shape()fun Shape.getName() = "Shape"fun Rectangle.getName() = "Rectangle"fun printClassName(s: Shape) {println(s.getName())} printClassName(Rectangle())
上面函數(shù)的最終返回結(jié)果是打印"Shape",因?yàn)殡m然printClassName的函數(shù)的傳入?yún)?shù)是Rectangle()函數(shù),但是最終會(huì)調(diào)用Shape類的getName()函數(shù),這是因?yàn)樽罱K返回的類型取決于printClassName函數(shù)傳入?yún)?shù)s的類型
成員函數(shù)與擴(kuò)展函數(shù)
如果一個(gè)類定義有一個(gè)成員函數(shù)與一個(gè)擴(kuò)展函數(shù),而這兩個(gè)函數(shù)又有相同的接收者類型、 相同的名字,并且都適用給定的參數(shù),這種情況總是取成員函數(shù)
示例
fun main() {class Example {fun printFunctionType() { println("Class method") }}fun Example.printFunctionType() { println("Extension function") }Example().printFunctionType()
}
這里最后會(huì)輸出:“Class method”,因?yàn)槭且灶惖某蓡T函數(shù)為準(zhǔn)的,優(yōu)先級(jí)為先成員函數(shù)
如果是擴(kuò)展類型的重載,那么以傳入?yún)?shù)為準(zhǔn)
由于靜態(tài)調(diào)用擴(kuò)展方法是在編譯時(shí)執(zhí)行,因此,如果父類和子類都擴(kuò)展了同名的一個(gè)擴(kuò)展方法,引用類型均為父類的情況下,會(huì)調(diào)用父類的擴(kuò)展方法
示例
fun main() {class Example {fun printFunctionType() { println("Class method") }}fun Example.printFunctionType(i: Int) { println("Extension function") }Example().printFunctionType(1)
}
最后輸出結(jié)果為:Extension function
top-level函數(shù)
不依賴于任何類的靜態(tài)函數(shù),經(jīng)Kotlin編譯成Java文件后,成為:<靜態(tài)函數(shù)所在的文件名> + "Kt"的Java類的靜態(tài)成員函數(shù)。如果原kotlin文件的文件名首字母為小寫時(shí),轉(zhuǎn)換成大寫。
示例
/** joinsample.kt */
package com.example.kotlinimport java.lang.StringBuilderfun <T> joinToString(collection: Collection<T>,separator: String = ", ",prefix: String = "",postfix: String = ""
): String {val result = StringBuilder(prefix)for ((index, element) in collection.withIndex()) {if (index > 0) {result.append(separator)}result.append(element)}result.append(postfix)return result.toString()
}
import com.example.kotlin.JoinsampleKt;
import java.util.Arrays;
import java.util.List;public class JavaSample {public static void main(String[] args) {List<String> list = Arrays.asList("hello", "world");JoinsampleKt.joinToString(list, ", ", "", "");}
}
擴(kuò)展屬性
由于擴(kuò)展沒有實(shí)際的將成員插入類中,因此對(duì)擴(kuò)展屬性來說幕后字段是無效的。這就是為什么擴(kuò)展屬性不能有初始化器。他們的行為只能由顯式提供的 getters/setters 定義。
擴(kuò)展聲明為成員
就是說有兩個(gè)類,可以在其中一個(gè)類中為另一個(gè)類聲明一個(gè)擴(kuò)展函數(shù),被聲明的擴(kuò)展函數(shù),此時(shí)是當(dāng)前類的成員函數(shù)
示例
class Host(val hostname: String) {fun printHostname() { print(hostname) }
}class Connection(val host: Host, val port: Int) {fun printPort() { print(port) }// 這里可以為Host類定義擴(kuò)展函數(shù),是因?yàn)轭怌onnection的主構(gòu)造函數(shù)中將Host類傳入進(jìn)來fun Host.printConnectionString() {printHostname() // 調(diào)用 Host.printHostname()print(":")printPort() // 調(diào)用 Connection.printPort()}fun connect() {/*……*/host.printConnectionString() // 調(diào)用擴(kuò)展函數(shù)}
}fun main() {// 這里直接使用類Connection的主構(gòu)造函數(shù)傳入Host類然后調(diào)用Host的主構(gòu)造函數(shù),然后調(diào)用Host類的printHostname()函數(shù)// 被定義的擴(kuò)展函數(shù)必須在定義該擴(kuò)展函數(shù)的類中使用// 意思就是說Host的printConnectionString()函數(shù)必須在類Connection中使用// Connection(Host("kotl.in"),443).printConnectionString()Connection(Host("kotl.in"), 443).connect()//Host("kotl.in").printConnectionString(443) // 錯(cuò)誤,該擴(kuò)展函數(shù)在 Connection 外不可用
}
class Connection {// 這里給Host類定義一個(gè)擴(kuò)展函數(shù)// 在這個(gè)擴(kuò)展函數(shù)中調(diào)用toString()// 因?yàn)樵趉otlin中所有的類都繼承自Any類,而toString方法是屬于Any類的// 如果想調(diào)用Connection類的toString()方法需要向下面那種寫法// this@Connection.toString()fun Host.getConnectionString() {toString() // 調(diào)用 Host.toString()this@Connection.toString() // 調(diào)用 Connection.toString()}
}
泛型中的in和out
這兩個(gè)泛型的定義為逆變與攜變
示例
// 逆變
interface Consumer<in T> {fun consume(item: T)
}
簡(jiǎn)單來說就是在接口中in標(biāo)記的泛型類型T,要作為函數(shù)consume的入?yún)㈩愋?/p>
// 攜變
interface Production<out T> {fun produce(): T
}
簡(jiǎn)單來說就是接口泛型中out標(biāo)記的T要做為函數(shù)produce返回的類型