天天看點

kotlin學習之函數

取自: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後端中支援。