Kotlin 操作符:run、with、let、also、apply、takeIf、takeUnless、repeat

Elson_6發表於2018-02-26

概述

分析Kotlin的 Standard.kt 程式碼,主要分為三部分:

  1. run、with、let、also、apply 的比較
  2. takeIf、takeUnless、repeat 的使用
  3. 異常類的使用

一、run、with、let、also、apply 的比較

所有的總結都源自於程式碼,所以最終還是要回到程式碼中找到答案。

示例

1. run()

/**
 * Calls the specified function [block] and returns its result.
 */
@kotlin.internal.InlineOnly
public inline fun <R> run(block: () -> R): R = block()
複製程式碼

兩種寫法:T.run({...})T.run {},第二種寫法是第一種的變種,當方法的最後一個引數是lambda表示式時,可以將表示式移出;

class Main {

    fun test(){
	    // 第一種寫法
        val run1 = run({
            Log.d("Main", "我是內部的Run")
            "Run1"
        })
        Log.d("Main", "我是內部的Result=$run1") //我是內部的Result=Run1

		//第二種寫法
        val run2 = run {
            Log.d("Main", "我是外部的Run")
            "Run2"
        }
        Log.d("Main", "我是外部的Result=$run2" ) ////我是外部的Result=Run2
    }
}
複製程式碼

2. T.run()

/**
 * Calls the specified function [block] with `this` value as its receiver and returns its result.
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R = block()
複製程式碼
class Main {
    fun test(){
	val run = "ABCDEF".run {
            substring(2) //可以在內部直接呼叫String的方法
        }
        Log.d("Main", "T.run()的值 = $run" ) //T.run()的值 = CDEF
    }
}
複製程式碼

3. with()

/**
 * Calls the specified function [block] with the given [receiver] as its receiver and returns its result.
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()
複製程式碼
class Main {
    fun test(){
	val with = with("ABCDEF") {
            substring(2)
        }
        Log.d("Main", "with()的值 = $with" ) //with()的值 = CDEF
    }
}
複製程式碼

4. apply()

/**
 * Calls the specified function [block] with `this` value as its receiver and returns `this` value.
 */
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
複製程式碼
class Main {
    fun test(){
	val apply = "ABCDEF".apply {
            it.substring(2)
        }
        Log.d("Main", "T.apply()的值 = $apply" )//T.apply()的值 = ABCDEF,值沒有被改變
    }
}
複製程式碼

5. 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 { block(this); return this }
複製程式碼
class Main {
    fun test(){
	val also = "ABCDEF".also {
            it.substring(2)
        }
        Log.d("Main", "T.also()的值 = $also" )//T.also()的值 = ABCDEF,值沒有被改變
    }
}
複製程式碼

6. 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 = block(this)
複製程式碼
class Main {
    fun test(){
	val let = "ABCDEF".let {
            it.substring(2) //這裡需要使用it
        }
        Log.d("Main", "T.let()的值 = $let" ) //T.let()的值 = CDEF
    }
}
複製程式碼

2. 結論

圖:(圖片來源於網路)

這裡寫圖片描述

原始碼:

public inline fun <R> run(block: () -> R): R = block()
public inline fun <T, R> T.run(block: T.() -> R): R = block()
public inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }
public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
複製程式碼

從三個維度去分析:

  1. 該操作符是被某個類的物件呼叫,還是直接呼叫(即:run() 還是 T.run() )。
  2. 物件T是否被當做引數(即:T.also()T.let())。
  3. 返回值是否是return this(即:T.apply()T.also())。
操作符 接收者(this) 引數(it) 返回值(result) 典型應用場景
run ( block: () -> R ) : R 當前類 / 類R(最後一行)
T.run ( block: T.() -> R ) : R 類T / 類R(最後一行)
with ( receiver: T, block: T.() -> R ) : R 類T / 類R(最後一行)
T.apply ( block: T.() -> Unit ) : T 類T / 類T
T.also ( block: (T) -> Unit ) : T 當前類 類T 類T
T.let ( block: (T) -> R ) : R 當前類 類T 類R(最後一行)

分析:

  1. run()T.run() 比較:

    • T.run() 的引數T.()表示在其block中可以直接使用T的所有public方法,而run() 卻不行;
  2. T.apply()、T.also() 和 其他幾個比較:

    • 返回值都是return this,所以返回的都是之前的物件T ;
    • 由於 T.also() 方法體中有 block(this),將 this 作為引數傳入,所以在Lambda型別的block內部就不能再用this 獲取當前的傳入引數T,而要使用it 獲取T,而this 實際代表的是當前所處的類;
  3. T.also()、T.let() 和 其他幾個比較:

    • 這兩個方法都將T作為引數傳入,且方法體中都將 this 傳入 block(this),所以T必須都要用it來獲取;

二、takeIf、takeUnless、repeat 的使用

原始碼

/**
 * Returns `this` value if it satisfies the given [predicate] or `null`, if it doesn't.
 * 
 * 根據當前predicate的返回值判斷:若為空,則返回null,若不為空,則返回原值
 * 根據上面的總結,在predicate中獲取傳入的引數T時要使用it,而不是this;
 */
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? = if (predicate(this)) this else null

/**
 * Returns `this` value if it _does not_ satisfy the given [predicate] or `null`, if it does.
 */
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? = if (!predicate(this)) this else null
複製程式碼

示例

class Main {

    fun test(){
        val takeif = "ABC".takeIf {
            Log.d("Main", "this = $this") //this = com.example.kotlindemo.Main@f17fc13
            Log.d("Main", "it = $it") //it = ABC
            true //如果是false,則返回null
        }
        Log.d("Main", "我是takeif=$takeif" ) //我是takeif=ABC
    }
}
複製程式碼
/**
 * Executes the given function [action] specified number of [times].
 *
 * A zero-based index of current iteration is passed as a parameter to [action].
 */
@kotlin.internal.InlineOnly
public inline fun repeat(times: Int, action: (Int) -> Unit) {
    for (index in 0..times - 1) {
        action(index)
    }
}
複製程式碼
class Main {
    fun test(){
        repeat(10) {
            Log.d("Main",  "this = $this") //this = com.example.kotlindemo.Main@f17fc13
            Log.d("Main",  "$it")
        }
    }
}
複製程式碼

輸出的值:

com.example.kotlindemo D/Main: 0 com.example.kotlindemo D/Main: 1 com.example.kotlindemo D/Main: 2 com.example.kotlindemo D/Main: 3 com.example.kotlindemo D/Main: 4 com.example.kotlindemo D/Main: 5 com.example.kotlindemo D/Main: 6 com.example.kotlindemo D/Main: 7 com.example.kotlindemo D/Main: 8 com.example.kotlindemo D/Main: 9


三、異常類的使用

原始碼

public class NotImplementedError(message: String = "An operation is not implemented.") : Error(message)
public inline fun TODO(): Nothing = throw NotImplementedError()
public inline fun TODO(reason: String): Nothing = throw NotImplementedError("An operation is not implemented: $reason")
複製程式碼

示例

//Kotlin程式碼
class Command {
	fun execute(){
		TODO() //丟擲異常
	}
}

class Test {
	val command = Command()
	command.execute()// 當執行到這行程式碼時,就會丟擲異常NotImplementedError
}
複製程式碼

相關文章