博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
从Java到Kotlin(八)
阅读量:6449 次
发布时间:2019-06-23

本文共 8793 字,大约阅读时间需要 29 分钟。

Kotlin的其他技术

目录

一、解构声明

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


一、解构声明

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

//创建一个数据类Userdata class User(var name: String, var age: Int)//获得User的实例var user = User("Czh", 22)//声明变量 name 和 agevar (name, age) = userprintln("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
()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) returnprint(a.length) // a 自动转换为String类型复制代码

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

// `&&` 右侧的 a 自动转换为Stringif (a is String && a.length > 0)// `||` 右侧的 a 自动转换为Stringif (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 = 123var str: String = int as String//抛出ClassCastException复制代码

4.可空转换操作符

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

var str = nullvar str2 = str as String//抛出TypeCastException复制代码

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

var str = nullvar str2 = str as? Stringprintln(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 = 1var b = 1if (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 = bvar d = aif (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
= listOf("A", null, "B")for (item in listWithNulls) { item?.let { println(it) }}复制代码

运行代码,输出结果为:

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

3.Elvis 操作符

先看一段代码:

val i: Int = if (b != null) b.length else -1val 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 = nulla!!//运行代码,抛出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
) -> Unit//当我们使用的时候var myType:MyType //而不需要写他原来的类型//var myType:(String, Int, Any, MutableList
) -> Unit复制代码

总结

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

参考文献:

、《Kotlin程序开发入门精要》
推荐阅读:


更多精彩文章请扫描下方二维码关注微信公众号"AndroidCzh":这里将长期为您分享原创文章、Android开发经验等! QQ交流群: 705929135

转载地址:http://sllwo.baihongyu.com/

你可能感兴趣的文章
PostgreSQL 百亿数据 秒级响应 正则及模糊查询
查看>>
【JSP开发】获取web应用的初始化参数
查看>>
iOS开发网络篇—HTTP协议
查看>>
Zabbix 监控 Nginx
查看>>
【C++注意事项】4 指针 Pointers
查看>>
js 深拷贝,浅拷贝
查看>>
LeetCode刷题: 整数反转
查看>>
#学习笔记# 记录一次java父类转子类的方法
查看>>
Vue源码分析系列四:Virtual DOM
查看>>
Git 版本回退
查看>>
Python:使用pypdf2合并、分割、加密pdf文件。
查看>>
rabbitmq java 应用实例
查看>>
Flutter Mac下环境配置
查看>>
springCloud学习1(集中式配置管理)
查看>>
React-Amap-HOC组件封装
查看>>
我的友情链接
查看>>
node.js操作MySQL数据库
查看>>
oracle常用字段类型
查看>>
mapreduce/spark/storm/Tez 框架
查看>>
20个简化开发任务的JavaScript库
查看>>