本文已参与「新人创作礼」活动,一起开启掘金创作之路。
Kotlin的数据类型
针对java的数据类型对比,我们来学习Kotlin的数据类型。
声明变量
声明变量可以说是一个语言最常用,最基础的了。在学习数据接口之前我们先来了解一下变量是如何声明的,以及后面可能会涉及到的几个kotlin特性
语法模板
我们来看一下Kotlin的变量声明的模板
修饰符 变量名: 类型 = 初始值
来个栗子尝一尝: val a:String = "Hello World"
声明变量的关键字有两个val和var
- val: 只读变量,类比java中的final关键字,只能进行赋值一次。
- var: 可读写变量
温馨提示:能用 val 就不用 var ,因为 val 的变量是赋值之后就不能修改的,这就减少了我们手误,逻辑实现错误等引起的程序实现BUG。
我们来看看例子
// 例子1:正确!定义的a后面就不能修改值了
val a:Int = 1
// 例子2:正确!定义的b后面可以进行重新赋值
var b:String = "this is a string"
// 例子3:正确!声明String类型的c,但是并未赋值。
val c:String
c = "set value after init" // 正确!对变量c进行赋值。
c = "set value again" // 错误!!报错:Val cannot be reassigned 使用val声明的变量只能进行赋值一次。
怎么样? 是不是很简单,但是不是说kotlin 语法很简单吗,这比java要敲的代码都多啊!别急,且听我娓娓道来。
类型推导
厉害的来了,在声明变量中有时候我们可将类型省略
- 情况1:同时声明类型和初始化赋值 这种是正确的写法,但是初始化赋值过程中,Kotlin就已经知道我们的类型了,所以 这种情况下类型可以省略
// 情况1的写法。
val a:Int = 1
var b:String = "this is a string"
// 根据类型推导的特性的简化版。
val a = 1
var b = "this is a string"
- 情况2:只声明类型,不初始化赋值 这种写法也是可以的,这种情况下就类型就不能省略了(很好理解,没有初始值,再没有类型,程序怎么知道你这个变量是干啥的,再说一遍,永远不要做让程序迷惑的事情),所以这种情况下类型不可以省略
// 情况2的写法。
val a:Int
var b:String
// 注意:a是val声明的,所以只能赋值一次的特性。
-
情况3:不声明类型,只初始化赋值 这种就是我们所说的情况1类型推导之后的写法。具体常看情况1中的简化版
-
情况4:不声明类型,不初始化赋值 这是不对的,只写一个var或者val 程序怎么知道你下一步要干啥,
你以为电脑会读心术嘛,如果真有那么一天,就是程序员灭绝的一天。
Long类型的特殊处理
众所周知,java中声明long类型的变量我们解决可以用 "l"(小写的L)或者 "L"。你看这个l,它又像I(ai)来又像1(yi),难受不,所以我们一般都规定long类型的变量使用 "L" 标识。
kotlin中在这个方面做了一个非常好的优化,对,kotlin不让你使用小写的 “L”,当我们使用小写的 kotlin找那个使用l 编译报错,必须写成大写
val d = 10000l //错误!!编译器会提示你使用"L"替换
var e = 10000L // 正确!
kotlin的数据类型转换
举个简单的例子,我们都知道在java中范围小的可以直接使用范围大的值,反过来需要强转,比如
int a = 1;
// long型使用int型赋值 完全没有问题
long b = a;
// int型使用long型赋值 就需要强制转换成int型
int c = (int) b;
那么,在kotlin中这部分有什么变化呢?我们先看一下例子
// 定义一个Int型的f
var f:Int = 100
// 定义了一个Long型的g,并将Int型的f赋值给g
var g:Long = f // 错误!!Type mismatch. 直接类型不匹配了
// Int转换为Long
var h:Long = f.toLong() // 正确!
//Long转换为Int
f = h.toInt() // 正确!
我们不难发现,Int型无法直接转换为Long了,需要调用toLong() 转换成Long然后进行赋值,Long转换为Int也同样如此。
思考:Kotlin为什么设计成这样呢?int转换成long也不会越界,为什么不让直接转换了呢,我觉得可能是因为这样不容易控制,容易产生问题吧。
官方解释:
显式转换
- kotlin中数值类型范围较小的无法直接隐式转换成范围较大的类型,需要显式转换,显式转换的方法有
- toByte(): Byte
- toShort(): Short
- toInt(): Int
- toLong(): Long
- toFloat(): Float
- toDouble(): Double
- toChar(): Char
- 算术运算时会有重载做适合的转换
val int = 1
val byte:Byte = int.toByte()
val a = int + byte // a自动识别成Int类型 Int+Byte ==>Int
val b = 'b'
val c = int + b // 报错
基本数据类型
kotlin中的所有东西都是对象,包括我们常用的基本数据类型
| Kotlin | Java | |
|---|---|---|
| 字节 | Byte | byte / Byte |
| 整型 | Int & Long | int / Integer & long / Long |
| 浮点型 | Float / Double | float / Float & double / Double |
| 字符 | Char | char / Character |
| 字符串 | String | String |
| 可以看到 Kotlin中只有一个关键字,不像Java分成了数值型还有对应类型的对象,还存在装包/拆包的操作,相比之下很方便。 |
整型
整型大体包括四种Byte、Short、Int、Long。
整型的取值范围
|类型|大小(比特数)|最小值|最大值| |:--:|:--:|:--:|:--:|:--:| |Byte|8|-128|127| |Short|16|-2^15|2^15 - 1| |Int|32|-2^31|2^31 - 1| |Long|64|-2^63|2^63 - 1|
注意: 所有未超过Int范围的整数初始化都会被推测成Int类型,如果想要声明Byte和Short类型,一定要显示声明类型。
- 超过Int范围的整数会被推测为Long类型,如果想要声明Long类型,在数值后面添加L(小写的L是不被允许的)
val int = 1 // Int
val long = 10000000000 // Long
val byte:Byte = 1 // Byte
val long2 = 1L // Long
无符号整型
⽆符号类型⾃ Kotlin 1.3 起才可⽤,并且⽬前处于 Beta 版。
Kotlin 为⽆符号整数引⼊了以下类型:
- kotlin.UByte: ⽆符号 8 ⽐特整数,范围是 0 到 255
- kotlin.UShort: ⽆符号 16 ⽐特整数,范围是 0 到 65535
- kotlin.UInt: ⽆符号 32 ⽐特整数,范围是 0 到 2^32 - 1
- kotlin.ULong: ⽆符号 64 ⽐特整数,范围是 0 到 2^64 - 1
简单了解一下就可以了。具体的我也不是太会,现在也是处于试运行阶段。
浮点型
- 浮点型分为Float和Double
- 没有隐式拓宽转换 Double参数的函数不能传入Float
浮点型的取值范围
|类型|大小(比特数)|有效数字比特数|指数比特数|十进制位数| |:--:|:--:|:--:|:--:|:--:|:--:| |Float|32|24|8|6-7| |Double|64|53|11|15-16|
浮点数比较
- 相等性检测: a == b 和 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<……>、 类型参数)时,这些操作使⽤为 Float 与 Double 实现的不符合标准的 equals 与 compareTo,这会出现:
- 认为 NaN 与其⾃⾝相等
- 认为 NaN ⽐包括正⽆穷⼤(POSITIVE_INFINITY)在内的任何其他元素都⼤
- 认为 -0.0 ⼩于 0.0
字面常量
- kotlin中无8进制表达
// 十进制, 默认进制
var a = 1123
// 二进制, 使用0b或者0B开头
var b = 0b00110010
// 16进制, 使用0x或0X开头
var c = 0x3748AF34
- Long类型只能使用"L"(不能用"l")标记
- Float类型可以使用"F"或者"f"标记
- 浮点型支持科学计数法表示
- 支持使用下划线分离数字常量
val phone = 132_1234_5678
val code = 0b00001100_11001010_11110000
字符
- 字符使用Char表示
- 不能直接被当做数字,如果需要需要显示转换
- 当需要可空引用时,字符像数字一样会被装箱。装箱操作不会保留统一性。
布尔
- 布尔使用Boolean表示,有两个值 true和false
- 若需要可空引用时,布尔也同样会呗装箱
- 内置的布尔运算有
- || 短路逻辑或
- && 短路逻辑与
- ! 逻辑非
字符串
- 字符串使用String表示
- 字符串不可变
- 字符串的元素可以使用索引运算符表示:s[i], 可以使用for循环迭代字符串
for (c in str) {
println(c)
}
- 支持使用 "+" 操作符拼接字符串,规则与java类似
字符串字面值
- 支持转义字符
- 支持使用原始字符串, 使用"""分界符
- 可以使用trimMargin()去除前导空格
- 默认 | ⽤作边界前缀,但你可以选择其他字符并作为参数传⼊,⽐如 trimMargin(">")。
val text = """
for (c in str) {
println(c)
}
""".trimMargin()
字符串模板
- 原始字符串和转义字符串内部都支持字符串模板
- $ 表示一个变量名或者变量值
- $varName 表示变量值
- ${varName.fun()} 表示变量的方法返回值: 例子如下:
var ten = 10;
var value = "ten is $ten."
ten += 2
var value1 = "${value.replace("ten", "twelve")} + 2. "
print("value:$value")
println("value1:$value1")
// 想在原始字符串使用$符号,可以参考下边的方法
val price = """ ${'$'}9.99 """
输出结果为: value:ten is 10. value1:twelve is 10. + 2.
字符串比较
kotlin中字符串比较分两种,与java的区别注意**"=="** 已经表达的含义不同了(搞过java的人都知道这玩意有多烦,这个就不举例子了,建议自己操作,掌握原理才是真谛)
- == 比较内容 等价于java中的equals
- === 比较对象是否是同一个对象,比较的是两个对象的内存地址
数组
- 数组使用Array类表示
- 定义了get和set函数(按照运算符重载约定会转换成[])以及size属性
- 数组是不型变的。我们不能把Array赋值给Array, 以防止可能的运行时失败(但是可以使用Array,这个是类型投影特性)
数组创建的几种方式
- 使用arrayOf()创建一个数组并进行初始化, 如arrayOf(1, 2, 3)创建了数组Array[1, 2, 3]
- 使用库函数arrayOfNulls()创建一个指定大小,所有元素都是空的数组
- 还可以通过Array的构造函数来创建数组
// 创建一个Array<String> 初始化为["0", "1", "4", "9", "16"]
val array = Array(5) { i -> (i * i).toString() }
原生类型数组
kotlin也存在无装箱开销的数组,如ByteArray、shortArray、IntArray等。原生类型数组与Array并无继承关系,但是他们有相同的方法属性集
// ⼤⼩为 5、值为 [0, 0, 0, 0, 0] 的整型数组
val arr = IntArray(5)
// 例如:⽤常量初始化数组中的值
// ⼤⼩为 5、值为 [42, 42, 42, 42, 42] 的整型数组
val arr = IntArray(5) { 42 }
// 例如:使⽤ lambda 表达式初始化数组中的值
// ⼤⼩为 5、值为 [0, 1, 2, 3, 4] 的整型数组(值初始化为其索引值
var arr = IntArray(5) { it * 1 }
运算
位运算
kotlin中的位运算不是使用特殊符号来表示的 位运算完整列表如下(只用于Int和Long)
- shl(bits) 有符号左移
- shr(bits) 有符号右移
- ushr(bits) 无符号右移
- and(bits) 位与
- or(bits) 位或
- xor(bits) 位异或
- inv(bits) 位非
val x = (1 shl 2) and 0x000FF000