【Kotlin】求生之路——Kotlin中常用的的基本类型【类比java学习】

187 阅读10分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

Kotlin的数据类型

针对java的数据类型对比,我们来学习Kotlin的数据类型。

声明变量

声明变量可以说是一个语言最常用,最基础的了。在学习数据接口之前我们先来了解一下变量是如何声明的,以及后面可能会涉及到的几个kotlin特性

语法模板

我们来看一下Kotlin的变量声明的模板

修饰符 变量名: 类型 = 初始值

来个栗子尝一尝: val a:String = "Hello World"

声明变量的关键字有两个valvar

  • 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中的所有东西都是对象,包括我们常用的基本数据类型

KotlinJava
字节Bytebyte / Byte
整型Int & Longint / Integer & long / Long
浮点型Float / Doublefloat / Float & double / Double
字符Charchar / Character
字符串StringString
可以看到 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