kotlin 函數以及作用域函數

kotlin 函數詳解

構造方法

在Kotlin中,一個類可以有一個primary構造方法以及一個或多個Secondary構造方法,primary構造方法是類頭(class header)的一部分,它位於類名後面,可以擁有若干參數,如果primary構造方法沒有任何註解或是可見性關鍵字修飾,那麼construction關鍵字可省略。

class Myclass constructor(user: String) {
    private val name: String = user.toLowerCase()

    init {
        println(user)
    }
}

fun main(args: Array<String>) {
    val myClass = Myclass("zhangsan")
}

次構造方法

class Person constructor(username: String) {
    private var username: String
    private var age: Int
    private var sex: String

    init {
        this.age = 20
        this.sex = "男"
        this.username = "張三"
    }

    constructor(username: String, age: Int) : this(username) { //次構造方法,this傳遞主構造方法
        this.username = username
        this.age = age
        this.sex = "女"
    }

    constructor(username: String, age: Int, sex: String) : this(username, age) {
        this.username = username
        this.age = age
        this.sex = sex
    }

    fun printInfo() {
        println("username:${this.username},age:${this.age},sex:${this.sex}")
    }
}

fun main(args: Array<String>) {
    val person = Person("李四")
    val person2 = Person("李四", 70)
    val person3 = Person("李四", 20, "男")
    println(person.printInfo())
    println(person2.printInfo())
    println(person3.printInfo())
}

var,val 在構造方法中定義參數:

class Student(private val name: String,private val age: Int) {
//加了var val就是成員變量,刪掉就會報錯,數據類中都的參數會加上var或者val
    fun printInfo() {
        println("name:$name,age:$age")
    }
}

如果構造方法擁有註解或是可見修飾符,那麼constructor關鍵字是不能省略的,並且位於修飾符後面

class Student2 private constructor(username: String) {}

默認參數(default arguments)

在jvm上,如果類的primary構造方法的所有參數都擁有默認值,那麼kotlin編譯器就會成爲這個類生成,一個不帶參數的構造方法,這個不帶參數的構造方法會使用這些參數的默認值,這樣做的目的在於可以跟Spring等框架更好的集成

class Student3(val username: String = "zhangsan") {}

fun main() {
    val student = Student3()//沒有傳遞任何參數,默認參數就是無參構造函數
    println(student.username) //zhangsan
}
fun test(a:Int=0,b:Int=2)= println(a-b)
fun main(args: Array<String>) {
    test(2) //a=2,默認傳遞第一個參數
    test(b=3)//b=3,具名參數,具體賦值給哪一個參數
    test(2,3)
}

對於重寫的方法來說,子類所擁有的重寫方法會使用與父類相同的默認參數值。在重寫一個擁有默認參數值的方法時,方法簽名中必須要將默認參數值省略掉。如下例子:

open class A{
    open fun method (a:Int,b:Int=3)=a+b
}
class B:A(){
    override fun method(a: Int, b: Int=5)=a+b //會報錯。當前b的值是3,不能去指定默認值
    override fun method(a: Int, b: Int)=a+b //正確
}
fun main(args: Array<String>) {
    println(A().method(1)) //6
    println(B().method(6)) //11
}

如果一個默認參數位於其他無默認值的參數前面,那麼默認值只能通過在調用函數時使用具名函數的方式來使用,如下例子:

fun test2(a:Int =1,b:Int)= println(a-b)

fun main(args: Array<String>) {
    test2(b=3)//具名參數
    test2(3)// 此時傳給的a=3,會報錯,因爲當前a=1了。
}

lamba 表達式的函數

如果函數的最後一個參數是lambda表達式,而且在調用時是位於圓括號之外。那麼就可以不指定lambda表達式的具名參數名。如下例子:

fun test3(a: Int=1,b: Int=2,compute:(x:Int,y:Int)->Unit){
    compute(a,b)
}
fun main(args: Array<String>) {
    test3(2,3, {a,b -> println(a-b)}) //-1
    test3(2,3) {a,b -> println(a-b)} //-1
    test3(2){a,b -> println(a-b)} //0
    test3{a,b -> println(a-b)} //0
    //以上表示都正確的
}

具名參數

在調用函數時,函數參數可以是具名的。當一個函數有大量參數或是一些參數擁有默認值,這種調用方式是比較方便的。如下例子:

fun test4(a: Int,b: Int=2,c: Int=1,d: Int)= println(a+b+c+d)
fun main(args: Array<String>) {
    test4(a=2,d = 5)
    test4(1,2,3,4)
}

可變參數 vararg

一個方法中,只允許一個參數爲vararg,通常作爲最後一個參數,如果vararg不是最後一個參數,那麼其後的參數就需要通過具名參數形式進行傳遞;如果其後的參數是函數類型,那麼還可以通過在圓括號外傳遞lambda表達式來實現。如下例子:

fun  test5(vararg strings:String){
    println(strings.javaClass) //strings 的java類型是數組類型
    strings.forEach { println(it) }
}
fun main(args: Array<String>) {
    test5("a","sf","sdf")
    test5(strings = arrayOf("a","c","f")) //此處是錯誤的
    //要加一個*號,分散運算符
    test5(strings = *arrayOf("a","c","f"))
    val arrays= arrayOf("a","b")
    test5(*arrays)
}

內聯函數(inline function)

inline fun myCalculate(a:Int,b:Int)=a+b
fun main(args: Array<String>) {
    println(myCalculate(1,2))
}

匿名函數 必須要放在圓括號裏面實現

fun(x: Int, y: Int) = x + y  //complier error必須要有一個方法名

fun main(args: Array<String>) {
    fun(x: Int, y: Int) = x + y
    fun(x: Int, y: Int): Int {
        return x + y
    }

    val strings = arrayOf("hello", "world")
    strings.filter(fun(item): Boolean = item.length > 3).forEach(fun(item) {
        println(item)
    })
}

注意:

  • 在調用函數時,如果同時使用了位置參數與具名參數,那麼所有的位置參數都必須要位於第一個具名參數之前。比如說,foo(1,x=2)是允許的,foo(x=2,1)是不允許的。
  • 在kotlin中調用java方法時不能使用具名參數語法,因爲java字節碼並不總是會保留方法參數名信息。

作用域函數(let、run、with、apply 以及 also)

下面是作用域函數的典型用法:

Person("Alice", 20, "Amsterdam").let {
    println(it)
    it.moveTo("London")
    it.incrementAge()
    println(it)
}

如果不使用 let 來寫這段代碼,就必須引入一個新變量,並在每次使用它時重複其名稱。

val alice = Person("Alice", 20, "Amsterdam")
println(alice)
alice.moveTo("London")
alice.incrementAge()
println(alice)
這5個作用域的區別:
  • 引用上下文對象的方式(this 還是 it)
  • 返回值
this 還是 it

run、with 以及 apply 通過關鍵字 this 引用上下文對象。因此,在它們的 lambda 表達式中可以像在普通的類函數中一樣訪問上下文對象。在大多數場景,當你訪問接收者對象時你可以省略 this,來讓你的代碼更簡短。相對地,如果省略了 this,就很難區分接收者對象的成員及外部對象或函數。因此,對於主要對對象成員進行操作(調用其函數或賦值其屬性)的 lambda 表達式,建議將上下文對象作爲接收者(this)。

val adam = Person("Adam").apply { 
    age = 20                       // 和 this.age = 20 或者 adam.age = 20 一樣
    city = "London"
}
println(adam)

let 及 also 將上下文對象作爲 lambda 表達式參數。如果沒有指定參數名,對象可以用隱式默認名稱 it 訪問。it 比 this 簡短,帶有 it 的表達式通常更容易閱讀。然而,當調用對象函數或屬性時,不能像 this 這樣隱式地訪問對象。因此,當上下文對象在作用域中主要用作函數調用中的參數時,使用 it 作爲上下文對象會更好。若在代碼塊中使用多個變量,則 it 也更好。

fun getRandomInt(): Int {
    return Random.nextInt(100).also {
        writeToLog("getRandomInt() generated value $it")
    }
}

val i = getRandomInt()
返回值
  • apply 及 also 返回上下文對象。
  • let、run 及 with 返回 lambda 表達式結果.

上下文對象:apply 及 also 的返回值是上下文對象本身。因此,它們可以作爲輔助步驟包含在調用鏈中:你可以繼續在同一個對象上進行鏈式函數調用。

val numberList = mutableListOf<Double>()
numberList.also { println("Populating the list") }
    .apply {
        add(2.71)
        add(3.14)
        add(1.0)
    }
    .also { println("Sorting the list") }
    .sort()

Lambda 表達式結果:let、run 及 with 返回 lambda 表達式的結果。所以,在需要使用其結果給一個變量賦值,或者在需要對其結果進行鏈式操作等情況下,可以使用它們。

val numbers = mutableListOf("one", "two", "three")
val countEndsWithE = numbers.run { 
    add("four")
    add("five")
    count { it.endsWith("e") }
}
println("There are $countEndsWithE elements that end with e.")
幾個函數具體分析:
  1. let 上下文對象作爲 lambda 表達式的參數(it)來訪問。返回值是 lambda 表達式的結果。
val numbers = mutableListOf("one", "two", "three", "four", "five")
numbers.map { it.length }.filter { it > 3 }.let { 
    println(it)
    // 如果需要可以調用更多函數
} 

若代碼塊僅包含以 it 作爲參數的單個函數,則可以使用方法引用(::)代替 lambda 表達式:

val numbers = mutableListOf("one", "two", "three", "four", "five")
numbers.map { it.length }.filter { it > 3 }.let(::println)

let 經常用於僅使用非空值執行代碼塊。如需對非空對象執行操作,可對其使用安全調用操作符 ?. 並調用 let 在 lambda 表達式中執行操作。

val str: String? = "Hello" 
//processNonNullString(str)       // 編譯錯誤:str 可能爲空
val length = str?.let { 
    println("let() called on $it")
    processNonNullString(it)      // 編譯通過:'it' 在 '?.let { }' 中必不爲空
    it.length
}
  1. with 一個非擴展函數:上下文對象作爲參數傳遞,但是在 lambda 表達式內部,它可以作爲接收者(this)使用。 返回值是 lambda 表達式結果。

我們建議使用 with 來調用上下文對象上的函數,而不使用 lambda 表達式結果。 在代碼中,with 可以理解爲“對於這個對象,執行以下操作。”

val numbers = mutableListOf("one", "two", "three")
with(numbers) {
    println("'with' is called with argument $this")
    println("It contains $size elements")
}
  1. run 上下文對象 作爲接收者(this)來訪問。 返回值 是 lambda 表達式結果。

run 和 with 做同樣的事情,但是調用方式和 let 一樣——作爲上下文對象的擴展函數.

val service = MultiportService("https://example.kotlinlang.org", 80)

val result = service.run {
    port = 8080
    query(prepareRequest() + " to port $port")
}

// 同樣的代碼如果用 let() 函數來寫:
val letResult = service.let {
    it.port = 8080
    it.query(it.prepareRequest() + " to port ${it.port}")
}
  1. also 上下文對象作爲 lambda 表達式的參數(it)來訪問。 返回值是上下文對象本身。
    當你在代碼中看到 also 時,可以將其理解爲“並且執行以下操作”。
val numbers = mutableListOf("one", "two", "three")
numbers
    .also { println("The list elements before adding new one: $it") }
    .add("four")
  1. apply 上下文對象 作爲接收者(this)來訪問。 返回值 是上下文對象本身
    對於不返回值且主要在接收者(this)對象的成員上運行的代碼塊使用 apply。apply 的常見情況是對象配置。這樣的調用可以理解爲“將以下賦值操作應用於對象”。
val adam = Person("Adam").apply {
    age = 32
    city = "London"        
}
println(adam)
函數選擇
函數 對象引用 返回值 是否是擴展函數
let it Lambda 表達式結果
also it 上下文對象
run this Lambda 表達式結果
with this Lambda 表達式結果 不是
apply this 上下文對象

以下是根據預期目的選擇作用域函數的簡短指南:

  • 對一個非空(non-null)對象執行 lambda 表達式:let
  • 將表達式作爲變量引入爲局部作用域中:let
  • 對象配置:apply
  • 對象配置並且計算結果:run
  • 在需要表達式的地方運行語句:非擴展的 run
  • 附加效果:also
  • 一個對象的一組函數調用:with
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章