Kotlin 基本类型
在 Kotlin 中,所有的东西都是对象。在这个意义上讲,我们可以在任何变量上调用成员函数与属性。在 Kotlin 中的基本类型,都是以对象的形式存在的。我们常用的类型基本有以下几类:数字,字符,布尔值,数组,字符串。
数字
Kotlin 中的数字类型类似于Java,内置了一组表示数字的内置类型。主要有以下一些:
类型 | 长度 |
---|---|
Double | 64 |
Float | 32 |
Long | 64 |
Int | 32 |
Short | 16 |
Byte | 8 |
当我们声明数字变量时,如果不指定变量的类型,Kotlin 就会根据当前变量的初始化的长度来推断其类型。整数的类型推断时会以 Int
为的最大值为准。未超出 Int
最大值的整型值初始化的变量都会推断为 Int
类型,如果初始值超过了Int
最大值,那么就会推断为 Long
类型。
对于浮点数,类型推断时会默认推断为 Double
类型;除非在声明时,在宿主后面添加 f
或者 F
后缀。
字面常量
在 Kotlin 中支持以下的数值类型
- 十进制:比如123,一般的,Long 类型需要在数字后缀加大写的
L
。 - 十六进制:比如 0x012;0xabc
- 二进制:比如:0b00110011
- 不支持
八进制 浮点类型
- 默认 Double 123.4,123.5e10
Float 后缀需要加
f
或者F
,否者会默认使用 Double在 Kotlin 1.1 起,数字常量支持下滑下
_
按位分隔,使数字更易读。比如:
按英文方式断位:
1_000
,1_000_000
- 按中文方式断位:
1_0000
,10000_0000
val oneThousand = 1_000
val oneMillion = 1_000_000
val oneBillion = 1_000_000_000
// 按中文表示 一万
val wan = 1_0000
// 按中文表示 一亿
val yi = 10000_0000
表示方式
在 Java 平台数字是物理存储为 JVM 的原生类型。比如下面的代码:
val a = 100
val b = 100.0
val c = 100.0F
val d = 100L
将上面的代码转换成 Java 代码:
private static final int a = 100;
private static final double b = 100.0D;
private static final float c = 100.0F;
private static final long d = 100L;
有一个疑问?在 Java 平台上,什么时候才会触发 Java 数据类型的装箱操作呢?
在 Kotlin 中,当我们的变量声明为可空类型时,即使用 ?
操作符修饰时。这个时候就会触发装箱的操作。比如下面的代码:
val e: Int? = 100
val f: Float? = 100.0F
val g: Double? = 100.0
val h: Long? = 100L
将上面的代码转换成 Java 代码:
@Nullable
private static final Integer e = 100;
@Nullable
private static final Float f = 100.0F;
@Nullable
private static final Double g = 100.0D;
@Nullable
private static final Long h = 100L;
涉及到装箱时,我们需要注意,变量只保留的相等性(使用 ==
判断,即数字的值相等),却不能保证同一性(使用 ===
判断,即引用相等,两个变量执行同一个内存地址)。
val i = 1000
val boxedJ : Int ? = i
println(i == boxedJ) // true
println(i === boxedJ) // false
我们将上面的代码转换成 Java 代码,结果就一目了然了:
int i = 1000;
// 将 i 装箱,生成一个 boxedJ 的引用,这个引用会在内存中开辟一块新的内存空间
Integer boxedJ = Integer.valueOf(i);
// 拆箱,将包装类转换为基本的数据类型进行比较两个变量的字面变量的值
boolean var2 = i == boxedJ;
System.out.println(var2);
// 当对比 两个引用时,会再次装箱,将变量 i 装箱,这是又会重新生成一个引用,然后在内存中重新分配一个新的内存空间
var2 = Integer.valueOf(i) == boxedJ;
System.out.println(var2);
对比一下 kotlin 和 Java 代码,不难看出,涉及到拆箱和装箱时一定要注意同一性的问题。
数据类型转换
Kotlin 不支持数字的隐式拓宽转换,我们不能直接将 Int
类型的值直接赋给 Long
类型变量。在 Kotlin 中所有的东西都是对象,如果我们将 Int
类型的值直接赋给 Long
类型变量,就是将一个 Int
对象 强制转换之后 赋值给 Long 对象。在编译时就会报错。
val x: Int? = 100 // 将 x 装箱成 Java Integer
val y: Long? = x // 将 y 装箱成 Java Long ,然后 Long y = (Long)x;
上面代码编译就会出现错误:Error:(42, 20) Kotlin: Type mismatch: inferred type is Int? but Long? was expected
。
除了上面的错误,当做算术运算时也需要注意:
val x: Int? = 100
val y: Long? = 100L
if (x != null && y != null) {
val z = x + y // Long + Int ==> Long
}
val a = 1L + 2 // Long + Int ==> Long
在 Kotlin 算术运算会有重载做适当的转换,所以上面的代码都是可以运行的。
在 Kotlin 中不能隐式的转换,只能显式转换。
在 Kotlin 中每个数字类型都提供了显式转换的方法:
- toByte() : Byte
- toShort : Short
- toInt() : Int
- toLong() : Long
- toFloat() : Float
- toDouble() : Double
- toChar() : char
运算
Kotlin 支持数字的标准运算。运算被定义为相应的类成员。像 + - × ÷
等这些运算符都有对应的预定义的函数。预定义运算符只能作用在数字类型的对象上。如果要在其它的对象中使用这些运算符,就需要重载这些操作符的预定义函数。
一元前缀操作符
操作符/表达式 | 预定义函数 |
---|---|
+a |
a.unaryPlus() |
-a |
a.unaryMinus() |
!a |
a.not() |
以 +a
为例,介绍上面的操作符的执行步骤:
- 根据 a 的值推断 a 的类型,比如 Int ;
- 然后在 Int 类中查找带有
operator
修饰符的无参函数unaryPlus()
,即成员函数或者扩展函数; - 如果函数没有找到,则会导致编译错误;
- 如果函数存在,则会继续确定其返回类型,如果返回类型为 Int,那么 +a 的值就是 Int 类型。
递增与递减
操作符/表达式 | 预定义函数 |
---|---|
a++/++a |
a.inc() |
a--/--a |
a.dec() |
对于 inc()
和 dec()
这两个函数,它们用于赋值给使用 ++
或者 --
操作的变量,所以这两个函数必须有返回值。
以 a++
为例,介绍编译器执行解析 后缀形式表达式 的步骤:
- 根据 a 的值推断 a 的类型,比如 Int ;
- 然后在 Int 类中查找带有
operator
修饰符的无参函数inc()
,即成员函数或者扩展函数; - 如果函数存在,则会继续确定其返回类型,如果返回类型为
Int
,那么a++
的值就是 Int 类型。
其计算表达式的步骤:
- 把
a
的初始值存储到临时变量a0
中; - 把
a.inc()
的结果赋值给a
; - 把
a0
作为表达式的结果返回。
对于 a--
其步骤都是完全相似的。
对于前缀形式的 ++a
和 --a
以相同方式解析,但是其不会在创建临时变量。再次以 ++a
为例,其计算步骤如下:
- 把
a.inc()
的结果直接赋值给a
; - 然后把
a
的新值作为表达式的结果返回。
二元操作
算术运算符
操作符/表达式 | 翻译成的表达式 |
---|---|
a + b |
a.plus(b) |
a - b |
a.minus(b) |
a * b |
a.times(b) |
a / b |
a.div(b) |
a % b |
a.rem(b) 、 a.mod(b) (已弃用) |
a..b |
a.rangeTo(b) |
对于上面这张表的操作,编译器只是翻译成列中搞得表达式。
比如上面的 a+b
;编译器就会翻译成 a.plus(b)
。
In 操作符
操作符/表达式 | 翻译成的表达式 |
---|---|
a in b |
b.contains(a) |
a !in b |
!b.contains(a) |
对于 in
和 !in
,过程是相同的,但是参数的顺序是相反的。
索引访问操作符
操作符/表达式 | 翻译成的表达式 |
---|---|
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) |
方括号转换为调用带有适当数量参数的 get
和 set
。不过在 数组 或者 List 中一般只会使用一个参数的 set 、get 。
调用操作符
表达式 | 翻译成的表达式 |
---|---|
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) |
圆括号转换为调用带有适当数量参数的 invoke
。
广义赋值
表达式 | 翻译为 |
---|---|
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)(已弃用) |
在 Kotlin 中赋值不是表达式。其没有返回值。
对于赋值操作,例如 a+b
,编译器质性以下步骤:
- 如果右列的函数可用
- 如果相应的二元函数(即
plusAssign()
对应于plus()
)也可用 ,那么报告错误(模糊),因为编译器不知道使用plusAssign()
还是使用plus()
。 - 确保其返回值是 Unit ,否知会报错;
- 生成
a.plusAssign(b)
的代码。 - 如果右列的函数不可用,则会试着生成
a = a + b
的代码(这里包含类型检查:a + b
的类型必须是a
的子类型)
相等与不等操作符
表达式 | 翻译成的表达式 |
---|---|
a == b |
a?.equals(b) ?: (b === null) |
a != b |
!(a?.equals(b) ?: (b === null)) |
这些操作符只使用函数 equals(other: Any?): Boolean
,可以覆盖它来提供自定义的相等性检测实现。不会调用任何其他同名函数(如 equals(other: Foo)
)。
注意:===
和 !==
(同一性检查)不可重载,因此不存在对他们的约定。
这个 ==
操作符有些特殊:它被翻译成一个复杂的表达式,用于筛选 null
值。 null == null
总是 true,对于非空的 x
,x == null
总是 false 而不会调用 x.equals()
。
比较操作符
表达式 | 翻译为 |
---|---|
a > b |
a.compareTo(b) > 0 |
a < b |
a.compareTo(b) < 0 |
a >= b |
a.compareTo(b) >= 0 |
a <= b |
a.compareTo(b) <= 0 |
所有的比较都转换为对 compareTo
的调用,这个函数需要返回 Int
值。
位运算
对于位运算,没有特殊字符来表示,而只可用中缀方式调用命名函数。
位运算函数 | 含义 |
---|---|
shl(bits) | 有符号左移 ( Java 的 << ) |
shr(bits) | 有符号右移 ( java 的 >> ) |
ushr(bits) | 无符号右移 ( Java 的 >>> ) |
and(bits) | 位与 ( & ) |
or(bits) | 位或 ( | ) |
xor(bits) | 位异或 ( ^ ) |
inv() | 位非 ( ! ) |
浮点数比较
- 相等性检测:
a == b
与a != b
- 比较操作符:
a < b
、a > b
、a <= b
、a >= b
- 区间实例以及区间检测:
a..b
、x in a..b
、x !in a..b
字符
在 Kotlin 中 字符使用 Char
类型表示。它们不能直接当做数字,但是可以通过 toInt()
等函数转换为数字。
字符的字面值使用单引号表示: 'a'
。 特殊字符需要使用转义字符 \
进行转义。支持这几个转义序列:\t
、 \b
、\n
、\r
、\'
、\"
、\\
与 \$
。 编码其他字符要用 Unicode 转义序列语法:'\uFF00'
。
当需要可空引用(声明为可空变量)时,像数字、字符会被装箱。装箱操作不会保留同一性。比如:
var char: Char? = 'c'
转换成 Java:
Character var21 = 'c';
布尔
布尔值采用 Boolen
类型表示,它只有两个值 true
和 false
。
它内置了如下的布尔运算:
||
==> 短路逻辑 或&&
==> 短路逻辑 与!
==> 逻辑 非
若需要可空引用(声明为可空变量)则布尔值会被装箱。比如:
var boolean: Boolean? = false
转换成 Java:
Boolean var23 = false;
数组
在 kotlin 中数组使用 Array
类来表示,它定义了 get
和 set
函数以及 size
属性,以及一些其他的有用的成员函数。
我们可以使用库函数 arryOf()
创建一个数组并传递元素值给它。或者使用库函数 arryOfNulls
创建一个指定大小的、所有元素都为空的数组。
// 创建一个数组,数组的元素为 1,2,3,4,5
var arrayOf = arrayOf(1,2,3,4,5)
// 创建一个长度为5的数组,数组的每个元素都是 Int 类型
var arrayOfNulls = arrayOfNulls<Int>(5)
将上面的代码转换成 Java 代码很容易就可以看出,这种数组的创建方式涉及到了Int的装箱操作:
Integer[] arrayOf = new Integer[]{1, 2, 3, 4, 5};
Integer[] arrayOfNulls = new Integer[5];
除了上面的两种方式,我们还可以通过 Array
的构造函数来创建数组。用接受数组大小以及一个函数参数的 Array
构造函数,用作参数的函数能够返回给定索引的每个元素初始值:
// 创建一个 Array<String> 初始化为 ["0","1","2","3","4"]
var asc = Array(5){
(it).toString()
}
[]
运算符代表调用成员函数 get()
与 set()
。
var array = arrayOf(1,2,3,4)
// 将数组的第0个元素的值设置为100;相当于调用array.set(0,100)
array[0] = 100
// 获取数组的第0个元素,并打印;相当于 array.get(0)
println(array[0])
原生类型数组
Kotlin 为了避免装箱开箱,专门提供了原生类型的数组: ByteArray
、 ShortArray
、IntArray
等等。这些类与 Array
并没有继承关系,但是它们有同样的方法属性集。它们都有相同的工厂方法,以 IntArray
为例:
val x: IntArray = intArrayOf(1, 2, 3)
x[0] = x[1] + x[2]
// Array of int of size 5 with values [0, 0, 0, 0, 0]
val arr = IntArray(5)
// e.g. initialise the values in the array with a constant
// Array of int of size 5 with values [42, 42, 42, 42, 42]
val arr = IntArray(5, { 42 })
// e.g. initialise the values in the array using a lambda
// Array of int of size 5 with values [0, 1, 2, 3, 4] (values initialised to their index value)
var arr = IntArray(5, { it * 1 })
字符串
在 Kotlin 中用 String
类型表示字符串,字符串是不可以变的。字符串的元素——字符可以使用索引运算符访问,也可以使用 for
循环迭代字符串:
val string: String = "Hello , World"
// 获取字符串的第0个字符
println(string[0])
// 使用 for 循环迭代字符串
for (c in string) {
println(c)
}
字符串可以使用 +
操作符进行连接。这也适用于连接字符串与其他类型的值, 只要表达式中的第一个元素是字符串:
val s = "abc" + 1
println(s + "def")
虽然 Kotlin 提供了使用 +
操作符进行连接字符串的方式,但是并不提倡使用,大多数情况下还是优先使用字符串模板。
字符串字面值
Kotlin 有两种类型的字符串字面值:
- 可以包含转义字符的字符串,使用双引号
" 字符串 "
包括起来; - 可以包含换行以及任意文本的原始字符串,使用三个引号
""" 字符串 """
包括起来。
转义采用传统的反斜杠方式
以下是转义字符串的一个示例:
val s = "Hello, world!\n"
原始字符串 使用三个引号("""
)分界符括起来,内部没有转义并且可以包含换行以及任何其他字符:
val text = """
for (c in "foo")
print(c)
"""
我们可以通过trimMargin()函数去除前导空格:
val text = """
|Tell me and I forget.
|Teach me and I remember.
|Involve me and I learn.
|(Benjamin Franklin)
""".trimMargin()
trimMargin()函数会去除 |
前缀,这里的 |
前缀是为了更好地可读性。
默认 |
用作边界前缀,但你可以选择其他字符并作为参数传入,比如 trimMargin(">")
。
字符串模板
Kotlin 支持在字符串中引用变量。可以通过 $
在字符串中引用某个常量或者变量。如果引用的是一个变量的函数、属性或者一个表达式,可以 ${}
来引用。如果需要在字符串中使用 $
就需要对其的进行转义 ${'$'}
,这样就可以在字符串中引用 $
。
val name = "Ricky"
val num = 10
val rise = 10000
val welcome = """
welcome
No. $num
name: $name ,
name length:${name.length} ,
social status:${'$'}$rise """
println(welcome)
上面的代码输出结果为:
welcome
No. 10
name: Ricky ,
name length:5 ,
social status:$10000
在 字符串中使用 表达式:
val range = 1..5
println("3 in range is ${3 in range}")
总结:
- 在 Kotlin 中,所有的东西都是对象,基本类型也是对象;
- 基本类型包含:Double、Float、Long、Int、Short、Byte、Char、Boolean,每个类型都是一个类;
- 声明变量时,如果不指定变量的类型,Kotlin 会根据当前变量值推断类型;
- 声明整形数字时,默认为 Int ;声明浮点型数字时,默认为 Double;
- 在 Kotlin 中,变量声明为可空类型时,即使用
?
操作符修饰时,就会触发装箱的操作; - 每个基本类型都是一个类,各个基本类型不能进行隐式转换;
- 每个基本都是提供了方法显式转换成其它的各个基本类型;
- Kotlin支持数字运算的标准集
- Kotlin支持各种位运算,但是没有特定的符号,需要使用中缀方式调用命名函数;
- 字符
Char
类型 不能直接当作数字; - 特殊字符需要使用转义字符
\
进行转义,支持常见的转义字符; - 数组使用
Array
类表示,它定义了get/set
函数以及size
属性; - 以使用库函数
arryOf()
创建一个数组并传递元素值给它。或者使用库函数arryOfNulls
创建一个指定大小的、所有元素都为空的数组; - 使用库函数
arryOf()/arryOfNulls()
会涉及到拆箱装箱; - 使用原生类型数组可以避免拆箱装箱;
- 字符串用
String
类型表示。字符串是不可变的。可以使用[]
通过下标访问字符串中的某个字符; - 可以使用字符串模板进行字符串连接;也可以使用
+
连接字符串,但不推荐; - 原始字符串使用三个引号(
"""
)分界符括起来,内部没有转义并且可以包含换行以及任何其他字符;并且可以通过trimMargin()
函数去除前导空格;