可空性
可空类型
1 2 3 4 5
| fun strLen(s:String) = s.length //使用可能为null的实参调用StrLen是不允许的,在编译期就会被标记成错误 >>>strLen(null) ERROR:Null can not be a value of a non-null type String
|
问号可以加在任何类型的后面来表示这个类型的变量可以存储null引用
没有问号的类型表示这种类型的变量不能存储null引用,这说明所有常见类型默认都是非空的,除非显式的把它标记为空
安全调用运算符:“?.”
它允许把一次null检查和一次方法调用合并成一个操作
1 2
| //表达式 s?.toUpperCase() 等同于下面这种烦琐的写法 if(s!=null) s.toUpperCase() else null
|
1 2 3 4 5
| foo != null -----------> foo.bar() foo?.bar() foo == null -----------> null
|
安全调用运算符只会调用非空值的方法
Elvis运算符:“?:”
Elvis运算符接收两个运算数,如果第一个运算数不为null,运算结果就是第一个运算数;如果第一个运算数为null,运算结果就是第二个运算数
1 2 3 4 5
| foo != null -----------> foo foo ?: bar foo == null -----------> bar
|
安全转换:“as?”
as?运算符尝试把值转换成指定的类型,如果值不是合适的类型就返回null
1 2 3 4 5
| foo is type -----------> foo as Type foo as? Type foo !is Type -----------> null
|
安全调用、安全转换和Elvis运算符都非常有用,它们出现在Kotlin代码中的频率很高。但有时你病不需要Kotlin的这些支持来处理null值,你只需要直接告诉编译器这个值实际上并不是null,所以我们就来继续看看如何做到这点
非空断言:“!!”
非空断言是Kotlin提供给你的最简单直率的处理可空类型值的工具。它使用双感叹号表示,可以把任何值转换成非空类型。如果对null值做非空断言,则会抛出异常
1 2 3 4 5
| foo != null -----------> foo foo!! foo == null -----------> null
|
“let”函数
let函数就是把一个调用它的对象变成lambda表达式的参数。如果结合安全调用语法,他能有效的把调用let函数的可空对象转变成非空类型
1 2 3 4 5 6
| foo != null -----------> 在lambda内部it是非空的 foo?.let{ ..it .. } foo == null -----------> 什么都不会发生
|
安全调用“let”只在表达式不为null时执行lambda
1 2 3 4 5 6 7 8 9 10
| val email:String? = "adu@163.com" fun sendEmailTo(email:String){ println("email:$email") } //1、email非空时调用,在lambda中把email当作非空实参使用 email?.let{ email -> sendEmailTo(email)} //2、使用自动生成的名字it email?.let{ sendEmailTo(it) }
|
注意,如果有一些很长的表达式结果不为null,而你又要使用这些结果时,let表示法特别方便。这种情况下你不必创建一个单独的变量。
1 2 3 4 5 6 7 8 9 10
| val person:Person? = getTheBestPerosnInTheWorld() if (perosn != null) sendEmailTo(person.email) //和功能相同但没有额外变量的代码 getTheBestPerosnInTheWorld()?.let{ sendEmailTo(it.email)} //这个函数返回null,所以lambda中的代码永远不会执行 fun getTheBestPerosnInTheWorld() : Person? = null
|
还有一种情况是,属性最终是非空的,但不能使用非空值在构造方法中初始化。接下来我们看看Kotlin如何处理这种情况
延迟初始化的属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class MyService { fun performAction() :String = "foo" } class MyTest { private lateinit var myService: MyService @Before fun setUp() { myService = MainActivity.MyService() } @Test fun testAction() { Assert.assertEquals("foo",myService.performAction()) } }
|
延迟初始化的属性都是var,因为需要在构造方法外修改它的值。尽管这个属性是非空类型,但是你不需要在构造方法中初始化它
可空类型的扩展
用可空接收者调用扩展函数
1 2 3 4 5 6 7 8 9 10
| fun verifyUserInput (input:String?){ //这里不需要安全调用 if (input.isNullOrBlank()) { println("Please fill in the required fields") } } >>>verifyUserInput(" ") Please fill in the required fields >>>verifyUserInput(null)//这个接收者调用isNullorBlank并不会导致任何异常 Please fill in the required fields
|
类型参数的可空性
Kotlin中所有泛型类和泛型函数的类型默认都是可空的。任何类型,包括可空类型在内。都可以替换类型参数
1 2 3 4 5 6 7 8
| //处理可空的参数类型 fun<T> printHashCode(t:T) { //因为“t”可能为null,所以必须使用安全调用 println(t?.hashCode()) } >>> printHashCode(null) //“T”被推导成“Any?” >>> null
|
上面代码中“T”被推导的类型时可空类型“Any?”,因此,尽管没有用问号结尾,实参t依然允许持有null
1 2 3 4 5 6 7 8
| //为类型参数声明非空上界 fun<T: Any> printHashCode(t:T) { //现在T就不是可空的 println(t.hashCode()) } >>> printHashCode(null) //这段代码是无法编译的,不能传递null,期望是非空值 >>> ERROR
|
可空性和Java
在实现Java类或者接口的方法时一定要搞清楚它的可空性。因为方法的实现可以在非Kotlin的代码中被调用,Kotlin编译器会为你声明的每一个非空的参数生成非空断言。如果Java代码传给这个方法一个null值,断言将会触发,你会得到一个异常,即便你从没有在你的实现中访问过这个参数的值。
基本数据类型和其他基本类型
Kotlin并不区分基本数据类型和它们的包装类
基本数据类型:Int、Boolean及其他
在Java中,不能对基本数据类型的值调用方法或者把他们存放在集合中,在需要对象的时候对基本数据进行封装。因此,你不能用Collection< int>来定义一个整数的集合,而必须用Collection< Integer>来定义
Kotlin并不区分基本类型和包装类型,你使用的永远是同一个类型(比如:Int)
1 2
| val i: Int = 1 Val list: List<Int> = listOf(1,2,3)
|
可空的基本数据类型:Int?、Boolean?及其他
1 2 3 4 5 6 7
| data class Person(val name: String,val age:Int? = null) { fun isOlderThan(other: Person):Boolean?{ if (age == null || other.age == null) return null return age > other.age } }
|
必须检查两个值都不为null,然后编译器才允许你正常的比较它们
数字转换
1 2 3 4 5 6
| val i =1 val l:Long = i //错误:类型不匹配 //因此,必须显式地进行转换 val i =1 val l:Long = i.toLong()
|
每一种基本数据类型(Boolean除外)都定义有转换函数:toByte()、toShort()、toChar()。这些函数都支持双向转换:既可以把小范围的类型括展到大范围,比如Int.toLong(),也可以把大范围的类型截取到小范围,比如Long.toInt()
Kotlin要求转换必须是显式的
1 2 3
| val x =1 println(x in listof(1L,2L,3L)) //假设Kotlin支持隐式转换的话仍是false println(x.toLong() in listof(1L,2L,3L))//true
|
kotlin标准库提供了一套相似的扩展方法,用来把字符串转换成基本数据类型(toInt、toByte、toBoolean等)
1 2
| println("42".toInt()) >>42
|
“Any”和“Any?”:根类型
在kotlin中,Any是所有类型的超类型(所有类型的根),包括像Int这样的基本数据类型
在java中,Object只是所有引用类型的超类(引用类型的根),而基本数据类型并不是类层级结构的一部分
注意Any是非空类型,所以Any类型的变量不可以持有null值。在Kotlin中如果需要可以持有任何可能值的变量,包括null在内,必须使用Any?类型
当Kotlin函数使用Any时,它会被编译成Java字节码中的Object
Unit类型:Kotlin的“void”
Kotlin中的Unit类型完成了Java中的void一样的功能。
Unit是一个完备的类型,可以作为类型参数,而void却不行。
当重写返回泛型参数的函数时,只需要让方法返回Unit类型的值
1 2 3 4 5 6 7 8 9 10
| interface Processor<T> { fun process() : T } class NoResultProcessor : Processor<Unit> { //返回Unit,但可以省略类型说明 override fun process() { //do stuff //这里不需要显式的return }
|
Nothing类型:“这个函数永不返回”
在kotlin中throw是表达式,所以你可以使用塔作为Elvis表达式的一部分
1
| val s = person.name ?: throw IllegalArgumentException("Name required)
|
throw表达式的类型是特殊类型Nothing。该类型没有值,而是用于标记永远不能达到的代码位置
集合与数组
kotlin以java集合库为基础构建,并通过扩展函数增加的特性来增强它
可空性和集合
kotlin完全支持类型参数的可空性
1 2 3 4 5 6 7 8 9 10 11 12
| fun readNumbers(reader: BufferedReader):List<Int?>{ val result = ArrayList<Int?>() //创建包含可空Int值的列表 for (line in reader.lineSequence()) { try { val number = line.toInt() result.add(number) //向列表添加整数(非空值) }catch (e:NumberFormatException){ result.add(null) //向列表添加null,因为当前行不能被解析成整数 } } return result }
|
对包含可空值的集合使用filterNotNull
1 2 3 4 5
| fun addValidNumbers(numbers:List<Int?>) { val validNumbers = numbers.filterNotNull() println("sum of valid numbers: ${validNumbers.sum()}") println("Invalid numbers: ${numbers.size - validNumbers.size}") }
|
上一篇:RxJava的简单使用
下一篇:Nginx反向代理完成域名映射