Kotlin 的文章现已很多了,这边就不做过多介绍了。说说项目中常用的,以及一些个人的了解吧。顺路复习复习

kotlin 根底操作符

根底这一块就不提了,用过的都了解。

Kotlin 复习篇

当然非空断语公司是禁用的

但是有一个疑问点,既然任何当地都可以用空安全,那么 lateinit 这个关键字又有什么作用?不知道咱们有没有想过这个原因?

其实看过转化后的代码就很容易知道了,示例代码(手打的不一定精确)

var user:User? = null
fun test(){
    user?.name = "1"
}

转化后的代码就是这样

User user;
fun test(){
    if(user != null){
        user.name = "1"
    }
}

就会多出来一个空判别,而 lateinit 就没有这个空判别(代码就不写了)。比较来说 lateinit 性能上会好一点。这也是 lateinit 存在的原因。在你保证目标不会为空的情况下可以运用 lateinit 优化性能。

Kotlin 常用操作符 let、run、apply、also、with

@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.also(block: (T) -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}

源码分析

let: 是把自己以参数的办法回调出去给运用,而且回来 block (及最终一行),自己一般用于需求传递该目标的时分

run: 是以调用者自身的一个扩展函数的办法履行,而且回来 block (及最终一行),自己一般用于目标履行操作

apply: 是以调用者自身的一个扩展函数的办法履行,而且回来调用者自身,自己一般用于目标创立时赋值

also: 是把自己以参数的办法回调出去给 block 运用,而且回来调用者自身,自己一般用于附加操作

with: 是以第一个参数的一个扩展函数的办法履行,而且回来第一个参数 block (及最终一行),自己一般用于设置时间监听时运用

Kotlin 关键字

inline 关键字、noinline 关键字、crossinline 关键字

inline:用于润饰函数,将函数的完成内联到调用途,可以减少函数调用的开支。

noinline:用于润饰函数参数,表明该参数不会被内联,默认情况下,Kotlin 编译器会将具有 lambda 表达式作为参数的函数进行内联优化,但运用 noinline 关键字可以禁止内联。

crossinline:用于润饰函数参数,表明该参数有必要被内联,但是不答应运用 return 句子从函数中回来。(面试时分这个玩意儿问题比较多)

详细的可以看这边文章讲的挺好的 Kotlin inline noinline crossinline 回答

reified

reified:用于润饰泛型类型参数,在运行时获取类型信息。

这个关键字也是面试中常问到的一个而且会提起 java 中类型擦除概念,那么 reifiedjava 类型擦除 有啥相关?

java 类型擦除:在 Java 中,类型擦除(Type Erasure)是指在编译时期,泛型类型信息会被擦除或转化为原始类型。这是由于 Java 泛型的完成办法——类型擦除机制。

Java 的泛型是在 JDK 5 中引进的,它答应咱们在编写代码时运用参数化类型,以提高代码的类型安全性和重用性。然而,在编译时,Java 编译器会将泛型类型擦除为其原始类型。

类型擦除的首要目的是为了兼容 Java 泛型的向后兼容性。由于 Java 泛型是在 JDK 5 之后引进的,为了坚持与旧版本的 Java 代码彼此操作的能力,编译器会将泛型类型擦除为非泛型的办法。

例如,关于一个泛型类 List<T>,编译器会将其中的泛型类型 T 擦除为其上界或许 Object 类型。也就是说,关于编译器来说,List<String>List<Integer> 都会被擦除成 List<Object>

reified 关键字:在 Kotlin 中,运用 reified 关键字可以获取泛型的详细类型信息。

它首要用于内联函数和泛型函数中。经过运用 reified 关键字,咱们可以在函数体内部获取泛型的实践类型,并对其进行操作,而不会受到类型擦除的约束。这使得在运行时可以操作泛型的详细类型。

by 关键字

Kotlin 中,什么是托付模式(Delegation Pattern)?怎样运用 by 关键字完成特点托付?

托付模式是一种规划模式,其中一个目标(被托付目标)将其某些职责托付给另一个目标(托付目标)。在 Kotlin 中,by 关键字用于完成特点托付。经过运用 by 关键字,咱们可以将特点的拜访和修正操作托付给另一个目标。

// 假设咱们有一个 `User` 类,它有一个特点 `name`,咱们期望在每次设置 `name` 特点时打印日志。
// 咱们可以运用 `by` 关键字来完成特点托付,将特点的拜访和修正操作托付给另一个目标。
class Logger {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        println("Getting ${property.name}")
        return "John Doe"
    }
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("Setting ${property.name} to $value")
    }
}
class User {
    var name: String by Logger()
}
// 在上述示例中,咱们定义了一个 `Logger` 类,它包含两个办法 `getValue()` 和 `setValue()`,分别用于获取特点值和设置特点值。
// 当咱们经过 `User` 目标拜访或修正 `name` 特点时,实践上是经过 `Logger` 目标来完成的。
// 例如:
fun main() {
    val user = User()
    println(user.name)     // 输出:Getting name  John Doe
    user.name = "Alice"    // 输出:Setting name to Alice
    println(user.name)     // 输出:Getting name  John Doe
}
// 经过运用 `by` 关键字和特点托付,咱们可以在每次拜访或修正特点时履行额定的逻辑,例如打印日志。
// 这种办法使得代码更加模块化和可重用,一同坚持了类的简洁性和可读性。

对了差点忘了 by 还有个好用的当地

class DotMapHelper @JvmOverloads constructor(hashMap: HashMap<String, Any?> = hashMapOf()) :
    BaseDotMapHelper<DotMapHelper>(hashMap) {
    var moduleId: String? by hashMap.withDefault { null }
    var modulePosition: Int? by hashMap.withDefault { null }
    var position: Int? by hashMap.withDefault { null }
    var type: Int? by hashMap.withDefault { null }
    var elementId: Long? by hashMap.withDefault { null }
    var direction: Int? by hashMap.withDefault { null }
    var url: String? by hashMap.withDefault { null }
}

个人感觉在传参上贼好用,例如埋点的情况。

out 关键字

Kotlin 中,outin 是用来润饰类型参数的关键字,用于定义泛型类和泛型函数的类型约束。

out:用于协变(covariant)类型参数。它答应咱们将子类型作为类型参数传递给泛型类或泛型函数。在运用out润饰类型参数时,只能将该类型参数作为输出(回来值)类型,不能用于输入(办法参数)类型。换句话说,咱们可以从泛型目标中获取数据,但不能将数据存储到泛型目标中。

源码中 out 关键字:

  • List<out T>List 是一个只读接口,运用 out 关键字使其具有协变性。这意味着假如 BA 的子类型,那么 List<B>List<A> 的子类型。这使得咱们可以安全地将子类型的列表分配给父类型的列表。

  • Array<out T>Array 是一个固定长度的数组类,运用 out 关键字使其具有协变性。这意味着假如 BA 的子类型,那么 Array<B>Array<A> 的子类型。这使得咱们可以安全地将子类型的数组分配给父类型的数组。

  • Iterable<out T>Iterable 是一个只读接口,运用 out 关键字使其具有协变性。这意味着假如 BA 的子类型,那么 Iterable<B>Iterable<A> 的子类型。这使得咱们可以安全地将子类型的可迭代目标分配给父类型的可迭代目标。

in 关键字

in:用于逆变(contravariant)类型参数。它答应咱们将超类型作为类型参数传递给泛型类或泛型函数。在运用in润饰类型参数时,只能将该类型参数作为输入(办法参数)类型,不能用于输出(回来值)类型。换句话说,咱们可以将数据存储到泛型目标中,但不能从泛型目标中获取数据。

源码中 in 关键字

  • Consumer<in T>Consumer 是一个接口,运用 in 关键字使其具有逆变性。这意味着假如 BA 的超类型,那么 Consumer<A>Consumer<B> 的超类型。这使得咱们可以安全地将超类型的顾客分配给子类型的顾客。

  • Comparable<in T>Comparable 是一个接口,运用 in 关键字使其具有逆变性。这意味着假如 BA 的超类型,那么 Comparable<A>Comparable<B> 的超类型。这使得咱们可以安全地将超类型的可比较目标传递给子类型的可比较目标。

关于 out 关键字 和 in 关键字是怎样来的这个可以看这篇文章讲的挺好的 # 一文读懂 kotlin 的协变与逆变 — 从 Java 说起

这边摘抄部分方便后期

Java 和 C# 前期都是没有泛型特性的。

但是为了支撑程序的多态性,所以将数组规划成了协变的。因为数组的很多办法应该可以适用于所有类型元素的数组。

比方下面两个办法:

boolean equalArrays (Object[] a1, Object[] a2);
void shuffleArray(Object[] a);

第一个是比较数组是否相等;第二个是打乱数组次序。

语言的规划者们期望这些办法关于任何类型元素的数组都可以调用,比方我可以调用 shuffleArray(String[] s) 来把字符串数组的次序打乱。

出于这样的考虑,在 Java 和 C# 中,数组规划成了协变的。

然而,关于泛型来说,却有以下问题:

// Illegal code - because otherwise life would be Bad
List\<Dog\> dogs = new List\<Dog\>();
List\<Animal\> animals = dogs; // Awooga awooga
animals.add(new Cat());// (1)
Dog dog = dogs.get(0); //(2) This should be safe, right?

假如上述代码可以经过编译,即 List<Dog> 可以赋值给 List<Animal>,List 是协变的。接下交游 List<Dog> 中 add 一个 Cat(),如代码 (1) 处。这样就有或许造成代码 (2) 处的接收者 Dog dogdogs.get(0) 的类型不匹配的问题。会引发运行时的异常。所以 Java 在编译期就要阻止这种行为,把泛型规划为默认不型变的。

Kotlin 泛型其实默认也是不型变的,只不过运用 out 和 in 关键字在类声明处型变,可以到达在运用途看起来像直接型变的作用。但是这样会约束类在声明时只能要么作为生产者,要么作为顾客。

作者:牛蛙点点申请出战
链接:/post/688236…
来源:稀土
著作权归作者所有。商业转载请联系作者取得授权,非商业转载请注明出处。

operator 关键字

以下是一些常见的运算符,以及对应的 operator 表明法:

  • 算术运算符:

    • +operator fun plus(other: T): T
    • -operator fun minus(other: T): T
    • *operator fun times(other: T): T
    • /operator fun div(other: T): T
    • %operator fun rem(other: T): T
  • 比较运算符:

    • ==operator fun equals(other: Any?): Boolean
    • !=operator fun notEquals(other: Any?): Boolean
    • >operator fun compareTo(other: T): Int
  • 逻辑运算符:

    • &&operator fun and(other: Boolean): Boolean
    • ||operator fun or(other: Boolean): Boolean
    • !operator fun not(): Boolean
  • 索引拜访运算符:

    • get(index: Int): Toperator fun get(index: Int): T
    • set(index: Int, value: T)operator fun set(index: Int, value: T)
  • 函数调用运算符:

    • invoke(parameters: ...) -> resultoperator fun invoke(parameters: ...): result

infix 关键字

中缀函数答应咱们以更具可读性和简洁性的办法调用函数,而且可以在函数名和参数之间运用中缀符号。

中缀函数的语法示例:

infix fun Int.addPlus(num: Int): Int {
    return this + num
}
fun main() {
    val result = 5 addPlus 3 // 运用中缀符号调用中缀函数
    println(result) // 输出:8
}

在上述示例中,咱们定义了一个名为 addPlus 的中缀函数,它接受一个整数参数 num,并回来两个整数相加的成果。经过在函数名和参数之间运用中缀符号 infix,咱们可以以更简洁的办法调用该函数。

main() 函数中,咱们运用中缀符号 addPlus 将整数 53 相加,并将成果赋值给变量 result。然后,咱们打印出成果 8

运用中缀函数可以使代码更加天然和易读,特别适用于描述某种关系或操作的情况。需求留意的是,中缀函数有必要满意以下条件:

  • 它们有必要是成员函数或扩展函数。
  • 它们只要一个参数。
  • 参数不能是可变数量参数(vararg)。
  • 参数和函数自身有必要标记为infix

sealed 关键字

用于润饰类。当一个类被声明为 sealed 时,它只能被同一个文件中的其他类继承,不答应在其他文件中创立该类的子类。

一般与 when 表达式一同运用,以保证在处理所有或许的子类时进行完整的掩盖

sealed class Result<out T>
data class Success<T>(val data: T) : Result<T>()
data class Error(val message: String) : Result<Nothing>()
fun handleResult(result: Result<Any>) {
    when (result) {
        is Success -> println("Success: ${result.data}")
        is Error -> println("Error: ${result.message}")
    }
}
fun main() {
    val successResult = Success(42)
    val errorResult = Error("Something went wrong")
    handleResult(successResult) // 输出:Success: 42
    handleResult(errorResult) // 输出:Error: Something went wrong
}

use 关键字

首要用于处理需求封闭或释放资源的情况,如文件、流和数据库连接等。运用 use 关键字可以保证在代码块履行完毕后主动封闭相关资源,无需手动调用封闭办法。

val file = File("example.txt")
file.inputStream().use { inputStream ->
    // 运用 inputStream 读取文件数据
    // 在代码块完毕后,inputStream 会被主动封闭
}
val reader = BufferedReader(FileReader("example.txt"))
reader.use { bufferedReader ->
    // 运用 bufferedReader 读取文件数据
    // 在代码块完毕后,bufferedReader 会被主动封闭
}

当代码块履行完毕时,不管是否产生异常,use 关键字会主动封闭相关资源(这里是输入流或缓冲读取器)。

留意,为了可以运用 use 关键字,你需求保证相关资源完成了 Closeable 接口,该接口供给了 close() 办法用于封闭资源。大多数的 I/O 类都完成了 Closeable 接口,包括 InputStreamOutputStreamReaderWriter 等。

运用 use 关键字可以简化代码,并保证及时封闭资源,防止资源走漏和过错。它是处理资源管理的一种引荐办法。

打完收工不写了,下班铁子们。加油!!!

2023.8.9 21:49