取自:https://www.kotlincn.net/docs/reference/functions.html
預設參數:
函數參數可以有預設值, 當省略相應的參數時使用預設值。與其他語言相比,這可以減少重載數量:
fun read(b:Array<Byte>,off:Int = 0,len: Int = b.size){...}
預設值通過類型後面的 = 及給出的值來定義。
覆寫方法總是使用與基類型方法相同的預設參數值。當覆寫一個地遨遊預設參數值的方法時,必須從簽名中省略預設參數值:
open class A{
open fun getA(a:Int = 0){...}
}
class B:A(){
override fun getA(a:Int ){...} //不能有預設值
}
如果一個預設參數再一個無預設值的參數之前,那麼該預設值隻能通過使用命名參數調用該函數來使用:
fun foo(bar: Int = 0, baz: Int ){...}
foo(baz = 1)//使用預設值 bar=0
如果再預設參數之後的最後一個參數是lambda表達式,那麼它既可以作為命名參數再括号内傳入,也可以在括号外傳入:
fun foo(bar: Int = 0,baz: Int = 1,qux: () -> Unit){...}
foo(1) { println("hello") } //使用預設值 baz = 1
foo(qux = { println("hello")} ) //使用兩個預設值 bar = 0 baz = 1
foo { println("hello") } //使用兩個預設值 bar = 0 baz = 1
命名函數
可以在調用函數時使用命名的函數參數。當一個函數有大量的參數或者預設參數時這回非常友善。
給定以下函數:
fun reformat( str: Stirng,
isA: Boolean = true,
isB: Boolean = true,
isC: Boolean = false,
a: Char = ''
){...}
我們可以使用預設參數來調用它:
reformat(str)
然而,當使用非預設參數調用它時,該調用看起來就像:
reformat(str,true,true,false,'-')
使用命名參數我們可以使代碼更具有可讀性:
reformat(str,
isA = true,
isB = true,
isC = false,
a = '-'
)
并且如果我們不需要所有的參數:
reformat( str , a = '-' )
當一個函數調用混用位置參數與命名參數時,所有位置參數都要放在第一個命名參數之前。例如,允許調用
f(1,y = 2)
但不允許
f(x=1,2)
。
可以通過使用 星号 操作符将 可變數量參數(vararg) 以命名形式傳入:
fun foo( vararg strings: String ) {...}
foo( strings = *arrayOf("a","b","c") )
請注意,在調用java函數時不能使用命名參數文法,因為java位元組碼并不總是保留函數參數的名稱。
可變數量的參數 (varargs)
函數的參數(通常是最後一個)可以用
vararg
修飾符标記:
fun <T> asList( vararg ts: T ): List<T>{
val result = ArrayList<T>()
for( t in ts ) //ts is an Array
result.add(t)
return result
}
允許将可變數量的參數傳遞給函數
val list = asList(1,2,3)
在函數内部,類型
T
的
vararg
參數的可見方式是作為
T
數組,即上例中的
ts
變量具有類型
Array <outT>
。
隻有一個參數可以标注為
vararg
。如果
vararg
參數不是清單中的最後一個參數,可以使用命名參數文法傳遞其後的參數的值,或者,如果參數具有函數類型,則通過在括号外部傳一個lambda。
當我們調用
vararg
函數時,我們可以一個接一個的傳參,例如
asList(1,2,3)
,或者,如果我們已經有一個數組并希望将其内容傳給該函數,我們使用 伸展(spread) 操作符(在數組前面加
*
):
val a = araryOf(1,2,3)
val list = asList(-1,0,*a,4)
單表達式函數
當函數傳回單個表達式時,可以省略花括号并且在=符号之後指定代碼體即可:
fun double( x: Int ): Int = x * 2
當傳回值類型可由編譯器推斷時,顯示聲明傳回類型時可選的:
fun double( x: Int ) = x * 2
中綴表示法
标有
infix
關鍵字的函數也可以使用中綴表示法(忽略該調用的點與圓括号)調用。中綴漢書必須滿足以下要求:
- 它們必須是成員函數或擴充函數;
- 它們必須隻有一個參數;
- 其參數不得
且不能有接受可變數量的參數
。預設值
infix fun Int.shl(x: Int): Int{...} //用中綴表示法調用該函數 1 shl 2 //等同于這樣 1.shl(2)
請注意,中綴函數總是要求指定接收者與參數。當使用中綴表示法在目前接收者上調用方法時,需要顯示使用
this
;不能像正常方法調用那樣省略。這是確定非模糊解析所必需的。
class MyStringCollection {
infix fun add(s: String){...}
fun build() {
this add "abc" //正确
add("abc") //正确
add "abc" //錯誤:必須指定接收者
}
}
函數作用域
在kotlin中函數可以再檔案頂層聲明,這意味着你不需要像一些語言如java、C# 或 Scala 那樣需要建立一個類來儲存一個函數。此外除了頂層函數,kotlin中函數也可以聲明在局部作用域、作為成員函數以及擴充函數。
局部函數
kotlin支援局部函數,即一個函數在另一個函數内部:
fun dfs(graph: Graph){
fun dfs(current: Vertex, visited: Set<Vertex>){
if(!visited.add(current)) return
for(v in current.neighbors)
dfs(v, visited)
}
dfs(graph.vertices[0], HashSet())
}
局部函數可以通路外部函數(即閉包)的局部變量,是以在上例中,visited 可以是局部變量:
fun dfs(graph: Graph) {
val visited = HashSet<Vertex>()
fun dfs(current: Vertex) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v)
}
dfs(graph.vertices[0])
}
成員函數
成員函數是在類或對象内部定義的函數:
class Sample(){
fun foo() { println("Foo") }
}
成員函數以點表示法調用:
Sample().foo() //建立類 Sample 執行個體并調用foo
泛型函數
函數可以有泛型參數,通過在函數名前使用尖括号指定:
fun <T> singletonList(item: T): List<T> {...}
内聯函數
擴充函數
高階函數和lambda表達式
尾遞歸函數
kotlin支援一種稱為
尾遞歸
的函數式程式設計風格。這允許一些通常用循環寫的算法改用遞歸函數來寫,而無堆棧溢出的風險。當一個函數用
tailrec
修飾符标記并滿足所需的形式時,編譯器會優化該遞歸,留下一個快速而高效的基于循環的版本:
val eps = 1E-10 // "good enough", could be 10^-15
tailrec fun findFixPoint(x: Double = 1.0): Double
= if (Math.abs(x - Math.cos(x)) < eps) x else findFixPoint(Math.cos(x))
這段代碼計算餘弦的不動點(fixpoint of cosine),這是一個數學常數。 它隻是重複地從 1.0 開始調用 Math.cos,直到結果不再改變,對于這裡指定的 eps 精度會産生 0.7390851332151611 的結果。最終代碼相當于這種更傳統風格的代碼:
val eps = 1E-10 // "good enough", could be 10^-15
private fun findFixPoint(): Double {
var x = 1.0
while (true) {
val y = Math.cos(x)
if (Math.abs(x - y) < eps) return x
x = Math.cos(x)
}
}
要符合
tailrec
修飾符的條件的話,函數必須将其自身調用作為它執行的最後一個操作。就在遞歸調用後有更多代碼時,不能使用尾遞歸,并且不能用在 try/catch/finally 塊中。目前尾部遞歸隻在JVM後端中支援。