Kotlin 数据类型

4,273 阅读10分钟

1 基本数据类型

与 Java 基本类型相对应,Kotlin 也有 8 种基本数据类型。

  • 整数类型:Byte、Short、Int、Long,Int 是默认类型
  • 浮点类型:Float、Double,Double是默认类型
  • 字符类型:Char
  • 布尔类型:Boolean

Kotlin 中去掉了原始类型,只有包装类型,编译器在编译代码的时候,会自动优化性 能,把对应的包装类型拆箱为原始类型。

Kotlin 系统类型分为可空类型和不可空类型。 Kotlin 中引入了可空类型,把有可能为 null 的值单独用可空类型来表示。这样就在可空引用与不可空引用之间划分出一条明确的、 显式的“界线”。

1.1 整数类型

与 Java类似, Kotlin也提供了4种整型。

  • Byte:1个字节(8位),取值范围 -128 ~ 127。
  • Short:2个字节(16位),取值范围 -32768(-2 ^ 15) ~ 32767(2 ^ 15 - 1)。
  • Int:4个字节(32位),取值范围 -2147483648(-2 ^ 31)~ 2147483647(2 ^ 31 - 1)。
  • Long:8个字节(64位),取值范围 (-2 ^ 63) ~ (2 ^ 63 - 1。

当程序直接给出一个较大的整数时,该整数默认可能就是 Long 型,如果将这个整数赋值给 Int、 Short 或 Byte 型,编译器将会报错。

Kotlin 的整型与 Java 不同, Kotlin 的整型不是基本类型,而是引用类型(大致相当于 Java 的包装类), Byte、 Short、 Int、 Long 都继承了 Number 类型,因此它们都可调用方法、访问属性。

通过访问不同整数类型的 MIN_VALUE 和 MAX _VALUE 属性来获取对应类型的最大值和最小值。

Kotlin 是 null 安全的语言,因此 Byte、 Short、 Int、 Long 型变 量都不能接受 null值,如果要存储 null值,则应该使用 Byte?、 Short、 Int?、 Long? 类型。

此外,整数类型添加 “?” 后缀与不加后缀还有一个区别:普通类型的整型变量将会映射成 Java 的基本类型;带 “?” 后缀的整型变量将会映射成基本类型的包装类。举例来说,Int 类型的变量将会映射成 Java 的 int 基本类型,但 Int? 类型的变量则会自动映射成 Java 的 Integer类型。

fun main(args: Array<String>) {
	// 下面代码是正确的
	var a:Int = 56
	// 下面代码需要隐式地将 2999999999 转换为 Int 型使用,因此编译器将会报错
	var bigValue : Int = 2999999999
	
	// 下面代码是正确的
	var bigValue2: Long = 2999999999
	println(bigValue2)
	println(Short.MIN_VALUE)
	println(Short.MAX_VALUE)

	// Int 型变量不支持 null 值,所以下面代码是错误的
	var notNull: Int = null
	// Int? 相当于支持 null 值的 Int 型,所以下面代码是正确的
	var nullable: Int? = null

	var pm1: Int = 200; // pml的类型是Java的int类型
	var pm2: Int = 200; // pm2的类型是Java的int类型
	println(pm1 === pm2); // 基本类型比较,输出 true
	var ob1: Int? = 100; // obl的类型是Java的Integer类型
	var ob2: Int? = 100; // ob2 的类型是 Java 的 Integer 类型
	println(ob1 === ob2); // 引用类型比较,输出 false
}

Kotlin 的整数数值有 3 种表示方式。

  1. 十进制:最普通的整数数值就是十进制的整数。
  2. 二进制:以 Ob或 OB开头的整数数值就是二进制的整数。
  3. 十六进制:以 0x 或 0X 开头的整数数值就是十六进制的整数,其中 10 ~ 15 分别以 a ~ f (a ~ f 不区分大小写〉来表示。

注意:Kotlin 不支持八进制的整数 。

1.2 浮点类型

浮点型数值可以包含小数部分,浮点型比整型的取值范围更大,可以存储比 Long 型更大或更小的数。

Kotlin 的浮点型有两种。

  1. Float:表示 32 位的浮点型,当精度要求不高时可以使用此种类型。
  2. Double:表示 64 位的双精度浮点型,当程序要求存储很大或者高精度的浮点数时使用这种类型。

Kotlin 的浮点数有两种表示形式。

  1. 十进制数形式:这种形式就是简单的浮点数,例如 5.12、 512.0、 0.512 等。 浮点数必须包含一个小数点,否则会被当成整数类型处理。
  2. 科学计数形式:例如 5.12e2 (即 5.12 x 10 ^ 2) 、 5.12E2 (也是 5.12 x 10 ^ 2)等。

只有浮点型的数值才可以使用科学计数形式表示。例如 51200 是一个 Int 型的值,但 512E2 则是浮点型的值。

如果声明一个常量或变量时没有指定数据类型,只是简单地指定了其初始值为浮点数,那 么 Kotlin 会自动判断该变量的类型为 Double。

1.3 字符类型

字符型通常用于表示单个的字符,字符型值必须使用单引号(')括起来。 Kotlin 语言使用 16 位的 Unicode 字符集作为编码方式,而 Unicode 被设计成支持世界上所有书面语言的字符,包括中文字符,因此 Kotlin 程序支持各种语言的字符。

字符型值有如下 3 种表示形式 。

  1. 直接通过单个字符来指定字符型值,例如 'A'、 '9' 等。
  2. 通过转义字符表示特殊字符型值,例如 '\n'、 '\t' 等。
  3. 直接使用 Unicode值来表示字符型值,格式是'\uXXXX’,其中 XXXX 代表一个十六进 制的整数。

Kotlin 语言中常用的转义字符

转义字符 说明 Unicode表示方式
\b 退格符 \u0008
\n 换行符 \u000a
\r 回车符 \u000d
\t 制表符 \u0009
\" 双引号 \u0022
\' 单引号 \u0027
\\ 反斜线 \u005c

1.4 布尔类型

布尔型只有一个 Boolean 类型,用于表示逻辑上的“真”或“假”。与 Java 类似, Kotlin 的 Boolean类型的值只能是 true 或 false,不能用0或者非0来代表。其他数据类型的值也不 能转换成 Boolean 类型。

1.5 整型之间的转换

Kotlin 与 Java 不同, Kotlin 不支持取值范围小的数据类型隐式转换为取值范围 大的类型。

由于不同整型支持的表数范围存在差异,因此进行类型转换时必须注意选择合适的类型。

Kotlin 为所有数值类型都提供了如下方法进行转换。

  • toByte():转换为 Byte类型。
  • toShort():转换为Short类型。
  • tolnt():转换为 Int类型。
  • toLong():转换为 Long类型。
  • toFloat():转换为 Float类型。
  • toDouble():转换为 Double 类型。
  • toChar():转换为 Char类型。

Kotlin 要求不同整型的变量或值之间必须进行显式转换。

fun main(args: Array<String>) {
    var bookPrice: Byte = 79
    var itemPrice: Short = 120
    // bookPrice 是 Byte 类型,但变量 a 是 Short 类型,因此下面代码错误
    // var a: Short = bookPrice
    // 显式将 bookPrice 强制转换为 Short 类型
    var a: Short = bookPrice.toShort()
    var b: Byte = itemPrice.toByte()
    println("a: ${a}, b: ${b}")
    val amount = 233
    // 将 Int 型变量转换为 Byte 类型,发生溢出
    val byteAmount: Byte = amount.toByte()
    println(byteAmount)

    // 算术表达式中的 bookPrice、 itemPrice 会自动提升为 Int 类型
    var total = bookPrice + itemPrice
    println("total 的值为:${total}")
    // 可以看到 total 映射的 Java 类型为 Int
    println("total 的类型为:${total.javaClass}")
    // 下面表达式中的 bookPrice 强制转换为 Long 类型,因此整个表达式类型为 Long
    val tot = bookPrice.toLong() + itemPrice.toByte()
    println("total的值为:${tot}")
    // 可以看到 total 映射的 Java 类型为 long
    println("total的类型为:${tot.javaClass}")
}

Kotlin 虽然不允许直接将 Char型值当成整数值使用,也不允许将整数值直接当成 Char型 值使用,但 Kotlin 依然可调用数值型的 toChar() 方法将数值型变量或表达式转换为 Char 类型。

fun main(args: Array<String>) {
    // 定义一个空字符串
    var result = "";
    // 进行 6 次循环
    for (i in 0..5) {
        // 生成一个 97~122 之间的 Int 类型整数
        val intVal = (Math.random() * 26 + 97).toInt();
        // 将 intValue 强制转换为 Char 类型后连接到 result 后面
        result = result + intVal.toChar();
    }
    // 输出随机字符串
    println(result);
}

此外,Char 型值虽然不能被当成整数进行算术运算,但 Kotlin 为 Char 类型提供了加、减 运算支持,其计算规则如下。

  • Char 型值加、减一个整型值:Kotlin 会先将该 Char 型值对应的字符编码进行加、减该整数,然后将计算结果转换为 Char 型值。
  • 两个 Char 型值进行相减:Kotlin 将用两个 Char 型值对应的字符编码进行减法运算, 最后返回 Int 类型的值。两个 Char 型值不能相加。
fun main(args: Array<String>) {
	var c1 = 'i'
	var c2 = 'k'
	println(c1 + 4); // 输出m
	println(c1 - 4); // 输出e
	println((c1 - c2)); // 输出-2
}

1.6 浮点型与整型之间的转换

Kotlin 的 Float、 Double 之间需要进行显式转换,浮点型与整型之间也需要进行显式转换,其转换过程与前面介绍的整型之间的转换过程基本相似。

fun main(args: Array<String>) {
    var width: Float = 2.3f
    var height: Double = 4.5
    // width 必须显式强制转换为 Double 之后,才能赋值给变盘 a
    var a: Double = width.toDouble()
    println("a的值为:${a}")
    // 将 height 强制转换为 Float 之后再进行计算 ,整个表达式的类型是 Float
    // 因此 areal 的类型也被推断为 Float
    var area1 = width * height.toFloat()
    // 表达式中的 height 是 Double 类型,它是等级最高的运算数
    // 因此整个表达式的类型是 Double, area2 的类型也被推断为 Double
    var area2 = width * height
    val multi: Int = 5
    // 因此 totalHeightl 的类型也被推断为 Double
    var totalHeight1 = height * multi
    // 将 height 强制转换为 Int 类型后进行计算,整个表达式的类型是 Int 
    // 因此 tota1Height2 的类型也被推断为 Int
    var totalHeight2 = height.toInt() * multi
}

进行类型转换时,应该尽量向表数范围大的数据类型转换,这样程序会更加安全,比如前面介绍的 Byte 向 Short 转换、 Int 向 Double 转换,而反过来转换则可能导致溢出。 Kotlin 语言的各种数值型的表数范围由小到大的顺序为 : Byte→ Short→ Int → Long→ Float→ Double。

1.7 表达式类型的自动提升

当一个算术表达式中包含多个数值型的值时,整个算术表达式的数据类型将发生自动提升。 Kotlin定义了与 Java基本相似的自动提升规则。

  • 所有的 Byte、 Short类型将被提升到 Int类型。
  • 整算术表达式的数据类型自动提升到与表达式中最高等级操作数同样的类型。
fun main(args: Array<String>) {
    // 定义一个 Short 类型交盘
    var sValue: Short = 5
    // 表达式中的 sValue 将自动提升到 Int 类型 , 贝u右边的表达式类型为 Int
    // 将一个 Int 类型值赋给 Short 类型变量将发生错误
    // sValue = sValue - 2

    var b: Byte = 40
    var c: Short = 97
    var i: Int = 23
    var d: Double = .314
    // 右边表达式中最高等级的操作数为 d (Double 类型)
    // 则右边表达式的类型为 Double, result 将会推断为 Double 类型
    val result = b + c + i * d
    // 将输出 144.222
    println(result)

    var iVal: Int = 3
    // 右边表达式中的两个操作数都是 Int 类型,故右边表达式的类型为 Int
    // 虽然 23/3 不能除尽 , 但依然得到一个 Int 类型整数
    val intResult = 23 / iVal;
    println(intResult) // 将输出 7

    // 输出字符串 Hello!a7
    println("Hello!" + 'a' + 7)
    // 输出字符串 hHello!
    println('a' + 7 + "Hello!")
}

1.8 null 安全

null 安全可以说是 Kotlin 语言对 Java 的重大改进之一,这样可以避免 Java 编程时令人恐 惧的 NullPointerException (简称 NPE)。

1.8.1 非空类型和可空类型

fun main(args: Array<String>) {
    var str = "hahaha"
    // 由于 str 转换为 Int 有可能失败,故 num 有可能没有值
    // 因此不能使用 Int 来声明 num 的类型
    var num: Int = str.toIntOrNull()  // 无法通过编译
    var num: Int? = str.toIntOrNull() // 能够通过编译
    println(num)
}

其中 Int? 就是可空类型,这种类型的变量可接受 Int 值和 null;而 Int 类型的变量则只接受 Int 值,不能接受 null。

由于 str 是一个 String 变量,当程序试图把 String 变量转换为 Int 值时,有可能转换成功(如果变量值是形如 “123” 的字符串),也有可能转换失败(本程序就转换失败了)。转换失败时,就无法成功返回 Int 值,此时将会返回 null,因此必须使用 Int? 类型的变量来存储转换结果。

需要指出的是,只有可空类型的变量或常量才能接受 null, 非空类型的变量或常量不能接受 null。

Kotlin 对可空类型进行了限制:如果不加任何处理,可空类型不允许直接调用方法、访问 属性。因此,通过可空类型与非空类型的区分。

fun main(args: Array<String>) {
    var aStr: String = "hahaha"
    var bStr: String? = "lalala"
//	aStr = null // 错误, aStr 不接受 null
    bStr = null // 正确
    // 编译通过, aStr 不可能为 null,运行时不可能导致 NPE
    println(aStr.length)
    // 编译不能通过,不可能导致 NPE
//	println(bStr.length)
}

1.8.2 先判断后使用

可空类型的变量不允许直接调用方法或属性,但可以先判断该变量不为 null,然后再调用 该变量的方法或属性。

fun main(args: Array<String>) {
    var b: String? = "come on"
    // 先判断 b 不为 null,然后访 问 b 的 length 属性
    var len = if (b != null) b.length else -1
    println("b的长度:${len}")
    b = null
    // 先判断 b 不为 null,然后调用 b 的 length 属性
    if (b != null && b.length > 0) {
        // 访问 b 的 length 属性
        println(b.length)
    } else {
        println("”空字符串")
    }
}

上面程序定义了 String?(可空类型)的变量 b,这样程序不能直接调用变量 b 的方法或属 性。 Kotlin 要求程序先判断 b 不为 null, 接下来程序即可在该条件下调用b的方法或属性。

对于非空的 Boolean类型而言,它可以接受 3个值,即 true、 false 或 null,因此对 Boolean? 类型变量进行判断时,需要使用 Boolean? 变量显式与 true、 false 值进行比较。

fun main(args: Array<String>) {
    var b: Boolean? = null
    if(b == true){
         println("为真")
    }
}

如果将 if 分支改为如下形式:编译器会报错,这是因 为 Kotlin 的 if条件必须是 Boolean 类型,而 Boolean? 与 Boolean 本质上是两种不同的类型。

if(b){
    println("为真")
}

1.8.3 安全调用

fun main(args: Array<String>) {
    var b: String? = "Java"
    println(b?.length) // 输出 4
    b = null
    println(b?.length) // 输出 null

    // 定义一个元索可空的数组
    val myArr: Array<String?> = arrayOf("Google", "Apple", null, "Facebook")
    for (item in myArr) {
        // 当 item 不为 null 时才调用 let 函数
        item?.let { println(it) }
    }
}

上面程序中变量 b 的类型是 S位ing?,因此程序使用了 “?.” 安全调用来访问 b 的 length 属 性,当 b 为 null 时,程序也不会引发 NPE,而是返回 null。此外,安全调用还可与 let 全局函数结合使用。使用安全调用来调用 let 函数,这样只有当 item 元素不为 null 时才会执行 let 函数。上面程序调用 let 函数时传入一个 Lambda 表达式作为函数参数。

1.8.4 Elvis 运算

Elvis 运算也是一种小技巧,其实就是 if else 的简化写法。

fun main(args: Array<String>) {
    var b: String? = "Kotlin"
    // 先判断 b 不为 null,然后访问 b 的 length 属性
    var len1 = if (b != null) b.length else -1
    println(len1)  // 输出 6
    b = null

    // 使用 Elvis 运算符
    var len2 = b?.length ?: -1
    println(len2)  // 输出 -1
}

Elvis 运算符 (?:) 含义是,如果“?:”左边的表达式不为 null,则返回左边表达式的值,否则返回右边表达式的值。由此可见,“?:” 其实就是 if分支的简化写法。

此外,由于 Kotlin 的 return、 throw 都属于表达式,因此它们也都可以用在 “?:” 运算符的右边。

val email = data [”email”] ?: throw IllegalArgumentException (”没有指定 E-mail 信息!”)

1.8.5 强制调用

Kotlin 用 “!!.” 即可强制调用可空变量的方法或属性,这样强制调用可能引发 NPE。

fun main(args: Array<String>) {
    var b: String? = "Java"
    println(b!!.length) // 输出 4
    b = null
    println(b!!.length) // 引发空指针异常(NPE)

    // 定义一个元素可空的数组
    val myArr: Array<String?> = arrayOf("C罗", "梅西", null, "迪巴拉")
    for (item in myArr) {
        item!!.let { println(it) }
    }
}

上面程序就是将前面的安全调用程序中的 “?.” 安全调用改为“!!.” 强制调用,当可空变量 b 为 null 时,b!!.length 将会引发 NPE。与前面的安全调用类似的是,强制调用也可作用于 let()函数,此时不管 item 元素是否为 null,程序都会对该元素调用 let()函数,因此也可能导致 NPE。