📜  Kotlin 空值安全

📅  最后修改于: 2022-05-13 01:54:49.956000             🧑  作者: Mango

Kotlin 空值安全

Kotlin 的类型系统旨在消除代码中空引用的危险,因为它是十亿美元的错误。 NullPointerExceptions由程序在运行时抛出,有时会导致应用程序失败或系统崩溃。
如果有人使用Java或其他具有空引用概念的语言进行编程,那么他一定经历过代码中的NullPointerException 。如果 Kotlin 编译器在没有执行任何其他语句的情况下发现任何空引用,它也会抛出 NullPointerException。
NullPointerException 的可能原因如下:

  • 显式调用 throw NullPointerException()
  • 使用!!运算符
  • 一些与初始化有关的数据不一致,例如未初始化的 this 作为参数传递。
  • Java互操作,例如尝试访问空引用上的成员、具有不正确可空性的泛型类型。

Kotlin 中的可空和不可空类型 –

Kotlin 类型系统区分了两种类型的引用,可以包含 null(可为 null 的引用)和不能包含的引用(非 null 引用)。
String 类型的变量不能保存null 。如果我们尝试将 null 分配给变量,则会出现编译器错误。

var s1: String = "Geeks"
s1 = null // compilation error

为了让一个变量保持为空,我们可以将一个变量声明为可以为空的字符串,写成String?

var s2: String? = "GeeksforGeeks"
s2 = null // ok
print(s2)

现在,如果我们要访问字符串s1 的长度,它保证不会抛出 NPE,所以我们可以有把握地说:

val l = s1.length

但是如果我们要访问字符串s2 的长度,那就不安全了,编译器会报错:

val l = s2.length         // error: variable 's2' can be null

不可为空类型的 Kotlin 程序 –

Kotlin
fun main(args: Array){
    // variable is declared as non-nullable
    var s1 : String = "Geeks"
  
    //s1 = null  // gives compiler error
      
    print("The length of string s1 is: "+s1.length)
}


Kotlin
fun main(args: Array) {
    // variable is declared as nullable
    var s2: String? = "GeeksforGeeks" 
      
    s2 = null    // no compiler error 
      
    println(s2.length)  // compiler error because string can be null
}


Kotlin
fun main(args: Array) {
    // variable declared as nullable
    var s: String? = "GeeksforGeeks"
    println(s)
    if (s != null) {
        println("String of length ${s.length}")
    } else {
        println("Null string")
    }
    // assign null
    s = null
    println(s)
    if (s != null) {
        println("String of length ${s.length}")
    } else {
        println("Null String")
    }
}


Kotlin
fun main(args: Array) {
    // variable declared as nullable
    var firstName: String? = "Praveen"
    var lastName: String? = null
  
    println(firstName?.toUpperCase())
    println(firstName?.length)
    println(lastName?.toUpperCase())
}


Kotlin
fun main(args: Array) {
    // created a list contains names
    var stringlist: List = listOf("Geeks","for", null, "Geeks")
    // created new list
    var newlist = listOf()
    for (item in stringlist) {
        // executes only for non-nullable values
        item?.let { newlist = newlist.plus(it) }
    }
    // to print the elements stored in newlist
    for(items in newlist){
        println(items)
    }
}


Kotlin
fun main(args: Array) {
    // created a list contains names
    var stringlist: List = listOf("Geeks","for", null, "Geeks")
    // created new list
    var newlist = listOf()
    for (item in stringlist) {
        // executes only for non-nullable values
        item?.let { newlist = newlist.plus(it) }
        item?.also{it -> println(it)}
    }
}


Kotlin
fun main(args: Array) {
    // created a list contains names
    var stringlist: List = listOf("Geeks","for", null, "Geeks")
    // created new list
    var newlist = listOf()
    for (item in stringlist) {
        // executes only for non-nullable values
        item?.run { newlist = newlist.plus(this) } // this reference
        item?.also{it -> println(it)}
    }
}


Kotlin
fun main(args: Array) {
    var str : String?  = "GeeksforGeeks"
    println(str?.length)
    str = null
    println(str?.length ?: "-1")
      
}


Kotlin
fun main(args: Array) {
    var str : String?  = "GeeksforGeeks"
    println(str!!.length)
    str = null
    str!!.length
}


输出:

The length of string s1 is: 5

在这里,如果我们尝试将null分配给一个不可为空的变量,那么它会给出编译器时间错误。但是,如果我们尝试访问字符串的长度,那么它保证不会通过 NullPointerException。
可空类型的 Kotlin 程序 -

科特林

fun main(args: Array) {
    // variable is declared as nullable
    var s2: String? = "GeeksforGeeks" 
      
    s2 = null    // no compiler error 
      
    println(s2.length)  // compiler error because string can be null
}

输出:

Error:(8, 15) Kotlin: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?

在这里,我们可以轻松地将 null 分配给可为 null 的类型变量。但是我们应该使用安全运算符来获取字符串的长度。

检查条件中的空值-

检查空引用的最常用方法是使用 if-else 表达式。我们可以显式检查变量是否为空,并分别处理这两个选项。
Kotlin 检查空值条件的程序 -

科特林

fun main(args: Array) {
    // variable declared as nullable
    var s: String? = "GeeksforGeeks"
    println(s)
    if (s != null) {
        println("String of length ${s.length}")
    } else {
        println("Null string")
    }
    // assign null
    s = null
    println(s)
    if (s != null) {
        println("String of length ${s.length}")
    } else {
        println("Null String")
    }
}

输出:

GeeksforGeeks
String of length 13
null
Null String

请注意,我们使用if-else块来检查可空性。如果字符串包含 null 则执行 if 块,否则执行 else 块。

安全呼叫运算符(?.) –

Null 比较很简单,但嵌套 if-else 表达式的数量可能会很麻烦。所以,Kotlin 有一个 Safe call运算符,?。这降低了这种复杂性并仅在特定引用包含非空值时执行操作。它允许我们将空检查和方法调用组合在单个表达式中。
以下表达式:

firstName?.toUpperCase()

相当于:

if(firstName != null) 
    firstName.toUpperCase()
else
    null 

Kotlin 使用安全运算符的程序——

科特林

fun main(args: Array) {
    // variable declared as nullable
    var firstName: String? = "Praveen"
    var lastName: String? = null
  
    println(firstName?.toUpperCase())
    println(firstName?.length)
    println(lastName?.toUpperCase())
}

输出:

PRAVEEN
7
null

如果值不为空,我们可以将安全调用运算符与 let()、also() 和 run() 一起使用——
let() 方法——
要仅在引用包含不可为空的值时执行操作,我们可以使用 let运算符。仅当变量 firstName 不为 null 时,才会执行 let 中存在的 lambda 表达式。

val firstName: String? = null
firstName?.let { println(it.toUpperCase()) }

这里,变量 firstName 为 null,因此不执行 lambda 表达式将字符串转换为大写字母。
使用 let 的 Kotlin 程序——

科特林

fun main(args: Array) {
    // created a list contains names
    var stringlist: List = listOf("Geeks","for", null, "Geeks")
    // created new list
    var newlist = listOf()
    for (item in stringlist) {
        // executes only for non-nullable values
        item?.let { newlist = newlist.plus(it) }
    }
    // to print the elements stored in newlist
    for(items in newlist){
        println(items)
    }
}

输出:

Geeks
for
Geeks

带有 let() 的also() 方法链 –
如果我们想应用一些额外的操作,比如打印列表中不可为空的项目,我们可以使用also()方法并将其与let()run()链接起来:

科特林

fun main(args: Array) {
    // created a list contains names
    var stringlist: List = listOf("Geeks","for", null, "Geeks")
    // created new list
    var newlist = listOf()
    for (item in stringlist) {
        // executes only for non-nullable values
        item?.let { newlist = newlist.plus(it) }
        item?.also{it -> println(it)}
    }
}

输出:

Geeks
for
Geeks

run() 方法——
Kotlin 有一个run()方法来对可为空的引用执行一些操作。它似乎与let()非常相似,但在函数体内部,run() 方法仅在我们使用引用而不是函数参数时才起作用:

科特林

fun main(args: Array) {
    // created a list contains names
    var stringlist: List = listOf("Geeks","for", null, "Geeks")
    // created new list
    var newlist = listOf()
    for (item in stringlist) {
        // executes only for non-nullable values
        item?.run { newlist = newlist.plus(this) } // this reference
        item?.also{it -> println(it)}
    }
}

输出:

Geeks
for
Geeks

猫王操作员(?:) –

Elvis运算符用于在原始变量为空时返回非空值或默认值。换句话说,如果左表达式不为空,则 elvis运算符返回它,否则返回右表达式。仅当发现左侧为空时,才计算右侧表达式。
以下表达式:

val name = firstName ?: "Unknown"

相当于:

val name = if(firstName!= null) 
         firstName
      else 
         "Unknown"

此外,我们还可以在 Elvis运算符的右侧使用throwreturn表达式,这在函数中非常有用。因此,我们可以抛出异常,而不是在 Elvis 运算符的右侧返回默认值。

val name = firstName ?: throw IllegalArgumentException("Enter valid name")

Kotlin 程序使用 Elvis运算符——

科特林

fun main(args: Array) {
    var str : String?  = "GeeksforGeeks"
    println(str?.length)
    str = null
    println(str?.length ?: "-1")
      
}

输出:

13
-1

非空断言:!!操作员

not null 断言 (!!)运算符将任何值转换为非 null 类型,如果值为 null,则引发异常。
如果有人想要 NullPointerException,那么他可以使用这个运算符明确地询问。

科特林

fun main(args: Array) {
    var str : String?  = "GeeksforGeeks"
    println(str!!.length)
    str = null
    str!!.length
}

输出:

13
Exception in thread "main" kotlin.KotlinNullPointerException
    at FirstappKt.main(firstapp.kt:8)