1 常規函式
1.1 無參函式
fun main() {
myFun()
}
fun myFun() {
println("myFun") // 列印: myFun
}
1.2 有參函式
1)常規呼叫
fun main() {
myFun("myFun") // 列印: myFun
}
fun myFun(str: String) {
println(str)
}
2)形參指定預設值
fun main() {
myFun() // 列印: abc
}
fun myFun(str: String = "abc") {
println(str)
}
3)實參指定變數名
fun main() {
myFun(b = 123, a = "abc") // 列印: abc123
}
fun myFun(a: String, b: Int) {
println(a + b)
}
1.3 有返回值函式
1)常規呼叫
fun main() {
var c = add(3, 5)
println(c) // 列印: 8
}
fun add(a: Int, b: Int): Int {
return a + b
}
說明:對於無返回值型別函式,其返回型別為 Unit,如下,也可以省略不寫。
fun myFun(str: String): Unit {
println(str)
}
2)單行函式體簡化
當函式內部只有一行程式碼時,可以簡化如下。
fun main() {
var c = add(3, 5)
println(c) // 列印: 8
}
fun add(a: Int, b: Int): Int = a + b
1.4 可變長引數函式(vararg)
1)常規呼叫
fun main() {
myFun("aa", "bb", "cc") // 列印: aa、bb、cc
}
fun myFun(vararg parms: String) {
for (str in parms) {
println(str)
}
}
說明:函式的可變長引數個數最多為 1 個。
2)使用陣列接收可變長引數
fun main() {
myFun("aa", "bb", "cc") // 列印: 3
}
fun myFun(vararg parms: String) {
var arr: Array<out String> = parms
println(arr.size)
}
3)將陣列傳給可變長引數函式
fun main() {
var arr: Array<String> = arrayOf("aa", "bb", "cc")
myFun(*arr) // 列印: 3
myFun("xx", *arr, "yy") // 列印: 5
}
fun myFun(vararg parms: String) {
println(parms.size)
}
2 函式型別變數
2.1 函式型別變數
1)無參函式變數
fun test() {
println("test")
}
fun main() {
var myFun: () -> Unit = ::test
myFun() // 列印: test
}
2)有參函式變數
fun test(a: Int, b: String): Unit {
println("test, $a, $b")
}
fun main() {
var myFun: (Int, String) -> Unit = ::test
myFun(123, "abc") // 列印: test, 123, abc
}
3)有返回值函式變數
fun test(a: Int, b: Int): Int {
return a + b
}
fun main() {
var myFun: (Int, Int) -> Int = ::test
println(myFun(3, 5)) // 列印: 8
}
2.2 匿名函式
匿名函式即沒有名字的函式,在宣告函式變數時,可以指向一個匿名函式。
fun main() {
var myFun: (Int, Int) -> Int = fun(a: Int, b: Int): Int {
return a + b
}
println(myFun(3, 5)) // 列印: 8
}
可以使用 Lambda 表示式簡化如下。
fun main() {
var myFun: (Int, Int) -> Int = { a, b ->
a + b
}
println(myFun(3, 5)) // 列印: 8
}
3 行內函數(inline)
行內函數是使用 inline 關鍵字修飾的函式,編譯後會自動將函式體內的程式碼複製到呼叫處,以最佳化程式碼執行效率。
3.1 常規行內函數
Test.kt
fun main() {
myFun()
}
inline fun myFun() {
println("行內函數")
}
以上程式碼經過編譯執行後,依次點選【Tools → Kotlin → Show Kotlin Bytecode】,生成位元組碼檔案。
再點選 DeCompile 按鈕反編譯位元組碼檔案,會生成對應的 Java 檔案。
public final class TestKt {
public static final void main() {
String var1 = "行內函數";
System.out.println(var1);
}
public static void main(String[] var0) {
main();
}
public static final void myFun() {
String var1 = "行內函數";
System.out.println(var1);
}
}
說明:可以看到 myFun 函式里的程式碼被複制到呼叫處了。
3.2 帶 return 的巢狀行內函數
1)return 不帶標籤
fun main() {
outFun {
println("inFun")
return // 等價於: return@main
}
println("main end") // 未列印
}
inline fun outFun(inFun: () -> Unit) {
inFun()
println("outFun end") // 未列印
}
執行結果如下。
inFun
"outFun end" 和 "main end" 未列印,這是因為行內函數會直接將 return 語句複製到 main 函式中。
2)return@標籤
fun main() {
outFun {
println("inFun")
return@outFun
}
println("main end")
}
inline fun outFun(inFun: () -> Unit) {
inFun()
println("outFun end")
}
執行結果如下。
inFun
outFun end
main end
4 泛型函式
泛型的型別檢查只存在於編譯階段,在原始碼編譯之後,不會保留任何關於泛型型別的內容,即型別擦除。
4.1 簡單泛型函式
1)單泛型引數
fun main() {
myFun(123) // 列印: 123
myFun("abc") // 列印: abc
myFun(true) // 列印: true
myFun(null) // 列印: null
}
fun <T> myFun(param: T) {
println(param)
}
2)多泛型引數
fun main() {
var res: Boolean = myFun("abc", 123, true) // 列印: abc, 123
println(res) // 列印: true
}
fun <R, T, S> myFun(a: T, b: S, c: R): R {
println("$a, $b")
return c
}
4.2 類中泛型函式
fun main() {
var c1: MyClass<String> = MyClass()
c1.myFun("abc") // 列印: abc
var c2: MyClass<Int> = MyClass()
c2.myFun(123) // 列印: 123
}
class MyClass<T> {
fun myFun(a: T) {
println(a)
}
}
4.3 自動推斷泛型型別
Kotlin 提供了下劃線(_)運算子可以自動推斷型別。
fun main() {
myFun<Int, _>()
}
fun <S : Comparable<T>, T> myFun() {
println("test _")
}
Int 類和 Comparable 類的定義如下。由於 Int 繼承了 Comparable
public interface Comparable<in T>
public class Int private constructor() : Number(), Comparable<Int>
4.4 抗變、協變和逆變
1)抗變
如下,Int 是 Number 的子類,Number 引用可以指向 Int 物件,但是 Data
2)協變
透過 out 關鍵字表示 Data
class Data<T>(var value: T)
fun main() {
var data1: Data<Int> = Data<Int>(1)
var data2: Data<out Number> = data1
println(data2.value) // 列印: 1
// data2.value = 1 // 編譯錯誤, setter方法被限制
}
說明:協變後,不能修改協變元素。使用 out 修飾的泛型不能用作函式的引數,對應型別的成員變數 setter 方法會被限制,只能當做一個生產者使用。
3)逆變
透過 in 關鍵字表示 Data
class Data<T>(var value: T)
fun main() {
var data1: Data<Number> = Data<Number>(1f)
var data2: Data<in Int> = data1
println(data2.value) // 列印: 1.0
data2.value = 2
var a: Any ?= data2.value // 只能用Any接收value
}
說明:逆變後,只能使用 Any 接收逆變元素。使用 in 修飾的泛型不能用作函式的返回值,對應型別的成員變數 getter 方法會被限制,只能當做一個消費者使用。
4)通配
在有些時候,我們可能並不在乎到底使用哪一個型別,我們希望一個變數可以接受任意型別的結果,而不是去定義某一個特定的上界或下界。在Kotlin 泛型中,星號(*)代表了一種特殊的型別投影,可以代表任意型別。
class Data<T>(var value: T)
fun main() {
var data1: Data<Int> = Data<Int>(1)
var data2: Data<*> = data1 // Data<*>等價於Data<out Any>
println(data2.value) // 列印: 1.0
// data2.value = 2 // 編譯錯誤, setter方法被限制
var a: Any ?= data2.value // 只能用Any接收value
}
說明:由於不確定具體型別,使用時只能是 Any 型別。
4.5 泛型的界
Kotlin 泛型中,可以為其指定上界。
1)單上界
class Data<T: Number>(var value: T)
fun main() {
var data1: Data<Int> = Data<Int>(1)
// var data1: Data<String> = Data<String>("abc") // 編譯錯誤, 指定了上界為Number
var data2: Data<*> = data1 // Data<*>等價於Data<out Number>
println(data2.value) // 列印: 1.0
// data2.value = 2 // 編譯錯誤, setter方法被限制
var a: Number = data2.value // 可以用Number接收value
}
2)多上界
open class A {}
interface B {}
class Data<T>(var value: T) where T: A, T: B
4.6 具化型別引數(reified)
Kotlin 的內聯(inline)函式可以使用 reified 關鍵字具化型別引數,允許在函式體內部檢測泛型型別,因為這些型別資訊會被編譯器內嵌在呼叫點。但是,這隻適用於行內函數,因為行內函數中的型別資訊在編譯時是可知的,並且實際型別會被編譯到使用它們的地方。
以下呼叫會編譯報錯。
透過 inline 和 reified 修飾符,可以解決編譯報錯問題,如下。
inline fun<reified T> isType(value: Any) : Boolean {
return value is T
}
fun main() {
println(isType<Int>("abc")) // 列印: false
println(isType<String>("abc")) // 列印: true
}
宣告:本文轉自【Kotlin】函式。