本文主要涉及內聯函數、擴展函數、lambada以及匿名函數等。作爲讀書筆記對於細節深入沒有過多的擴展,後續將對於各個知識點作進一步的研度。本文的內容主要是參考官方教程以及博客內容,作爲讀書筆記以及後續知識點擴展的一個大綱。學海無涯,希望能在Android這條路上越走越遠!
1.內聯函數
1.1 內聯函數的使用
首先看個例子,一個是用了inline
修飾,另一個沒有:
init{
functionWithInline()
functionWithoutInline()
}
inline fun functionWithInline(){
println("functionWithInline:執行具體代碼")
}
fun functionWithoutInline(){
println("functionWithoutInline:執行具體代碼")
}
接下來看下反編譯出來的java
代碼
public final void functionWithInline() {
int $i$f$functionWithInline = 0;
String var2 = "functionWithInline:執行具體代碼";
boolean var3 = false;
System.out.println(var2);
}
public final void functionWithoutInline() {
String var1 = "functionWithoutInline:執行具體代碼";
boolean var2 = false;
System.out.println(var1);
}
public test() {
int $i$f$functionWithInline = false;
String var3 = "functionWithInline:執行具體代碼";
boolean var4 = false;
System.out.println(var3);
this.functionWithoutInline();
}
可以看到functionWithInline
處的執行代碼直接複製到了test()調用處,也就是說inline
修飾的話直接是把嵌入對應函數的代碼,減少了普通函數調用過程中壓彈棧過程,提高了效率。
1.2 禁用內聯
如果內聯函數中的某個參數不想內聯,那麼可以用noinline
修飾。
inline fun sum(a: Int, b: Int, lambda: (result: Int) -> Unit, noinline lambda2: (result: Int) -> Unit): Int {
val r = a + b
lambda.invoke(r)
lambda2.invoke(r)
return r
}
fun main(args: Array<String>) {
sum(1, 2,
{ println("Result is: $it") },
{ println("Invoke lambda2: $it") }
)
}
反編譯之後的代碼爲:
public static final int sum(int a, int b, @NotNull Function1 lambda, @NotNull Function1 lambda2) {
int r = a + b;
lambda.invoke(r);
lambda2.invoke(r);
return r;
}
public static final void main(@NotNull String[] args) {
byte a$iv = 1;
byte b$iv = 2;
Function1 lambda2$iv = (Function1)null.INSTANCE;
int r$iv = a$iv + b$iv;
String var8 = "Result is: " + r$iv;
System.out.println(var8);
lambda2$iv.invoke(r$iv);
可以看到只有lambda的代碼拷貝過來了,而noinline
修飾的lambda2
並沒有嵌入到調用處。
1.3 非局部返回
首先,在 Kotlin
中,我們只能對具名或匿名函數使用正常的、非限定的 return 來退出。下面以一個例子來說明:
fun main() {
test {
return //error: 'return' is not allowed here
println("test")
}
}
fun test(body: () -> Unit) {
body()
}
lambda不能直接使用return,那麼有什麼方法呢?內聯函數是可以用retrun
返回:
fun main() {
test {
println("test")
return
}
}
inline fun test(body: () -> Unit) {
body()
}
有一種情況,如果函數作爲內聯函數的參數,那麼我們可以禁止其使用return。如下例子所示:
fun main() {
var result = sum(1, 2){
value ->
println("First result is: $value")
return
}
result *= 10
println("Second result is: $result")
}
inline fun sum(a: Int, b: Int, lambda: (result: Int) -> Unit): Int {
val r = a + b
lambda.invoke(r)
return r
}
結果:First result is: 3
可以看到結果只是First result is: 3,也就是說下面的第二個打印被屏蔽掉了。然後有時候並不想因爲外部的lambda影響到調用處。那如果想要屏蔽這個返回要怎麼做呢?
fun main() {
var result = sum(1, 2){
value ->
println("First result is: $value")
return //error: 'return' is not allowed here
}
result *= 10
println("Second result is: $result")
}
inline fun sum(a: Int, b: Int, crossinline lambda: (result: Int) -> Unit): Int {
val r = a + b
lambda.invoke(r)
return r
}
加了crossinline
之後在return那一行會報錯error: ‘return’ is not allowed here。也就是說crossinline
限制了return的返回。
1.4 具體化的類型參數
衆所周知,泛型在運行時會有個擦除的動作。也就是說像is這樣子的判斷是不可以用的。那麼就有個問題,如果要判斷某個變量是否數據某個類型就很麻煩。kotlin提供了具體化的類型參數。首先該函數必須是內聯的,如下所示:
fun main() {
jisuan<Int>()
jisuan<String>()
}
inline fun <reified T> jisuan(){
var p = 2
if(p !is T){
println("p is not the type = "+p)
}else{
println("p is the type = "+p)
}
}
例子中泛型前面加了reified
修飾符,那麼該函數的正常的操作符如 !is
和 as
現在都能用了。
1.5 內聯屬性
內聯屬性就是 修飾符可用於沒有幕後字段的屬性的訪問器,也可以標註整個屬性。
val foo: Foo
inline get() = Foo()
var bar: Bar
get() = ……
inline set(v) { …… }
inline var bar: Bar
get() = ……
set(v) { …… }
2.擴展函數
擴展函數,第一個想到的是裝飾器模式。在不破壞原有代碼封裝性的前提下對功能做了增強。如下所示:
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // “this”對應該列表
this[index1] = this[index2]
this[index2] = tmp
}
對MutableList
做了數據交換的功能增強。那麼如果要交換數據可以如下操作:
val list = mutableListOf(1, 2, 3)
list.swap(0, 2) // “swap()”內部的“this”會保存“list”的值
2.1 擴展是靜態解析
擴展是靜態分發的。比如說父類定義了擴展函數,子類也定義了擴展函數。而父類的擴展函數並不能被子類的擴展函數所覆蓋:
open class Shape
class Rectangle: Shape()
fun Shape.getName() = "Shape"
fun Rectangle.getName() = "Rectangle"
fun printClassName(s: Shape) {
println(s.getName())
}
printClassName(Rectangle())
如果被擴展的函數本身以及擴展函數擁有着相同的簽名,那麼優先取成員函數
class Example {
fun printFunctionType() { println("Class method") }
}
fun Example.printFunctionType() { println("Extension function") }
Example().printFunctionType()
2.2 可以爲可空的接收者類型定義擴展
這個從理解成裝飾器模式。被裝飾的對象爲空也理應存在的:
fun Any?.toString(): String {
if (this == null) return "null"
// 空檢測之後,“this”會自動轉換爲非空類型,所以下面的 toString()
// 解析爲 Any 類的成員函數
return toString()
}
2.3 擴展屬性
kotlin
支持屬性的擴展:
val <T> List<T>.lastIndex: Int
get() = size - 1
擴展屬性不能有初始化器,如下行爲則會報錯:
val House.number = 1 // 錯誤:擴展屬性不能有初始化器
2.4 伴生對象的擴展
class MyClass {
companion object { } // 將被稱爲 "Companion"
}
fun MyClass.Companion.printCompanion() { println("companion") }
fun main() {
MyClass.printCompanion()
}
2.5 擴展的作用域以及擴展聲明爲成員
作用域:如果是同一個包裏面直接使用即可。而如果在別的包裏面,那麼得用import導入。
擴展聲明爲成員:在官方的文檔中描述爲分發接收者以及擴展接收者 。所謂分發接收者,就是被擴展的對象。而分發接收者表示擴展定義所在函數。
擴展接收者與擴展分發者如果有相同的函數名衝突,那麼優先考慮擴展接收者。
擴展接收者是靜態的,而擴展分發者是虛擬的。也就是說擴展接收者在函數定義的時候就已經確定了,而擴展分發者是在調用的時候才確定的。
open class Base { }
class Derived : Base() { }
open class BaseCaller {
open fun Base.printFunctionInfo() {
println("Base extension function in BaseCaller")
}
open fun Derived.printFunctionInfo() {
println("Derived extension function in BaseCaller")
}
fun call(b: Base) {
b.printFunctionInfo() // 調用擴展函數
}
}
class DerivedCaller: BaseCaller() {
override fun Base.printFunctionInfo() {
println("Base extension function in DerivedCaller")
}
override fun Derived.printFunctionInfo() {
println("Derived extension function in DerivedCaller")
}
}
fun main() {
BaseCaller().call(Base()) // “Base extension function in BaseCaller”
DerivedCaller().call(Base()) // “Base extension function in DerivedCaller”——分發接收者虛擬解析
DerivedCaller().call(Derived()) // “Base extension function in DerivedCaller”——擴展接收者靜態解析
}
3.高階函數和 Lambda 表達式
所謂高階函數,就是參數或者返回值是一個函數。
fun <T, R> Collection<T>.fold(
initial: R,
combine: (acc: R, nextElement: T) -> R
): R {
var accumulator: R = initial
for (element: T in this) {
accumulator = combine(accumulator, element)
}
return accumulator
}
// lambda 表達式的參數類型是可選的,如果能夠推斷出來的話:
val joinedToString = items.fold("Elements:", { acc, i -> acc + " " + i })
函數類型:
- 一般的寫法就是參數寫在括號內:(A,B)->C
- 有額外接收者的函數:A.(B)->C
- suspend()協程中涉及到再介紹
如需將函數類型指定爲可空,請使用圓括號:
((Int, Int) -> Int)?
。函數類型可以使用圓括號進行接合:
(Int) -> ((Int) -> Unit)
箭頭表示法是右結合的,
(Int) -> (Int) -> Unit
與前述示例等價,但不等於((Int) -> (Int)) -> Unit
。
函數類型實例化
-
使用函數字面值的代碼塊,採用以下形式之一:
- lambda 表達式:
{ a, b -> a + b }
, - 匿名函數:
fun(s: String): Int { return s.toIntOrNull() ?: 0 }
帶有接收者的函數字面值可用作帶有接收者的函數類型的值。
- lambda 表達式:
-
使用已有聲明的可調用引用:
- 頂層、局部、成員、擴展函數:
::isOdd
、String::toInt
, - 頂層、成員、擴展屬性:
List<Int>::size
, - 構造函數:
::Regex
這包括指向特定實例成員的綁定的可調用引用:
foo::toString
。 - 頂層、局部、成員、擴展函數:
-
使用實現函數類型接口的自定義類的實例:
class IntTransformer: (Int) -> Int {
override operator fun invoke(x: Int): Int = TODO()
}
val intFunction: (Int) -> Int = IntTransformer()
函數類型實例調用
val stringPlus: (String, String) -> String = String::plus
val intPlus: Int.(Int) -> Int = Int::plus
println(stringPlus.invoke("<-", "->"))
println(stringPlus("Hello, ", "world!"))
println(intPlus.invoke(1, 1))
println(intPlus(1, 2))
println(2.intPlus(3)) // 類擴展調用
3.1 Lambda 表達式語法
Lambda的完整表達式如下所示:
val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
等號左邊是簽名,等號右邊是具體的代碼實現。如果推斷出的該 lambda 的返回類型不是 Unit,那麼該 lambda 主體中的最後一個(或可能是單個) 表達式會視爲返回值。
3.1.1 傳遞末尾的 lambda 表達式
如果函數的最後一個參數是函數的話,則可以放在外面:
val product = items.fold(1) { acc, e -> acc * e }
3.1.2 it
:單個參數的隱式名稱
如果參數只有一個話,那麼可以用it來代表這個參數的對象
ints.filter { it > 0 } // 這個字面值是“(it: Int) -> Boolean”類型的
3.1.3 從 lambda 表達式中返回一個值
可以使用限定的返回語法從 lambda 顯式返回一個值。 否則,將隱式返回最後一個表達式的值。
ints.filter {
val shouldFilter = it > 0
shouldFilter
}
ints.filter {
val shouldFilter = it > 0
return@filter shouldFilter
}
3.2 匿名函數
匿名函數和正常的函數類似,就是把函數名給去掉了。
fun(x: Int, y: Int): Int = x + y//等同於下面的寫法
fun(x: Int, y: Int): Int {
return x + y
}
3.3 閉包
閉包的概念:函數裏面聲明函數,函數裏面返回函數,就是閉包
fun test():()->Unit{
var a=3 //狀態
return fun(){
a++;
println(a);
}
}
fun main(args: Array<String>) {
var t=test()
t();
t();
t();
}
神奇之處就是函數中的變量a的值會保存,打印出4,5,6
3.4 帶有接收者的函數字面值
Lambda寫法:
val sum: Int.(Int) -> Int = { other -> plus(other) }
匿名函數寫法:
val sum = fun Int.(other: Int): Int = this + other
當接收者類型可以從上下文推斷時,lambda 表達式可以用作帶接收者的函數字面值
class HTML {
fun body() { …… }
}
fun html(init: HTML.() -> Unit): HTML {
val html = HTML() // 創建接收者對象
html.init() // 將該接收者對象傳給該 lambda
return html
}
html {
body() // body()可以從上下文推出
}
//以上的函數換個寫法會比較容易理解
html (HTML::body)
4.尾遞歸函數
在瞭解尾遞歸函數之前,先用java
寫一個遞歸函數,循環10000次:
fun main() {
recycle(10000)
}
fun recycle(count:Int){
println("count = "+count)
if(count<=0){
return
}else{
recycle(count - 1)
}
}
運行到9000多次的時堆棧時會出現堆棧溢出的問題:Exception in thread "main" java.lang.StackOverflowError
。而kotlin
的尾遞歸函數無堆棧溢出的風險,但是有個要求就是遞歸函數必須是最後調用的。如下所示:
fun main() {
recycle(10000)
}
tailrec fun recycle(count: Int):Unit = if (count <= 0) println("遞歸完成") else {
println("count = "+count)
recycle(count - 1)
println("count = "+count)//如果遞歸的函數後還有代碼出現,會出現警告
}
運行的結果是,用kotlin
尾遞歸函數的話,是不會出現堆棧溢出的。