Bootstrap

【Kotlin】上手学习之类型篇

一、类型

1.1 基本类型

主要分为

1.1.1 数字

整数类型

Kotlin 提供了一组表示数字的内置类型。 对于整数,有四种不同大小的类型,因此值的范围也不同:

类型大小(比特数)最小值最大值
Byte8-128127
Short16-3276832767
Int32-2,147,483,648 (-2 ^ 31)2,147,483,647 (2 ^ 31 - 1)
Long64-9,223,372,036,854,775,808 (-2 ^ 63)9,223,372,036,854,775,807 (2^ 63 - 1)

当初始化一个没有显式指定类型的变量时,编译器会自动推断为自 Int 起足以表示该值的最小类型。 如果不超过 Int 的表示范围,那么类型是 Int。 如果超过了,那么类型是 Long。 如需显式指定 Long 值,请给该值追加后缀 L。 显式指定类型会触发编译器检测该值是否超出指定类型的表示范围。

val one = 1 // Int
val threeBillion = 3000000000 // Long
val oneLong = 1L // Long
val oneByte: Byte = 1

这里与Java不同的是,Java里整型有int和Integer两种写法,前者表示的是基本的数据类型,后者表示的是int对象类型, 但是在kotlin里只有Int这一种。

浮点类型

这两个类型的大小不同,并为两种不同精度的浮点数提供存储:

类型大小(比特数)有效数字比特数指数比特数十进制位数
Float322486-7
Double64531115-16

与一些其他语言不同,Kotlin 中的数字没有隐式拓宽转换

例如,具有 Double 参数的函数只能对 Double 值调用,而不能对 FloatInt 或者其他数字值调用:

在这里插入图片描述

运行之后会报错提示:

/Users/dxm/IdeaProjects/KotlinProject/src/main/kotlin/test/Test.kt:16:22
Kotlin: Argument type mismatch: actual type is 'kotlin.Int', but 'kotlin.Double' was expected.

/Users/dxm/IdeaProjects/KotlinProject/src/main/kotlin/test/Test.kt:18:22
Kotlin: Argument type mismatch: actual type is 'kotlin.Float', but 'kotlin.Double' was expected.

比如Java运行类似上面那串代码的话,就可以正常运行

public class Hello {

    public static void main(String[] args) {
        Hello hello = new Hello();
        int a = 1;
        float f = 1.0f;
        double d = 1.0d;
        hello.printDouble(a);
        hello.printDouble(f);
        hello.printDouble(d);
    }

    public void printDouble(double num) {
        System.out.println(num);
    }
}

运行之后可以正常输出1.0

在这里插入图片描述

JVM 平台的数字表示

在 JVM 平台数字存储为原生类型 intdouble 等。 例外情况是当创建可空数字引用如 Int? 或者使用泛型时。 在这些场景中,数字会装箱为 Java 类 IntegerDouble 等。

对相同数字的可为空引用可能会引用不同的对象:

fun main() {
    val a: Int = 100
    val boxedA: Int? = a
    val anotherBoxedA: Int? = a

    val b: Int = 10000
    val boxedB: Int? = b
    val anotherBoxedB: Int? = b

    println(boxedA === anotherBoxedA) // true
    println(boxedB === anotherBoxedB) // false
}

第一个相等,是因为值是在[-128,127]之内,所以取的是Integer里缓存中的,指向的是缓存中同一块内存地址,所以会是同一个对象。

第二个不相等,是因为值是在[-128,127]之外,所以取的不是Integer里缓存中的,而是各自申请的一块内存地址,所以不会是同一个对象。

类似的可以参照下面Java代码运行的结果

Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true

Integer e = 128;
Integer f = 128;
System.out.println(e == f); // false

Integer c = new Integer(128);
Integer d = new Integer(128);
System.out.println(c == d); // false
数据的比较

比较两个Int的变量相等,包含两个方面

  • == 是比较两个值是否相等,类似于Java的equals
  • === 是比较两个变量对象是否相等,类似于Java的==
显式数字转换

由于不同的表示方式,较小类型并不是较大类型的子类型。 如果它们是的话,就会出现下述问题:

// 假想的代码,实际上并不能编译:
val a: Int? = 1 // 一个装箱的 Int (java.lang.Integer)
val b: Long? = a // 隐式转换产生一个装箱的 Long (java.lang.Long)
print(b == a) // 惊!这将输出“false”鉴于 Long 的 equals() 会检测另一个是否也为 Long

因此较小的类型不能隐式转换为较大的类型。 这意味着把Byte型值赋给一个Int变量必须显式转换:

val b: Byte = 1 // OK, 字面值会静态检测
// val i: Int = b // 错误
val i1: Int = b.toInt()

所有数字类型都支持转换为其他类型:

  • toByte(): Byte
  • toShort(): Short
  • toInt(): Int
  • toLong(): Long
  • toFloat(): Float
  • toDouble(): Double

很多情况都不需要显式类型转换,因为类型会从上下文推断出来, 而算术运算会有重载做适当转换,例如:

val l = 1L + 3 // Long + Int => Long

1.1.2 布尔

Boolean 类型表示可以有 truefalse 两个值的布尔对象。

布尔值的内置运算有:

  • ||——(逻辑
  • &&——(逻辑
  • !——(逻辑
fun main() {
    val myTrue: Boolean = true
    val myFalse: Boolean = false
    val boolNull: Boolean? = null

    println(myTrue || myFalse)
    // true
    println(myTrue && myFalse)
    // false
    println(!myTrue)
    // false
    println(boolNull)
    // null
}

1.1.3 字符

如果字符变量的值是数字,那么可以使用 digitToInt() 函数将其显式转换为 Int 数字。

fun main() {
    val a : Char = '1'
    val aDigitToInt = a.digitToInt()
    println(aDigitToInt) // 1
}

1.1.4 字符串

字符串双引号中的字符序列

val str = "abcd 123"

字符串的元素——字符可以使用索引运算符访问:s[i]。 可以使用for循环遍历这些字符:

fun main() {
    val str = "abcd"
    for (c in str) {
        println(c)
    }
}

字符串是不可变的。 一旦初始化了一个字符串,就不能改变它的值或者给它赋新值。

字符串模板

模板表达式以美元符($)开头,要么由一个变量名构成:

fun main() {
    val i = 10
    println("i = $i") 
    // i = 10

    val letters = listOf("a","b","c","d","e")
    println("Letters: $letters") 
    // Letters: [a, b, c, d, e]
}

要么是用花括号括起来的表达式:

fun main() {
    val s = "abc"
    println("$s.length is ${s.length}") 
    // abc.length is 3
}

1.1.5 数组

数组是一种保存固定数量相同类型或其子类型的值的数据结构。 Kotlin 中最常见的数组类型是对象类型数组,由Array类表示。

如果在对象类型数组中使用原生类型,那么会对性能产生影响,因为原生值都装箱成了对象。 为了避免装箱开销,请改用原生类型数组

创建数组

创建数组一般有两种方式:

  • 函数,例如 arrayOf()、arrayOfNulls()) 或 emptyArray()。
  • Array 类的构造函数
函数创建数组
  1. 此示例使用 arrayOf() 函数并将项目值传递给它:
fun main() {
    // Creates an array with values [1, 2, 3]
    val simpleArray = arrayOf(1, 2, 3)
    println(simpleArray.joinToString())
    // 1, 2, 3
}

joinToString 是 Kotlin 中一个非常有用的扩展函数,通常用于将集合或数组中的元素连接成一个字符串。它可以接收多个参数来定制输出字符串的格式。这个函数的作用是将集合中的每个元素按照给定的分隔符连接成一个字符串,可以指定前缀、后缀、分隔符以及如何转换元素。

  1. 此示例使用 arrayOfNulls()) 函数创建一个给定大小的数组,其中填充 null 元素:
fun main() {
    // Creates an array with values [null, null, null]
    val nullArray: Array<Int?> = arrayOfNulls(3)
    println(nullArray.joinToString())
    // null, null, null
}
  1. 此示例使用emptyArray()函数创建一个空数组:
    var exampleArray = emptyArray<String>()
Array构造函数创建数组

Array 构造函数采用数组大小和一个函数,该函数根据给定索引返回数组元素的值:

fun main() {
    // Creates an Array<Int> that initializes with zeros [0, 0, 0]
    val initArray = Array<Int>(3) { 0 }
    println(initArray.joinToString())
    // 0, 0, 0

    // 创建一个 Array<String> 初始化为 ["0", "1", "4", "9", "16"]
    val asc = Array(5) { i -> (i * i).toString() }
    asc.forEach { print(it) }
    // 014916
}
嵌套数组

数组可以相互嵌套以创建多维数组:

fun main() {
    // Creates a two-dimensional array
    val twoDArray = Array(2) { Array<Int>(2) { 0 } }
    println(twoDArray.contentDeepToString())
    // [[0, 0], [0, 0]]

    // Creates a three-dimensional array
    val threeDArray = Array(3) { Array(3) { Array<Int>(3) { 0 } } }
    println(threeDArray.contentDeepToString())
    // [[[0, 0, 0], [0, 0, 0], [0, 0, 0]], [[0, 0, 0], [0, 0, 0], [0, 0, 0]], [[0, 0, 0], [0, 0, 0], [0, 0, 0]]]
}
数组求和
fun main() {
    val sumArray = arrayOf(1, 2, 3)

    // Sums array elements
    println(sumArray.sum())
    // 6
}
将数组转换为集合
转换为 List 或 Set

要将数组转换为 List 或 Set,请使用 .toList() 和 .toSet() 函数。

fun main() {

    val simpleArray = arrayOf("a", "b", "c", "c")

    // Converts to a Set
    println(simpleArray.toSet())
    // [a, b, c]

    // Converts to a List
    println(simpleArray.toList())
    // [a, b, c, c]

}
转换为 Map
fun main() {

    val pairArray = arrayOf("apple" to 120, "banana" to 150, "cherry" to 90, "apple" to 140)

    // Converts to a Map
    // The keys are fruits and the values are their number of calories
    // Note how keys must be unique, so the latest value of "apple"
    // overwrites the first
    println(pairArray.toMap())
    // {apple=140, banana=150, cherry=90}

}
原生类型数组

如果将 Array 类与原始值一起使用,这些值将被装箱到对象中。作为替代方案,您可以使用基元类型数组,它允许您将基元存储在数组中,而不会产生装箱开销的副作用:

Primitive-type arrayEquivalent in Java
BooleanArrayboolean[]
ByteArraybyte[]
CharArraychar[]
DoubleArraydouble[]
FloatArrayfloat[]
IntArrayint[]
LongArraylong[]
ShortArrayshort[]

这些类与 Array 类没有继承关系,但它们具有相同的函数和属性集。

下面示例创建 IntArray 类的实例:

fun main() {
    // Creates an array of Int of size 5 with the values initialized to zero
    val exampleArray = IntArray(5)
    println(exampleArray.joinToString())
    // 0, 0, 0, 0, 0
}
  • 要将原始类型数组转换为对象类型数组,请使用 .toTypedArray() 函数。
  • 要将对象类型数组转换为原始类型数组,请使用 .toBooleanArray()、.toByteArray()、.toCharArray() 等。

1.1.6 无符号整型

除了整数类型,对于无符号整数,Kotlin 还提供了以下类型:

TypeSize (bits)Min valueMax value
UByte80255
UShort16065,535
UInt3204,294,967,295 (2 ^ 32 - 1)
ULong64018,446,744,073,709,551,615 (2 ^ 64 - 1)

1.2 类型检测与类型转换

在 Kotlin 中,可以执行类型检查以在运行时检查对象的类型。类型转换将对象转换为不同的类型。

1.2.1 is 与 !is 操作符

使用 is 操作符或其否定形式 !is 在运行时检测对象是否符合给定类型:

if (obj is String) {
    print(obj.length)
}

if (obj !is String) { // 与 !(obj is String) 相同
    print("Not a String")
} else {
    print(obj.length)
}

这个类似于Java的instanceof关键字

1.2.2 智能转换

大多数场景都不需要在 Kotlin 中使用显式转换操作符,因为编译器跟踪不可变值的is-检测以及显式转换,并在必要时自动插入(安全的)转换:

fun demo(x: Any) {
    if (x is String) {
        print(x.length) // x 自动转换为字符串
    }
}

编译器足够聪明,能够知道如果反向检测导致返回那么该转换是安全的:

if (x !is String) return

print(x.length) // x 自动转换为字符串

或者转换在 &&|| 的右侧,而相应的(正常或否定)检测在左侧:

// `||` 右侧的 x 自动转换为 String
if (x !is String || x.length == 0) return

// `&&` 右侧的 x 自动转换为 String
if (x is String && x.length > 0) {
    print(x.length) // x 自动转换为 String
}

智能转换用于when表达式while循环也一样:

when (x) {
    is Int -> print(x + 1)
    is String -> print(x.length + 1)
    is IntArray -> print(x.sum())
}

智能转换适用于以下情形:

val 局部变量总是可以,局部委托属性除外。
val 属性如果属性是 privateinternal,或者该检测在声明属性的同一模块中执行。 智能转换不能用于 open 的属性或者具有自定义 getter 的属性。
var 局部变量如果变量在检测及其使用之间未修改、没有在会修改它的 lambda 中捕获、并且不是局部委托属性。
var 属性决不可能,因为该变量可以随时被其他代码修改。

1.2.3 “不安全的”转换操作符

通常,如果转换是不可能的,转换操作符会抛出一个异常。因此,称为不安全的。 Kotlin 中的不安全转换使用中缀操作符 as

val x: String = y as String

请注意,null不能转换为String因该类型不是可空的。 如果y为空,上面的代码会抛出一个异常。 为了让这样的代码用于可空值,请在类型转换的右侧使用可空类型:

val x: String? = y as String?

1.2.4 “安全的”(可空)转换操作符

为了避免异常,可以使用安全转换操作符as?,它可以在失败时返回null

val x: String? = y as? String

请注意,尽管事实上as?的右边是一个非空类型的String,但是其转换的结果是可空的。

比如下面这段代码

fun main() {
    val x = IntArray(5)
    val str: String = x as String
    println("str is $str")
}

运行之后会抛出异常

Exception in thread "main" java.lang.ClassCastException: class [I cannot be cast to class java.lang.String ([I and java.lang.String are in module java.base of loader 'bootstrap')

如果换成可空的话

fun main() {
    val x = IntArray(5)
    val str: String? = x as? String
    println("str is $str")
}

至少是可以正常执行的,运行结果是

str is null

所以我们平时如果写代码的时候,如果为了防止异常的话,在类型转换的时候尽量用这种可空的。如果业务中必须要转换过来的类型的话,那就不用可空的。

;