本文将伴随大家进入Kotlin语言的正式学习生涯中,希望大家不要半途而废哦!笔者将Kotlin用于Android开发中,因此将从Android开发的视角叙述相关内容,同时将与Java语言有所联系。

1. Kotlin基本类型

1.1 数字

1.1.1 整型

整型,即整数,与Java类似,有四种内置的整型类,表示的数值范围有所不同:

类型 位数 可表示的最小值 可表示的最大值
Byte 8 -128 127
Short 16 -32768 32767
Int 32 -2,147,483,648 (-231) 2,147,483,647 (231-1)
Long 64 -9,223,372,036,854,775,808 (-263) 9,223,372,036,854,775,807 (263-1)

所有以未超出Int最大值的整型值初始化的变量都会推断为Int类型。如果初始值超过了其最大值,那么推断为Long类型。 如需显式指定Long型值,请在该值后追加lL后缀。

1
2
3
4
val one = 1 // Int
val threeBillion = 3000000000 // Long
val oneLong = 1L // Long
val oneByte: Byte = 1

1.1.2 浮点型

Kotlin提供了FloatDouble类型。 根据IEEE 754标准, 两种浮点类型的十进制位数(即可以存储多少位十进制数)不同:

类型 位数 可表示的最小值 可表示的最大值
Float 32 1.4E-45 (2-149) 3.4028235E+38 (2128-1)
Double 64 4.9E-324 (2-1074) 1.7976931348623157E+308 (21024-1)

与Java所不同的是,Kotlin中的数字没有隐式拓宽转换。 例如,具有Double参数的函数只能对Double值调用,而不能对FloatInt或者其他数字值调用。

1
2
3
4
5
6
7
8
9
10
11
fun main() {
fun printDouble(d: Double) { print(d) }

val i = 1
val d = 1.1
val f = 1.1f

printDouble(d)
printDouble(i) // 错误:类型不匹配
printDouble(f) // 错误:类型不匹配
}

1.1.3 字面常量

字面常量这个词很有意思,字面,代表浅显不深入。在Java中,字面常量指的就是普通的数字、字符、字符串,不是这里说的基本类型。比如下面一段Java代码:

1
2
3
4
int a = 1024
double b = 20.48
char c = 'A'
String d = "Hello World!"

上述代码中,=右边的就是字面常量,而=左边的是变量。同理,Kotlin中也是这样的情况,下面是所有的字面常量:

  • 十进制:123
  • 长整型以大写的 L 结尾:123L
  • 16 进制以 0x 开头:0x0F
  • 2 进制以 0b 开头:0b00001011

注意:8进制不支持

在Kotlin里有一个很方便的设定,你可以使用下划线使数字常量更易读,而这些下划线并不会编入你的软件中,如:

1
2
3
4
5
val oneMillion = 1_000_000
val creditCardNumber = 1234_5678_9012_3456L
val socialSecurityNumber = 999_99_9999L
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010

1.2 布尔

Kotlin中的布尔值与Java中一致,我就不多赘述了。

布尔用 Boolean 类型表示,它有两个值:true 和 false。若需要可空引用布尔会被装箱。内置的布尔运算有:

1
2
3
4
5
6
val boolA = true
val boolB = false

boolA || boolB // 短路逻辑或,结果为true
boolA && boolB // 短路逻辑与,结果为false
!boolA // 逻辑非,结果为false

1.3 字符

字符在日常开发中不是特别常用,但是它是字符串的组成部分,字符串在日常开发中确实有很重要的地位。

Char(字符型) 必需是单引号 ‘ 包含起来的,比如普通字符 ‘0’,’a’。与 Java 不一样,Kotlin 中的 Char 不能直接和数字操作,如下代码在Kotlin中是不可行的:

1
2
3
4
5
fun check(c: Char) {
if (c == 1) { // 错误:类型不兼容
// ...
}
}

和Java一样,特殊字符可以用反斜杠转义。 支持这几个转义序列:\t、 \b、\n、\r、\'、\"、\\ 和 \$。 编码其他字符要用 Unicode 转义序列语法:\uFF00

1.4 字符串

1.4.1 String类型

和 Java 一样,String 是不可变的。Kotlin提供了方括号 [] 语法可以很方便的获取字符串中的某个字符,也可以通过 for 循环来遍历:

1
2
3
4
5
6
7
val str = "String"

pritln(str[3]) // 打印出 r

for (c in str) {
println(c) // 逐行打印出 S t r i n g
}

Kotlin 支持三个引号 “”” 扩起来的字符串,支持多行字符串,比如:

1
2
3
4
5
6
7
fun main(args: Array<String>) {
val text = """
多行字符串
多行字符串
"""
println(text) // 输出时保留编码时的格式,此次输出的两行字符串之前有一些前置空格
}

可以通过 trimMargin() 方法来删除多余的空白。默认 | 用作边界前缀,但你可以选择其他字符并作为参数传入,比如 trimMargin(“>”)。

1
2
3
4
5
6
7
8
9
fun main(args: Array<String>) {
val text = """
|多行字符串
|菜鸟教程
|多行字符串
|Runoob
""".trimMargin()
println(text) // 前置空格删除了
}

1.4.2 字符串模板

Kotlin中,字符串可以包含模板表达式 ,即一些小段代码,会求值并把结果合并到字符串中。 模板表达式以美元符$开头,由一个简单的名字构成:

1
2
3
4
5
fun main(args: Array<String>) {
val i = 10
val s = "i = $i" // 相当于Java中的String.format("i = %d", i)
println(s) // 输出结果为 "i = 10"
}

除了插入变量还可插入一行表达式,用花括号扩起来:

1
2
3
4
5
fun main(args: Array<String>) {
val s = "runoob"
val str = "$s.length is ${s.length}"
println(str) // 输出结果为 "runoob.length is 6"
}

原生字符串和转义字符串内部都支持模板。 如果你需要在原生字符串中表示字面值 $ 字符(它不支持反斜杠转义),你可以用下列语法:

1
2
3
4
fun main(args: Array<String>) {
val price = "${'$'}9.99"
println(price) // 输出结果为 $9.99
}

1.5 数组

和Java中的类型后加上[]即可声明数组变量不同,Kotlin的数组用类Array实现,并且还有一个 size 属性及 getset 方法,由于使用 [] 重载了 getset 方法,所以我们可以通过下标很方便的获取或者设置数组对应位置的值,使用时与Java可以达到一致。

数组的创建两种方式:一种是使用函数arrayOf();另外一种是使用工厂函数。如下所示,我们分别是两种方式创建了两个数组:

1
2
3
4
5
6
7
8
9
10
fun main(args: Array<String>) {
//[1,2,3]
val a = arrayOf(1, 2, 3)
//[0,2,4]
val b = Array(3, { i -> (i * 2) })

//读取数组内容
println(a[0]) // 输出结果:1
println(b[1]) // 输出结果:2
}

如上所述,[] 运算符代表调用成员函数 get() 和 set()。

注意: 与 Java 不同的是,Kotlin 中数组是不型变的(invariant)。

除了类Array,还有ByteArray, ShortArray, IntArray,用来表示各个类型的数组,省去了装箱操作,因此效率更高,其用法同Array一样。

2. 基本数据类型间的关系

2.1 类型比较

实际上 Kotlin 中没有基础数据类型,只有封装的类型,你每定义的一个变量,其实 Kotlin 帮你封装了一个对象,这样可以保证不会出现空指针。基本类型也一样,所以在比较两个类型的时候,就有比较数据大小和比较两个对象是否相同的区别了。

在Java中大家都知道有两种比较类型相等的方式:==双等号与equals()方法。equals()方法可以为我们比较两个变量的数值或者说大小是否相等,==双等号则用于比较两个变量的地址是否相同,基本数据类型这两种方式得到的结果一致。

与Java不同的是,在 Kotlin 中,三个等号 === 表示比较对象地址,两个 == 表示比较两个值大小。

1
2
3
4
5
6
7
8
9
10
11
12
fun main(args: Array<String>) {
val a: Int = 10000
println(a === a) // true,值相等,对象地址相等

//经过了装箱,创建了两个不同的对象
val boxedA: Int? = a
val anotherBoxedA: Int? = a

//虽然经过了装箱,但是值是相等的,都是10000
println(boxedA === anotherBoxedA) // false,值相等,对象地址不一样
println(boxedA == anotherBoxedA) // true,值相等
}

2.2 类型转换

在Java中,类型装换相当方便,隐式转换和显式转换用起来不亦乐乎。而在Kotlin中,由于不同的表示方式,较小类型并不是较大类型的子类型,较小的类型不能隐式转换为较大的类型。 这意味着在不进行显式转换的情况下我们不能把 Byte 型值赋给一个 Int 变量。

1
2
val b: Byte = 1 // OK, 字面值是静态检测的
val i: Int = b // 错误

我们可以代用其toInt()方法。

1
2
val b: Byte = 1 // OK, 字面值是静态检测的
val i: Int = b.toInt() // OK

Kotlin为每种数据类型都提供了下面的这些方法,可以转化为其它的类型:

1
2
3
4
5
6
7
toByte(): Byte
toShort(): Short
toInt(): Int
toLong(): Long
toFloat(): Float
toDouble(): Double
toChar(): Char

有些情况下也是可以使用自动类型转化的,前提是可以根据上下文环境推断出正确的数据类型而且数学操作符会做相应的重载。例如下面是正确的:

1
val l = 1L + 3 // Long + Int => Long

另外Kotlin也提供了as关键字来完成类型的显式转换,使用方式与二元运算符相同:

1
2
val b: Byte = 1 // OK, 字面值是静态检测的
val i: Int = b as Int // OK