拓展函数

修改程序,但不修改源代码
接收者对象:函数被调用的对象

val readOnlyShapes: List<String> = listOf("triangle", "square", "circle")  
println(readOnlyShapes.first()) //readOnlyShapes就是接收者对象

接收者类型

fun main() {  
    println("hello".bold())  
}  
fun String.bold(): String = "<b>$this</b>"

以上例子中给String定义了一个拓展函数,其中一些说明如下:
String是接收者类型
bold是函数名称
.bold()函数返回类型是String
“hello”是接收者对象
函数体内部通过$this来访问接收者对象的值

面向拓展的设计

拓展函数是静态的,可以在任何地方定义拓展函数,不同定义域中拓展函数的使用区别:
顶层(文件级别):可以在同一包内使用
类内部:可见性受访问修饰符限制
(爱来自豆包)

class StringUtils {  
    // 在类内部定义的扩展函数,仅在 StringUtils 类内部可见  
    private fun String.toUpperFirst(): String {  
        return if (isEmpty()) this  
        else first().uppercase() + substring(1)  
    }  
  
    fun process(str: String): String {  
        return str.toUpperFirst() // 内部调用扩展函数  
    }  
}

伴生对象
类似静态方法

name: String) {  
    companion object { /* 伴生对象 */ }  
}  
  
// 为 Person 的伴生对象定义扩展函数  
fun Person.Companion.createDefault(): Person {  
    return Person("Default Name")}  
  
fun main() {  
    // 调用方式:类似静态方法    val defaultPerson = Person.createDefault()  
    println(defaultPerson.name)}

接口和抽象类
所有实现类都能使用拓展函数,但无法被重写,因为拓展函数是静态的

面向拓展设计http

Ktor库中的代码,起到一个再封装,隐藏参数的效果,结果跟vue中简化http请求一样

class HttpClient {  
    fun request(method: String, url: String, headers: Map<String, String>): HttpResponse {  
        println("Requesting $method to $url with headers: $headers")  
        return HttpResponse("Response from $url")  
    }  
}  
  
fun HttpClient.get(url: String): HttpResponse = request("GET", url, emptyMap())  
  
fun main() {  
    val client = HttpClient()  
  
    // Making a GET request using request() directly  
    val getResponseWithMember = client.request("GET", "https://example.com", emptyMap())  
  
    // Making a GET request using the get() extension function  
    val getResponseWithExtension = client.get("https://example.com")  
}

作用域函数

为一个对象创建一个临时作用域并执行一些代码
Kotlin总共5个作用域函数:let、apply、run、also、with
每个作用域都接受一个lambda表达式,然后返回表达式的结果

let

使用场景:当想在代码中进行空值检查,然后对返回值进行进一步操作时
以可空入参传入非空函数为例:

fun sendNotification(recipientAddress: String): String {  
    println("Yo $recipientAddress!")  
    return "Notification sent!"  
}  
  
fun getNextAddress(): String {  
    return "sebastian@jetbrains.com"  
}  
  
fun main() {  
    val address: String? = getNextAddress()  
    sendNotification(address)  //问题,这条语句会报参数类型不匹配的错,因为address的类型是String? 而sendNotification函数是String
}

//使用if解决
val confirm = if(address != null) {  
    sendNotification(address)  
} else { null }

//使用Elvis运算符解决
//这里写法不对,应该要拆分变量存空值
//val confirm = sendNotification(address ?: return)

//推荐使用let解决,简洁且语义清晰
//如果传入null,?.直接短路返回,不会执行let域中代码
val confirm = address?.let { sendNotification(it) }
apply

使用场景:创建对象时需要进行一系列初始化操作时

class Client() {  
    var token: String? = null  
    fun connect() = println("connected!")  
    fun authenticate() = println("authenticated!")  
    fun getData(): String = "Mock data"  
}  
  
//val client = Client()  
//使用apply作用域函数,省略掉对象名调用,可以直接使用对象的属性和方法
//创建对象和初始化连在一起
val client = Client().apply {  
    token = "asdf"  
    connect()  
    authenticate()  
}  
  
fun main() {  
//    client.token = "asdf"  
//    client.connect()  
//    // connected!  
//    client.authenticate()  
  
    // authenticated!   
    client.getData()  
}

run

类似apply函数,也可以用来初始化对象,但是官方推荐用来初始化+使用对象计算一个结果

client.run {   
connect()  
    authenticate()  
    getData()  
}

apply与run的区别

特性 apply run
返回值 对象本身 代码块最后一行执行结果
用途 配置对象 基于对象计算结果
also

对对象进行一个额外操作,然后返回该对象
使用场景:记日志

fun main() {  
    val medals: List<String> = listOf("Gold", "Silver", "Bronze")  
    val reversedLongUppercaseMedals: List<String> =  
        medals  
            .map { it.uppercase() }  
            .also { println(it) }  //这样不用额外显示的使用medal去调用打印函数了,而且加入了函数式的调用链中,可读性更高
            .filter { it.length > 4 }  
            .also { println(it) }  
            .reversed()  
    println(reversedLongUppercaseMedals)  
    // [BRONZE, SILVER]  
}
with

with不是拓展函数
需要将接收者对象传入with函数去使用(类似when语句带主语的调用方式)
使用场景:当想要在对象上调用多个函数时

//模拟画面渲染过程,渲染了文字,矩形,圆形
class Canvas {  
    fun rect(x: Int, y: Int, w: Int, h: Int): Unit = println("$x, $y, $w, $h")  
    fun circ(x: Int, y: Int, rad: Int): Unit = println("$x, $y, $rad")  
    fun text(x: Int, y: Int, str: String): Unit = println("$x, $y, $str")  
}  
  
fun main() {  
    val mainMonitorSecondaryBufferBackedCanvas = Canvas()  
    with(mainMonitorSecondaryBufferBackedCanvas) {  
        text(10, 10, "Foo")  
        rect(20, 30, 100, 50)  
        circ(40, 60, 25)  
        text(15, 45, "Hello")  
        rect(70, 80, 150, 100)  
        circ(90, 110, 40)  
        text(35, 55, "World")  
        rect(120, 140, 200, 75)  
        circ(160, 180, 55)  
        text(50, 70, "Kotlin")  
    }  
}
简单总结
函数 上下文引用 返回值 应用场景
let it lambda结果 空值检查,返回对象进行下一步操作
apply this 接收者对象 初始化对象
run this lambda结果 在创建时初始化对象并计算结果
also it 接收者对象 在返回对象之前进行一些额外操作
with this lambda结果 对一个对象调用多个函数
一些疑问:
  1. 为什么also上下文引用是it,却又返回接收者对象?
    1. also的设计意图"附加操作"
    2. it与this的语义差异
      • this内部视角,适合对内操作
      • it外部视角,适合对外操作
    3. apply 形成互补
      Kotlin 的作用域函数设计上形成了互补:
      • applythis + 返回对象):适合 “配置对象内部状态”(如设置属性);
      • alsoit + 返回对象):适合 “对对象执行外部附加操作”(如日志、校验)。
        两者都返回上下文对象,但引用方式不同,恰好好区分了 “内部配置” 和 “外部操作” 两种场景。

带有接收者的lambda表达式

可以直接使用对象的属性和成员函数,无需显示的使用对象引用
格式:[接收者类型].[函数类型]

MutableList<Int>.()->Unit//这是一个函数类型
//MutableList<Int>是接收者类型
//()表示没有参数
//返回值是Unit

fun MutableList<Int>.test(){println("hello")}

fun main() {  
    val list: MutableList<Int> = mutableListOf()  
    list.test()  
}

例子1,拓展StringBuilder

fun StringBuilder.appendText(){append("hello")}
fun main() {  
    val sb = StringBuilder("abc")  
    sb.appendText()  
    println(sb.toString())//abc  
    println(StringBuilder("abc").appendText().toString())//输出kotlin.Unit,因为appendText无返回值,不能链式调用
}

例子2,构建菜单

class MenuItem(val name: String)  
  
class Menu(val name: String){  
    val items = mutableListOf<MenuItem>()  
    fun item(name: String) {  
        items.add(MenuItem(name))  
    }  
}  
//init是一个函数类型参数,指外部传一个函数进来
//这个init就是一个DSL
fun menu(name: String, init: Menu.() -> Unit): Menu {  
    val menu = Menu(name)  
    menu.init()
    return menu  
}  
  
fun printMenu(menu: Menu) {  
    println("Menu: ${menu.name}")  
    menu.items.forEach { println("  Item: ${it.name}") }  
}  
  
fun main() {  
    val menu = menu("Kotlin"){  
        item("a")  
        item("b")  
        item("c")  
    }  
    printMenu(menu)  
}

类和接口

类继承

通常Kotlin不允许继承,但支持单继承
如果要使用类继承来共享代码,首先考虑抽象类

抽象类

可以被默认继承
有构造函数,但无法创建实例
子类用override关键字"重写"父类的属性和函数,称为实现
抽象类可以同时包含已经实现和未实现的函数与属性(与Java中一样)

创建

抽象函数和属性不能在top level,必须在抽象类中

abstract class Animal(){  
    abstract val name: String  
    abstract fun eat()  
}


abstract class Product(val name: String, var price: Int) {  
    abstract val category: String  
    fun productInfo(): String {  
        return "Category:$category, Name:$name, Price:$price"  
    }  
}  

/**
疑问:1.为什么子类的属性不需要val/var定义了?
因为子类已经从父类继承了name和price,这里的name和price只作为临时变量传给父类,而warranty是一个新的属性,是需要定义为子类的属性,于是使用了val
**/
class Electronic(name: String, price: Int, val warranty: Int) : Product(name, price) {  
    override val category: String = "Electronic"  
}
接口

没有构造函数和头
函数和属性默认"开放",即继承

声明一个接口

interface Animal
接口实现
interface PaymentMethod{  
    fun initiatePayment(amount: Double): String  
}  
class CreditCardPayment(val cardNumber: String, val cardHolderName: String, val expiryDay: String): PaymentMethod{  
    override fun initiatePayment(amount: Double): String {  
        return "Payment of $$amount initiated using Credit Card ending in ${cardNumber.takeLast(4)}."  
    }  
}  
  
fun main() {  
    val paymentMethod = CreditCardPayment("1234 5678 9012 3456", "John Doe", "12/25")  
    println(paymentMethod.initiatePayment(100.0))  
    // Payment of $100.0 initiated using Credit Card ending in 3456.  
}

多接口实现用逗号隔开

class CreditCardPayment : PaymentMethod, PaymentType

可以在构造函数中声明override实现属性,入下例子,Audio在构造函数中用override实现了结构中的title属性
注意:接口没有构造函数

interface Media{  
    val title: String  
    fun play()  
}  
  
class Audio(override val title: String, val composer: String) : Media {  
    override fun play() {  
        println("Playing audio: $title, composed by $composer")  
    }  
}  
  
fun main() {  
    val audio = Audio("Symphony No. 5", "Beethoven")  
    audio.play()  
    // Playing audio: Symphony No. 5, composed by Beethoven  
}
委托

问题:现在有个接口A,有个实现了接口A的AA类,然后有个需求要创建一个AA类的子类AAA,但是继承AA类之后AAA类依然需要冗余一堆接口A的实现代码
解决方案:使用委托,把接口实现委托给父类AA的对象

interface Drawable {  
    fun draw()  
    fun resize()  
    val color: String?  
}  
  
class Circle : Drawable {  
    override fun draw() {  
        TODO("An example implementation")  
    }  
  
    override fun resize() {  
        TODO("An example implementation")  
    }  
    override val color = null  
}

class RedCircle(param: Circle) : Drawable by param {  
    override val color = "red"  //选择性的覆盖需要覆盖的属性或者方法
}

对象

对象声明

使用对象声明来声明一个具有单个实例的类。
懒加载:用到的时候才去加载
所有对象都以线程安全方式创建

object DoAuth{}

对象体{}花括号内定义属性和成员函数
对象没有构造函数,没头部

一个简单的例子
object DoAuth{  
    fun takeParams(username: String, password: String){  
        println("input Auth parameters = $username:$password")  
    }  
}  
fun main() {  
	//DoAuth对象只在第一次调用时创建
    DoAuth.takeParams("Sean", "123456")  
    
}
对象可以从接口中继承
interface Auth {  
    fun takeParams(username: String, password: String)  
}  
  
object DoAuth : Auth {  
    override fun takeParams(username: String, password: String) {  
        println("input Auth parameters = $username:$password")  
    }  
}  
  
fun main() {  
    DoAuth.takeParams("Sean", "123456")  
}
数据对象

额外提供成员函数:toString() 和 equals()
不自动提供copy(),因为只有一个对象

data object AppConfig {}
伴生对象

一个类只能有一个伴生对象
伴生对象只在其类第一次引用时创建
在伴生对象中声明的任何属性和函数都将在所有实例中共享
一个伴生对象不必有名字。如果你不定义一个,默认是 Companion

fun main() {   
    BigBen.getBongs(10)  
}  
  
class BigBen{  
    companion object Bonger{  
        fun getBongs(nTimes: Int){  
            repeat(nTimes){print("Bong ")}  
        }  
    }  
}
开放类

如果不能使用接口和抽象类,可以使用open关键字显式的声明一个类开放,使它可以继承

open class Vehicle(val make: String, val model: String)
class Car(make: String, model: String, val numberOfDoors: Int): Vehicle(make, model)
重写继承行为

重写父类的属性和成员函数

父类用open开放成员函数
open fun displayInfo()
子类用override重写
override fun displayInfo()
属性

属性可以重写,但不建议
对于open类的子类来说,可以直接访问父类的属性,推荐

//属性继承,与函数继承一样
open class Vehicle(val make: String, val model: String) {  
    open val transmissionType: String = "Manual"  
}  
  
class Car(make: String, model: String, val numberOfDoors: Int) : Vehicle(make, model) {  
    override val transmissionType: String = "Automatic"  
}

//通过将属性放在构造函数中,子类可以传值初始化这个属性,就是直接访问
open class Vehicle(val make: String, val model: String, val transmissionType: String = "Manual")  
  
class Car(make: String, model: String, val numberOfDoors: Int) : Vehicle(make, model, "Automatic")
开放类和接口

一个类可以继承某个类的同时实现多个接口,但是必须把父类放在前面,接口放在后面

class ElectricCar(  
    make: String,  
    model: String,  
    numberOfDoors: Int,  
    val capacity: Double,  
    val emission: String  
) : Car(make, model, numberOfDoors), EcoFriendly, ElectricVehicle {  
    override val batteryCapacity: Double = capacity  
    override val emissionLevel: String = emission  
}
特殊类
密封类

应用场景:限制类的继承范围
是一种特殊的抽象类
只能在同一个包内创建它的子类,包外的不能继承

sealed class Mammal

密封类可以配合when使用
普通抽象类也可以,但是要注意,普通抽象类必须添加else分支,
因为普通抽象类无法确定自己有多少子类,
而密封类可以穷举自己所有子类,然后可以不加else

sealed class Mammal(val name: String)  
  
class Cat(val catName: String) : Mammal(catName)  
class Human(val humanName: String, val job: String) : Mammal(humanName)  
  
fun greetMammal(mammal: Mammal): String {  
    when (mammal) {  
        is Human -> return "Hello ${mammal.name}; You're working as a ${mammal.job}"  
        is Cat -> return "Hello ${mammal.name}"  
        //else -> return "Unknown" abstract类必须加这句
    }  
}  
  
fun main() {  
    println(greetMammal(Cat("Snowy")))  
    // Hello Snowy  
}
枚举类

应用场景:表示一组有限的、不同的值

enum class State{  
    IDLE, RUNNING, FINISHED  
}
fun main() {  
	val state = State.RUNNING
    val message = when (state) {  
        State.IDLE -> "It's idle"  
        State.RUNNING -> "It's running"  
        State.FINISHED -> "It's finished"  
    }  
    println(message)  
}

枚举类可以有属性和成员函数

enum class Color(val rgb: Int) {  
    RED(0xFF0000),  
    GREEN(0x00FF00),  
    BLUE(0x0000FF),  
    YELLOW(0xFFFF00);//用;号分隔开成员函数

fun containsRed(rgb: Int): Boolean = (this.rgb and 0xFF0000 != 0)
}
内联值类

使用场景:从类中创建小型对象,并且只使用它们很短的时间
内联值类 必须 在类头部有一个初始化的单一属性
只能包含值
不能包含非private的属性(避免被当作普通类使用)
不能有状态相关的复杂结构(如其他对象引用)
成员函数是允许的
可以实现接口,因为接口没有状态,但不能继承

interface Contact{  
  
}  
  
@JvmInline  
value class Email(val address: String): Contact{  
    fun test() = "hello world"  
}

属性

支持字段

属性具有默认的属性访问器,就是get set方法
为了避免在get set中直接访问属性导致无限递归,kotlin把属性的实际值存在backing field中,也就是所谓的支持字段

如果满足以下任一条件,则存在后备字段:

  • 您使用属性的默认 get() 或 set() 函数。
  • 你尝试使用 field 关键字在代码中访问属性值。
为什么会无限递归?
  1. getter 中的递归
    当你调用 example.value 时,会触发 getter 方法。而 getter 中写了 return value + 1,这又会再次调用 value 的 getter,形成循环:
    getter() → value → getter() → value → ...
    直到栈溢出报错。

  2. setter 中的递归
    当你执行 example.value = 5 时,会触发 setter 方法。而 setter 中写了 value = newValue * 2,这又会再次调用 value 的 setter,形成循环:
    setter(5) → value = 10 → setter(10) → value = 20 → ...
    同样会导致栈溢出。

解决方案:使用 field 关键字

field 是 Kotlin 提供的特殊关键字,专门用于在访问器中直接访问后备字段(存储属性实际值的底层变量),而非再次触发访问器:

class Example {  
    var value: Int = 0  
        get() {  
            return field + 1 // 正确:访问后备字段  
        }  
        set(newValue) {  
            field = newValue * 2 // 正确:修改后备字段  
        }  
}
拓展属性

没有backing fields
需要自己编写get set
无法保存任何状态
扩展属性不能覆盖类的现有属性

data class Person(val firstName: String, val lastName: String)

// Extension property to get the full name
val Person.fullName: String
    get() = "$firstName $lastName"

fun main() {
    val person = Person(firstName = "John", lastName = "Doe")

    // Use the extension property
    println(person.fullName)
    // John Doe
}
委托属性

将属性的属性访问器委托给另一个对象
使用场景:存储简单属性无法处理的复杂属性,比如数据表、浏览器存储值,就是这个属性获取或者存储时需要插入一段复杂逻辑
每个委托的对象都必须有一个getVlue函数,如果是可变属性还要有setValue函数
定义:

val displayName: String by Delegate

getValue/setValue结构

operator fun getValue(thisRef: Any?, property: KProperty<*>): String {} 
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {}

operator 关键字将这些函数标记为操作符函数,使它们能够重载 get() 和 set() 函数
thisRef 参数指的是包含委托属性的对象 。默认情况下,类型被设置为 Any?,但您可能需要声明更具体的类型
属性参数指的是其值被访问或更改的属性。您可以使用此参数来访问属性名称或类型等信息。默认情况下,类型设置为 Any?(可空引用类型,Any是Kotlin中引用类型的根)
例子,用委托属性缓存显示名称

class CachedStringDelegate {  
    var cachedValue: String? = null  
    operator fun getValue(thisRef: User, property: Any?): String{  
        if(cachedValue == null){  
            cachedValue = "${thisRef.firstName} ${thisRef.lastName}"  
            println("Computed and cached: $cachedValue")  
        }else{  
            println("Accessed from cache: $cachedValue")  
        }  
        return cachedValue ?: "Unknown"  
    }  
}  
  
class User(val firstName: String, val lastName: String) {  
    val displayName: String by CachedStringDelegate()  
}  
  
fun main() {  
    val user = User("John", "Doe")  
  
    // First access computes and caches the value  
    println(user.displayName)  
    // Computed and cached: John Doe  
    // John Doe  
    // Subsequent accesses retrieve the value from cache
    println(user.displayName)  
    // Accessed from cache: John Doe  
    // John Doe}
标准委托

Kotlin标准库提供的委托
不需要手动定义getValue,setValue

lazy

lazy接口用来实现属性的懒加载委托
在get函数第一次调用时执行,get函数的后续任何一次调用都返回第一次的结果
lazy接受一个lambda参数,用尾随lambda方式传入
默认线程安全

class Database {
    fun connect() {
        println("Connecting to the database...")
    }

    fun query(sql: String): List<String> {
        return listOf("Data1", "Data2", "Data3")
    }
}

val databaseConnection: Database by lazy {
    val db = Database()
    db.connect()
    db
}

fun fetchData() {
    val data = databaseConnection.query("SELECT * FROM data")
    println("Data: $data")
}

fun main() {
    // First time accessing databaseConnection
    fetchData()
    // Connecting to the database...
    // Data: [Data1, Data2, Data3]

    // Subsequent access uses the existing connection
    fetchData()
    // Data: [Data1, Data2, Data3]
}
可观察属性

监控属性值是否发生变化,触发反应
使用场景:日志,调试,数据验证

import kotlin.properties.Delegates.observable

class Thermostat {
//observable() 函数接受 20.0 作为参数,并使用它来初始化属性
/**
传递给 observable() 函数的 lambda 表达式:
有三个参数:
    _,它指的是属性本身。
    old,这是属性的旧值。
    new,这是属性的新值
**/
    var temperature: Double by observable(20.0) { _, old, new ->
        if (new > 25) {
            println("Warning: Temperature is too high! ($old°C -> $new°C)")
        } else {
            println("Temperature updated: $old°C -> $new°C")
        }
    }
}

fun main() {
    val thermostat = Thermostat()
    thermostat.temperature = 22.5
    // Temperature updated: 20.0°C -> 22.5°C

    thermostat.temperature = 27.0
    // Warning: Temperature is too high! (22.5°C -> 27.0°C)
}
空安全
智能类型转换和空安全类型转换
is和!is运算符

is 和 !is 运算符与 when 或 if 条件表达式:

  • is 检查对象是否具有该类型,并返回一个布尔值。
  • !is 检查对象 不 具有该类型,并返回一个布尔值。
fun printObjectType(obj: Any) {  
    when (obj) {  
        is Int -> println("It's an Integer with value $obj")  
        !is Double -> println("It's NOT a Double")  
        else -> println("Unknown type")  
    }  
}  
  
fun main() {  
    val myInt = 42  
    val myDouble = 3.14  
    val myList = listOf(1, 2, 3)  
  
    // The type is Int  
    printObjectType(myInt)  
    // It's an Integer with value 42  
  
    // The type is List, so it's NOT a Double.    printObjectType(myList)  
    // It's NOT a Double  
  
    // The type is Double, so the else branch is triggered.    printObjectType(myDouble)  
    // Unknown type  
}
as 和 as? 运算符

显式的把对象转换为其他类型使用as
包括从可空类型转换到其非可空对应类型
如果转换不可能,程序会在 运行时崩溃。这就是它被称为 不安全 转换运算符的原因

fun main() {  
    val a: String? = null  
//    val b = a as String  程序运行时会崩溃
    val b = a as? String  //可以打印出null
    println(b)  
}

使用as?与Elvis运算符简化代码

//简化前
fun calculateTotalStringLength(items: List<Any>): Int {  
    var totalLength = 0  
  
    for (item in items) {  
        totalLength += if (item is String) {  
            item.length  
        } else {  
            0  // Add 0 for non-String items  
        }  
    }  
    return totalLength  
}

//简化后
fun calculateTotalStringLength(items: List<Any>): Int {  
    return items.sumOf { (it as? String)?.length ?: 0 }  
}
/**
说明:
- 对列表中的每个项目,使用 `String` 进行安全类型转换,通过 `as?`。
- 如果调用不返回 `null` 值,则使用安全调用 `?.` 来访问 `length` 属性。
- 如果安全调用返回 `null` 值,则使用 Elvis 运算符 `?:` 返回 `0`
**/
空值和集合

filterNotNull函数用来过滤null值

val emails: List<String?> = listOf("Bob@example.com", null, "Saul@example.com", null)  
val validEmails = emails.filterNotNull()  
println(validEmails)

listOfNotNull函数,在创建时就过滤

val serverConfig = mapOf(  
    "appConfig.json" to "App Configuration",  
    "dbConfig.json" to "Database Configuration"  
)  
  
val requestedFile = "appConfig.json"  
val configFiles = listOfNotNull(serverConfig[requestedFile])  
  
println(configFiles)

maxOrNull()查找最大值。如果不存在,则返回一个 null 值。
minOrNull()查找最小值。如果不存在,则返回一个 null 值

maxOrNull() 和 minOrNull() 函数设计用于与不包含 null 值的集合一起使用。否则,你无法判断函数是找不到期望的值,还是找到了一个 null 值

val temperatures = listOf(15, 18, 21, 21, 19, 17, 16)  
    val maxTemperature = temperatures.maxOrNull()  
    println("Highest temperature recorded: ${maxTemperature ?: "No data"}")  
    
    val minTemperature = temperatures.minOrNull()  
    println("Lowest temperature recorded: ${minTemperature ?: "No data"}")  

singleOrNull
判断集合是否存在单个的目标元素,如果不存在或者存在多个都返回null
用于处理不包含null值的集合(推荐)

val temperatures = listOf(15, 18, 21, 21, 19, 17, 16)  
  
val singleHotDay = temperatures.singleOrNull{ it == 30 }  
println("Single hot day with 30 degrees: ${singleHotDay ?: "None"}")

firstNotNullOfOrNull
lambda表达式转换集合并返回第一个非null值
如果不存在则返回null值

data class User(val name: String?, val age: Int?)

fun main() {
val users = listOf(  
        User(null, 25),  
        User("Alice", null),  
        User("Bob", 30)  
    )  
    val firstNonNullName = users.firstNotNullOfOrNull { it.name }  
    println(firstNonNullName)  //Alice
}

reduceOrNull
将集合中的元素通过指定的操作合并为单个结果,如果集合为空返回null
reduceOrNull() 函数设计用于不包含 null 值的集合(编译会报错)

// Prices of items in a shopping cart  
    val itemPrices = listOf(20, 35, 15, 40, 10)  
  
// Calculate the total price using the reduceOrNull() function  
    val totalPrice = itemPrices.reduceOrNull { runningTotal, price -> runningTotal + price }  
    println("Total price of items in the cart: ${totalPrice ?: "No items"}")  
// Total price of items in the cart: 120  
  
    val emptyCart = listOf<Int>()  
    val emptyTotalPrice = emptyCart.reduceOrNull { runningTotal, price -> runningTotal + price }  
    println("Total price of items in the empty cart: ${emptyTotalPrice ?: "No items"}")  
// Total price of items in the empty cart: No items
提前返回与Elvis运算符结合
//return与Elvis版本可读性更高
fun getNumberOfFriends(users: Map<Int, User>, userId: Int): Int {  
    val user = users[userId] ?: return -1  
    return user.friends.size  
}

fun getNumberOfFriends(users: Map<Int, User>, userId: Int): Int {  
    return users[userId]?.friends?.size ?: -1
}
库和API
标准库

大部分可以直接在Kotlin文件中使用,部分需要导入
标准库已经提供的类、函数、属性列表:

Kotlin库

Klibs.io

选择使用API

有些API被定义为需要选择才能使用,比如一些正在开发中的API

要启用,请编写 @OptIn 后跟包含分类 API 的类名的括号,然后附加两个冒号 :: 和 class

以标准库中的UIntArray函数为例

@ExperimentalUnsignedTypes 
inline fun uintArrayOf(vararg elements: UInt): UIntArray
@OptIn(ExperimentalUnsignedTypes::class)  
fun main() {  
    // Create an unsigned integer array  
    val unsignedArray: UIntArray = uintArrayOf(1u, 2u, 3u, 4u, 5u)  
  
    // Modify an element  
    unsignedArray[2] = 42u  
    println("Updated array: ${unsignedArray.joinToString()}")  
    // Updated array: 1, 2, 42, 4, 5  
}

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐