怎么用 Kotlin 去提高生產(chǎn)力

匯總 Kotlin 相對于 Java 的優(yōu)勢,以及怎么用 Kotlin 去簡潔、務(wù)實(shí)、高效、安全的開發(fā),每個(gè)小點(diǎn)tip都有詳細(xì)的說明和案例代碼,爭取把每個(gè)tip分析得清楚易懂,會(huì)不斷的更新維護(hù)tips,歡迎 fork 進(jìn)來加入我們一起來維護(hù),有問題的話歡迎提Issues。

  • 推薦一個(gè)Kotlin的實(shí)踐項(xiàng)目debug_view_kotlin,用kotlin實(shí)現(xiàn)的Android浮層調(diào)試控制臺(tái),實(shí)時(shí)的顯示內(nèi)存、FPS、文字 log

Tip1- 更簡潔的字符串

詳見案例代碼KotlinTip1

Kotlin中的字符串基本Java中的類似,有一點(diǎn)區(qū)別是加入了三個(gè)引號(hào)"""來方便長篇字符的編寫。
而在 Java 中,這些都需要轉(zhuǎn)義,先看看java中的式例

    public void testString1() {
        String str1 = "abc";
        String str2 = "line1\n" +
                "line2\n" +
                "line3";
        String js = "function myFunction()\n" +
                "{\n" +
                "    document.getElementById(\"demo\").innerHTML=\"My First JavaScript Function\";\n" +
                "}";
        System.out.println(str1);
        System.out.println(str2);
        System.out.println(js);
    }

kotlin除了有單個(gè)雙引號(hào)的字符串,還對字符串的加強(qiáng),引入了三個(gè)引號(hào),"""中可以包含換行、反斜杠等等特殊字符:

/*
* kotlin對字符串的加強(qiáng),三個(gè)引號(hào)"""中可以包含換行、反斜杠等等特殊字符
* */
fun testString() {
    val str1 = "abc"
    val str2 = """line1\n
        line2
        line3
        """
    val js = """
        function myFunction()
        {
            document.getElementById("demo").innerHTML="My First JavaScript Function";
        }
        """.trimIndent()
    println(str1)
    println(str2)
    println(js)
}

同時(shí),Kotlin中引入了字符串模版,方便字符串的拼接,可以用$符號(hào)拼接變量和表達(dá)式

/*
* kotlin字符串模版,可以用$符號(hào)拼接變量和表達(dá)式
* */
fun testString2() {
    val strings = arrayListOf("abc", "efd", "gfg")
    println("First content is $strings")
    println("First content is ${strings[0]}")
    println("First content is ${if (strings.size > 0) strings[0] else "null"}")
}

值得注意的是,在Kotlin中,美元符號(hào)是特殊字符,在字符串中不能直接顯示,必須經(jīng)過轉(zhuǎn)義,方法1是用反斜杠,方法二是{'$'}

/*
*Kotlin中,美元符號(hào)$是特殊字符,在字符串中不能直接顯示,必須經(jīng)過轉(zhuǎn)義,方法1是用反斜杠,方法二是${'$'}
* */
fun testString3() {
    println("First content is \$strings")
    println("First content is ${'$'}strings")
}

Tip2- Kotlin中大多數(shù)控制結(jié)構(gòu)都是表達(dá)式

首先,需要弄清楚一個(gè)概念語句和表達(dá)式,然后會(huì)介紹控制結(jié)構(gòu)表達(dá)式的優(yōu)點(diǎn):簡潔

語句和表達(dá)式是什么?

  • 表達(dá)式有值,并且能作為另一個(gè)表達(dá)式的一部分使用
  • 語句總是包圍著它的代碼塊中的頂層元素,并且沒有自己的值

Kotlin與Java的區(qū)別

  • Java中,所有的控制結(jié)構(gòu)都是語句,也就是控制結(jié)構(gòu)都沒有值
  • Kotlin中,除了循環(huán)(for、do和do/while)以外,大多數(shù)控制結(jié)構(gòu)都是表達(dá)式(if/when等)

詳見案例代碼tip2

Example1:if語句

java中,if 是語句,沒有值,必須顯式的return

/*
* java中的if語句
* */
public int max(int a, int b) {
    if (a > b) {
        return a;
    } else {
        return b;
    }
}

kotlin中,if 是表達(dá)式,不是語句,因?yàn)楸磉_(dá)式有值,可以作為值return出去

/*
* kotlin中,if 是表達(dá)式,不是語句,類似于java中的三目運(yùn)算符a > b ? a : b
* */
fun max(a: Int, b: Int): Int {
    return if (a > b) a else b
}

上面的if中的分支最后一行語句就是該分支的值,會(huì)作為函數(shù)的返回值。這其實(shí)跟java中的三元運(yùn)算符類似,

/*
* java的三元運(yùn)算符
* */
public int max2(int a, int b) {
    return a > b ? a : b;
}

上面是java中的三元運(yùn)算符,kotlin中if是表達(dá)式有值,完全可以替代,故kotlin中已沒有三元運(yùn)算符了,用if來替代。
上面的max函數(shù)還可以簡化成下面的形式

/*
* kotlin簡化版本
* */
fun max2(a: Int, b: Int) = if (a > b) a else b

Example2:when語句

Kotlin中的when非常強(qiáng)大,完全可以取代Java中的switch和if/else,同時(shí),when也是表達(dá)式,when的每個(gè)分支的最后一行為當(dāng)前分支的值
先看一下java中的switch

    /*
    * java中的switch
    * */
    public String getPoint(char grade) {
        switch (grade) {
            case 'A':
                return "GOOD";
            case 'B':
            case 'C':
                return "OK";
            case 'D':
                return "BAD";
            default:
                return "UN_KNOW";
        }
    }

java中的switch有太多限制,我們再看看Kotlin怎樣去簡化的

/*
* kotlin中,when是表達(dá)式,可以取代Java 中的switch,when的每個(gè)分支的最后一行為當(dāng)前分支的值
* */
fun getPoint(grade: Char) = when (grade) {
    'A' -> "GOOD"
    'B', 'C' -> {
        println("test when")
        "OK"
    }
    'D' -> "BAD"
    else -> "UN_KNOW"
}

同樣的,when語句還可以取代java中的if/else if,其是表達(dá)式有值,并且更佳簡潔

    /*
    * java中的if else
    * */
    public String getPoint2(Integer point) {
        if (point > 100) {
            return "GOOD";
        } else if (point > 60) {
            return "OK";
        } else if (point.hashCode() == 0x100) {
            //...
            return "STH";
        } else {
            return "UN_KNOW";
        }
    }

再看看kotlin的版本,使用不帶參數(shù)的when,只需要6行代碼

/*
* kotlin中,when是表達(dá)式,可以取代java的if/else,when的每個(gè)分支的最后一行為當(dāng)前分支的值
* */
fun getPoint2(grade: Int) = when {
    grade > 90 -> "GOOD"
    grade > 60 -> "OK"
    grade.hashCode() == 0x100 -> "STH"
    else -> "UN_KNOW"
}

Tip3- 更好調(diào)用的函數(shù):顯式參數(shù)名/默認(rèn)參數(shù)值

Kotlin的函數(shù)更加好調(diào)用,主要是表現(xiàn)在兩個(gè)方面:1,顯式的標(biāo)示參數(shù)名,可以方便代碼閱讀;2,函數(shù)可以有默認(rèn)參數(shù)值,可以大大減少Java中的函數(shù)重載
例如現(xiàn)在需要實(shí)現(xiàn)一個(gè)工具函數(shù),打印列表的內(nèi)容:
詳見案例代碼KotlinTip3

/*
* 打印列表的內(nèi)容
* */
fun <T> joinToString(collection: Collection<T>,
                     separator: String,
                     prefix: String,
                     postfix: String): String {
    val result = StringBuilder(prefix)
    for ((index, element) in collection.withIndex()) {
        if (index > 0) result.append(separator)
        result.append(element)
    }
    result.append(postfix)
    return result.toString()
}
/*
* 測試
* */
fun printList() {
    val list = listOf(2, 4, 0)
    /*不標(biāo)明參數(shù)名*/
    println(joinToString(list, " - ", "[", "]"))
    /*顯式的標(biāo)明參數(shù)名稱*/
    println(joinToString(list, separator = " - ", prefix = "[", postfix = "]"))
}

如上面的代碼所示,函數(shù)joinToString想要打印列表的內(nèi)容,需要傳入四個(gè)參數(shù):列表、分隔符、前綴和后綴。
由于參數(shù)很多,在后續(xù)使用該函數(shù)的時(shí)候不是很直觀的知道每個(gè)參數(shù)是干什么用的,這時(shí)候可以顯式的標(biāo)明參數(shù)名稱,增加代碼可讀性。
同時(shí),定義函數(shù)的時(shí)候還可以給函數(shù)默認(rèn)的參數(shù),如下所示:

/*
* 打印列表的內(nèi)容,帶有默認(rèn)的參數(shù),可以避免java的函數(shù)重載
* */
fun <T> joinToString2(collection: Collection<T>,
                      separator: String = ", ",
                      prefix: String = "",
                      postfix: String = ""): String {
    val result = StringBuilder(prefix)
    for ((index, element) in collection.withIndex()) {
        if (index > 0) result.append(separator)
        result.append(element)
    }
    result.append(postfix)
    return result.toString()
}
/*
* 測試
* */
fun printList3() {
    val list = listOf(2, 4, 0)
    println(joinToString2(list, " - "))
    println(joinToString2(list, " , ", "["))
}

這樣有了默認(rèn)參數(shù)后,在使用函數(shù)時(shí),如果不傳入該參數(shù),默認(rèn)會(huì)使用默認(rèn)的值,這樣可以避免Java中大量的函數(shù)重載。

Tip4- 擴(kuò)展函數(shù)和屬性

擴(kuò)展函數(shù)和屬性是Kotlin非常方便實(shí)用的一個(gè)功能,它可以讓我們隨意的擴(kuò)展第三方的庫,你如果覺得別人給的SDK的api不好用,或者不能滿足你的需求,這時(shí)候你可以用擴(kuò)展函數(shù)完全去自定義。
例如String類中,我們想獲取最后一個(gè)字符,String中沒有這樣的直接函數(shù),你可以用.后聲明這樣一個(gè)擴(kuò)展函數(shù):
詳見案例代碼KotlinTip4

/*
* 擴(kuò)展函數(shù)
* */
fun String.lastChar(): Char = this.get(this.length - 1)
/*
*測試
* */
fun testFunExtension() {
    val str = "test extension fun";
    println(str.lastChar())
}

這樣定義好lastChar()函數(shù)后,之后只需要import進(jìn)來后,就可以用String類直接調(diào)用該函數(shù)了,跟調(diào)用它自己的方法沒有區(qū)別。這樣可以避免重復(fù)代碼和一些靜態(tài)工具類,而且代碼更加簡潔明了。
例如我們可以改造上面tip3中的打印列表內(nèi)容的函數(shù):

/*
* 用擴(kuò)展函數(shù)改造Tip3中的列表打印內(nèi)容函數(shù)
* */
fun <T> Collection<T>.joinToString3(separator: String = ", ",
                                    prefix: String = "",
                                    postfix: String = ""): String {
    val result = StringBuilder(prefix)
    for ((index, element) in withIndex()) {
        if (index > 0) result.append(separator)
        result.append(element)
    }
    result.append(postfix)
    return result.toString()
}

fun printList4() {
    val list = listOf(2, 4, 0)
    println(list.joinToString3("/"))
}

除了擴(kuò)展函數(shù),還可以擴(kuò)展屬性,例如我想實(shí)現(xiàn)String和StringBuilder通過屬性去直接獲得最后字符:

/*
* 擴(kuò)展屬性 lastChar獲取String的最后一個(gè)字符
* */
val String.lastChar: Char
    get() = get(length - 1)
/*
* 擴(kuò)展屬性 lastChar獲取StringBuilder的最后一個(gè)字符
* */
var StringBuilder.lastChar: Char
    get() = get(length - 1)
    set(value: Char) {
        setCharAt(length - 1, value)
    }
/*
* 測試
* */
fun testExtension(){
    val s = "abc"
    println(s.lastChar)
    val sb = StringBuilder("abc")
    println(sb.lastChar)
}

定義好擴(kuò)展屬性后,之后只需import完了就跟使用自己的屬性一樣方便了。

Why?Kotlin為什么能實(shí)現(xiàn)擴(kuò)展函數(shù)和屬性這樣的特性?

在Kotlin中要理解一些語法,只要認(rèn)識(shí)到Kotlin語言最后需要編譯為class字節(jié)碼,Java也是編譯為class執(zhí)行,也就是可以大致理解為Kotlin需要轉(zhuǎn)成Java一樣的語法結(jié)構(gòu),
Kotlin就是一種強(qiáng)大的語法糖而已,Java不具備的功能Kotlin也不能越界的。

  • 那Kotlin的擴(kuò)展函數(shù)怎么實(shí)現(xiàn)的呢?介紹一種萬能的辦法去理解Kotlin的語法:將Kotlin代碼轉(zhuǎn)化成Java語言去理解,步驟如下:
    • 在Android Studio中選擇Tools ---> Kotlin ---> Show Kotlin Bytecode 這樣就把Kotlin轉(zhuǎn)化為class字節(jié)碼了
    • class碼閱讀不太友好,點(diǎn)擊左上角的Decompile就轉(zhuǎn)化為Java
  • 再介紹一個(gè)小竅門,在前期對Kotlin語法不熟悉的時(shí)候,可以先用Java寫好代碼,再利用AndroidStudio工具將Java代碼轉(zhuǎn)化為Kotlin代碼,步驟如下:
    • 在Android Studio中選中要轉(zhuǎn)換的Java代碼 ---> 選擇Code ---> Convert Java File to Kotlin File

我們看看將上面的擴(kuò)展函數(shù)轉(zhuǎn)成Java后的代碼

/*
* 擴(kuò)展函數(shù)會(huì)轉(zhuǎn)化為一個(gè)靜態(tài)的函數(shù),同時(shí)這個(gè)靜態(tài)函數(shù)的第一個(gè)參數(shù)就是該類的實(shí)例對象
* */
public static final char lastChar(@NotNull String $receiver) {
    Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
    return $receiver.charAt($receiver.length() - 1);
}
/*
* 獲取的擴(kuò)展屬性會(huì)轉(zhuǎn)化為一個(gè)靜態(tài)的get函數(shù),同時(shí)這個(gè)靜態(tài)函數(shù)的第一個(gè)參數(shù)就是該類的實(shí)例對象
* */
public static final char getLastChar(@NotNull StringBuilder $receiver) {
    Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
    return $receiver.charAt($receiver.length() - 1);
}
/*
* 設(shè)置的擴(kuò)展屬性會(huì)轉(zhuǎn)化為一個(gè)靜態(tài)的set函數(shù),同時(shí)這個(gè)靜態(tài)函數(shù)的第一個(gè)參數(shù)就是該類的實(shí)例對象
* */
public static final void setLastChar(@NotNull StringBuilder $receiver, char value) {
    Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
    $receiver.setCharAt($receiver.length() - 1, value);
}

查看上面的代碼可知:對于擴(kuò)展函數(shù),轉(zhuǎn)化為Java的時(shí)候其實(shí)就是一個(gè)靜態(tài)的函數(shù),同時(shí)這個(gè)靜態(tài)函數(shù)的第一個(gè)參數(shù)就是該類的實(shí)例對象,這樣把類的實(shí)例傳入函數(shù)以后,函數(shù)內(nèi)部就可以訪問到類的公有方法。
對于擴(kuò)展屬性也類似,獲取的擴(kuò)展屬性會(huì)轉(zhuǎn)化為一個(gè)靜態(tài)的get函數(shù),同時(shí)這個(gè)靜態(tài)函數(shù)的第一個(gè)參數(shù)就是該類的實(shí)例對象,設(shè)置的擴(kuò)展屬性會(huì)轉(zhuǎn)化為一個(gè)靜態(tài)的set函數(shù),同時(shí)這個(gè)靜態(tài)函數(shù)的第一個(gè)參數(shù)就是該類的實(shí)例對象。
函數(shù)內(nèi)部可以訪問公有的方法和屬性。

  • 從上面轉(zhuǎn)換的源碼其實(shí)可以看到擴(kuò)展函數(shù)和擴(kuò)展屬性適用的地方和缺陷,有兩點(diǎn):
    • 擴(kuò)展函數(shù)和擴(kuò)展屬性內(nèi)只能訪問到類的公有方法和屬性,私有的和protected是訪問不了的
    • 擴(kuò)展函數(shù)不能被override,因?yàn)镴ava中它是靜態(tài)的函數(shù)
  • 下面再舉幾個(gè)擴(kuò)展函數(shù)的例子,讓大家感受一下擴(kuò)展函數(shù)的方便:
/*
* show toast in activity
* */
fun Activity.toast(msg: String){
    Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
}

val Context.inputMethodManager: InputMethodManager?
    get() = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager

/*
* hide soft input
* */
fun Context.hideSoftInput(view: View) {
    inputMethodManager?.hideSoftInputFromWindow(view.windowToken, 0)
}


/**
 * screen width in pixels
 */
val Context.screenWidth
    get() = resources.displayMetrics.widthPixels

/**
 * screen height in pixels
 */
val Context.screenHeight
    get() = resources.displayMetrics.heightPixels

/**
 * returns dip(dp) dimension value in pixels
 * @param value dp
 */
fun Context.dip2px(value: Int): Int = (value * resources.displayMetrics.density).toInt()

Tip5- 懶初始化by lazy 和 延遲初始化lateinit

懶初始化by lazy

懶初始化是指推遲一個(gè)變量的初始化時(shí)機(jī),變量在使用的時(shí)候才去實(shí)例化,這樣會(huì)更加的高效。因?yàn)槲覀兺ǔ?huì)遇到這樣的情況,一個(gè)變量直到使用時(shí)才需要被初始化,或者僅僅是它的初始化依賴于某些無法立即獲得的上下文。
詳見案例代碼KotlinTip5

/*
* 懶初始化api實(shí)例
* */
val purchasingApi: PurchasingApi by lazy {
    val retrofit: Retrofit = Retrofit.Builder()
            .baseUrl(API_URL)
            .addConverterFactory(MoshiConverterFactory.create())
            .build()
    retrofit.create(PurchasingApi::class.java)
}

像上面的代碼,retrofit生成的api實(shí)例會(huì)在首次使用到的時(shí)候才去實(shí)例化。需要注意的是by lazy一般只能修飾val不變的對象,不能修飾var可變對象。

class User(var name: String, var age: Int)

/*
* 懶初始化by lazy
* */
val user1: User by lazy {
    User("jack", 15)
}

延遲初始化lateinit

另外,對于var的變量,如果類型是非空的,是必須初始化的,不然編譯不通過,這時(shí)候需要用到lateinit延遲初始化,使用的時(shí)候再去實(shí)例化。

/*
* 延遲初始化lateinit
* */
lateinit var user2: User

fun testLateInit() {
    user2 = User("Lily", 14)
}

by lazy 和 lateinit 的區(qū)別

  • by lazy 修飾val的變量
  • lateinit 修飾var的變量,且變量是非空的類型

Tip6- 不用再手寫findViewById

在Android的View中,會(huì)有很多代碼是在聲明一個(gè)View,然后通過findViewById后從xml中實(shí)例化賦值給對應(yīng)的View。在kotlin中可以完全解放出來了,利用kotlin-android-extensions插件,不用再手寫findViewById。步驟如下:
詳見案例代碼KotlinTip6

  • 步驟1,在項(xiàng)目的gradle中 apply plugin: 'kotlin-android-extensions'
  • 步驟2,按照原來的習(xí)慣書寫布局xml文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tip6Tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <ImageView
        android:id="@+id/tip6Img"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/tip6Btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>
  • 步驟3,在java代碼中import對應(yīng)的布局就可以開始使用了,View不用提前聲明,插件會(huì)自動(dòng)根據(jù)布局的id生成對應(yīng)的View成員(其實(shí)沒有生成屬性,原理見下面)
import com.sw.kotlin.tips.R
/*
* 導(dǎo)入插件生成的View
* */
import kotlinx.android.synthetic.main.activity_tip6.*


class KotlinTip6 : Activity(){

    /*
    * 自動(dòng)根據(jù)layout的id生成對應(yīng)的view
    * */
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_tip6)
        tip6Tv.text = "Auto find view for TextView"
        tip6Img.setImageBitmap(null)
        tip6Btn.setOnClickListener{
            test()
        }
    }

    private fun test(){
        tip6Tv.text = "update"
    }

}

像上面代碼這樣,Activity里的三個(gè)View自動(dòng)生成了,不用再去聲明,然后findViewById,然后轉(zhuǎn)型賦值,是不是減少了很多沒必要的代碼,讓代碼非常的干凈。

Why?原理是什么?插件幫我們做了什么?

要看原理還是將上面的代碼轉(zhuǎn)為java語言來理解,參照tips4提供的方式轉(zhuǎn)換為如下的java代碼:

public final class KotlinTip6 extends Activity {
   private HashMap _$_findViewCache;

   protected void onCreate(@Nullable Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      this.setContentView(2131296284);
      TextView var10000 = (TextView)this._$_findCachedViewById(id.tip6Tv);
      Intrinsics.checkExpressionValueIsNotNull(var10000, "tip6Tv");
      var10000.setText((CharSequence)"Auto find view for TextView");
      ((ImageView)this._$_findCachedViewById(id.tip6Img)).setImageBitmap((Bitmap)null);
      ((Button)this._$_findCachedViewById(id.tip6Btn)).setOnClickListener((OnClickListener)(new OnClickListener() {
         public final void onClick(View it) {
            KotlinTip6.this.test();
         }
      }));
   }

   private final void test() {
      TextView var10000 = (TextView)this._$_findCachedViewById(id.tip6Tv);
      Intrinsics.checkExpressionValueIsNotNull(var10000, "tip6Tv");
      var10000.setText((CharSequence)"update");
   }

   public View _$_findCachedViewById(int var1) {
      if(this._$_findViewCache == null) {
         this._$_findViewCache = new HashMap();
      }

      View var2 = (View)this._$_findViewCache.get(Integer.valueOf(var1));
      if(var2 == null) {
         var2 = this.findViewById(var1);
         this._$_findViewCache.put(Integer.valueOf(var1), var2);
      }

      return var2;
   }

   public void _$_clearFindViewByIdCache() {
      if(this._$_findViewCache != null) {
         this._$_findViewCache.clear();
      }

   }
}

如上面的代碼所示,在編譯階段,插件會(huì)幫我們生成視圖緩存,視圖由一個(gè)Hashmap結(jié)構(gòu)的_$_findViewCache變量緩存,
會(huì)根據(jù)對應(yīng)的id先從緩存里查找,緩存沒命中再去真正調(diào)用findViewById查找出來,再存在HashMap中。

在fragment中findViewByID

在fragment中也類似,有一點(diǎn)區(qū)別,例子如下:

class Tip6Fragment : Fragment() {

    override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val view = inflater?.inflate(R.layout.fragment_tip6, container, false)
        /*
        * 這時(shí)候不能在onCreateView方法里用view,需要在onViewCreate里,原理是插件用了getView來findViewById
        * */
        //tip6Tv.text = "test2"
        return view
    }

    /*
    * 需要在onViewCreate里,原理是插件用了getView來findViewById
    * */
    override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        tip6Tv.text = "test"
    }
}

如上所示,F(xiàn)ragment需要注意,不能在onCreateView方法里用view,不然會(huì)出現(xiàn)空指針異常,需要在onViewCreate里,原理是插件用了getView來findViewById,
我們看看將上面的代碼轉(zhuǎn)成java后的代碼:

public final class Tip6Fragment extends Fragment {
   private HashMap _$_findViewCache;

   @Nullable
   public View onCreateView(@Nullable LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
      View view = inflater != null?inflater.inflate(2131296286, container, false):null;
      return view;
   }

   public void onViewCreated(@Nullable View view, @Nullable Bundle savedInstanceState) {
      super.onViewCreated(view, savedInstanceState);
      TextView var10000 = (TextView)this._$_findCachedViewById(id.tip6Tv);
      Intrinsics.checkExpressionValueIsNotNull(var10000, "tip6Tv");
      var10000.setText((CharSequence)"test");
   }

   public View _$_findCachedViewById(int var1) {
      if(this._$_findViewCache == null) {
         this._$_findViewCache = new HashMap();
      }

      View var2 = (View)this._$_findViewCache.get(Integer.valueOf(var1));
      if(var2 == null) {
         View var10000 = this.getView();
         if(var10000 == null) {
            return null;
         }

         var2 = var10000.findViewById(var1);
         this._$_findViewCache.put(Integer.valueOf(var1), var2);
      }

      return var2;
   }

   public void _$_clearFindViewByIdCache() {
      if(this._$_findViewCache != null) {
         this._$_findViewCache.clear();
      }

   }

   // $FF: synthetic method
   public void onDestroyView() {
      super.onDestroyView();
      this._$_clearFindViewByIdCache();
   }
}

跟Activity中類似,會(huì)有一個(gè)View的HashMap,關(guān)鍵不同的地方在__findCachedViewById里面,需要getView或者當(dāng)前Fragment的View, 故在onViewCreated中g(shù)etView還是空的,原理就好理解了。另外在onDestroyView會(huì)調(diào)用__clearFindViewByIdCache方法清掉緩存。

Tip7- 利用局部函數(shù)抽取重復(fù)代碼

Kotlin中提供了函數(shù)的嵌套,在函數(shù)內(nèi)部還可以定義新的函數(shù)。這樣我們可以在函數(shù)中嵌套這些提前的函數(shù),來抽取重復(fù)代碼。如下面的案例所示:
詳見案例代碼KotlinTip7

class User(val id: Int, val name: String, val address: String, val email: String)

fun saveUser(user: User) {
    if (user.name.isEmpty()) {
        throw IllegalArgumentException("Can't save user ${user.id}: empty Name")
    }
    if (user.address.isEmpty()) {
        throw IllegalArgumentException("Can't save user ${user.id}: empty Address")
    }
    if (user.email.isEmpty()) {
        throw IllegalArgumentException("Can't save user ${user.id}: empty Email")
    }
    //save to db ...
}

上面的代碼在判斷name、address等是否為空的處理其實(shí)很類似。這時(shí)候,我們可以利用在函數(shù)內(nèi)部嵌套的聲明一個(gè)通用的判空函數(shù)將相同的代碼抽取到一起:

/*
*利用局部函數(shù)抽取相同的邏輯,去除重復(fù)的代碼
* */
fun saveUser2(user: User) {
    fun validate(value: String, fildName: String) {
        if (value.isEmpty()) {
            throw IllegalArgumentException("Can't save user ${user.id}: empty $fildName")
        }
    }

    validate(user.name, "Name")
    validate(user.address, "Address")
    validate(user.email, "Email")
    //save to db ...
}

除了利用嵌套函數(shù)去抽取,此時(shí),其實(shí)也可以用擴(kuò)展函數(shù)來抽取,如下所示:

/*
* 利用擴(kuò)展函數(shù)抽取邏輯
* */
fun User.validateAll() {
    fun validate(value: String, fildName: String) {
        if (value.isEmpty()) {
            throw IllegalArgumentException("Can't save user $id: empty $fildName")
        }
    }

    validate(name, "Name")
    validate(address, "Address")
    validate(email, "Email")
}

fun saveUser3(user: User) {
    user.validateAll()
    //save to db ...
}

Tip8- 使用數(shù)據(jù)類來快速實(shí)現(xiàn)model類

在java中要聲明一個(gè)model類需要實(shí)現(xiàn)很多的代碼,首先需要將變量聲明為private,然后需要實(shí)現(xiàn)get和set方法,還要實(shí)現(xiàn)對應(yīng)的hashcode equals toString方法等,如下所示:
詳見案例代碼Tip8

    public static class User{

        private String name;
        private int age;
        private int gender;
        private String address;
        
        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        public int getGender() {
            return gender;
        }

        public void setGender(int gender) {
            this.gender = gender;
        }

        public String getAddress() {
            return address;
        }

        public void setAddress(String address) {
            this.address = address;
        }

        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", gender=" + gender +
                    ", address='" + address + '\'' +
                    '}';
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            User user = (User) o;

            if (age != user.age) return false;
            if (gender != user.gender) return false;
            if (name != null ? !name.equals(user.name) : user.name != null) return false;
            return address != null ? address.equals(user.address) : user.address == null;
        }

        @Override
        public int hashCode() {
            int result = name != null ? name.hashCode() : 0;
            result = 31 * result + age;
            result = 31 * result + gender;
            result = 31 * result + (address != null ? address.hashCode() : 0);
            return result;
        }
    }

這段代碼Java需要70行左右,而如果用kotlin,只需要一行代碼就可以做到。

/*
* Kotlin會(huì)為類的參數(shù)自動(dòng)實(shí)現(xiàn)get set方法
* */
class User(val name: String, val age: Int, val gender: Int, var address: String)

/*
* 用data關(guān)鍵詞來聲明一個(gè)數(shù)據(jù)類,除了會(huì)自動(dòng)實(shí)現(xiàn)get set,還會(huì)自動(dòng)生成equals hashcode toString
* */
data class User2(val name: String, val age: Int, val gender: Int, var address: String)

對于Kotlin中的類,會(huì)為它的參數(shù)自動(dòng)實(shí)現(xiàn)get set方法。而如果加上data關(guān)鍵字,還會(huì)自動(dòng)生成equals hashcode toString。原理其實(shí)數(shù)據(jù)類中的大部分代碼都是模版代碼,Kotlin聰明的將這個(gè)模版代碼的實(shí)現(xiàn)放在了編譯器處理的階段。

Tip9- 用類委托來快速實(shí)現(xiàn)裝飾器模式

通過繼承的實(shí)現(xiàn)容易導(dǎo)致脆弱性,例如如果需要修改其他類的一些行為,這時(shí)候Java中的一種策略是采用裝飾器模式:創(chuàng)建一個(gè)新類,實(shí)現(xiàn)與原始類一樣的接口并將原來的類的實(shí)例作為一個(gè)成員變量。
與原始類擁有相同行為的方法不用修改,只需要直接轉(zhuǎn)發(fā)給原始類的實(shí)例。如下所示:
詳見案例代碼KotlinTip9

/*
* 常見的裝飾器模式,為了修改部分的函數(shù),卻需要實(shí)現(xiàn)所有的接口函數(shù)
* */
class CountingSet<T>(val innerSet: MutableCollection<T> = HashSet<T>()) : MutableCollection<T> {

    var objectAdded = 0
    
    override val size: Int
        get() = innerSet.size

    /*
    * 需要修改的方法
    * */
    override fun add(element: T): Boolean {
        objectAdded++
        return innerSet.add(element)
    }

    /*
    * 需要修改的方法
    * */
    override fun addAll(elements: Collection<T>): Boolean {
        objectAdded += elements.size
        return innerSet.addAll(elements)
    }

    override fun contains(element: T): Boolean {
        return innerSet.contains(element)
    }

    override fun containsAll(elements: Collection<T>): Boolean {
        return innerSet.containsAll(elements)
    }

    override fun isEmpty(): Boolean {
        return innerSet.isEmpty()
    }

    override fun clear() {
        innerSet.clear()
    }

    override fun iterator(): MutableIterator<T> {
        return innerSet.iterator()
    }

    override fun remove(element: T): Boolean {
        return innerSet.remove(element)
    }

    override fun removeAll(elements: Collection<T>): Boolean {
        return innerSet.removeAll(elements)
    }

    override fun retainAll(elements: Collection<T>): Boolean {
        return innerSet.retainAll(elements)
    }

}

如上所示,想要修改HashSet的某些行為函數(shù)add和addAll,需要實(shí)現(xiàn)MutableCollection接口的所有方法,將這些方法轉(zhuǎn)發(fā)給innerSet去具體的實(shí)現(xiàn)。雖然只需要修改其中的兩個(gè)方法,其他代碼都是模版代碼。
只要是重復(fù)的模版代碼,Kotlin這種全新的語法糖就會(huì)想辦法將它放在編譯階段再去生成。
這時(shí)候可以用到類委托by關(guān)鍵字,如下所示:

/*
* 通過by關(guān)鍵字將接口的實(shí)現(xiàn)委托給innerSet成員變量,需要修改的函數(shù)再去override就可以了
* */
class CountingSet2<T>(val innerSet: MutableCollection<T> = HashSet<T>()) : MutableCollection<T> by innerSet {

    var objectAdded = 0

    override fun add(element: T): Boolean {
        objectAdded++
        return innerSet.add(element)
    }

    override fun addAll(elements: Collection<T>): Boolean {
        objectAdded += elements.size
        return innerSet.addAll(elements)
    }
}

通過by關(guān)鍵字將接口的實(shí)現(xiàn)委托給innerSet成員變量,需要修改的函數(shù)再去override就可以了,通過類委托將10行代碼就可以實(shí)現(xiàn)上面接近100行的功能,簡潔明了,去掉了模版代碼。

Tip10- Lambda表達(dá)式簡化OnClickListener

詳見案例代碼KotlinTip10
lambda表達(dá)式可以簡化我們的代碼。以Android中常見的OnClickListener來說明,在Java中我們一般這樣設(shè)置:

        TextView textView = new TextView(context);
        textView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //handle click
            }
        });

Java中需要聲明一個(gè)匿名內(nèi)部類去處理,這種情況可以用lambda表達(dá)式來簡化。

  • lambda表達(dá)式一般長這樣
    • { x:Int, y:Int -> x+y }
    • 參數(shù) -> 表達(dá)式 并且始終在大括號(hào)中
    • it作為默認(rèn)參數(shù)名
    • lambda捕捉,當(dāng)捕捉final變量時(shí),它的值和lambda代碼一起存儲(chǔ)
    • 非final變量,它的值被封裝在一個(gè)特殊的包裝器中,這個(gè)包裝器的引用會(huì)和lambda代碼一起存儲(chǔ)

我們來看看Kotlin中的例子:

    val textView = TextView(context)

    /*
    * 傳統(tǒng)方式
    * */
    textView.setOnClickListener(object : android.view.View.OnClickListener {
        override fun onClick(v: android.view.View?) {
            //handle click
        }
    })

    /*
    * lambda的方式
    * */
    textView.setOnClickListener({ v ->
        {
            //handle click
        }
    })

當(dāng)lambda的參數(shù)沒有使用時(shí)可以省略,省略的時(shí)候用it來替代

    /*
    * lambda的參數(shù)如果沒有使用可以省略,省略的時(shí)候用it來替代
    * */
    textView.setOnClickListener({
        //handle click
    })

lambda在參數(shù)的最后一個(gè)的情況可以將之提出去

    /*
    * lambda在參數(shù)的最后一個(gè)的情況可以將之提出去
    * */
    textView.setOnClickListener() {
        //handle click
    }

lambda提出去之后,函數(shù)如果沒有其他參數(shù)括號(hào)可以省略

    /*
    * lambda提出去之后,函數(shù)如果沒有其他參數(shù)括號(hào)可以省略
    * */
    textView.setOnClickListener {
        //handle click
    }

我們再看看如果自己去實(shí)現(xiàn)一個(gè)帶lambda參數(shù)的函數(shù)應(yīng)該怎么去定義:

interface OnClickListener {
    fun onClick()
}

class View {
    var listener: OnClickListener? = null;

    /*
    * 傳統(tǒng)方式
    * */
    fun setOnClickListener(listener: OnClickListener) {
        this.listener = listener
    }

    fun doSth() {
        //some case:
        listener?.onClick()
    }

    /*
    * 聲明lambda方式,listener: () -> Unit
    * */
    fun setOnClickListener(listener: () -> Unit) {

    }
}

在函數(shù)參數(shù)中需要聲明lambda的類型后,再調(diào)用該函數(shù)的時(shí)候就可以傳入一個(gè)lambda表達(dá)式了。

Tip11- with語句來簡化代碼

  • with 函數(shù)原型:
inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()
  • with函數(shù)并不是擴(kuò)展函數(shù),返回值是最后一行,可以直接調(diào)用對象的方法

Kotlin中可以用with語句來省略同一個(gè)變量的多次聲明,例如下面的函數(shù)
詳見案例代碼KotlinTip11

/*
*打印字母表函數(shù),在函數(shù)內(nèi)result變量在好幾處有使用到
* */
fun alphabet(): String {
    val result = StringBuilder()
    result.append("START\n")
    for (letter in 'A'..'Z') {
        result.append(letter)
    }
    result.append("\nEND")
    return result.toString()
}

在上面的函數(shù)中,result變量出現(xiàn)了5次,如果用with語句,可以將這5次都不用再出現(xiàn)了,我們來一步一步地看是怎么實(shí)現(xiàn)的:

/*
* 通過with語句,將result作為參數(shù)傳入,在內(nèi)部就可以通過this來表示result變量了
* */
fun alphabet2(): String {
    val result = StringBuilder()
    return with(result) {
        this.append("START\n")
        for (letter in 'A'..'Z') {
            this.append(letter)
        }
        this.append("\nEND")
        this.toString()
    }
}

通過with語句,將result作為參數(shù)傳入,在內(nèi)部就可以通過this來表示result變量了,而且這個(gè)this是可以省略的

/*
* 通過with語句,將result參數(shù)作為參數(shù),在內(nèi)部this也可以省略掉
* */
fun alphabet3(): String {
    val result = StringBuilder()
    return with(result) {
        append("START\n")
        for (letter in 'A'..'Z') {
            append(letter)
        }
        append("\nEND")
        toString()
    }
}

在內(nèi)部this省略掉后,現(xiàn)在只有一個(gè)result了,這個(gè)其實(shí)也是沒必要的,于是出現(xiàn)了下面的最終版本:

/*
* 通過with語句,可以直接將對象傳入,省掉對象的聲明
* */
fun alphabet4(): String {
    return with(StringBuilder()) {
        append("START\n")
        for (letter in 'A'..'Z') {
            append(letter)
        }
        append("\nEND")
        toString()
    }
}

像上面這樣,我們可以把同一個(gè)變量的顯式調(diào)用從5次變?yōu)?次,發(fā)現(xiàn)Kotlin的魅力了吧。

Tip12- apply語句來簡化代碼

  • apply 函數(shù)原型:
inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
  • apply函數(shù),在函數(shù)范圍內(nèi),可以任意調(diào)用該對象的任意方法,并返回該對象

除了用上面的with可以簡化同一個(gè)變量的多次聲明,還可以用apply關(guān)鍵字,我們來改造一下tip11中的函數(shù):
詳見案例代碼KotlinTip12

/*
* 用apply語句簡化代碼,在apply的大括號(hào)里可以訪問類的公有屬性和方法
* */
fun alphabet5() = StringBuilder().apply {
    append("START\n")
    for (letter in 'A'..'Z') {
        append(letter)
    }
    append("\nEND")
}.toString()

像上面這樣的,通過apply后,在apply的大括號(hào)里可以訪問類的公有屬性和方法。這在對應(yīng)類的初始化是非常方便的,例如下面的例子

/*
* 用apply語句簡化類的初始化,在類實(shí)例化的時(shí)候,就可以通過apply把需要初始化的步驟全部實(shí)現(xiàn),非常的簡潔
* */
fun testApply(context: Context) {
    var imgView = ImageView(context).apply {
        setBackgroundColor(0)
        setImageBitmap(null)
    }

    var textView = TextView(context).apply {
        text = "content"
        textSize = 20.0f
        setPadding(10, 0, 0, 0)
    }
    
    var user = User().apply { 
        age = 15
        name = "Jack"
        val a = address
        address = "bbb"
    }
}

在類實(shí)例化的時(shí)候,就可以通過apply把需要初始化的步驟全部實(shí)現(xiàn),非常的簡潔

Tip13- 在編譯階段避免掉NullPointerException

NullPointerException是Java程序員非常頭痛的一個(gè)問題,我們知道Java 中分受檢異常和非受檢異常,NullPointerException是非受檢異常,也就是說NullPointerException不需要顯示的去catch住,
往往在運(yùn)行期間,程序就可能報(bào)出一個(gè)NullPointerException然后crash掉,Kotlin作為一門高效安全的語言,它嘗試在編譯階段就把空指針問題顯式的檢測出來,把問題留在了編譯階段,讓程序更加健壯。
詳見案例代碼KotlinTip13

  • Kotlin中類型分為可空類型和不可空類型,通過?代表可空,不帶?代表不可為空
fun testNullType() {
    val a: String = "aa"
    /*
    * a是非空類型,下面的給a賦值為null將會(huì)編譯不通過
    * */
    //a = null
    a.length

    /*
   * ?聲明是可空類型,可以賦值為null
   * */
    var b: String? = "bb"
    b = null
    
    /*
   * b是可空類型,直接訪問可空類型將編譯不通過,需要通過?.或者!!.來訪問
   * */
    //b.length
    b?.length
    b!!.length
}
  • 對于一個(gè)不可為空類型:如果直接給不可為空類型賦值一個(gè)可能為空的對象就在編譯階段就不能通過
  • 對于一個(gè)可空類型:通過?聲明,在訪問該類型的時(shí)候直接訪問不能編譯通過,需要通過?.或者!!.
    • ?. 代表著如果該類型為空的話就返回null不做后續(xù)的操作,如果不為空的話才會(huì)去訪問對應(yīng)的方法或者屬性
    • !!. 代表著如果該類型為空的話就拋出NullPointerException,如果不為空就去訪問對應(yīng)的方法或者屬性,
      所以只有在很少的特定場景才用這種符號(hào),代表著程序不處理這種異常的case了,會(huì)像java代碼一樣拋出NullPointerException。
      而且代碼中一定不用出現(xiàn)下面這種代碼,會(huì)讓代碼可讀性很差而且如果有空指針異常,我們也不能馬上發(fā)現(xiàn)是哪空了:
    /*
    * 不用鏈?zhǔn)降倪B續(xù)用!!.
    * */
    val user = User()
    user!!.name!!.subSequence(0,5)!!.length

對應(yīng)一個(gè)可空類型,每次對它的訪問都需要帶上?.判斷

val user: User? = User()

    /*
    * 每次訪問都用用?.判斷
    * */
    user?.name
    user?.age
    user?.toString()

但這樣多了很多代碼,kotlin做了一些優(yōu)化,

    /*
    * 或者提前判斷是否為空,如果不為空在這個(gè)分支里會(huì)自動(dòng)轉(zhuǎn)化為非空類型就可以直接訪問了
    * */
    if (user != null) {
        user.name
        user.age
        user.toString()
    }

通過if提前判斷類型是否為空,如果不為空在這個(gè)分支里會(huì)自動(dòng)轉(zhuǎn)化為非空類型就可以直接訪問了。

let語句簡化對可空對象對訪問

  • let 函數(shù)原型:
inline fun <T, R> T.let(block: (T) -> R): R = block(this)
  • let函數(shù)默認(rèn)當(dāng)前這個(gè)對象作為閉包的it參數(shù),返回值是函數(shù)里面最后一行,或者指定return。

上面的代碼還可以用?.let語句進(jìn)行,如下所示:

    /*
    * 通過let語句,在?.let之后,如果為空不會(huì)有任何操作,只有在非空的時(shí)候才會(huì)執(zhí)行l(wèi)et之后的操作
    * */
    user?.let {
        it.name
        it.age
        it.toString()
    }

通過let語句,在?.let之后,如果為空不會(huì)有任何操作,只有在非空的時(shí)候才會(huì)執(zhí)行l(wèi)et之后的操作

Elvis操作符 ?: 簡化對空值的處理

如果值可能為空,對空值的處理可能會(huì)比較麻煩,像下面這樣:

/*
* 對空值的處理
* */
fun testElvis(input: String?, user: User?) {
    val a: Int?
    if (input == null) {
        a = input?.length
    } else {
        a = -1;
    }

    if (user == null) {
        var newOne = User()
        newOne.save()
    } else {
        user.save()
    }
}

Elvis操作符?:能夠簡化上面的操作,?:符號(hào)會(huì)在對于空的情況才會(huì)進(jìn)行下面的處理,跟?.let正好相反,例如我們可以用兩行代碼來簡化上面從操作:

/**
 * Elvis操作符 ?: 簡化對空值的處理
 */
fun testElvis2(input: String?, user: User?) {
    val b = input?.length ?: -1;
    user?.save() ?: User().save()
}

Tip14- 運(yùn)算符重載

Kotlin支持對運(yùn)算符的重載,這對于對一些對象的操作更加靈活直觀。

  • 使用operator來修飾plus\minus函數(shù)
  • 可重載的二元算術(shù)符
    • A * B times
    • A / B div
    • A % B mod
    • A + B plus
    • A - B minus

以下面對坐標(biāo)點(diǎn)Point的案例說明怎么去重載運(yùn)算符:
詳見案例代碼KotlinTip14

class Point(val x: Int, val y: Int) {

    /*
    * plus函數(shù)重載對Point對象的加法運(yùn)算符
    * */
    operator fun plus(other: Point): Point {
        return Point(x + other.x, y + other.y)
    }

    /*
    * minus函數(shù)重載對Point對象的減法運(yùn)算符
    * */
    operator fun minus(other: Point): Point {
        return Point(x - other.x, y - other.y)
    }

    override fun toString(): String {
        return "[x:$x, y:$y]"
    }

}

如上所示,通過plus函數(shù)重載對Point對象的加法運(yùn)算符,通過minus函數(shù)重載對Point對象的減法運(yùn)算符,然后就可以用+、-號(hào)對兩個(gè)對象進(jìn)行操作了:

fun testOperator() {
    val point1 = Point(10, 10)
    val point2 = Point(4, 4)
    val point3 = point1 + point2
    println(point3)
    println(point1 - point2)
}

Tip15- 高階函數(shù)簡化代碼

  • 高階函數(shù):以另一個(gè)函數(shù)作為參數(shù)或者返回值的函數(shù)
  • 函數(shù)類型
    • (Int, String) -> Unit
    • 參數(shù)類型->返回類型 Unit不能省略
    val list = listOf(2, 5, 10)
    /*
    * 傳入函數(shù)來過濾
    * */
    println(list.filter { it > 4 })
      
    /*
    * 定義函數(shù)類型
    * */
    val sum = { x: Int, y: Int -> x + y }
    val action = { println(42) }

    val sum2: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
    val action2: () -> Unit = { println(42) }      

函數(shù)作為參數(shù)

函數(shù)作為參數(shù),即高階函數(shù)中,函數(shù)的參數(shù)可以是一個(gè)函數(shù)類型,例如要定義一個(gè)函數(shù),該函數(shù)根據(jù)傳入的操作函數(shù)來對2和3做相應(yīng)的處理。
詳見案例代碼KotlinTip15

/*
* 定義對2和3的操作函數(shù)
* */
fun twoAndThree(operator: (Int, Int) -> Int) {
    val result = operator(2, 3)
    println("Result:$result")
}

fun test03() {
    twoAndThree { a, b -> a + b }
    twoAndThree { a, b -> a * b }
}

operator是函數(shù)類型,函數(shù)的具體類型為(Int, Int) -> Int,即輸入兩個(gè)Int返回一個(gè)Int值。定義完了后就可以像上面這樣使用了。
再舉一個(gè)例子,實(shí)現(xiàn)String類的字符過濾:

/*
* 函數(shù)作為參數(shù),實(shí)現(xiàn)String類的字符過濾
* */
fun String.filter(predicate: (Char) -> Boolean): String {
    val sb = StringBuilder()
    for (index in 0 until length) {
        val element = get(index)
        if (predicate(element)) sb.append(element)
    }
    return sb.toString()
}

fun test04() {
    println("12eafsfsfdbzzsa".filter { it in 'a'..'f' })
}

像上面這樣predicate是函數(shù)類型,它會(huì)根據(jù)傳入的char來判斷得到一個(gè)Boolean值。

函數(shù)作為返回值

函數(shù)作為返回值也非常實(shí)用,例如我們的需求是根據(jù)不同的快遞類型返回不同計(jì)價(jià)公式,普通快遞和高級快遞的計(jì)價(jià)規(guī)則不一樣,這時(shí)候我們可以將計(jì)價(jià)規(guī)則函數(shù)作為返回值:

enum class Delivery {
    STANDARD, EXPEDITED
}

/*
* 根據(jù)不同的運(yùn)輸類型返回不同的快遞方式
* */
fun getShippingCostCalculator(delivery: Delivery): (Int) -> Double {
    if (delivery == Delivery.EXPEDITED) {
        return { 6 + 2.1 * it }
    }
    return { 1.3 * it }
}

fun test05(){
    val calculator1 = getShippingCostCalculator(Delivery.EXPEDITED)
    val calculator2 = getShippingCostCalculator(Delivery.STANDARD)
    println("Ex costs ${calculator1(5)}")
    println("St costs ${calculator2(5)}")
}

如果是普通快遞,采用6 + 2.1 * it的規(guī)則計(jì)算價(jià)格,如果是高級快遞按照6 + 2.1 * it計(jì)算價(jià)格,根據(jù)不同的類型返回不同的計(jì)價(jià)函數(shù)。

Tip16- 用Lambda來簡化策略模式

策略模式是常見的模式之一,java的例子如下。
詳見案例代碼Tip16

/**
     * 定義策略接口
     */
    public interface Strategy {
        void doSth();
    }

    /**
     * A策略
     */
    public static class AStrategy implements Strategy {
        @Override
        public void doSth() {
            System.out.println("Do A Strategy");
        }
    }

    /**
     * B策略
     */
    public static class BStrategy implements Strategy {
        @Override
        public void doSth() {
            System.out.println("Do B Strategy");
        }
    }

    /**
     * 策略實(shí)施者
     */
    public static class Worker {

        private Strategy strategy;

        public Worker(Strategy strategy) {
            this.strategy = strategy;
        }

        public void work() {
            System.out.println("START");
            if (strategy != null) {
                strategy.doSth();
            }
            System.out.println("END");
        }
    }

如上面的例子所示,有A、B兩種策略,Worker根據(jù)不同的策略做不同的工作,使用策略時(shí):

    Worker worker1 = new Worker(new AStrategy());
    Worker worker2 = new Worker(new BStrategy());
    worker1.work();
    worker2.work();

在java中實(shí)現(xiàn)這種策略模式難免需要先定義好策略的接口,然后根據(jù)接口實(shí)現(xiàn)不同的策略,
在Kotlin中完全可以用用Lambda來簡化策略模式,上面的例子用Kotlin實(shí)現(xiàn):

/**
 * 策略實(shí)施者
 * @param strategy lambda類型的策略
 */
class Worker(private val strategy: () -> Unit) {
    fun work() {
        println("START")
        strategy.invoke()
        println("END")
    }

}

/*
* 測試
* */
fun testStrategy() {
    val worker1 = Worker({
        println("Do A Strategy")
    })
    val bStrategy = {
        println("Do B Strategy")
    }
    val worker2 = Worker(bStrategy)
    worker1.work()
    worker2.work()
}

不需要先定義策略的接口,直接把策略以lambda表達(dá)式的形式傳進(jìn)來就行了。

參考文檔

文章來源

https://github.com/heimashi/kotlin_tips

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容