学习Kotlin(八)其他技术

 

 

推荐阅读:
学习Kotlin(一)为什么使用Kotlin
学习Kotlin(二)基本语法
学习Kotlin(三)类和接口
学习Kotlin(四)对象与泛型
学习Kotlin(五)函数与Lambda表达式
学习Kotlin(六)扩展与委托
学习Kotlin(七)反射和注解
学习Kotlin(八)其他技术
Kotlin学习资料总汇

 

目录

一、解构声明
二、区间
三、类型检查与转换
四、this表达式
五、相等性
六、操作符重载
七、空安全
八、异常
九、类型别名


一、解构声明

解构声明能同时创建多个变量,将对象中的数据解析成相对的变量。举个例子:

//创建一个数据类User
data class User(var name: String, var age: Int)

//获得User的实例
var user = User("Czh", 22)
//声明变量 name 和 age
var (name, age) = user

println("name:$name  age:$age")
//输出结果为:name:Czh  age:22

上面代码中用解构声明同时创建两个变量的时候,会被编译成以下代码:

//指定变量name的值为user第一个参数的值
var name = user.component1()
//指定变量name的值为user第二个参数的值
var age = user.component2()

println("name:$name  age:$age") 
//输出结果为:name:Czh  age:22
  • 解构声明和Map
    Map可以保存一组key-value键值对,通过解构声明可以把这些值解构出来。如下所示:
var map = mutableMapOf<String, Any>()
map.put("name", "Czh")
map.put("age", 22)
for ((key, value) in map) {
    println("$key:$value")
}

运行代码,输出结果:

二、区间

1.in

假如现在要判断 i 是否在 1-5 内,可以这样写:

if (i in 1..5) {
    println("i 在 1-5 内")
}

上面代码中,1..5指的是 1-5,in指的是在...范围内,如果 i 在范围 1-5 之内,将会执行后面的代码块,输出结果。如果想判断 i 是否不在 1-5 内,可以这样写:

//!in表示不在...范围内
if (i !in 1..5) {
    println("i 不在 1-5 内")
}

上面两段代码等同于:

if (i >= 1 && i <= 5) {
    println("i 在 1-5 内")
}
if (i <= 1 && i >= 5) {
    println("i 不在 1-5 内")
}

2.downTo

如果想输出 1-5 ,可以这样写:

for (i in 1..5) println(i)
//输出12345

如果倒着来:

for (i in 5..1) println(i)
//什么也不输出

这个时候可以用downTo函数倒序输出 5-1

for (i in 5 downTo 1) println(i)

3.step

上面的代码顺序输出12345或倒序54321,按顺序+1或者-1,也就是步长为1。如果要修改步长,可以用step函数,如下所示:

 for (i in 1..5 step 2) println(i) 
//输出135

//倒序
for (i in 1 downTo 5 step 2) println(i) 
//输出531

4.until

上面的代码中,使用的范围都是闭区间,例如1..5的区间是[1,5],如果要创建一个不包括其结束元素的区间,即区间是[1,5),可以使用until函数,如下所示:

for (i in 1 until 5) println(i)
//输出1234

三、类型检查与转换

1.is操作符

在Kotlin中,可以通过is操作符判断一个对象与指定的类型是否一致,还可以使用is操作符的否定形式!is,举个例子:

var a: Any = "a"
if (a is String) {
    println("a是String类型")
}
if (a !is Int) {
    println("a不是Int类型")
}

运行代码,输出结果为:

2.智能转换

在Kotlin中不必使用显式类型转换操作,因为编译器会跟踪不可变值的is检查以及显式转换,并在需要时自动插入(安全的)转换。举个例子:

var a: Any = "a"
if (a is String) {
    println("a是String类型")
    println(a.length) // a 自动转换为String类型
    //输出结果为:1
}

还可以反向检查,如下所示:

if (a !is String) return
print(a.length) // a 自动转换为String类型

在 && 和 || 的右侧也可以智能转换:

// `&&` 右侧的 a 自动转换为String
if (a is String && a.length > 0)

// `||` 右侧的 a 自动转换为String
if (a !is String || a.length > 0)

在when表达式和while循环里也能智能转换:

when(a){
    is String -> a.length
    is Int -> a + 1
}

需要注意的是,当编译器不能保证变量在检查和使用之间不可改变时,智能转换不能用。智能转换能否适用根据以下规则:

  • val 局部变量——总是可以,局部委托属性除外;
  • val 属性——如果属性是 private 或 internal,或者该检查在声明属性的同一模块中执行。智能转换不适用于 open 的属性或者具有自定义 getter 的属性;
  • var 局部变量——如果变量在检查和使用之间没有修改、没有在会修改它的 lambda 中捕获、并且不是局部委托属性;
  • var 属性——决不可能(因为该变量可以随时被其他代码修改)

3.强制类型转换

在Kotlin中,用操作符as进行强制类型转换,如下所示:

var any: Any = "abc"
var str: String = any as String

但强制类型转换是不安全的,如果类型不兼容,会抛出一个异常,如下所示:

var int: Int = 123
var str: String = int as String
//抛出ClassCastException

4.可空转换操作符

null不能转换为 String,因该类型不是可空的。举个例子:

var str = null
var str2 = str as String
//抛出TypeCastException

解决这个问题可以使用可空转换操作符as?,如下所示:

var str = null
var str2 = str as? String
println(str2) //输出结果为:null

使用安全转换操作符as?可以在转换失败时返回null,避免了抛出异常。

四、this表达式

为了表示当前的接收者我们使用this表达式。当this在类的成员中,this指的是该类的当前对象;当this在扩展函数或者带接收者的函数字面值中,this表示在点左侧传递的接收者参数。

  • 限定的this如果this没有限定符,它指的是最内层的包含它的作用域。如果要访问来自外部作用域的this(一个类或者扩展函数, 或者带标签的带接收者的函数字面值)我们使用this@label,其中 @label 是一个代指this来源的标签。举个例子:
class A { // 隐式标签 @A
    inner class B { // 隐式标签 @B
        fun Int.foo() { // 隐式标签 @foo
            val a = this@A // A 的 this
            val b = this@B // B 的 this

            val c = this // foo() 的接收者,一个 Int
            val c1 = this@foo // foo() 的接收者,一个 Int

            val funLit = lambda@ fun String.() {
                val d = this // funLit 的接收者
            }


            val funLit2 = { s: String ->
                // foo() 的接收者,因为它包含的 lambda 表达式
                // 没有任何接收者
                val d1 = this
            }
        }
    }
}

五、相等性

在Kotlin中存在结构相等和引用相等两中相等判断。

1.结构相等

使用equals()==判断,如下所示:

var a = "1"
var b = "1"
if (a.equals(b)) {
    println("a 和 b 结构相等")
    //输出结果为:a 和 b 结构相等
}

var a = 1
var b = 1
if (a == b) {
    println("a 和 b 结构相等")
    //输出结果为:a 和 b 结构相等
}

2.引用相等

引用相等指两个引用指向同一对象,用===判断,如下所示:

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

var a = User("Czh", 22)
var b = User("Czh", 22)
var c = b
var d = a
if (c == d) {
    println("a 和 b 结构相等")
} else {
    println("a 和 b 结构不相等")
}
if (c === d) {
    println("a 和 b 引用相等")
} else {
    println("a 和 b 引用不相等")
}

运行代码,输出结果为:

六、操作符重载

Kotlin允许对自己的类型提供预定义的一组操作符的实现,这些操作符具有固定的符号表示 (如 + 或 *)和固定的优先级。为实现这样的操作符,我们为相应的类型(即二元操作符左侧的类型和一元操作符的参数类型)提供了一个固定名字的成员函数或扩展函数。 重载操作符的函数需要用 operator 修饰符标记。

重载操作符

+是一个一元操作符,下面来对一元操作符进行重载:

//用 operator 修饰符标记
operator fun String.unaryPlus(): String {
    return this + this
}

//调用
var a = "a"
println(+a)  //输出结果为:aa

当编译器处理例如表达式 +a 时,它执行以下步骤:

  • 确定 a 的类型,令其为 T;
  • 为接收者 T 查找一个带有 operator 修饰符的无参函数 unaryPlus(),即成员函数或扩展函数;
  • 如果函数不存在或不明确,则导致编译错误;
  • 如果函数存在且其返回类型为 R,那就表达式 +a 具有类型 R;

除对一元操作符进行重载外,还可以对其他操作符进行重载,其重载方式和原理大致相同。下面来一一列举:

1.一元操作符

表达式 对应的函数
+a a.unaryPlus()
-a a.unaryMinus()
!a a.not()
a++ a.inc()
a-- a.dec()

2.二元操作符

表达式 对应的函数
a+b a.plus(b)
a-b a.minus(b)
a*b a.times(b)
a/b a.div(b)
a%b a.mod(b)
a..b a.rangeTo(b)

3.in操作符

表达式 对应的函数
a in b b.contains(a)
a !in b !b.contains(a)

4.索引访问操作符

表达式 对应的函数
a[i] a.get(i)
a[i, j] a.get(i, j)
a[i_1, ……, i_n] a.get(i_1, ……, i_n)
a[i] = b a.set(i, b)
a[i, j] = b a.set(i, j, b)
a[i_1, ……, i_n] = b a.set(i_1, ……, i_n, b)

5.调用操作符

表达式 对应的函数
a() a.invoke()
a(i) a.invoke(i)
a(i, j) a.invoke(i, j)
a(i_1, ……, i_n) a.invoke(i_1, ……, i_n)

6.广义赋值

表达式 对应的函数
a += b a.plusAssign(b)
a -= b a.minusAssign(b)
a *= b a.timesAssign(b)
a /= b a.divAssign(b)
a %= b a.remAssign(b), a.modAssign(b)(已弃用)

7.相等与不等操作符

表达式 对应的函数
a == b a?.equals(b) ?: (b === null)
a != b !(a?.equals(b) ?: (b === null))

8.比较操作符

表达式 对应的函数
a > b a.compareTo(b) > 0
a < b a.compareTo(b) < 0
a >= b a.compareTo(b) >= 0
a <= b a.compareTo(b) <= 0

七、空安全

在Java中,NullPointerException 可能是最常见的异常之一,而Kotlin的类型系统旨在消除来自代码空引用的危险。

1.可空类型与非空类型

在Kotlin中,只有下列情况可能导致出现NullPointerException:

  • 显式调用 throw NullPointerException();
  • 使用了下文描述的 !! 操作符;
  • 有些数据在初始化时不一致;
  • 外部 Java 代码引发的问题。

在 Kotlin 中,类型系统区分一个引用可以容纳 null (可空引用)还是不能容纳(非空引用)。 例如,String 类型的常规变量不能容纳 null:

如果要允许为空,我们可以声明一个变量为可空字符串,在字符串类型后面加一个问号?,写作 String?,如下所示:

 

var b: String? = "b"
b = null

2.安全调用操作符

接着上面的代码,如果你调用a的方法或者访问它的属性,不会出现NullPointerException,但如果调用b的方法或者访问它的属性,编译器会报告一个错误,如下所示:

这个时候可以使用安全调用操作符,写作?.,在b后面加安全调用操作符,表示如果b不为null则调用b.length,如下所示:

 

b?.length

安全调用操作符还能链式调用,例如一个员工 Bob 可能会(或者不会)分配给一个部门, 并且可能有另外一个员工是该部门的负责人,那么获取 Bob 所在部门负责人(如果有的话)的名字,我们写作:

Bob?.department?.head?.name
//如果Bob分配给一个部门
//执行Bob.department.head?获取该部门的负责人
//如果该部门有一个负责人
//执行Bob.department.head.name获取该负责人的名字

如果该链式调用中任何一个属性为null,整个表达式都会返回null。如果要只对非空值执行某个操作,安全调用操作符可以与let一起使用,如下所示:

val listWithNulls: List<String?> = listOf("A", null, "B")
for (item in listWithNulls) {
    item?.let { println(it) }
}

运行代码,输出结果为:

  • 安全的类型转换
    如果对象不是目标类型,那么常规类型转换可能会导致 ClassCastException。 另一个选择是使用安全的类型转换,如果尝试转换不成功则返回null,如下所示:
val i: Int? = i as? Int
  • 可空类型的集合
    如果你有一个可空类型元素的集合,并且想要过滤非空元素,你可以使用filterNotNull来实现。如下所示:
val nullableList: List<Int?> = listOf(1, 2, null, 4)
val intList: List<Int> = nullableList.filterNotNull()

3.Elvis 操作符

先看一段代码:

val i: Int = if (b != null) b.length else -1
val i = b?.length ?: -1

这两行代码表达的都是“如果b不等于null,i = b.length;如果b等于null,i = -1”。第一行代码用的是if表达式,而第二行代码使用了Elvis操作符,写作?:Elvis操作符表示如果?:左侧表达式非空,就使用左侧表达式,否则使用右侧表达式。请注意,因为throwreturn在Kotlin中都是表达式,所以它们也可以用在Elvis操作符右侧。如下所示:

fun foo(node: Node): String? {
    val parent = node.getParent() ?: return null
    val name = node.getName() ?: throw IllegalArgumentException("name expected")
    // ……
}

4. !! 操作符

!!操作符将任何值转换为非空类型,若该值为空则抛出异常。如下所示:

var a = null
a!!
//运行代码,抛出KotlinNullPointerException

八、异常

Kotlin中所有异常类都是Throwable类的子类。每个异常都有消息、堆栈回溯信息和可选的原因。使用throw表达式可以抛出异常。举个例子:

throw NullPointerException("NPE")

使用try表达式可以捕获异常。一个try表达式可以有多个catch代码段;finally代码段可以省略。举个例子:

try {
    //捕获异常
} catch (e: NullPointerException) {
    //异常处理
} catch (e: ClassNotFoundException) {
    //异常处理
} finally {
    //可选的finally代码段
}

因为Try是一个表达式,所以它可以有一个返回值。举个例子:

val a: Int? = try {
    parseInt(input) 
} catch (e: NumberFormatException) {
    null 
}

try表达式的返回值是 try块中的最后一个表达式或者是catch块中的最后一个表达式。finally块中的内容不会影响表达式的结果。

九、类型别名

Kotlin提供类型别名来代替过长的类型名称,这些类型别名不会引入新类型,且等效于相应的底层类型。可以通过使用关键字typealias修改类型别名,如下所示:

//使用关键字typealias修改类型别名Length
//相当于 Length 就是一个 (String) -> Int 类型
typealias Length = (String) -> Int

//调用
fun getLength(l: Length) = l("Czh")
//编译器把 Length 扩展为 (String) -> Int 类型
val l: Length = { it.length }
println(getLength(l)) //输出结果为:3

使用类型别名能让那些看起来很长的类型在使用起来变得简洁,如下所示:

typealias MyType = (String, Int, Any, MutableList<String> ) -> Unit
//当我们使用的时候
var myType:MyType 
//而不需要写他原来的类型
//var myType:(String, Int, Any, MutableList<String> ) -> Unit

总结

相对于Java来说,Kotlin有很多新的技术和语法糖,这也是为什么使用Kotlin来开发Android要优于Java。运用好这些新的东西,能大大加快开发速度。


原文链接:https://juejin.im/post/5a9cdc7df265da2393768e06

展开阅读全文

没有更多推荐了,返回首页