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.")
幾個函數具體分析:
- 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
}
- 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")
}
- 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}")
}
- also 上下文對象作爲 lambda 表達式的參數(it)來訪問。 返回值是上下文對象本身。
當你在代碼中看到 also 時,可以將其理解爲“並且執行以下操作”。
val numbers = mutableListOf("one", "two", "three")
numbers
.also { println("The list elements before adding new one: $it") }
.add("four")
- 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