Kotlin 教程之「基础类型」

作者: 老梁写代码 | 来源:发表于2019-12-05 10:56 被阅读0次

    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
    """
    

    相关文章

      网友评论

        本文标题:Kotlin 教程之「基础类型」

        本文链接:https://www.haomeiwen.com/subject/gzhdgctx.html