Kotlin
的文章已经很多了,这边就不做过多介绍了。说说项目中常用的,以及一些个人的理解吧。顺道复习复习
kotlin 基础操作符
基础这一块就不提了,用过的都熟悉。
当然非空断言公司是禁用的
但是有一个疑问点,既然任何地方都可以用空安全,那么 lateinit
这个关键字又有什么作用?不知道大家有没有想过这个原因?
其实看过转换后的代码就很容易知道了,示例代码(手打的不一定准确)
kotlin
var user:User? = null
fun test(){
user?.name = "1"
}
转换后的代码就是这样
java
User user;
fun test(){
if(user != null){
user.name = "1"
}
}
就会多出来一个空判断,而 lateinit
就没有这个空判断(代码就不写了)。相比来说 lateinit
性能上会好一点。这也是 lateinit
存在的原因。在你确保对象不会为空的情况下可以使用 lateinit
优化性能。
Kotlin 常用操作符 let、run、apply、also、with
kotlin
@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 关键字、oninline 关键字、crossinline 关键字
inline
:用于修饰函数,将函数的实现内联到调用处,可以减少函数调用的开销。
noinline
:用于修饰函数参数,表示该参数不会被内联,默认情况下,Kotlin
编译器会将具有 lambda
表达式作为参数的函数进行内联优化,但使用 noinline
关键字可以禁止内联。
crossinline
:用于修饰函数参数,表示该参数必须被内联,但是不允许使用 return
语句从函数中返回。(面试时候这个玩意儿问题比较多)
详细的可以看这边文章讲的挺好的 Kotlin inline noinline crossinline 解答
reified
reified
:用于修饰泛型类型参数,在运行时获取类型信息。
这个关键字也是面试中常问到的一个并且会提起 java 中类型擦除概念,那么 reified
和 java 类型擦除
有啥关联?
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
关键字,我们可以将属性的访问和修改操作委托给另一个对象。
kotlin
// 假设我们有一个 `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 还有个好用的地方
kotlin
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
中,out
和 in
是用来修饰类型参数的关键字,用于定义泛型类和泛型函数的类型约束。
out
:用于协变(covariant)类型参数。它允许我们将子类型作为类型参数传递给泛型类或泛型函数。在使用 out
修饰类型参数时,只能将该类型参数作为输出(返回值)类型,不能用于输入(方法参数)类型。换句话说,我们可以从泛型对象中获取数据,但不能将数据存储到泛型对象中。
源码中 out 关键字:
-
List<out T>
:List
是一个只读接口,使用out
关键字使其具有协变性。这意味着如果B
是A
的子类型,那么List<B>
是List<A>
的子类型。这使得我们可以安全地将子类型的列表分配给父类型的列表。 -
Array<out T>
:Array
是一个固定长度的数组类,使用out
关键字使其具有协变性。这意味着如果B
是A
的子类型,那么Array<B>
是Array<A>
的子类型。这使得我们可以安全地将子类型的数组分配给父类型的数组。 -
Iterable<out T>
:Iterable
是一个只读接口,使用out
关键字使其具有协变性。这意味着如果B
是A
的子类型,那么Iterable<B>
是Iterable<A>
的子类型。这使得我们可以安全地将子类型的可迭代对象分配给父类型的可迭代对象。
in 关键字
in
:用于逆变(contravariant)类型参数。它允许我们将超类型作为类型参数传递给泛型类或泛型函数。在使用 in
修饰类型参数时,只能将该类型参数作为输入(方法参数)类型,不能用于输出(返回值)类型。换句话说,我们可以将数据存储到泛型对象中,但不能从泛型对象中获取数据。
源码中 in 关键字
-
Consumer<in T>
:Consumer
是一个接口,使用in
关键字使其具有逆变性。这意味着如果B
是A
的超类型,那么Consumer<A>
是Consumer<B>
的超类型。这使得我们可以安全地将超类型的消费者分配给子类型的消费者。 -
Comparable<in T>
:Comparable
是一个接口,使用in
关键字使其具有逆变性。这意味着如果B
是A
的超类型,那么Comparable<A>
是Comparable<B>
的超类型。这使得我们可以安全地将超类型的可比较对象传递给子类型的可比较对象。
operator
这个本人用的很少,有兴趣的可以自行了解。
打完收工不写了,下班铁子们。加油!!!
2023.8.9 21:49