4.4 基本类型(Primitive Types)
本节我们来探讨学习:Kotlin的基础类型:数字、字符、布尔和数组等。
我们知道Java的类型分成两种:一种是基本类型,一种是引用类型。它们的本质区别是:
基本类型是在堆栈处分配空间存“值”,而引用类型是在堆里面分配空间存“值”。
Java的基本类型有: byte、int、short、long、float、double、char、boolean,这些类都有对应的装箱类(引用类型)。
另外,void也可以算是一种特殊的基本类型,它也有一个装箱类Void
(跟我们后文讲到的Unit、Nothing相关)。因为,Void是不能new出来的,也就是不能在堆里面分配空间存对应的值。所以,Void是一开始在堆栈处分配好空间。所以,将Void归成基本类型。
在Kotlin中,一切皆是对象。所有类型都是引用类型。没有类似Java中的基本类型。但是,可以把Kotlin中对应的这几种基本数据类型,理解为Java的基本类型的装箱类。
Integer.java
public final class Integer extends Number implements Comparable<Integer> {
/**
* A constant holding the minimum value an {@code int} can
* have, -2<sup>31</sup>.
*/
@Native public static final int MIN_VALUE = 0x80000000;
/**
* A constant holding the maximum value an {@code int} can
* have, 2<sup>31</sup>-1.
*/
@Native public static final int MAX_VALUE = 0x7fffffff;
/**
* The {@code Class} instance representing the primitive type
* {@code int}.
*
* @since JDK1.1
*/
@SuppressWarnings("unchecked")
public static final Class<Integer> TYPE = (Class<Integer>) Class.getPrimitiveClass("int");
...
}
Kotlin中的Int
类型:
public class Int private constructor() : Number(), Comparable<Int> {
companion object {
/**
* A constant holding the minimum value an instance of Int can have.
*/
public const val MIN_VALUE: Int = -2147483648
/**
* A constant holding the maximum value an instance of Int can have.
*/
public const val MAX_VALUE: Int = 2147483647
}
...
}
我们通过Java的Integer封装类,跟Kotlin的Int类的定义可以看出两者的思想上的同源性。
Kotlin的基本类型的类图结构如下图所示
4.4.1 数字(Number)类型
Kotlin 提供了如下的内置类型来表示数字(与 Java 很相近):
类型 | 宽度(Bit) |
---|---|
Double | 64 |
Float | 32 |
Long | 64 |
Int | 32 |
Short | 16 |
Byte | 8 |
从上面的Kotlin的基本类型的类的结构图,我们可以看出这些内置的数据类型,都继承了Number
和 Comparable
类。例如,Byte
类型的声明:
public class Byte private constructor() : Number(), Comparable<Byte> {
...
}
Kotlin 的数字类型跟 Java 基本相同。有一点不同的是,Kotlin 对于数字没有隐式拓宽转换(如 Java 中 int
可以隐式转换为long
)。
注意在 Kotlin 中字符Char
不是数字。这些基本数据类型,会在运行时自动优化为Java的double、float、long、int、short、byte。
字面常量值(literal constant values)
数值常量字面值有以下几种:
- 十进制:
123
- Long 类型用大写
L
标记:123L
- 十六进制:
0x0F
- 二进制:
0b00001011
代码示例:
>>> 123
123
>>> 123::class
class kotlin.Int
>>> 123::class.java
int
>>> 123L
123
>>> 123L::class
class kotlin.Long
>>> 123L::class.java
long
>>> val b:Byte=128
error: the integer literal does not conform to the expected type Byte
val b:Byte=128
^
>>> val b:Byte=127
>>> b::class
class kotlin.Byte
>>> b::class.java
byte
>>> 0x0f
15
>>> 0x0F
15
>>> 0b1000
8
同样的,当我们赋值超过变量的类型的取值范围时,编译器会直接抛错。
注意: 不支持八进制
Kotlin 同样支持浮点数的常规表示方法:
- 默认 double:
123.5
、123.5e10
- Float 用
f
或者F
标记:123.5f
代码示例:
>>> 1234.5
1234.5
>>> 1234.5::class
class kotlin.Double
>>> 1234.5::class.java
double
>>> 12.3e10
1.23E11
>>> 12.3e10::class
class kotlin.Double
>>> 456.7f
456.7
>>> 456.7f::class
class kotlin.Float
>>> 456.7f::class.java
float
我们也可以使用数字字面值中的下划线(自 1.1 起),使数字常量更易读:
>>> 1_000_000
1000000
>>> 1234_5678_9012_3456L
1234567890123456
>>> 0xFF_EC_DE_5E
4293713502
>>> 0b11010010_01101001_10010100_10010010
3530134674
在 Java 平台数字是物理存储为 JVM 的原生类型,除非我们需要一个可空的引用(如 Int?
)或泛型。
后者情况下会把数字装箱。
显式转换
由于不同的表示方式,值范围较小类型并不是较大类型的子类型,是不能隐式转换的。
代码示例:
>>> val a: Int? = 1
>>> val b: Long? = a
error: type mismatch: inferred type is Int? but Long? was expected
val b: Long? = a
^
>>> val b: Byte = 1
>>> val i: Int = b
error: type mismatch: inferred type is Byte but Int was expected
val i: Int = b
^
这意味着在不进行显式转换的情况下我们不能把 Int
型值赋给一个 Long
变量。也不能把 Byte
型值赋给一个 Int
变量。
我们可以显式转换来拓宽数字
>>> val i: Int = b.toInt() // OK: 显式拓宽
每个数字类型都继承Number抽象类,其中定义了如下的转换函数:
toDouble(): Double
toFloat(): Float
toLong(): Long
toInt(): Int
toChar(): Char
toShort(): Short
toByte(): Byte
所以,在数字之间的转换,我们直接调用上面的这些转换函数即可。
运算符+
重载
缺乏隐式类型转换并不显著,因为类型会从上下文推断出来,而算术运算会有重载做适当转换,例如:
val l = 1L + 3 // Long + Int => Long
这个是通过运算符+
重载实现的。我们可以在Long类的源代码中看到这个plus
运算符函数的定义:
public operator fun plus(other: Byte): Long
public operator fun plus(other: Short): Long
public operator fun plus(other: Int): Long
public operator fun plus(other: Long): Long
public operator fun plus(other: Float): Float
public operator fun plus(other: Double): Double
也就是说, 编译器会把1L + 3
翻译成 1L.plus(3)
,然后这个传入的参数类型必须是Byte、Short、Int、Long、Float、Double中的一种。例如,我们传入一个字符Char
参数,编译器就会直接抛错:
>>> 'a'
a
>>> 'a'::class
class kotlin.Char
>>> 'a'::class.java
char
>>> 1L+'a'
error: none of the following functions can be called with the arguments supplied:
public final operator fun plus(other: Byte): Long defined in kotlin.Long
public final operator fun plus(other: Double): Double defined in kotlin.Long
public final operator fun plus(other: Float): Float defined in kotlin.Long
public final operator fun plus(other: Int): Long defined in kotlin.Long
public final operator fun plus(other: Long): Long defined in kotlin.Long
public final operator fun plus(other: Short): Long defined in kotlin.Long
1L+'a'
^
运算
Kotlin支持数字运算的标准集,运算被定义为相应的类成员(但编译器会将函数调用优化为相应的指令)。
对于位运算,没有特殊字符来表示,而只可用中缀方式调用命名函数(infix fun
),例如:
val x = (1 shl 2) and 0x000FF000
这是完整的位运算列表(只用于 Int
和 Long
):
shl(bits)
– 有符号左移 (Java 的<<
)shr(bits)
– 有符号右移 (Java 的>>
)ushr(bits)
– 无符号右移 (Java 的>>>
)and(bits)
– 位与or(bits)
– 位或xor(bits)
– 位异或inv()
– 位非4.4.2 Char: 字符(Character)类型与转义符(Escape character)
字符用 Char
类型表示。它们不能直接当作数字
fun check(c: Char) {
if (c == 1) { // 错误:类型不兼容
// ……
}
}
字符字面值用 单引号 括起来: '1'
。
特殊字符可以用反斜杠转义。
Kotlin支持如下转义字符:
\t
\b
\n
\r
\`
\"
\\
\$
编码其他字符要用 Unicode 转义序列语法,例如:'\uFF00'
。
Char类的函数接口定义如下:
public class Char private constructor() : Comparable<Char> {
/**
* Compares this value with the specified value for order.
* Returns zero if this value is equal to the specified other value, a negative number if it's less than other,
* or a positive number if it's greater than other.
*/
public override fun compareTo(other: Char): Int
/** Adds the other Int value to this value resulting a Char. */
public operator fun plus(other: Int): Char
/** Subtracts the other Char value from this value resulting an Int. */
public operator fun minus(other: Char): Int
/** Subtracts the other Int value from this value resulting a Char. */
public operator fun minus(other: Int): Char
/** Increments this value. */
public operator fun inc(): Char
/** Decrements this value. */
public operator fun dec(): Char
/** Creates a range from this value to the specified [other] value. */
public operator fun rangeTo(other: Char): CharRange
/** Returns the value of this character as a `Byte`. */
public fun toByte(): Byte
/** Returns the value of this character as a `Char`. */
public fun toChar(): Char
/** Returns the value of this character as a `Short`. */
public fun toShort(): Short
/** Returns the value of this character as a `Int`. */
public fun toInt(): Int
/** Returns the value of this character as a `Long`. */
public fun toLong(): Long
/** Returns the value of this character as a `Float`. */
public fun toFloat(): Float
/** Returns the value of this character as a `Double`. */
public fun toDouble(): Double
}
我们来用代码示例这些函数的使用:
如果两个字符相等:
>>> 'a'.compareTo('a')
0
如果两个字符不相等:
>>> 'a'.compareTo('b')
-1
>>> 'a'.compareTo('c')
-1
>>> 'b'.compareTo('a')
1
>>> 'c'.compareTo('a')
1
Char字符只重载了加上Int
类型的数字的+
运算符:
>>> 'a'+1
b
>>> 'a'+1L
error: the integer literal does not conform to the expected type Int
'a'+1L
所以,当我们把一个Char
类型值和不是Int
类型的值相加,就报错了。
相减:
>>> 'a'-1
`
>>> 'c'-'a'
2
自增计算:
>>> var a='a'
>>> val b=a++
>>> a
b
>>> b
a
>>> val c=++a
>>> c
c
我们不能在字符的字面量上直接使用++
:
>>> 'a'++
error: variable expected
'a'++
^
>>> ++'a'
error: variable expected
++'a'
^
范围
>>> 'a'.rangeTo('z')
a..z
>>> for(c in 'a'..'z') {print(c)}
abcdefghijklmnopqrstuvwxyz
Char
的显式类型转换函数如下:
/** Returns the value of this character as a `Byte`. */
public fun toByte(): Byte
/** Returns the value of this character as a `Char`. */
public fun toChar(): Char
/** Returns the value of this character as a `Short`. */
public fun toShort(): Short
/** Returns the value of this character as a `Int`. */
public fun toInt(): Int
/** Returns the value of this character as a `Long`. */
public fun toLong(): Long
/** Returns the value of this character as a `Float`. */
public fun toFloat(): Float
/** Returns the value of this character as a `Double`. */
public fun toDouble(): Double
例如,我们显式把字符转换为 Int
数字:
fun decimalDigitValue(c: Char): Int {
if (c !in '0'..'9')
throw IllegalArgumentException("Out of range")
return c.toInt() - '0'.toInt() // 显式转换为数字
}
测试代码:
>>> decimalDigitValue('a')
java.lang.IllegalArgumentException: Out of range
at Line24.decimalDigitValue(Unknown Source)
>>> decimalDigitValue('1')
1
4.4.3 Boolean: 布尔类型
Kotlin的布尔类型用 Boolean
类来表示,它有两个值:true
和 false
。
>>> true::class
class kotlin.Boolean
>>> true::class.java
boolean
对应Java中的boolean
类型。
其源码定义如下:
package kotlin
/**
* Represents a value which is either `true` or `false`. On the JVM, non-nullable values of this type are
* represented as values of the primitive type `boolean`.
*/
public class Boolean private constructor() : Comparable<Boolean> {
/**
* Returns the inverse of this boolean.
*/
public operator fun not(): Boolean
/**
* Performs a logical `and` operation between this Boolean and the [other] one.
*/
public infix fun and(other: Boolean): Boolean
/**
* Performs a logical `or` operation between this Boolean and the [other] one.
*/
public infix fun or(other: Boolean): Boolean
/**
* Performs a logical `xor` operation between this Boolean and the [other] one.
*/
public infix fun xor(other: Boolean): Boolean
public override fun compareTo(other: Boolean): Int
}
从上面我们可以看出,Boolean类的内置的布尔运算有:
!
逻辑非not()
&&
短路逻辑与and()
||
短路逻辑或or()
xor
异或(相同false,不同true)
另外,Boolean
还继承实现了Comparable
的compareTo()
函数。
代码示例:
>>> !true
false
>>> true.not()
false
>>> true && true
true
>>> true.and(false)
false
>>> true || false
true
>>> false.or(false)
false
>>> true xor true
false
>>> true xor false
true
>>> false xor false
false
>>> true > false
true
>>> true < false
false
>>> true.compareTo(false)
1
>>> true.compareTo(false)
1
>>> true.compareTo(true)
0
>>> false.compareTo(true)
-1
4.4.4 String: 字符串类型
Kotlin的字符串用 String
类型表示。对应Java中的java.lang.String
。字符串是不可变的。
>>> "abc"::class
class kotlin.String
>>> "abc"::class.java
class java.lang.String
另外,在Kotlin中,String同样是final不可继承的。
代码示例:
>>> class MyString:String
error: this type is final, so it cannot be inherited from
class MyString:String
^
索引运算符 s[i]
字符串的元素——字符可以使用索引运算符 s[i]
来访问。
>>> val s="abc"
>>> s
abc
>>> s[0]
a
当我们下标越界时,会抛越界错误:
>>> s[-1]
java.lang.StringIndexOutOfBoundsException: String index out of range: -1
at java.lang.String.charAt(String.java:646)
>>> s[3]
java.lang.StringIndexOutOfBoundsException: String index out of range: 3
at java.lang.String.charAt(String.java:646)
从出错信息,我们可以看出,索引运算符 s[i]
会被翻译成java.lang.String.charAt()
, 背后调用的是Java的String类。其调用的方法是:
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
for
循环迭代字符串
我们可以用 for
循环迭代字符串:
>>> for(c in "abc") { println(c) }
a
b
c
关于字符串String
类的完整的操作方法,我们可以看下源码:
public class String : Comparable<String>, CharSequence {
companion object {}
/**
* Returns a string obtained by concatenating this string with the string representation of the given [other] object.
*/
public operator fun plus(other: Any?): String
public override val length: Int
public override fun get(index: Int): Char
public override fun subSequence(startIndex: Int, endIndex: Int): CharSequence
public override fun compareTo(other: String): Int
}
类似的,字符串有一个length
属性:
>>> "abc".length
3
重载+
操作符
字符串类重载了+
操作符,作用对象可以是任何对象,包括空引用:
>>> "abc".plus(true)
abctrue
>>> "abc"+false
abcfalse
>>> "abc"+1
abc1
>>> "abc"+1.20
abc1.2
>>> "abc"+100L
abc100
>>> "abc"+"cdef"
abccdef
>>> "abc"+null
abcnull
>>> "abc"+'z'
abcz
>>> "abc"+arrayOf(1,2,3,4,5)
abc[Ljava.lang.Integer;@3d6f0054
截取字符串的子串:
>>> "abc".subSequence(0,1)
a
>>> "abc".subSequence(0,2)
ab
>>> "abc".subSequence(0,3)
abc
>>> "abc".subSequence(0,4)
java.lang.StringIndexOutOfBoundsException: String index out of range: 4
at java.lang.String.substring(String.java:1951)
at java.lang.String.subSequence(String.java:1991)
字符串字面值
字符串的字面值,可以包含原生字符串可以包含换行和任意文本,也可以是带有转义字符(Escape Charactor)的转义字符串。
>>> val s = "Hello,World!\n\n\n"
>>> s
Hello,World!
>>>
转义采用传统的反斜杠方式。
原生字符串使用三个引号("""
)分界符括起来,内部没有转义并且可以包含换行和任何其他字符:
>>> val text = """
... for (c in "abc")
... print(c)
... """
>>> text
for (c in "foo")
print(c)
>>>
另外,在package kotlin.text
下面的Indent.kt代码中,Kotlin还定义了String
类的扩展函数:
fun String.trimMargin(marginPrefix: String = "|"): String
fun String.trimIndent(): String
我们可以使用trimMargin()
、trimIndent()
裁剪函数来去除前导空格。可以看出,trimMargin()
函数默认使用 "|"
来作为边界字符:
>>> val text = """
... |理论是你知道是这样,但它却不好用。
... |实践是它很好用,但你不知道是为什么。
... |程序员将理论和实践结合到一起:
... |既不好用,也不知道是为什么。
... """
>>> text.trimMargin()
理论是你知道是这样,但它却不好用。
实践是它很好用,但你不知道是为什么。
程序员将理论和实践结合到一起:
既不好用,也不知道是为什么。
默认 |
用作边界前缀,但你可以选择其他字符并作为参数传入,比如 trimMargin(">")
。
trimIndent()
函数,则是把字符串行的左边空白对齐切割:
>>> val text="""
... Hello
... World!
... """
>>> text.trimIndent()
Hello
World!
>>> val text="""
... Hello,
... World!
... """
>>> text.trimIndent()
Hello,
World!
字符串模板
字符串可以包含模板表达式,即一些小段代码,会求值并把结果合并到字符串中。
模板表达式以美元符($
)开头,由一个简单的名字构成:
>>> val h=100
>>> val str = "A hundred is $h"
>>> str
A hundred is 100
或者用花括号扩起来的任意表达式:
>>> val s = "abc"
>>> val str = "$s.length is ${s.length}"
>>> str
abc.length is 3
原生字符串和转义字符串内部都支持模板。
>>> val price=9.9
>>> val str="""Price is $$price"""
>>> str
Price is $9.9
>>> val str="Price is $$price"
>>> str
Price is $9.9
>>> val quantity=100
>>> val str="Quantity is $quantity"
>>> str
Quantity is 100
>>> val str="""Quantity is $quantity"""
>>> str
Quantity is 100
4.4.5 Array: 数组类型
数组在 Kotlin 中使用 Array
类来表示,它定义了 get
和 set
函数(映射到重载运算符 []
)和 size
属性,以及一个用于变量数组的iterator()
函数:
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()
来创建一个数组并传递元素值给它。这个函数签名如下:
public inline fun <reified @PureReifiable T> arrayOf(vararg elements: T): Array<T>
其中,vararg表示是一个参数个数是一个变量。
例如, arrayOf(1, 2, 3)
创建了 array [1, 2, 3] :
>>> arrayOf(1,2,3)
[Ljava.lang.Integer;@4a37191a
>>> arrayOf(1,2,3)::class
class kotlin.Array
>>> arrayOf(1,2,3)::class.java
class [Ljava.lang.Integer;
另外,Kotlin还允许不同类型元素放到一个数组中,例如:
>>> val arr = arrayOf(1,"2",true)
>>> arr
[Ljava.lang.Object;@61af1510
>>> arr.forEach{ println(it) }
1
2
true
>>> arr.forEach{ println(it::class) }
class kotlin.Int
class kotlin.String
class kotlin.Boolean
Kotlin自动把这个数组元素的类型升级为java.lang.Object
, 同时,由于Kotlin拥有的类型推断的功能,我们仍然可以看到每个数组元素对应的各自的类型。
函数 arrayOfNulls()
可以用于创建一个指定大小、元素都为空的数组。这个特殊的空数组在创建的时候,我们需要指定元素的类型。如果不指定,直接按照下面这样写,会报错:
>>> arrayOfNulls(10)
error: type inference failed: Not enough information to infer parameter T in fun <reified T> arrayOfNulls(size: Int): Array<T?>
Please specify it explicitly.
arrayOfNulls(10)
^
也就是说,我们要指定
>>> arrayOfNulls<Int>(10)
[Ljava.lang.Integer;@77c10a5f
>>> arrayOfNulls<Int>(10).forEach{println(it)}
null
null
null
null
null
null
null
null
null
null
数组Array
类,还提供了一个构造函数:
public inline constructor(size: Int, init: (Int) -> T)
第1个参数是数组大小,第2个参数是一个初始化函数类型的参数(关于函数类型,我们将在后面章节介绍)。
代码示例:
>>> val square = Array(10, { i -> (i*i)})
>>> square
[Ljava.lang.Integer;@6f9e08d4
>>> square.forEach{ println(it) }
0
1
4
9
16
25
36
49
64
81
如上所述,[]
运算符代表调用成员函数 get()
和 set()
。
代码示例:
>>> square[3]
9
>>> square[3]=1000
>>> square.forEach{ println(it) }
0
1
4
1000
16
25
36
49
64
81
与 Java 不同的是,Kotlin 中数组不是型变的(invariant)。 Kotlin中,我们不能把 Array<String>
赋值给 Array<Any>
。这地方Kotlin类型检查的限制强于Java的数组类型。
代码示例:
>>> val arrstr = arrayOf<String>("1","2","3")
>>> arrstr
[Ljava.lang.String;@39374689
>>> var arrany = arrayOf<Any>(Any(),Any(),Any())
>>> arrany
[Ljava.lang.Object;@156324b
>>> arrany = arrstr
error: type mismatch: inferred type is Array<String> but Array<Any> was expected
arrany = arrstr
^
原生数组类型
Kotlin 也有无装箱开销的专门的类来表示原生类型数组。这些原生数组类如下:
- BooleanArray
- ByteArray
- CharArray
- ShortArray
- IntArray
- LongArray
- FloatArray
- DoubleArray
- BooleanArray
这些类和 Array
并没有继承关系,但它们有同样的函数和属性集。它们也都有相应的工厂方法:
/**
* Returns an array containing the specified [Double] numbers.
*/
public fun doubleArrayOf(vararg elements: Double): DoubleArray
/**
* Returns an array containing the specified [Float] numbers.
*/
public fun floatArrayOf(vararg elements: Float): FloatArray
/**
* Returns an array containing the specified [Long] numbers.
*/
public fun longArrayOf(vararg elements: Long): LongArray
/**
* Returns an array containing the specified [Int] numbers.
*/
public fun intArrayOf(vararg elements: Int): IntArray
/**
* Returns an array containing the specified characters.
*/
public fun charArrayOf(vararg elements: Char): CharArray
/**
* Returns an array containing the specified [Short] numbers.
*/
public fun shortArrayOf(vararg elements: Short): ShortArray
/**
* Returns an array containing the specified [Byte] numbers.
*/
public fun byteArrayOf(vararg elements: Byte): ByteArray
/**
* Returns an array containing the specified boolean values.
*/
public fun booleanArrayOf(vararg elements: Boolean): BooleanArray
代码示例:
>>> val x: IntArray = intArrayOf(1, 2, 3)
>>> x[0]
1