平台类型是 kotlin 对 java 所作的一种平衡性设计。kotlin 将对象的类型分为了可空类型和不可空类型两种,但 java 平台的一切对象类型均为可空的,当在 kotlin 中引用 java 变量时,如果将所有变量均归为可空类型,最终将多出许多 null 检查;如果均看成不可空类型,那么就很容易就写出忽略了NPE 风险的代码。为了平衡两者,kotlin 引入了平台类型,即当在 kotlin 中引用 java 变量值时,既可以将之看成可空类型,也可以将之看成不可空类型,由开发者自己来决定是否进行 null 检查
九、类型的检查与转换
9.1、类型检查
is 与 !is 操作符用于在运行时检查对象是否符合给定类型:
fun main() {
val strValue = “leavesC”
parserType(strValue) //value is String , length : 7
val intValue = 100
parserType(intValue) //value is Int , toLong : 100
val doubleValue = 100.22
parserType(doubleValue) //value !is Long
val longValue = 200L
parserType(longValue) //unknown
}
fun parserType(value: Any) {
when (value) {
is String -> println(“value is String , length : ${value.length}”)
is Int -> println(“value is Int , toLong : ${value.toLong()}”)
!is Long -> println(“value !is Long”)
else -> println(“unknown”)
}
}
9.2、智能转换
在许多情况下,不需要在 kotlin 中使用显式转换操作符,因为编译器跟踪不可变值的 is 检查以及显式转换,并在需要时自动插入安全的转换
例如,对于以下例子来说,当判断 value 为 String 类型通过时,就可以直接将 value 当做 String 类型变量并调用其内部属性
fun main() {
val strValue = “leavesC”
parserType(strValue) //value is String , length : 7
val intValue = 100
parserType(intValue) //value is Int , toLong : 100
val doubleValue = 100.22
parserType(doubleValue) //value !is Long
val longValue = 200L
parserType(longValue) //unknown
}
fun parserType(value: Any) {
when (value) {
is String -> println(“value is String , length : ${value.length}”)
is Int -> println(“value is Int , toLong : ${value.toLong()}”)
!is Long -> println(“value !is Long”)
else -> println(“unknown”)
}
}
编译器会指定根据上下文环境,将变量智能转换为合适的类型
if (value !is String) return
//如果 value 非 String 类型时直接被 return 了,所以此处可以直接访问其 length 属性
println(value.length)
// || 右侧的 value 被自动隐式转换为字符串,所以可以直接访问其 length 属性
if (value !is String || value.length > 0) {
}
// && 右侧的 value 被自动隐式转换为字符串,所以可以直接访问其 length 属性
if (value is String && value.length > 0) {
}
9.3、不安全的转换操作符
如果转换是不可能的,转换操作符 as
会抛出一个异常。因此,我们称之为不安全的转换操作符
fun main() {
parserType(“leavesC”) //value is String , length is 7
parserType(10) //会抛出异常 ClassCastException
}
fun parserType(value: Any) {
val tempValue = value as String
println(“value is String , length is ${tempValue.length}”)
}
需要注意的是,null 不能转换为 String 变量,因为该类型不是可空的
因此如下转换会抛出异常
val x = null
val y: String = x as String //会抛出异常 TypeCastException
为了匹配安全,可以转换的类型声明为可空类型
val x = null
val y: String? = x as String?
9.4、安全的转换操作符
可以使用安全转换操作符 as? 来避免在转换时抛出异常,它在失败时返回 null
val x = null
val y: String? = x as? String
尽管以上例子 as? 的右边是一个非空类型的 String,但是其转换的结果是可空的
十、类
10.1、基本概念
类的概念就是把数据和处理数据的代码封装成一个单一的实体。在 Java 中,数据存储在一个私有字段中,通过提供访问器方法:getter 和 setter 来访问或者修改数据
在 Java 中以下的示例代码是很常见的,Point 类包含很多重复的代码:通过构造函数把参数赋值给有着相同名称的字段,通过 getter 来获取属性值
public final class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public final int getX() {
return this.x;
}
public final int getY() {
return this.y;
}
}
使用 kotlin 来声明 Point 类则只需要一行代码,两者完全等同
class Point(val x: Int, val y: Int)
kotlin 也使用关键字 class 来声明类,类声明由类名、类头(指定其类型参数、主构造函数等)以及由花括号包围的类体构成,类头与类体都是可选的,如果一个类没有类体,可以省略花括号。此外,kotlin 中类默认是 publish(公有的) 且 final (不可继承)的
kotlin 区分了主构造方法(在类体外部声明)和次构造方法(在类体内部声明),一个类可以有一个主构造函数和多个次构造函数,此外也允许在初始化代码块中 init
添加额外的初始化逻辑
10.2、主构造函数
主构造函数是类头的一部分,跟在类名(和可选的类型参数)后,主构造函数的参数可以是可变的(var)或只读的(val)
class Point constructor(val x: Int, val y: Int) {
}
如果主构造函数没有任何注解或者可见性修饰符,可以省略 constructor 关键字
class Point(val x: Int, val y: Int) {
}
//如果不包含类体,则可以省略花括号
class Point(val x: Int, val y: Int)
如果构造函数有注解或可见性修饰符,则 constructor 关键字是必需的,并且这些修饰符在它前面
class Point public @Inject constructor(val x: Int, val y: Int) {
}
主构造函数不能包含任何的代码,初始化的代码可以放到以 init 关键字作为前缀的初始化块(initializer blocks)中,初始化块包含了在类被创建时执行的代码,主构造函数的参数可以在初始化块中使用。如果需要的话,也可以在一个类中声明多个初始化语句块。需要注意的是,构造函数的参数如果用 val/var 进行修饰,则相当于在类内部声明了一个同名的全局属性。如果不加 val/var 进行修饰,则构造函数只能在 init 函数块和全局属性初始化时进行引用
此外,要创建一个类的实例不需要使用 Java 中的 new 关键字,像普通函数一样调用构造函数即可
class Point(val x: Int, val y: Int) {
init {
println(“initializer blocks , x value is: $x , y value is: $y”)
}
}
fun main() {
Point(1, 2) // initializer blocks , x value is: 1 , y value is: 2
}
主构造函数的参数也可以在类体内声明的属性初始化器中使用
class Point(val x: Int, val y: Int) {
private val localX = x + 1
private val localY = y + 1
init {
println(“initializer blocks , x value is: $x , y value is: $y”)
println(“initializer blocks , localX value is: $localX , localY value is: $localY”)
}
}
fun main() {
Point(1, 2)
//initializer blocks , x value is: 1 ,