kotlin 好用的功能技巧和踩到的坑

前言,本文章並不是教學向的文章,本文意在總結和分享在實際使用kotlin進行開發時踩到的坑和好用的特性的分享,需要具備kotlin基礎知識後在來閱讀,如果你想找一篇教學類的文章,那可能中文官網更適合你。

讓人關注的特性

官網最大宣傳點有四個:

  1. *安全
    1. 空檢查
  2. *互操作Java,kotlin互相調用
    1. 可以繞過kotin的空檢查,在kotlin文件獲取java對象時
  3. 簡潔
    1. 方便的使用技巧
    2. 值得注意的特性和功能
  4. 工具友好 as的支持

中文官網

英文官網

一,空檢查,空判斷

簡單來說就是:聲明變量的時候就必須聲明變量是否可空,不可空的變量不能賦值爲空

摘自官網

/*
 徹底告別那些煩人的 NullPointerException——著名的十億美金的錯誤
*/

var output: String //竟然可以這樣寫,但是在使用時會編譯出錯
output = null   // 編譯錯誤

// Kotlin 可以保護你避免對可空類型進行誤操作

val name: String? = null    // 可空類型
println(name.length())      // 編譯錯誤
// 補充
println(name?.length())     // 當變量爲空時不會調用length()方法

二,互操作,kotlin可以和java互相調用

1.我們是不是一定會遇到與java交互的情況?

  1. 在已經有項目的情況下很難一下把項目的java全轉kotlin,因爲工作量太大,風險太大,轉變需要過程
  2. 就算項目是全kotlin的也避免不了合java的三方庫調用

2.在與java互相調用的時候需要注意的一些問題

1.可以繞過kotin的空檢查,在kotlin文件中調用java的方法返回對象時

kotlin從java獲取對象有三種情況

空檢查測試文件

  1. 不寫類的類型,用kotlin的類型自動推導

​ 可以看到調用java的方法,然後用自動推導出來的類型是強制轉換成非空的,但是不管返回是否爲空,編譯是可以通過的

​ 如果這個時候getTestModel()的返回值是空的就會引發NullPointerException

  1. 寫類的類型但是不可爲空

類型不可爲空

​ 首先這樣寫編譯也是沒問題的

​ 但是如果getTestModel()返回空值的話會爆IllegalStateException如下圖:

IllException

3.寫類的類型可空

在與java互相調用時,從java來的返回值推薦直接這樣寫,比較安全

三.一切皆是對象

Kotlin 中沒有基礎數據類型,只有封裝的數字類型

四.kotlin中函數是頭等的(可以瞭解一下函數式編程)

這意味着它們可以存儲在變量與數據結構中、作爲參數傳遞給其他高階函數(高階函數是將函數用作參數或返回值的函數。)以及從其他高階函數返回。可以像操作任何其他非函數值一樣操作函數。

存變量

存數組裏

做參數

函數做返回

最直接的帶來的好處就是java的回調寫起來更容易了

以前java想寫個回調

java calback

kotlin寫一個

kotlin callback

五.擴展函數和擴展屬性

1.擴展函數

從面對對象編程的角度來說擴展函數是很有意義的,比如 將String 轉爲 int 理應String.toInt()更符合面對對象的思想而不是Integer.paseInt();

而且在我們自己寫這種工具方法時也通常放在工具類裏,但是這個工具類本身還需要我們去項目裏找或者問一些資歷比較老的同學,也有可能它根本不存在,這無形之中增加了開發和維護的成本,而使用擴展函數來代替Utils+靜態方法的組合就很大程度的解決了這個問題。

不管怎麼說把一個方法直接掛到一個類上這種操作都很magic那我們看一下它到底是什麼東西

1.1kotlin的擴展函數示例,沒錯,直接這樣寫道kt的文件裏,外面不需要也不可以包個什麼class之類的東西,這個例子在一個叫Expand.kt的文件裏

fun String.toMyInt(): Int? {
    return try {
        Integer.parseInt(this)
    } catch (e: NumberFormatException) {
        null
    }
}
//簡單展示一下
fun show(){
    val str = "test"
    str.toMyInt()
}

1.2.編譯爲java之後

@Metadata(
   mv = {1, 1, 16},
   bv = {1, 0, 3},
   k = 2,
   d1 = {"\u0000\u000e\n\u0000\n\u0002\u0010\b\n\u0002\u0010\u000e\n\u0002\b\u0002\u001a\u0011\u0010\u0000\u001a\u0004\u0018\u00010\u0001*\u00020\u0002¢\u0006\u0002\u0010\u0003¨\u0006\u0004"},
   d2 = {"toMyInt", "", "", "(Ljava/lang/String;)Ljava/lang/Integer;", "app"}
)
public final class ExpandKt {
   @Nullable
   public static final Integer toMyInt(@NotNull String $this$toMyInt) {
      Intrinsics.checkParameterIsNotNull($this$toMyInt, "$this$toMyInt");

      Integer var1;
      try {
         var1 = Integer.parseInt($this$toMyInt);
      } catch (NumberFormatException var3) {
         var1 = null;
      }

      return var1;
   }
}

所以擴展方法是不能Override的,如果想在java中調用kotlin的擴展方法其實就是調用了一個靜態方法而已,也是沒問題的。

2.擴展屬性

同樣的kotlin的擴展屬性也跟擴展函數的方式相似

2.1kotlin擴展屬性

val String.lastChar: Char
    get() = get(length - 1)
var StringBuilder.lastChar: Char
    get() = get(length - 1)
    set(value) {
        this.setCharAt(length - 1, value)
    }

2.2編譯爲Java後

public static final char getLastChar(@NotNull String $this$lastChar) {
   Intrinsics.checkParameterIsNotNull($this$lastChar, "$this$lastChar");
   return $this$lastChar.charAt($this$lastChar.length() - 1);
}

public static final char getLastChar(@NotNull StringBuilder $this$lastChar) {
   Intrinsics.checkParameterIsNotNull($this$lastChar, "$this$lastChar");
   return $this$lastChar.charAt($this$lastChar.length() - 1);
}

public static final void setLastChar(@NotNull StringBuilder $this$lastChar, char value) {
   Intrinsics.checkParameterIsNotNull($this$lastChar, "$this$lastChar");
   $this$lastChar.setCharAt($this$lastChar.length() - 1, value);
}

同樣也是編譯爲靜態方法

五.密封類 sealed class,協程

類的繼承只能寫在相同的文件裏

這個東西它有什麼用?

在java中很難僅通過父類就獲取到它有多少個自類的,而密封類限制了子類必須父類寫在一個文件中,這種功能某種意義上說實際上是枚舉的一種擴展,枚舉的常量裏不能寫方法,密封類的子類還是類,所以可以像普通的類一樣寫方法寫功能都不受到限制。

同時,ide也容易知道你的有哪些子類,反應到實際開發中就是這樣:

sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()

fun eval(expr: Expr): Double = when(expr) {
    is Const -> expr.number
    is Sum -> eval(expr.e1) + eval(expr.e2)
    NotANumber -> Double.NaN
    // 不再需要 else 子句,因爲我們已經覆蓋了所有的情況
}

?

感覺還是沒啥用啊!!

好吧,實際上經典的應用場景是在使用協程做異步時,對返回的數據做處理時用的,比如網絡請求:

在實際開發中的應用舉例:

不使用協程的網絡請求:

HttpUtils.getData({ data ->
    //do something
}, { errCode ->
    //error 
})

如果使用協程還用這種寫法來寫的話,多少有點浪費了協程的一些特性(異步代碼寫法),所以我們大概率會寫成這樣:

1.返回的密封類

sealed class HttpResponse
class Succeed :HttpResponse(){
    fun getResData():String{
        //.....
        return "succeed"
    }
}

class Failed : HttpResponse(){
    fun getHttpCode():Int{
        return 500
    }
    fun showErrorMsgToast(){
        //大概不會有這樣的需求把,大概
    }
}

2.網絡請求的實現

suspend fun getData():HttpResponse{
    //假裝我做了網絡請求
    return Succeed()
}

3.實際的應用

suspend fun coroutinesGetApi() {
    val response: HttpResponse = getData()
    when (response) {
        is Succeed -> {
            response.getResData()
        }
        is Failed -> {
            response.showErrorMsgToast()
        }
    }
}

哎···我也分不清這是精妙的設計還是被逼的了····

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章