整数类型
Kotlin 内置了几种类型来表示数字,有以下几种类型来表示整数:
Type | Size (bits) | Min value | Max value |
---|---|---|---|
Byte | 8 | -128 | 127 |
Short | 16 | -32768 | 32767 |
Int | 32 | -2,147,483,648 | 2,147,483,647 |
Long | 64 | -9,223,372,036,854,775,808 | 9,223,372,036,854,775,807 |
如果在初始化变量的时候,没有指定具体的类型。那么编译器会自动推断类型,只不过会从Int类型开始往上推断。如果说我们声明的变量超过了Int的最小值和最大值,那么就会推断为Long。如果没有超过Int类型的范围,那就是Int。
所以,只要我们声明的整数在-2,147,483,648 到 2,147,483,647范围内,只要没指定类型,那就是Int类型。
如果我们想要明确为Long类型,只需要在数字后面加上L就行。如果我们想要使用 Byte 或者 Short 类型,就需要在声明的时候明确声明出类型来。
只要我们明确声明了类型,就会触发编译器检查我们给的数字是否在类型的范围内。
kotlin
val one = 1 // Int 没有明确类型,推断为Int
val threeBillion = 3000000000 // Long 超过了Int的范围,推断为Long
val oneLong = 1L // Long 添加L后缀,强制为Long
val oneByte: Byte = 1 // 声明为Byte,强制为Byte
浮点类型
对于实数来说,Kotlin 提供了遵循 IEEE 754 标准的浮点类型 Float 和 Double,Float是单精度,Double是双精度。
两种类型的比较如下:
Type | Size (bits) | Significant bits | Exponent bits | Decimal digits |
---|---|---|---|---|
Float | 32 | 24 | 8 | 6-7 |
Double | 64 | 53 | 11 | 15-16 |
简单来说:Float类型能表示的十进制有效数字位数大概是 6 - 7 位;Double类型能表示的十进制有效数字位数大概是 15 - 16 位。
对我们开发者来说,我们只能用小数来初始化 Double 和 Float 类型的变量,并且编译器类型推断小数是Double类型。
kotlin
val pi = 3.14 // Double
val one: Double = 1 // Int is inferred 编译器会报错 只能使用小数来初始化Double
// Initializer type mismatch
val oneDouble = 1.0 // Double 编译器推断为Doube
如果我们开发者想要声明Float类型,那么只需要在小数后面加上f或者F即可,如果一个超过7为小数的变量,被强制声明为F,那么精度会缺失:
kotlin
val e = 2.7182818284 // Double
val eFloat = 2.7182818284f // Float, actual value is 2.7182817
// 2.7182818284小数超过了7位,强制为Float类型,那么真实的数值就会变成2.7182817。
和其他编程语言不同,Kotlin没有隐式的类型转换。在其他语言中,小范围的数字可以放在大范围的数字里面,比如int到long。
但在Kotlin中,这是不允许的。如果一个方法接受一个Doube类型的参数,那么只可以传入Doube,传入Float,Int或者其他数字类型都是不被允许的。
kotlin
fun printDouble(x: Double) { print(x) }
val x = 1.0
val xInt = 1
val xFloat = 1.0f
printDouble(x) //正确,x推断为Doubel
printDouble(xInt) //错误,xInt推断为Int
// Argument type mismatch
printDouble(xFloat)//错误,xFloat强制为Float
// Argument type mismatch
number字面常量
整型值的字面常量有以下几种类型:
- 十进制:123
- 长整数以大写字母 L 结尾:123L
- 十六进制:0x0F
- 二进制:0b00001011
Kotlin 也支持浮点数的常规表示法:
- 双精度浮点数(当小数部分不以字母结尾时默认为此类型):123.5、123.5e10
- 单精度浮点数,以字母 f 或 F 结尾:123.5f
也可以使用下划线来让数字常量更易读:
kotlin
val oneMillion = 1_000_000
val creditCardNumber = 1234_5678_9012_3456L
val socialSecurityNumber = 999_99_9999L
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010
val bigFractional = 1_234_567.7182818284
JVM 数字类型装箱和缓存
因为 JVM 会缓存字节大小的数字,所以有一些代码可能会和开发者预期有所不同。
JVM 会将数字存储类原始的类型:int、doubel等等。但是假如我们使用了泛型或者创建了可空的数字引用比如Int?,数字会被包装为包装类型,比如Integer。
JVM 对表示 -128 到 127 之间数字的 Integer 及其他数值对象采用了一种内存优化技术。所有指向这类对象的可空引用都会指向同一个缓存对象。例如,以下代码中的可空对象在引用上是相等的:
kotlin
val a: Int = 100
val boxedA: Int? = a
val anotherBoxedA: Int? = a
println(boxedA === anotherBoxedA) // true
如果超过了-128到127,可空对象的引用就是不用的了,但是结构是相同的。
kotlin
val b: Int = 10000
val boxedB: Int? = b
val anotherBoxedB: Int? = b
println(boxedB === anotherBoxedB) // false 超过了范围,所以地址不用
println(boxedB == anotherBoxedB) // true 超过了范围,结构相同,也就是equal相等。
鉴于此,当我们开发者使用包装类型和字面类型进行比较的时候,就会有警告信息: "Identity equality for arguments of types ... and ... is prohibited.",所以在比较Int, Short, Long, 和 Byte 类型 (包括 Char 和 Boolean)时,使用结构性相等比较会有更加稳定的结果。

显示转换
由于表示方式不同,数字类型之间不存在父子类型关系。因此,较小的类型不会隐式转换为较大的类型,反之亦然。例如,将 Byte 类型的值赋给 Int 变量需要显式转换:
kotlin
val byte: Byte = 1 //确定为byte类型
// OK, literals are checked statically
val intAssignedByte: Int = byte //编译器报错 小类型无法给大类型
// Initializer type mismatch
val intConvertedByte: Int = byte.toInt() //显示的类型转换
println(intConvertedByte)

数字类型之间支持相互显示转换:
toByte(): Byte ( Float and Double 已经弃用)
toShort(): Short
toInt(): Int
toLong(): Long
toFloat(): Float
toDouble(): Double
不过,在很多情况下,不需要进行显式类型转换,因为类型会从上下文推断得出,而且算术运算符经过重载可以自动处理转换。例如:
kotlin
val l = 1L + 3 // Long + Int => Long
println(l is Long) // true
不支持隐式转换的考量
Kotlin 不支持隐式转换是因为防止出现意外的行为。
如果不同类型可以隐式转换,可能有的时候比较相等会出现意想不到的结果。比如,int和long。
kotlin
val a: Int? = 1
val b: Long? = a
print(b == a) // Prints "false"
因为Long在比较的时候,会先比较是否是Long类型
number 运算
Kotlin支持标准的数字运算:+, -, *, /, %,这些运算被包装成了成员函数。
kotlin
println(1 + 2)
println(2_500_000_000L - 1L)
println(3.14 * 2.71)
println(10.0 / 3)
当然了,开发者也可以在自定义的数字类中重载这些运算。
整数除法
整数之前相除得到的结果也是整数,小数部分会被舍弃。
kotlin
val x = 5 / 2
println(x == 2.5)
// Operator '==' cannot be applied to 'Int' and 'Double'
println(x == 2)
// true


想要得到小数的结果,只需要两个整数中的任意一个变成浮点数即可。

位运算
Kotlin 为整数提供了一系列的位运算操作,这些运算直接对数字表示的二进制位进行操作。位运算使用中缀形式的函数来表示。
kotlin
fun main() {
//sampleStart
val x = 1
val xShiftedLeft = (x shl 2)
println(xShiftedLeft)
// 4
val xAnd = x and 0x000FF000
println(xAnd)
// 0
//sampleEnd
}
- shl(bits) -- 带符号左移
- shr(bits) -- 带符号右移
- ushr(bits) -- 无符号右移
- and(bits) -- 按位与
- or(bits) -- 按位或
- xor(bits) -- 按位异或
- inv() -- 按位取反
浮点数比较
本节讨论的浮点数操作包括:
相等性检查: a == b and a != b
比较运算: a < b, a > b, a <= b, a >= b
范围实例和检查: a..b, x in a..b, x !in a..b
当操作数 a 和 b 在静态类型上明确为 Float 或 Double,或它们的可空对应类型(类型已声明、推断或为智能类型转换的结果)时,这些数字上的操作及其形成的范围遵循 IEEE 754 浮点数算术标准。
然而,为了支持泛型场景并提供全序关系,对于静态类型非浮点数的操作数(例如 Any、Comparable<...> 或 Collection 类型),行为会有所不同。在这种情况下,操作会使用 Float 和 Double 的 equals 和 compareTo 实现。因此:
- NaN 被视为与自身相等
- NaN 被视为大于任何其他元素,包括 POSITIVE_INFINITY(正无穷)
- 0.0 被视为小于 0.0
下面的示例展示了静态类型为浮点数的操作数(Double.NaN
)与静态类型非浮点数的操作数(listOf(T)
)之间的行为差异:
kotlin
// Operand statically typed as floating-point number
println(Double.NaN == Double.NaN) // false
//Double.NaN == Double.NaN 结果为 false,是因为 NaN(Not a Number,非数值)在 IEEE 754 浮点数标准中的特殊定义。
//IEEE 754 标准明确规定:NaN 与任何值(包括它自身)都不相等。这是因为 NaN 代表 "无效的数值结果"(例如 0/0、sqrt (-1) 等运算的结果),而逻辑上 "两个无效结果是否相等" 是无法定义的。因此,标准强制规定 NaN 与自身的相等性检查返回 false。
// Operand NOT statically typed as floating-point number
// So NaN is equal to itself
println(listOf(Double.NaN) == listOf(Double.NaN)) // true
//基本类型的 NaN 比较遵循 IEEE 754 标准(不等),而包装类型的 NaN 通过 equals() 比较时被视为相等,列表比较依赖后者,因此结果为 true
// Operand statically typed as floating-point number
println(0.0 == -0.0) // true
// Operand NOT statically typed as floating-point number
// So -0.0 is less than 0.0
println(listOf(0.0) == listOf(-0.0)) // false
println(listOf(Double.NaN, Double.POSITIVE_INFINITY, 0.0, -0.0).sorted())
// [-0.0, 0.0, Infinity, NaN]