Kotlin是一门强类型的语言,Kotlin编译器对类型检查非常严格,这种严格保证了Kotlin程序的健壮,因此Kotlin不同类型的值经常需要进行相互转换,数值型之间的变量和值也可以相互转换。
整型之间的转换
不同整型的变量能支持的表数范围是不同的,比如Byte类型的变量或常量只能接受-128~127之间的整数,Short类型的变量或常量只能接受-32768~32767之间的整数。如果数值超出了变量或常量所支持的表数范围,编译器就会报错。例如如下代码:
// Byte类型支持的表数范围为-32768~32767,所以下面代码报错
var negative: Short = 40000
// Byte类型支持的表数范围为-128~127,所以下面代码报错
var big: Byte = 128
简单来说,Kotlin与Java不同,Kot不支持取值范围小的数据类型隐式转换为取值范围大的类型。
由于不同整型支持的表数范围存在差异,因此进行类型转换时必须注意选择合适的类型。Kotlin为所有数值类型都提供了如下方法进行转换。
- toByte():转换为Byte类型 。
- toShort():转换为Short类型。
- toInt():转换为Int类型。
- toLong():转换为Long类型。
- toFloat():转换为Float类型。
- toDouble():转换为Double类型。
- toChar():转换为Char类型。
Kotlin要求不同整型的变量或值之间必须进行显式转换。例如如下代码。
程序清单:codes\02\2.6\IntConvert.kt
fun main(args: Array<String>) {
var bookPrice : Byte = 79
var itemPrice : Short = 120
// bookPrice是Byte类型,但变量a是Short类型,因此下面代码错误
// var a: Short = bookPrice
// 显式将bookPrice强制转换为Int16类型
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(Byte类型的变量)赋值给变量a,但由于变量a是Short类型,虽然它们都是整型,但Kotlin依然不允许直接赋值,因此这行代码无法通过编译。
程序中①号粗体字代码先将bookPrice强制转换为Short类型,这样即可将转换后的结果赋值给Short类型的变量a了;接下来的代码则调用itemPrice的toByte()方法将Short类型的变量转换为Byte类型。
与Java类似的是,把取值范围大的变量或表达式强转为取值范围小的类型时,可能发生溢出。例如上面程序中②号代码所示。上面程序还把233强制类型转换为Byte型整数,从而变成了23,这就是典型的溢出。图2.3示范了这个转换过程。
图2.3 int类型向byte类型强制类型转换
从图3.11可以看出,32位Int类型的233在内存中如图2.3上面所示,强制类型转换为8位的Byte类型,则需要截断前面的24位,只保留右边8位,最左边的1是符号位,此处表明这是一个负数,负数在计算机里是以补码形式存在的,因此还需要换算成原码。
将补码减1得到反码形式,再将反码取反就可以得到原码。
最后的二进制原码为10010111,这个byte类型的值为-(16 + 4 + 2 + 1),也就是-23。
从图2.3很容易看出,当试图强制把表数范围大的类型转换为表数范围小的类型时,必须格外小心,因为非常容易引起信息丢失。
虽然Kotlin缺乏隐式转换,但Kotlin在表达式中又可可以自动转换,这种转换是基于上下文推断出来,而且算术运算会有重载做适当转换,例如如下代码(程序清单同上)。
// 算术表达式中bookPrice、itemPrice会自动提升为Int类型
var total = bookPrice + itemPrice // ③
println("total的值为:${total}")
// 可看到total映射的Java类型为int
println("total的类型为:${total.javaClass}")
// 下面表达式中bookPrice强制转换为Logn类型,因此整个表达式类型为Long
val tot = bookPrice.toLong() + itemPrice.toByte() // ④
println("total的值为:${tot}")
// 可看到total映射的Java类型为long
println("total的类型为:${tot.javaClass}")
上面③号代码将bookPrice(Byte类型)和itemPrice(Short类型)相加,Kotlin并不需要对它们进行强制转换,Kotlin将会自动把它们提升到Int类型之后再进行计算(与Java表达式对所有整型的处理规则相同),因此整个表达式计算得到的结果是Int类型。
上面④号代码先将bookPrice的类型强转换为Long类型,这样整个表达式中最高等级的操作数是Long类型,因此整个表达式计算得到的结果也是Long类型。
提示:上面程序中使用了变量的javaClass属性,该属性来自Any类型(Any类型是Kotlin所有类型的根父类),javaClass属性用于获取指定变量对应的Java类型(大致相当于Java反射中的getClass()方法)。
Kotlin虽然不允许直接将Char当成整数值使用,也不允许将整数值直接当成Char使用,但Kotlin依然可调用数值型的toChar()方法将数值型变量或表达式转换为Char类型。例如下面程序示范了如何生成一个6位的随机字符串,这个程序中用到了后面的循环控制,不理解循环的读者可以参考后面章节的介绍。
程序清单:codes\02\2.6\RandomStr.kt
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类型提供了加、减运算支持,其计算规则如下:
q Char型的值加、减一个整型值:Kotlin会先将该Char值对应的字符编码进行加、减该整数,然后将计算结果转换为Char值。
q 两个Char值进行相减:Kotlin将用两个Char值对应的字符编码进行减法运算,最后返回Int类型的值。两个Char型的值不能相加。
例如如下程序。
程序清单:codes\02\2.6\CharAdd.kt
fun main(args: Array<String>) {
var c1 = 'i'
var c2 = 'k'
println(c1 + 4); // 输出m
println(c1 - 4); // 输出e
println((c1 - c2)); // 输出-2
}
浮点型与整型之间的转换
Kotlin的Float、Double之间需要进行显式转换,浮点型与整型之间也需要进行显式转换—这些转换过程与前面介绍的整型之间的转换过程基本相似。例如如下程序。
程序清单:codes\02\2.6\FloatConvert.kt
fun main(args: Array<String>) {
var width: Float =
var height: Double = 4.5
// width必须显式强制转换为Double之后,才能赋值给变量a
var a: Double = width.toDouble()
println("a的值为: ${a}")
// 将height强制转换为Float之后再进行计算,整个表达式的类型都是Float类型
// 因此area1的类型也被推断为Float类型
var area1 = width * height.toFloat()
// 表达式中height是Double类型,它是等级最高的运算数
// 因此整个表达式的类型都是Double类型,area2的类型也被推断为Double类型
var area2 = width * height
val multi: Int = 5
// 因此totalHeight1的类型也被推断为Double类型
var totalHeight1 = height * multi // ①
// 将height强制转换为Int后进行计算,整个表达式的类型都是Int类型
// 因此totalHeight2的类型也被推断为Int类型
var totalHeight2 = height.toInt() * multi // ②
}
上面程序中第一行粗体字代码希望将Float类型的width变量赋值给Double类型的变量a,因此程序必须先进行类型转换。接下来程序试图将width和height相乘的积赋给area1、area2,如果我们希望area1变量的类型是Float,这就需要将height强转为Float类型,这样整个表达式中width、height都是Float类型,整个表达式的计算结果也是Float类型。area2则将被推断为Double类型。
上面程序中①号粗体字代码计算multi、height的乘积,由于height的类型是Double类型,因此整个表达式的类型是double类型,因此totalHeight1的类型也是Double类型。
程序中②号粗体字代码先将height强制转换为Int类型,然后与multi(也是Int类型)变量相乘,这样代码也是正确的。
将Double类型或Float类型的值、变量或常量强制转换为整型时,浮点值的小数部分会被截断,例如4.75将会变成4、-2.9将会被截断为-2。对于②号粗体字代码而言,height被强制转换为Int类型,此时小数部分将会截断,因此height将会被截断为4,因此计算得到的totalHeight2的值为20。
通过上面的介绍不难发现,当进行类型转换时,应该尽量向表数范围大的数据类型转换,这样程序会更加安全,比如前面介绍的Byte向Short转换、Int向Double转换,而反过来转换则可能导致溢出。Kotlin语言各种数值型的表数范围由小到大的顺序为:Byte→Short→Int→Long→Float→Double。
表达式类型的自动提升
当一个算术表达式中包含多个数值型的值时,整个算术表达式的数据类型将发生自动提升。Kotlin定义了与Java基本相似的自动提升规则。
- 所有的Byte类型、Short类型将被提升到Int类型。
- 整个算术表达式的数据类型自动提升到与表达式中最高等级操作数同样的类型。操作数的等级排列如图3.10所示,位于箭头右边类型的等级高于位于箭头左边类型的等级。
下面程序示范了一个典型的错误。
程序清单:codes\02\2.6\AutoPromote.kt
// 定义一个Short类型变量
var sValue: Short = 5
// 表达式中的sValue将自动提升到Int类型,则右边的表达式类型为Int
// 将一个Int类型值赋给Short类型变量将发生错误
sValue = sValue - 2
上面的“sValue–2”表达式的类型将被提升到Int类型,这样就把右边的Int类型值赋给左边的Short类型变量,从而引起错误。
下面代码是表达式类型自动提升的正确示例代码(程序清单同上)。
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)
必须指出,表达式的类型将严格保持和表达式中最高等级操作数相同的类型。下面代码中两个Int类型整数进行除法运算,即使无法除尽,也将得到一个int类型结果(程序清单同上)。
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!"))
对于第一个表达式“"Hello!" + 'a' +
以上内容节选自《疯狂Kotlin讲义》:一本让您最直接认识Kotlin的疯狂讲义