Kotlin——初級篇(六): 可空型別、空安全、非空斷言、型別轉換等特性總結

Jetictors發表於2018-01-16

Kotlin——初級篇(六): 可空型別、空安全、非空斷言、型別轉換等特性總結

在我們熟知的Java中,定義一個變數可以預設不賦值,因為Java的系統會給我們預設賦一個預設值,並且Java可定義一個賦值為null的變數,這樣在使用這個變數的時候都會去顯示判斷該變數是否為null。從程式碼的簡潔性以及程式碼的閱讀性來說,就差了Koltin一籌了,那麼Kotlin定義一個變數可為null的變數怎麼定義呢?下面針對Kotlin的這些特性,作出一個詳細的講解。

目錄

Kotlin——初級篇(六): 可空型別、空安全、非空斷言、型別轉換等特性總結

一、可空型別、空安全

在前面的變數、常量中我們已經講解到了變數的定義。這裡不作詳述。若你有興趣,請參見Kotlin——初級篇(二):變數、常量、註釋

1.1、定義一個可空型別的變數

定義一個可空型別的變數的格式為:修飾符 變數名 : 型別? = 值

這裡為了演示,定義變數和定義可空變數的區別,會提到定義變數的程式碼。

例:

// 定義一個不可為空的變數,用var修飾的變數可以被重新賦值,用val修飾的變數則不能,但是不能賦值為null
var a : Int = 12
val b : Int = 13

a = 20
// a = null 不能賦值為null   
// b = 20   不能被重新賦值

if(a == null){
    // 這樣的判斷毫無意義,因為變數a永遠不可能null
}

/*
    定義可空型別的變數,即變數可以被賦值為null
    定義格式為:修飾符 變數名 : 型別? = 值
*/
var nullA : Int? = 12
val nullB : Int? = 13

nullA = null

if(nullA == null){
    println("nullA = $nullA")
}
複製程式碼

可以看出:變數 nullA 的值為 null

分析:要定義一個可空型別的變數時,即在定義變數的型別後面加上?符號就行了。在使用的時候,記住要判斷該段該變數是否為空,這個操作在Java中經常會用到...,如果定義一個不可為空型別的變數時,則判斷將毫無意義,因為這個變數永遠不會為空。

1.2、判斷可空型別的兩種使用方式

在上面我們提到,可空型別需要判斷在使用,這裡介紹除了if ... else...之外的其他方式

1.2.1、if...else...判斷

例:

var str : String? = "123456"
str = null

if (str == null){
    println("變數str為空")
}else{
    println("str.length => ${str.length}")
}
複製程式碼

輸出結果為:

變數str為空
複製程式碼

1.2.2、使用符號?.判斷

  • 該符號的用法為:可空型別變數?.屬性/方法。如果可空型別變數為null時,返回null
  • 這種用法大量用於鏈式操作的用法中,能有效避免空引用異常(NullPointException),因為只要鏈式其中的一個為null,則整個表示式都為null

例:

var str : String? = "123456"
str = null

println(str?.length)   // 當變數str為null時,會返回空(null)
複製程式碼

輸出結果為:

null
複製程式碼

1.2.3、鏈式呼叫

?.這種符號去判斷是否為null,在Kotlin中使用的地方是很多,特別是對於鏈式呼叫來說體驗性更好。

例:這裡簡單寫一個建造者模式,來模擬?.在鏈式呼叫中的用法

class Test{

    class Builder{
        private var name : String? = "Tom"
        private var age : Int? = 0
        private var sex : String? = "男"

        fun setName(name : String) : Builder?{
            this.name = name
            return this
        }

        fun setAge(age : Int) : Builder?{
            this.age = age
            return this
        }

        fun setSex(sex: String?) : Builder?{
            this.sex = sex
            return this
        }

        override fun toString(): String {
            return "Builder(name=$name, age=$age, sex=$sex)"
        }
    }
}

fun main(args: Array<String>) {
    val builder : Test.Builder? = Test.Builder().setName("Lily")?.setSex("nv")?.setAge(10)
    println(builder.toString())
}
複製程式碼

輸出結果為:

Builder(name=Lily, age=10, sex=女)
複製程式碼

如果你上面的程式碼看不懂可以看下的程式碼,你可以看下面的例子:頂一個可空型別的字串的長度加5再減去10

val testStr : String? = null
val result = testStr?.length?.plus(5)?.minus(10)
println(result)
複製程式碼

可以看出輸出結果為: null

1.2.4、函式中使用可空型別的情況下

當一個函式/方法有返回值時,如果方法中的程式碼使用?.去返回一個值,那麼方法的返回值的型別後面也要加上?符號

例:

fun funNullMethod() : Int? {
    val str : String? = "123456"
    return str?.length
}
複製程式碼

輸出結果為:

6
複製程式碼

1.2.5、let操作符

  • let操作符的作用:當時用符號?.驗證的時候忽略掉null
  • let的用法:變數?.let{ ... }

例:排除掉陣列中的空元素

val arrTest : Array<Int?> = arrayOf(1,2,null,3,null,5,6,null)

// 傳統寫法
for (index in arrTest) {
    if (index == null){
        continue
    }
    println("index => $index")
}

// let寫法
for (index in arrTest) {
    index?.let { println("index => $it") }
}
複製程式碼

輸出結果為:

index => 1
index => 2
index => 3
index => 5
index => 6
複製程式碼

注意:上面的兩種寫法的效果是相同的,可以看出使用let{}高階函式減少了好幾行程式碼

Elvis操作符

Evils其實不是一個操作符,而是evil的複數,而evil的意思在這裡可以理解為遮蔽、安全的操作符,這樣的操作符有三種:

  1. ?: 這個操作符表示在判斷一個可空型別時,會返回一個我們自己設定好的預設值.
  2. !! 這個操作符表示在判斷一個可空型別時,會顯示的丟擲空引用異常(NullPointException).
  3. as? 這個操作符表示為安全的型別轉換.

2.1、?:操作符

當我們定義了一個可空型別的變數時,如果該變數不為空,則使用,反之使用另外一個不為空的值

例:

val testStr : String? = null

var length = 0

// 例: 當testStr不為空時,輸出其長度,反之輸出-1

// 傳統寫法
length = if (testStr != null) testStr.length else -1

// ?: 寫法
length = testStr?.length ?: -1

println(length)
複製程式碼

輸出結果為:

-1
複製程式碼

分析:此操作符一般和?.操作符連用。當且僅當?:左邊的表示式為null時,才會執行?:右邊的表示式。

2.2、!!操作符

!!操作符可謂是給愛好空引用異常(NullPointException)的開發者使用,因為在使用一個可空型別變數時,在該變數後面加上!!操作符,會顯示的丟擲NullPointException異常

例:

val testStr : String? = null
println(testStr!!.length)
複製程式碼

輸出結果為:

Kotlin——初級篇(六): 可空型別、空安全、非空斷言、型別轉換等特性總結

可以看出,在未做空判斷的情況下直接使用操作符!!的情況下,丟擲了空異常

2.3、as?操作符

其實這裡是指as操作符,表示型別轉換,如果不能正常轉換的情況下使用as?操作符。當使用as操作符的使用不能正常的轉換的情況下會丟擲型別轉換(ClassCastException)異常,而使用as?操作符則會返回null,但是不會丟擲異常

2.3.1、使用as

例:

// 會丟擲ClassCastException異常
val num1 : Int? = "Koltin" as Int
println("nun1 = $num1")
複製程式碼

輸出結果為:

Kotlin——初級篇(六): 可空型別、空安全、非空斷言、型別轉換等特性總結

2.3.2、使用as?

例:

val num2 : Int? = "Koltin" as? Int
println("nun2 = $num2)
複製程式碼

輸出結果為:

num2 = null
複製程式碼

參考

Kotlin 空安全
Koltin官網文件

總結

這一章在實際的專案開發當中用到的地方是很多的,如果用好了各種空安全的操作符,估計你的專案中就不會丟擲以及異常了,在這裡我做了一個總結,希望會對各位有所幫助:

  • 專案中會丟擲空引用(NullPointerException)異常的情況
    1. 在可空型別變數的使用時,用了!!操作符
    2. 顯式丟擲空引用異常 throw NullPointerException()
    3. 外部 Java 程式碼導致的
    4. 對於初始化,有一些資料不一致(如一個未初始化的 this 用於建構函式的某個地方)
  • 專案中會丟擲型別轉換(ClassCastException)異常的情況
    1. 在型別轉換中使用了as操作符
    2. 使用了toXXX()方法不能轉換的情況下
    3. 外部 Java 程式碼導致的
  • 儘量避免使用的操作符
    1. 儘可能的不要使用!!操作符,多使用?:?.操作符,以及let{}函式
    2. 儘可能的使用as?操作符去替換掉as,在不確定是否可以安全轉換的情況下不使用toXXX()方法

原始碼

如果各位大佬看了之後感覺還闊以,就請各位大佬隨便star一下,您的關注是我最大的動力。
我的個人部落格Jetictors
我的githubJetictors


歡迎各位大佬進群共同研究、探索

QQ群號:497071402

Kotlin——初級篇(六): 可空型別、空安全、非空斷言、型別轉換等特性總結

相關文章