Kotlin 教程之「基础类型」

Kotlin 中,我们可以调用任何变量的成员函数和属性,从这个角度来说,一切皆对象。某些类型可以有特殊的内部表现 - 例如,数字、字符和布尔型在运行时可以表现为基础类型(primitive types),但是对用户来说,他们看上去就是是普通的类。这一章节主要描述 Kotlin 的基本类型:数字、字符、布尔、数组和字符串。

数值

Kotlin 处理数字的方式与 Java 类似,但不是完全一致。例如,数值没有隐式的拓宽转换(implicit widening conversions),某些情况下,字面意思也会稍有不同。

Kotlin 提供了如下内置类型来表示数值(接近 Java):

类型 位宽
Double 64
Float 32
Long 64
Int 32
Short 16
Byte 8

注意:字符不是一种数值。

字面常量

整形值的字面常量有如下形式:

  • 十进制:123
    • 长整型用 L 做标记:123L
  • 十六进制:0x0F
  • 二进制:0b00001011

注意:不支持八进制。

浮点数也支持约定的标记:

  • double 类型:123.5123.5e10
  • float 用 f 或者 F 标记:123.5f

数值字面值中的下划线(1.1开始)

下划线可以使数值常量更具可读性:

val oneMillion = 1_000_000
val creditCardNumber = 1234_5678_9012_3456L
val socialSecurityNumber = 999_99_9999L
val hexBytes = 0xFF_EC_D5_5E
val bytes = 0b11010010_01101001_10010100_10010010

表现形式

Java 平台会把数值作为 JVM 基础类型来物理存储。除非是一个可为空的数值引用(例如 Int?)或者有泛型引入。如果是后者,数值会装箱。

注意:装箱后的数值不会保持 identity

val a: Int = 10000
print(a === a) // Prints 'true'
val boxedA: Int? = a
val anotherBoxedA: Int? = a
print(boxedA === anotherBoxedA) // !!!Prints 'false`!!!

但是仍然会有相等性:

val a: Int = 10000
print(a == a) // Prints 'true'
val boxedA: Int? = a
val anotherBoxedA: Int? = a
print(boxedA == anotherBoxedA) // Prints 'true'

显示转换

由于不同的表现形式,小类型并非大类型的子类型。如果是的话,可能会带来如下麻烦:

// Hypothetical code, does not actually compile:
val a: Int? = 1 // A boxed Int (java.lang.Integer)
val b: Long? = a // implicit conversion yields a boxed Long (java.long.Long)
print(a == b) // Surprise! This prints "false" as Long's equals() check for other part to be Long as well

所以,不只是身份(identity),连相等性(equality)也会静默丢失。

因此,小类型不会隐式转换成大类型。这就意味着:不通过显示转换,我们无法把一个 Byte 赋值给 Int

val b: Byte = 1 // OK, literals are checked statically
val i: Int = b // ERROR

通过显示转换可以“拓宽(widen)”数值。

val i: Int = b.toInt()  // OK: explicitly widened

每个数值类型都支持如下转换:

  • toByte(): Byte
  • toShort(): Short
  • toInt(): Int
  • toLong(): Long
  • toFloat(): Float
  • toDouble(): Double
  • toChar(): Char

缺少隐式转换并不会引起注意,因为通过上下文可以推导出类型,并且算术操作符也有支持类型转换的重载,例如:

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

运算

Kotlin 支持数值的标准算术运算,这些运算被声明为相应类的成员(但是编译器会把函数调用优化成相应的指令)。参考操作符重载

位运算操作符也没有特殊之处,他们也只是支持中缀调用的命名函数,例如:

val x = (1 shl 2) and 0x000FF000

如下是位运算操作符的完整列表(只用于 IntLong):

  • shl(bits) - 有符号左移(Java 的 <<
  • shr(bits) - 有符号右移(Java 的 >>
  • ushr(bits) - 无符号右移(Java 的 >>>
  • and(bits) - 位的与运算
  • or(bits) - 位的或运算
  • xor(bits) - 位的异或运算
  • inv(bits) - 位的非运算

浮点数比较

本节所要讨论的浮点数运算符有:

  • 相等检查:a == ba != b
  • 比较操作符:a < ba > ba <= b, a >=b
  • 范围初始化和范围检查:a..bx in a..bx !in a..b

当操作数 ab 静态已知为类型 FloatDouble,以及它们对应的可空类型(得出方式包括:声明、推断或者智能转换),数值的运算以及它们形成的范围(range)遵守 IEEE 754 制定的浮动点数运算规范。

但是为了支持通用的使用场景以及提供完整的排序,当操作数不是浮点数的静态类型(如 AnyComparable<...>,类型参数)时,运算操作会使用 FloatDoubleequalscompareTo 实现,这会导致异与标准,因此:

  • NaN 等于它自己
  • NaN 大与所有其他元素,包括 POSITIVE_INFINITY
  • -0.0 小于 0.0

字符

Char 表示字符,不能直接用作数值:

fun check(c: Char) {
    if (c == 1) { // ERROR: incompatible types
        // ...
    }
}

字符用单引号来表示:'1'。特殊字符可以使用反斜杠来转义。

特殊字符可以用反斜杠转义。支持的转义序列有:\t\b\n\r\'\"\\\$。如果要编译其他字符,可以使用 Unicode 转义序列语法:\uFF00

我们可以显示地把一个字符转换成一个 Int 数值:

fun decimalDigitValue(c: Char): Int {
    if (c !in '0'..'9')
        throw IllegalArgumentException("Out of range")
    return c.toInt() - '0'.toInt() // Explicit conversions to numbers
}

就像数值那样,字符的空引用也会自动装箱。装箱操作不会保留字符的身份(identity)。

布尔型

Boolean 表示布尔型,有两个值:truefalse

布尔的可空引用会自动装箱。

内置操作符包括:

  • || - lazy disjunction
  • && - lazy conjunction
  • ! - negation

数组

Kotlin 用类 Array 来表示数组,有 getset 函数(利用操作符重载的约定可转换成 [] 操作),还有 size 属性,除此之外还有其他有用的成员函数:

class Array<T> private constructor() {
    val size: Int
    operator fun get(index: Int): T
    operator fun set(index: Int, value: T): Unit
    operator fun iterator(): Iterator<T>
    // ...
}

使用库函数 arrayOf() 并传入元素值可以创建一个数组:arrayOf(1, 2, 3) 创建了 [1, 2, 3]。另外,arrayOfNulls() 可以创建一个所有元素都是 null 的数组。

另一种创建方式是调用 Array 的构造函数,传入数组大小和一个根据下标返回初始值的函数:

// Creates an Array<String> with values ["0", "1", "4", "9", "16"]
val asc = Array(5, { i -> (i * i).toString() })

上面已经说过,[] 操作等价于调用成员函数 get()set()

注意:与 Java 不同,Kotlin 的数组是不可变的(invariant)。这就意味着 Kotlin 不允许我们把 Array<String> 赋给 Array<Any>,这样能避免运行时的失败(但是能用 Array<out Any>,可参考类型映射)。

Kotlin 也有特定的类用于表示基础类型数组(没有装箱的开销):ByteArrayShortArrayIntArray 等。这几个类和 Array 没有直接的继承关系,但是他们有同样的方法和属性。每个类型都有相应的工厂函数:

val x: IntArray = intArrayOf(1, 2, 3)
x[0] = x[1] + x[2]

字符串

字符串由 String 表示。字符串是不可变的。字符串的元素可通过下标访问:s[i]。字符串可通过 for 循环遍历:

for (c in str) {
    println(c)
}

字符串字面值

Kotlin 支持两种类型的字符串字面值:包含转义字符的转义字符串和包含换行和任意文本的纯字符串。转义字符串跟 Java 类似:

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()

默认情况下,| 用作 margin 前缀,但是也可以使用其他字符作为参数传给 trimMargin,例如 trimMargin(">")

字符串模板

字符串可以包含模板表达式,例如,可被求值的代码片段,求值结果可以连接到字符串中。模板表达式以美元符号($)开始,由一个简单的名称组成:

val i = 10
val s = "i = $i" // evaluates to "i = 10"

或者是大括号内的任意表达式:

val s = "abc"
val str = "$s.length is ${s.length}" // evaluates to "abc.length is 3"

模板可用于纯字符串和转义后的字符串内。如果要在纯字符串(不支持转义)中展示 $ 符号,可以使用如下语法:

val price = """
${'$'}9.99
"""
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容