《Kotlin从小白到大牛》第6章:数据类型

第6章 数据类型

数据类型在计算机语言中是非常重要的,在前面介绍变量或常量时已经用到一些数据类型,例如Int、Double和String等。本章主要介绍Kotlin的基本数据类型和可空类型。

6.1 回顾Java数据类型

Kotlin作为依赖于Java虚拟机运行的语言,它的数据类型最终被编译成为Java数据类型,所以本节先回顾一下Java数据类型的基础知识。
Java语言的数据类型分为:基本类型和引用类型。基本类型变量在计算机中保存的是数值,当赋值或作为参数传递给函数时基本类型数据会创建一个副本,把副本赋值或传递给函数,这副本被改变不会影响原始数据。引用类型在计算机中保存的是指向数据的内存地址,即引用,当赋值或作为参数传递给函数时引用类型数据会把引用赋值或传递给函数,事实上引用有多少个副本,都是指向相同的数据,通过任何一个引用修改数据,都会导致数据的变化。
基本类型表示简单的数据,基本类型分为4大类,共8种数据类型。
o 整数类型:byte、short、int和long,int是默认类型。
o 浮点类型:float和double,double是默认类型。
o 字符类型:char。
o 布尔类型:boolean。
基本数据类型如图6-1所示,其中整数类型、浮点类型和字符类型都属于数值类型,它们之间可以互相转换。
《Kotlin从小白到大牛》第6章:数据类型_第1张图片
图6-1所示的8种基本数据类型不属于类,不具备“对象”的特征,没有成员变量和成员函数,不方便进行面向对象的操作。为此,Java提供包装类(Wrapper
Class)来将基本数据类型包装成类,每个Java基本数据类型在java.lang包中都有一个相应的包装类,每个包装类对象封装一个基本数据类型数值。对应关系如表6-1所示,除int和char类型外,其他的类型对应规则就是第一个字母大写。
《Kotlin从小白到大牛》第6章:数据类型_第2张图片

6.2 Kotlin基本数据类型

与Java基本类型相对应,Kotlin也有8中基本数据类型。
o 整数类型:Byte、Short、Int和Long,Int是默认类型。
o 浮点类型:Float和Double,Double是默认类型。
o 字符类型:Char。
o 布尔类型:Boolean。
Kotlin基本数据类型如图6-2所示,其中整数类型和浮点类型都是属于数值类型,而字符类型不再属于数值类型。
《Kotlin从小白到大牛》第6章:数据类型_第3张图片
Kotlin的8个基本数据类型没有对应的包装类,Kotlin编译器会根据不同的场景将其编译成为Java中的基本类型数据还是包装类对象。例如,Kotlin的Int用来声明变量、常量、属性、函数参数类型和函数返回类型等情况时,被编译为Java的int类型;当作为集合泛型类似参数时,则被编译为Java的java.lang.Integer,这是因为Java集合中只能保存对象,不能是基本数据类型。Kotlin编译器如此设计是因为基本类型数据能占用更少的内存,运行时效率更高。

6.2.1 整型类型
从图6-2可见Kotlin中整数类型包括:Byte、Short、Int和Long,它们之间的区别仅仅是宽度和范围的不同。
Kotlin的数据类型与Java一样都是跨平台的(与平台无关),也就是计算机是32位的还是64位的,Byte类型整数都是一个字节(8位)。这些整数类型的宽度和范围如表6-2所示。
《Kotlin从小白到大牛》第6章:数据类型_第4张图片
Kotlin语言中整型类型默认是Int类型,例如16表示为Int类型常量,而不是Short或Byte,更不是Long,Long类型需要在数值后面加L,示例代码如下:
//代码文件:chapter6/src/com/a51work6/ch6.2.1.kt
package com.a51work6

fun main(args: Array) {
// 声明整数变量
// 输出一个默认整数常量
println("默认整数常量 = " + 16) ①
val a: Byte = 16 ②
val b: Short = 16 ③
val c = 16 ④
val d = 16L ⑤

println("Byte整数     = " + a)
println("Short整数    =  " + b)
println("Int整数      =  " + c)
println("Long整数     =  " + d) 

}
上述代码多次用到了16整数,但它们是有所区别的。其中代码①行和第④行的16是默认整数类型,即Int类型常量。代码②行的16是Byte整数类型。代码③行的16是Short类型。代码第⑤行的16后加了L,这是说明Long类型整数。
在这里插入图片描述
在Java和Swift等语言中为了增强可读性,可以在较大的数字常量中添加下划线分割数字,Kotlin在1.1之后的版本增加了这一功能。示例代码如下:
//数字常量添加下划线,增强可读性
val e = 160_000_000L //表示160000000数字
println("数字常量添加下划线 = " + e)
分割的位置一般是按照统计习惯3位数字分割一下,但也不受这个限制。另外,下划线分割数字也适用于浮点数。
在使用整数变量赋值,还可以使用二进制和十六进制表示,但不支持八进制,它们的表示方式分别如下:
o 二进制数:以 0b 或0B为前缀,注意0是阿拉伯数字,不要误认为是英文字母o。
o 十六进制数:以 0x 或0X为前缀,注意0是阿拉伯数字。
例如下面几条语句都是表示int整数28。
val decimalInt = 28 //十进制表示
val binaryInt1 = 0b11100 //二进制表示
val binaryInt2 = 0B11100 //二进制表示
val hexadecimalInt1 = 0x1C //十六进制表示
val hexadecimalInt2 = 0X1C //十六进制表示

6.2.2 浮点类型
浮点类型主要用来储存小数数值,也可以用来储存范围较大的整数。它分为浮点数(Float)和双精度浮点数(Double)两种,双精度浮点数所使用的内存空间比浮点数多,可表示的数值范围与精确度也比较大。浮点类型说明如表6-3所示。
《Kotlin从小白到大牛》第6章:数据类型_第5张图片

Kotlin语言的浮点类型默认是Double类型,例如0.0表示Double类型常量,而不是Float类型。如果想要表示Float类型,则需要在数值后面加f或F,示例代码如下:
//代码文件:chapter6/src/com/a51work6/ch6.2.2.kt
package com.a51work6

fun main(args: Array) {
// 声明浮点数
// 输出一个默认浮点常量
println("默认浮点常量 = " + 360.66) ①
val myMoney = 360.66f ②
val yourMoney = 360.66 ③
val pi = 3.14159F ④

println("Float浮点数  = " + myMoney)
println("Double浮点数 =  " + yourMoney)
println("pi       = " + pi)

}
上述代码①行的360.66是默认浮点类型Double。代码②行和第④行的360.66f是Float浮点类型,float浮点类型常量表示时,数值后面需要加f或F。代码第③行的360.66表示是Double浮点类型。
在这里插入图片描述
进行数学计算时往往会用到指数表示的浮点数,表示指数需要使用大写或小写的e表示幂,e2表示102。示例如下:
// 指数表示方式
val ourMoney = 3.36e2 //指数表示336.0
val interestRate = 1.56E-2 //指数表示0.0156
其中3.36e2表示的是3.36×102,1.56e-2表示的是1.56×10-2。

6.2.3 字符类型
字符类型表示单个字符,Kotlin中Char声明字符类型,Kotlin中的字符常量必须用单引号括起来的单个字符,如下所示:
val c: Char = ‘A’
Kotlin字符采用双字节Unicode编码,占两个字节(16位),因而可用十六进制(无符号的)编码形式表示,它们的表现形式是\un,其中n为16位十六进制数,所以’A’字符也可以用Unicode编码’\u0041’表示,如果对字符编码感兴趣可以到维基百科(https://zh.wikipedia.org/wiki/Unicode字符列表)查询。
示例代码如下:
//代码文件:chapter6/src/com/a51work6/ch6.2.3.kt
package com.a51work6
fun main(args: Array) {
val c1 = ‘A’
val c2 = ‘\u0041’
val c3: Char = ‘花’

println(c1)
println(c2)
println(c3)

}
上述代码变量c1和c2都是保存的’A’,所以输出结果如下:
A
A

在Kotlin中,为了表示一些特殊字符,前面要加上反斜杠(\),这称为字符转义。常见的转义符的含义参见表6-4。
《Kotlin从小白到大牛》第6章:数据类型_第6张图片
示例如下:
//转义符
//在Hello和World插入制表符
val specialCharTab1 = “Hello\tWorld.”
//在Hello和World插入制表符,制表符采用Unicode编码\u0009表示
val specialCharTab2 = “Hello\u0009World.”
//在Hello和World插入换行符
val specialCharNewLine = “Hello\nWorld.”
//在Hello和World插入双引号
val specialCharQuotationMark = “Hello"World”."
//在Hello和World插入单引号
val specialCharApostrophe = “Hello’World’.”
//在Hello和World插入反斜杠
val specialCharReverseSolidus = “Hello\World.”
//使用退格符
val specialCharReverseBack = “Hello\bWorld.”
//在Hello和World插入美元符
val specialCharReverseUSD = “Hello$World.”

println("水平制表符tab1: " + specialCharTab1)
println("水平制表符tab2: " + specialCharTab2)
println("换行: " + specialCharNewLine)
println("双引号: " + specialCharQuotationMark)
println("单引号: " + specialCharApostrophe)
println("反斜杠: " + specialCharReverseSolidus)
println("退格符: " + specialCharReverseBack)
println(“美元符: " + specialCharReverseUSD)
输出结果如下:
水平制表符tab1: Hello World.
水平制表符tab2: Hello World.
换行: Hello
World.
双引号: Hello"World”.
单引号: Hello’World’.
反斜杠: Hello\World.
退格符: HellWorld.
美元符: Hello$World.

6.2.4 布尔类型
在Kotlin语言中声明布尔类型的关键字是Boolean,它只有两个值:true和false。
在这里插入图片描述
示例代码如下:
val isMan = true
val isWoman = false
如果试图给它们赋值true和false之外的常量,代码如下所示:
val isMan1: Boolean = 1
val isWoman1: Boolean = ‘A’
则发生类型不匹配编译错误。

6.3 数值类型之间的转换

学习了前面的数据类型后,大家会思考一个问题,数据类型之间是否可以转换呢?数据类型的转换情况比较复杂。在基本数据类型中数值类型之间可以互相转换,字符类型和布尔类型不能与它们之间进行转换。
本节讨论数值类型之间互相转换,数值在进行赋值时采用的是显示转换,而在数学计算时采用的是隐式转换。

6.3.1 赋值与显式转换
Kotlin是一种安全的语言,对于类型的检查非常严格,不同类型数值进行赋值是禁止的,示例代码如下:
val byteNum: Byte = 16
val shortNum: Short = byteNum //编译错误
上述代码试图将Byte数值16赋值给Short类型常量shortNum,Kotlin语言会发生编译错误,而在C、Objective-C和Java等其他语言中是可以编译成功的,这些语言中从小范围数到大范围数转换是隐式的(自动的)。
Kotlin中要想实现这种赋值转换,需要使用转换函数显式转换。Kotlin的6种数值类型(Byte、Short、Int、Long、Float和Double),以及Char类型都有如下7个转换函数:
toByte(): Byte
toShort(): Short
toInt(): Int
toLong(): Long
toFloat(): Float
toDouble(): Double
toChar(): Char
通过上述7个转换函数可以实现7种类型(Byte、Short、Int、Long、Float、Double和Char)之间的任意转换。
在这里插入图片描述
示例代码如下:
//代码文件:chapter6/src/com/a51work6/ch6.3.1.kt
package com.a51work6

fun main(args: Array) {

// 声明整数常量
val byteNum: Byte = 16
//val shortNum: Short = byteNum //编译错误
val shortNum: Short =byteNum.toShort()// Byte类型转换为Short类型
var intNum = 16

val longNum: Long = intNum.toLong()//Int类型转换为Long类型       ①
intNum = longNum.toInt()    // Long类型转换为Int类型         ②

val doubleNum =10.8  

println("doubleNum.toInt : " + doubleNum.toInt())// Double类型转换为Int类型,结果是10 ③
// 声明Char常量
val charNum =‘A’
println("charNum.toInt : " + charNum.toInt())// Char类型转换为Int类型,结果是65 ④

//精度丢失问题
val llongNum =6666666666L    ⑤  

println("llongNum : " + llongNum)
println("llongNum.toInt : " + llongNum.toInt())//结果是-1923267926,精度丢失 ⑥

}
转换函数可以实现双向转换,上述代码第①行是将Int类型转换为Long类型,代码第②行是将Long类型转换为Int类型。代码第③行是将浮点数转换为整数,这种转换是将小数部分截掉。代码第④行是将Char类型转换为Int类型,Char类型在计算机中存放的Unicode编码,所以转换的结果是Unicode编码数值,65是A字符的Unicode编码数值。代码第⑤行的Long数值比较大,在代码第⑥行转换为Int类型发生了精度丢失。

6.3.2 数学计算与隐式转换
多个数值类型数据可以数学计算,由于参与进行数学计算的数值类型可能不同,编译器会根据上下文环境进行隐式转换。计算过程中隐式转换类型转换规则如表6-5所示。
《Kotlin从小白到大牛》第6章:数据类型_第7张图片
示例如下:
//代码文件:chapter6/src/com/a51work6/ch6.3.2.kt
package com.a51work6

fun main(args: Array) {

// 声明整数常量
val b: Byte = 16
val s: Short = 16
val i = 16
val l = 16L

// 声明浮点变量
val f = 10.8f
val d = 10.8

val result1 = b + b        //结果是Int类型
val result2 = b + s        //结果是Int类型
val result3 = b + s - i     //结果是Int类型
val result4 = b + s - i + l  //结果是Long类型

val result5 = b * s + i + f / l         //结果是Float类型
val result6 = b * s + i + f / l +d     //结果是Double类型

}
从上述代码表达式的运算结果类型,可知表6-5所示的类型转换规则,这里不再赘述。

6.4 可空类型

Kotlin语言与Swift语言类似,默认情况下所有的数据类型都是非空类型(Non-Null),声明的变量都是不能接收空值(null)的。这一点与Java和Objective-C等语言有很大的不同。

6.4.1 可空类型概念
Kotlin的非空类型设计能够有些防止空指针异常(NullPointerException),空指针异常引起的原因是试图调用一个空对象的函数或属性,则抛出空指针异常。在Kotlin中可以将一个对象的声明为非空类型,那么它就永远不会接收空值,否则会发生编译错误。示例代码如下:
var n: Int = 10
n = null //发生编译错误
上述代码n = null会发生编译错误,因为Int是非空类型,它所声明的变量n不能接收空值。但有些场景确实没有数据,例如查询数据库记录时,没有查询出符合条件的数据是很正常的事情。为此,Kotlin为每一种非空类型提供对应的可空类型(Nullable),就是在非空类型后面加上问号(?)表示可空类型。修改上面示例代码:
var n: Int? = 10
n = null //可以接收空值(null)
Int?是可空类型,它所声明的变量n可以接收空值。可空类型在具体使用时会有一些限制:
不能直接调用可空类型对象的函数或属性。
不能把可空类型数据赋值给非空类型变量。
不能把可空类型数据传递给非空类型参数的函数。
为了“突破”这些限制,Kotlin提供了如下运算符:
安全调用运算符:?.
安全转换运算符:as?
Elvis运算符:?:
非空断言:!!
此外,还一个let函数帮助处理可空类型数据。本章重点介绍安全调用运算符(?.)、Elvis运算符(?:)和非空断言(!!)。

6.4.2 使用安全调用运算符(?.)
可空类型变量使用安全调用运算符(?.)可以调用非空类型的函数或属性。安全调用运算符(?.)会判断可空类型变量是否为空,如果是则不会调用函数或属性,直接返回空值;否则返回调用结果。
示例代码如下:
//代码文件:chapter6/src/com/a51work6/ch6.4.2.kt
//安全调用运算符(?.)
package com.a51work6

//声明除法运算函数
fun divide(n1: Int, n2: Int): Double? {

if (n2 == 0) {//判断分母是否为0
    return null
}
return n1.toDouble() / n2

}

fun main(args: Array) {

val divNumber1 = divide(100, 0)         ①
val result1 =divNumber1?.plus(100)//divNumber1+100,结果null     ②
println(result1)

val divNumber2 = divide(100, 10)       ③
val result2 =divNumber2?.plus(100)//divNumber2+100,结果110.0  ④
println(result2)

}
上述代码自定义了divide函数进行除法运算,当参数n2为0的情况下,函数返回空值,所以函数返回类型必须是Double的可空类型,即Double?。
代码第①行和第③行都调用divide函数,返回值divNumber1和divNumber2都是可空类型,不能直接调用plus函数,需要使用“?.”调用plus函数。事实上由于divNumber1为空值,代码第②行并没有调用plus函数,而直接返回空值。而代码第④行是调用了plus函数进行计算返回结果。
在这里插入图片描述
6.4.3 非空断言运算符(!!)
可空类型变量可以使用非空断言运算符(!!)调用非空类型的函数或属性。非空断言运算符(!!)顾名思义就是断言可空类型变量不会为空,调用过程是存在风险的,如果可空类型变量真的为空,则会抛出空指针异常;如果非则可以正常调用函数或属性。
修改6.4.2节代码如下:
//代码文件:chapter6/src/com/a51work6/ch6.4.3.kt
// 非空断言运算符(!!)
package com.a51work6

fun main(args: Array) {

val divNumber1= divide(100, 10)       
val result1 =divNumber1!!.plus(100)//divNumber1+100,结果110.0  ①       

println(result1)

val divNumber2= divide(100, 0)
val result2 =divNumber2!!.plus(100)//divNumber2+100,结果抛出异常②        

println(result2)
}
运行结果
110.0
Exception in thread “main”
kotlin.KotlinNullPointerException
at com.a51work6.Ch6_4_4Kt.main(ch6.4.4.kt:12)
上述代码第①行和第②行都调用plus函数,代码第①行可以正常调用,而代码第②行,由于divNumber2是空值,非空断言调用会发生异常。

6.4.4 使用Elvis运算符(?:)
有的时候在可空类型表达式中,当表达式为空值时,并不希望返回默认的空值,而是其他数值。此时可以使用Elvis运算符(?:),也称为空值合并运算符,Elvis运算符有两个操作数,假设有表达式:A ?: B,如果A不为空值则结果为A;否则结果为B。
Elvis运算符经常与安全调用运算符结合使用,重写上一节示例代码如下:
//代码文件:chapter6/src/com/a51work6/ch6.4.4.kt
//使用Elvis运算符(?:)
package com.a51work6

fun main(args: Array) {

val divNumber1 = divide(100, 0)
val result1 = divNumber1?.plus(100)?: 0//divNumber1+100,结果0           ①
println(result1)

val divNumber2 = divide(100, 10)
val result2 = divNumber2?.plus(100)

?: 0//divNumber2+100,结果110.0 ②
println(result2)
}
代码第①行和第②行都是用了Elvis运算符,divNumber1?.plus(100)表达式为空值,则返回0。divNumber2?.plus(100)表达式不为空值,则返回110.0。
在这里插入图片描述
《Kotlin从小白到大牛》第6章:数据类型_第8张图片

本章小结

本章主要介绍了Kotlin中的数据类型,重点介绍基本数据类型,其中数值类型如何互相转换是学习的难点。最后介绍了可空类型,可空类型是Kotlin语言的特色,读者需要理解并掌握它的几个运算符的使用。

你可能感兴趣的:(Kotlin从小白到大牛)