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_000val oneMillion = 1_000_000val oneBillion = 1_000_000_000// 按中文表示 一万val wan = 1_0000// 按中文表示 一亿val yi = 10000_0000
表示方式
在 Java 平台数字是物理存储为 JVM 的原生类型。比如下面的代码:
val a = 100val b = 100.0val c = 100.0Fval 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? = 100val f: Float? = 100.0Fval g: Double? = 100.0val h: Long? = 100L
将上面的代码转换成 Java 代码:
@Nullableprivate static final Integer e = 100;@Nullableprivate static final Float f = 100.0F;@Nullableprivate static final Double g = 100.0D;@Nullableprivate static final Long h = 100L;
涉及到装箱时,我们需要注意,变量只保留的相等性(使用 == 判断,即数字的值相等),却不能保证同一性(使用 === 判断,即引用相等,两个变量执行同一个内存地址)。
val i = 1000val boxedJ : Int ? = iprintln(i == boxedJ) // trueprintln(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 Integerval 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? = 100val y: Long? = 100Lif (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,5var 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" + 1println(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 = 10val rise = 10000val welcome = """welcomeNo. $numname: $name ,name length:${name.length} ,social status:${'$'}$rise """println(welcome)
上面的代码输出结果为:
welcomeNo. 10name: Ricky ,name length:5 ,social status:$10000
在 字符串中使用 表达式:
val range = 1..5println("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()函数去除前导空格;
