【Kotlin】上手学习之类型篇

一、类型

1.1 基本类型

主要分为

1.1.1 数字

整数类型

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

类型 大小(比特数) 最小值 最大值
Byte 8 -128 127
Short 16 -32768 32767
Int 32 -2,147,483,648 (-2 ^ 31) 2,147,483,647 (2 ^ 31 - 1)
Long 64 -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。 显式指定类型会触发编译器检测该值是否超出指定类型的表示范围。

kotlin 复制代码
val one = 1 // Int
val threeBillion = 3000000000 // Long
val oneLong = 1L // Long
val oneByte: Byte = 1

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

浮点类型

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

类型 大小(比特数) 有效数字比特数 指数比特数 十进制位数
Float 32 24 8 6-7
Double 64 53 11 15-16

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

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

运行之后会报错提示:

kotlin 复制代码
/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运行类似上面那串代码的话,就可以正常运行

kotlin 复制代码
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 等。

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

kotlin 复制代码
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代码运行的结果

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的==
显式数字转换

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

kotlin 复制代码
// 假想的代码,实际上并不能编译:
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变量必须显式转换:

kotlin 复制代码
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

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

kotlin 复制代码
val l = 1L + 3 // Long + Int => Long

1.1.2 布尔

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

布尔值的内置运算有:

  • ||------(逻辑
  • &&------(逻辑
  • !------(逻辑
kotlin 复制代码
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 数字。

kotlin 复制代码
fun main() {
    val a : Char = '1'
    val aDigitToInt = a.digitToInt()
    println(aDigitToInt) // 1
}

1.1.4 字符串

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

kotlin 复制代码
val str = "abcd 123"

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

plain 复制代码
fun main() {
    val str = "abcd"
    for (c in str) {
        println(c)
    }
}

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

字符串模板

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

kotlin 复制代码
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]
}

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

kotlin 复制代码
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() 函数并将项目值传递给它:
kotlin 复制代码
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 元素:
kotlin 复制代码
fun main() {
    // Creates an array with values [null, null, null]
    val nullArray: Array<Int?> = arrayOfNulls(3)
    println(nullArray.joinToString())
    // null, null, null
}
  1. 此示例使用emptyArray()函数创建一个空数组:
kotlin 复制代码
    var exampleArray = emptyArray<String>()
Array构造函数创建数组

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

kotlin 复制代码
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
}
嵌套数组

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

kotlin 复制代码
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]]]
}
数组求和
kotlin 复制代码
fun main() {
    val sumArray = arrayOf(1, 2, 3)

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

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

kotlin 复制代码
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
kotlin 复制代码
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 array Equivalent in Java
BooleanArray boolean[]
ByteArray byte[]
CharArray char[]
DoubleArray double[]
FloatArray float[]
IntArray int[]
LongArray long[]
ShortArray short[]

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

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

kotlin 复制代码
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 还提供了以下类型:

Type Size (bits) Min value Max value
UByte 8 0 255
UShort 16 0 65,535
UInt 32 0 4,294,967,295 (2 ^ 32 - 1)
ULong 64 0 18,446,744,073,709,551,615 (2 ^ 64 - 1)

1.2 类型检测与类型转换

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

1.2.1 is 与 !is 操作符

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

kotlin 复制代码
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-检测以及显式转换,并在必要时自动插入(安全的)转换:

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

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

kotlin 复制代码
if (x !is String) return

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

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

kotlin 复制代码
// `||` 右侧的 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循环也一样:

kotlin 复制代码
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

kotlin 复制代码
val x: String = y as String

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

kotlin 复制代码
val x: String? = y as String?

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

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

kotlin 复制代码
val x: String? = y as? String

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

比如下面这段代码

kotlin 复制代码
fun main() {
    val x = IntArray(5)
    val str: String = x as String
    println("str is $str")
}

运行之后会抛出异常

kotlin 复制代码
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')

如果换成可空的话

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

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

kotlin 复制代码
str is null

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

相关推荐
Kapaseker1 天前
一杯美式搞懂 Any、Unit、Nothing
android·kotlin
Kapaseker2 天前
一杯美式搞定 Kotlin 空安全
android·kotlin
FunnySaltyFish3 天前
什么?Compose 把 GapBuffer 换成了 LinkBuffer?
算法·kotlin·android jetpack
Kapaseker3 天前
Compose 进阶—巧用 GraphicsLayer
android·kotlin
Kapaseker4 天前
实战 Compose 中的 IntrinsicSize
android·kotlin
A0微声z6 天前
Kotlin Multiplatform (KMP) 中使用 Protobuf
kotlin
alexhilton6 天前
使用FunctionGemma进行设备端函数调用
android·kotlin·android jetpack
lhDream7 天前
Kotlin 开发者必看!JetBrains 开源 LLM 框架 Koog 快速上手指南(含示例)
kotlin
RdoZam7 天前
Android-封装基类Activity\Fragment,从0到1记录
android·kotlin
Kapaseker7 天前
研究表明,开发者对Kotlin集合的了解不到 20%
android·kotlin