天天看點

Kotlin學習筆記(六)-- 标準函數和靜态方法

文章目錄

        • 1. 标準函數with、run 和 apply
          • 1.1 with
          • 1.2 run
          • 1.3 apply
          • 1.4 let 和also
          • 1.5 普通函數*with* 和 *T.run、T.apply*等擴充函數的比較
        • 2. 靜态方法
          • 2.1 companion object
          • 2.2 注解
          • 2.3 頂層方法

概念:Kotlin的标準函數指的是Standard.kt檔案中定義的函數,任何kotlin代碼都可以自由地調用所有的标準函數。

1. 标準函數with、run 和 apply

1.1 with
  • 函數結構:
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}
           
  • 參數說明:

    with函數接收兩個參數:第一個參數可以是一個任意類型的對象,第二個參數是一個Lambda表達式。with函數會在Lambda表達式中提供第一個參數對象的上下文,并使用Lambda中的最後一行代碼作為傳回值傳回。

  • 作用:

    它可以在連續調用同一個對象的多個方法時讓代碼變得更加精簡。

  • 舉個栗子:

    有一個水果清單,我們想吃完所有水果并将結果列印出來,通常可以這樣寫:

val list = listOf("apple", "orange", "grape", "banana")
        val sb = StringBuilder()
        sb.append("Start eating fruits.\n")
        for (fruit in list) {
            sb.append(fruit).append("\n")
        }
        sb.append("Ate all fruits.")
        print(sb.toString())
           

使用with精簡後的寫法:

val list = listOf("apple", "orange", "grape", "banana")
        val result = with(StringBuilder()){
            append("Start eating fruits.\n")
            for (fruit in list) {
                append(fruit).append("\n")
            }
            append("Ate all fruits.")
            toString()
        }
        print(result)
           
1.2 run
  • 函數結構:
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
   contract {
       callsInPlace(block, InvocationKind.EXACTLY_ONCE)
   }
   return block()
}
           
  • 參數說明:

    run函數是一個拓展函數,其用法和使用場景和with是非常類似的,隻是文法上稍微做了一些改動而已。首先run函數是不能直接調用的,而是一定要調用某個對象的run函數才行;其次run函數隻接收一個Lambda參數,并且會在Lambda中提供調用對象的上下文。其它方面和with是一樣的,包括使用Lambda的最後一行代碼作為傳回值傳回。

  • 舉個栗子:

    使用run函數修改一下吃水果的栗子:

val list = listOf("apple", "orange", "grape", "banana")
    val result = StringBuilder().run{
        append("Start eating fruits.\n")
        for (fruit in list) {
            append(fruit).append("\n")
        }
        append("Ate all fruits.")
        println("result length $length")
        toString()
    }
    print(result)
           

T.run就是用擴充函數的方式調用了block: T.(). 是以在T.run函數的代碼塊中, 可以使用this關鍵字來得到對主變量T的引用. 在實際程式設計中, 通過this關鍵字的調用通常可以不寫this… 是以在上面的示例代碼中, 我們直接使用了println( l e n g t h ) 而 不 是 p r i n t l n ( length) 而不是println( length)而不是println({this.length})

1.3 apply
  • 函數結構:
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
   contract {
       callsInPlace(block, InvocationKind.EXACTLY_ONCE)
   }
   block()
   return this
}
           
  • 參數說明:

    apply函數和run函數是非常相似的,它也是一個拓展函數,也就是都要在某個對象上調用,并且隻接收一個Lambda參數,也會在Lambda中提供調用對象的上下文,但是apply函數無法指定傳回值,而是會自動傳回對象本身。

  • 舉個栗子:

    使用apply函數修改一下吃水果的栗子:

val list = listOf("apple", "orange", "grape", "banana")
    val result = StringBuilder().apply{
        append("Start eating fruits.\n")
        for (fruit in list) {
            append(fruit).append("\n")
        }
        append("Ate all fruits.")
    }
    print(result.toString())
           

實際應用舉例:

我們在項目中經常需要在啟動某個Activity的時候進行參數傳遞:

val intent=Intent(context,SecondActivity::class.java)
intent.putExtra("param1","data1")
intent.putExtra("param2","data2")
context.startActivity(intent)
           

沒傳遞一個參數都要調用一次intent.putExtra(),有多少個參數就得調用多少次,顯得有些累贅,如果使用apply函數則可以去掉這種累贅:

val intent=Intent(context,SecondActivity::class.java).apply{
	putExtra("param1","data1")
	putExtra("param2","data2")
}
context.startActivity(intent)
           
1.4 let 和also
  • let函數結構:
/**
 * Calls the specified function [block] with `this` value as its argument and returns its result.
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}
           
  • also函數結構:
/**
 * Calls the specified function [block] with `this` value as its argument and returns `this` value.
 */
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.also(block: (T) -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}
           

let和also的函數結構是比較相似的,細微的差别在于各自的傳回值不同,T.let傳回的是block函數參數的結果,而T.also傳回的是this,也就是調用者T本身。

1.5 普通函數with 和 T.run、T.apply等擴充函數的比較

舉個栗子:實際項目中我們經常會碰到需要臨時改變某個View的屬性并改變其顯示的狀态的需求,現在有一個Checkbox之前被設定為不可點選選中的狀态,現在需要改為可點選選中狀态并讓其選中:

使用with:

fun enable(cb:CheckBox){
    with(cb){
        cb?.isClickable=true;
        cb?.isSelected=true;
    }
}
           

使用run之類的擴充函數:

fun enable(cb:CheckBox){
    cb?.run{
        isClickable=true;
        isSelected=true;
    }
}
           

差别一目了然,就此類應用場景而言,run、apply之類的拓展函數在調用之前就能做好判空操作,更簡潔實用

2. 靜态方法

2.1 companion object

在java中定義一個靜态方法非常簡單,隻需要在方法上聲明一個static關鍵字即可,通常我們在寫工具類的時候都會習慣将裡面需要給外部調用的方法定義成static靜态方法。那類似的功能到了kotlin中要怎麼寫呢?

像工具類這種功能,在kotlin中非常推薦使用單例類的方式來實作,比如:

object Util{
	fun doSomething(){
		println("do something")
	}
}
           

然後就可以調用Util.doSomething()了。

不過,使用單例類的寫法會将整個類中的所有方法全部變成類似于靜态方法的調用方式,而如果我們隻是希望讓類中的某個方法變成靜态方法的調用方式該怎麼辦呢?kotlin為我們提供了companion object:

class Util{
	fun doSomething1(){
		println("do something 1")
	} 
	
	companion object{
		fun doSomething2(){
			println("do something 2")
		} 
	}
}
           

這樣就隻有doSomething2()變成了類似于靜态方法的調用,為什麼說是類似于呢?因為其實doSomething2()并不是靜态方法,companion object關鍵字實際上會在Util類的内部建立一個伴生類,而doSomething2()方法就是定義在這個伴生類裡面的執行個體方法。kotlin會保證Util類始終隻會存在一個伴生對象,是以調用Util.doSomething2()實際上調用的是Util類中伴生對象的doSomething2()方法。

然而如果我們确實需要定義真正的靜态方法,kotlin仍然提供了兩種實作方式:注解和頂層方法。

2.2 注解

前面使用的單例類和companion object都隻是在文法形式上模仿了靜态方法的調用方式,它們都不是真正的靜态方法。是以,如果你在java代碼中以靜态方法的形式去調用的話,你會發現這些方法并不存在,那如果我們想在java中這樣調用怎麼辦呢?kotlin已經為我們考慮到了這種情形,為此,kotlin提供了**@JvmStatic**注解,隻要我們給單例類或companion object中的方法加上@JvmStatic注解,那麼kotlin編譯器就會将這些方法編譯成真正的靜态方法。如:

class Util{
	fun doSomething1(){
		println("do something 1")
	} 
	
	companion object{
		@JvmStatic
		fun doSomething2(){
			println("do something 2")
		} 
	}
}
           

注意:@JvmStatic注解隻能加載單例類或companion object中的方法上,如果加在一個普通方法上,會直接文法報錯。

2.3 頂層方法

頂層方法指的是那些沒有定義在任何類中方法,比如main()方法。kotlin編譯器會将所有的頂層方法全部編譯成靜态方法。

想要定義一個頂層方法很簡單,建立kotlin檔案的時候将檔案類型選為File即可。我們建立一個File類型的Util.kt檔案,内容如下:

fun doSomething(){
		println("do something")
	} 
           

在kotlin代碼中,所有的頂層方法都可以在任何位置被直接調用,不用管包名路徑,也不用建立執行個體。

而java代碼沒有頂層方法這個概念,所有的方法必須定義在類中,是以是沒法直接調用doSomething()這個方法的。那我們要怎麼在java代碼中調用這種靜态方法呢?我們在建立Util.kt之後,kotlin編譯器會自動建立一個UtilKt的java類,doSomething()方法以靜态方法的形式定義在UtilKt類中,是以在java中使用UtilKt.doSomething()的寫法調用即可。