【Kotlin学习】Kotlin的类型系统——基本数据类型和其他基本类型、集合与数组

343 阅读10分钟

基本数据类型和其他基本类型

基本数据类型:Int、Boolean及其他

kotlin并不区分基本数据类型和包装类型,你使用的永远是同一个类型。在运行时,数字类型会尽可能地使用最高效的方式来表示。Int类型会被编译成java基本数据类型int。不可行的例外是泛型类,比如集合。用作泛型类型参数的基本数据类型会被编译成对应的java包装类型。
整数类型——Byte、Short、Int、Long
浮点数类型——Float、Double
字符类型——Char
布尔类型——Boolean

可空的基本数据类型:Int?、Boolean?及其他

kotlin中的可空类型不能用java的基本数据类型表示,因为null只能被存储在java的引用类型的变量中,只要使用了基本类型的可空版本,它就会被编译成对应的包装类型。JVM不支持用基本数据类型作为类型参数,所以泛型类必须始终使用类型的包装表示

数字转换

kotlin不会自动地把数字从一种类型转换成另一种

image.png
必须进行显式转换

image.png

每一种基本数据类型(Boolean除外)都定义有转换函数:toByte()、toShort()等。这些函数支持双向转换。为了避免意外情况,kotlin要求转换必须是显式的,尤其是在比较装箱值的时候。比较两个装箱值的equals方法不仅会检查它们存储的值,还要比较装箱类型。在java中new Integer(42).equals(new Long(42))会返回false

基本数据类型字面值

kotlin支持一下这些在代码中书写数字字面值的方式
1.使用后缀L表示Long类型(长整型)字面值:123L
2.使用标准浮点数表示Double(双精度浮点数)字面值:0.12、2.0、1.2e10
3.使用后缀F表示Float类型(浮点数)字面值:123.4f、456F
4.使用前缀0x或者0X表示十六进制字面值:0x123
5.使用前缀0b或者0B表示二进制字面值:0b0101

当你书写数字字面值的时候一般不需要使用转换函数,即使你没有用上面的这些语法,当你使用数字字面值去初始化一个类型已知的变量时,又或是把字面值作为实参传给函数时,必要的转换会自动发生。此外算术运算符也被重载了,它们可以接收所有适当的数字类型

Any和Any?:根类型

Any是kotlin所有非空类型的超类型。但在java中,Object只是所有引用类型的超类型,而基本数据类型并不是类层级结构的一部分。和java一样,把基本数据类型的值赋给Any类型的变量时会自动装箱。Any是非空类型,所以Any类型的变量不能持有null值,在kotlin中如果你要持有任何可能值的变量,包括null就要使用Any?类型。在底层Any类型对应Object。所有kotlin类都包含toString equals hashCode,这些方法都继承自Any。但Any不能使用其他Object的方法,比如wait和notify,但可以通过手动把值转换成Object来调用。

Unit类型:Kotlin的void

Unit类型完成了java中void一样的功能,而且可以省略。大多是情况下你不会留意到void和Unit之间的区别。若果你的kotlin函数使用Unit作为返回类型并且没有重写泛型函数,在底层它会被编译成旧的void函数。它们的不同之处在于Unit是一个完备的类型,可以作为类型参数,而void不行。只存在一个值是Unit类型,这个值也叫做Unit,并且在函数中会被隐式返回。当你在重写返回泛型参数的函数时非常有用,只需让方法返回Unit类型的值。

Nothing类型:这个函数永不返回

许多测试库都有一个叫做fail的函数,它通过抛出带有特定消息的异常来让当前测试失败,一个包含无限循环的函数也永远不会成功结束。

Nothing类型没有任何值,只有被当作函数返回值使用,或者被当作泛型函数返回值的类型参数使用才会有意义。

注意!返回Nothing的函数可以放在Elvis运算符的右边来做先决条件检查

image.png

集合与数组

可空性和集合

对前后一致的类型系统来说有一点十分关键:知道集合是否可以持有null元素,和知道变量值是否可以为null同等重要

image.png

List<Int?>是能持有Int?类型值的列表:换句话说,可以持有Int或者null。如果一行文本可以被解析,那么就向result列表中添加一个整数,否则添加null。从kotlin1.1开始可以使用函数String.toIntOrNull来简化例子。

注意!变量自己类型的可空性和用作类型参数的可空性是有区别的,一个包含可空Int的列表和包含Int的可空列表是有区别的。List<Int?>是列表中的单个值是可空的,列表本身不为null,但列表的每个值可以为null,而List?是整个列表是可空的。我们要小心决定什么是可空的,是集合元素还是集合本身

遍历一个包含可空值的集合并过滤掉null是一个非常常见的操作,因此kotlin提供了filterNotNull函数来完成它。这种过滤也影响了集合的类型,此时List<Int?>会变成List<Int>,因为过滤保证了集合不会再包含任何为null的元素

只读集合与可变集合

与java有别,kotlin把访问集合数据的接口和修改集合数据的接口分开了。这种区别存在于最基础的使用集合的接口之中:Collection。使用这个接口,可以遍历集合中的元素、获取集合大小、判断集合中是否包含某个元素,以及执行其他从该集合中读取数据的操作,但这个接口没有任何添加或移除元素的方法。使用MutableCollection接口可以修改集合中的数据。它继承了Collection接口,还提供了方法来添加和移除元素、清空集合等。

一般的规则是在代码的任何地方都使用只读接口,只在代码需要修改集合的地方使用可变接口的变体

使用集合接口时牢记只读集合不一定是不可变的。如果你使用的变量有一个只读接口类型,他可能只是同一个集合的众多引用中的一个,任何其他的引用都可能拥有一个可变接口类型,所以只读集合并不总是线程安全的。

kotlin集合与java

每一种java集合接口在kotlin中都有两种表示:一种是只读的,一种是可变的

image.png
可变接口直接对应java.util包中的接口,而它们的只读版本中缺少了所有产生改变的方法

image.png

注意!setOf和mapOf返回的是java标准类库中类的实例(1.0中是这样),在底层它们都是可变的,kotlin的未来版本可能会使用真正不可变的实现类作为它们两个的返回值。

当你需要调用一个java方法并把集合作为实参传给它时,可以把任意只读或可变接口的值作为实参传递。 因为java并不会区分只读与可变集合,即使kotlin声明为只读,java代码也能修改这个集合。所以,如果你写了一个kotlin函数,使用了集合并传递给了java,你有责任使用正确的参数类型,这取决于你调用的java代码是否会去修改集合。同时也要注意包含非空类型元素的集合类,如果你向java方法传递了这样的集合,该方法就可能在其中写入null值,kotlin没有办法在不影响性能的情况下禁止它的发生。

作为平台类型的集合

我们提到过kotlin把那些定义在java代码中的类型看成平台类型,kotlin没有任何关于平台类型的可空型信息,所以编译器允许kotlin代码将其视为可空或非空。同样java中声明的集合类型的变量也被视为平台类型。一个平台类型的集合本质上就是可变性未知的集合,kotlin代码将其视为只读或者可变的,实际上你想要执行的所有操作都能正常工作

当你重写或者实现签名中有集合类型的java方法时这种差异才变得重要,你需要决定使用哪一种kotlin类型来表示这个java类型
1.集合是否可空
2.集合中的元素是否可空
3.你的方法会不会修改集合

对象和基本数据类型的数组

kotlin中的一个数组是一个带有类型参数的类,其元素类型被指定为相应的类型参数

在kotlin中创建数组

1.arrayOf函数创建一个数组,它包含的元素是指定为该函数的实参

2.arrayOrNulls函数创建一个给定大小的数组,包含的是null元素,它只能用来创建包含元素类型可空的数组

3.Array构造方法接受数组的大小和一个lambda表达式,调用表达式来创建每一个数组元素。这就是使用非空元素类型来初始化数组,但不用显式地传递每个元素的方式

image.png
lambda接收数组元素的下标并返回放在数组下标位置的值,这里可以省略数组元素的类型

kotlin代码中最常见的创建数组的情况之一是需要调用参数为数组的java方法时,或是调用带有vararg参数的kotlin函数时。在这些情况下通常已经将数据存储在集合中,只需将其转换成数组即可,可使用toTypedArray方法来执行此操作

image.png
和其它类型一样,数组类型的类型参数始终会变成对象类型。如果你需要创建没有装箱的基本数据类型的数组,必须使用一个基本数据类型数组的特殊类

kotlin提供了若干独立的类,如Int类型值的数组叫做IntArray。还有CharArray等其他类型。所有这些类型都被编译成普通的java基本数据类型数组,比如int[]等。因此这些数组中的值存储时并没有装箱,而是使用了可能的最高效的方式

要创建一个基本数据类型的数组
1.该类型的构造方法接收size参数并返回一个使用对应基本数据类型默认值(通常是0)初始化好的数组

2.工厂函数(IntArray的intArrayOf,以及其他数组类型的函数)接受变长参数的值并创建存储这些值的数组

3.另一种构造方法,接受一个大小和一个用来初始化每个元素的lambda

image.png

假如你有一个持有基本数据类型装箱后的值的数组或者集合,可以用对赢得转换函数把它们转换成基本数据类型的数组,比如toIntArray

kotlin中也有一套和集合相同的用于数组的扩展函数,lambda编程中的绝大部分函数也适用于数组,包括基本数据类型的数组,注意这些方法的返回值是列表不是数组