🔥 31、简述Kotlin 中的内联类,我们什么时候需要?🔥
有时,业务逻辑需要围绕某种类型创建包装器。
但是,由于额外的堆分配,它会引入运行时开销。
此外,如果包装的类型是原始类型,那么性能损失会很严重,因为原始类型通常由运行时进行大量优化。
内联类为我们提供了一种包装类型的方法,从而添加功能并自行创建新类型。
与常规(非内联)包装器相反,它们将受益于改进的性能。发生这种情况是因为数据被内联到其用法中,并且在生成的编译代码中跳过了对象实例化。
inline class Name(val s: String) {val length: Intget() = s.lengthfun greet() {println("Hello, $s")}
}fun main() {val name = Name("Kotlin")name.greet() // method `greet` is called as a static methodprintln(name.length) // property getter is called as a static method
}
关于内联类的一些注意事项:
在主构造函数中初始化的单个属性是内联类的基本要求
内联类允许我们像普通类一样定义属性和函数
不允许初始化块、内部类和支持字段
内联类只能从接口继承
内联类也是有效的最终类
🔥 32、 解释什么是 Coroutine Scope,它与 Coroutine Context 有什么不同?🔥
协程总是在由Kotlin 标准库中定义的CoroutineContext类型的值表示的某些上下文中执行。
协程上下文是一组不同的元素。主要元素是协程的Job。
CoroutineScope本身没有数据,它只包含一个CoroutineContext。它的关键作用是作为你传递给的块的隐式接收者launch,async等等。
runBlocking {val scope0 = this// scope0 is the top-level coroutine scope.scope0.launch {val scope1 = this// scope1 inherits its context from scope0. It replaces the Job field// with its own job, which is a child of the job in scope0.// It retains the Dispatcher field so the launched coroutine uses// the dispatcher created by runBlocking.scope1.launch {val scope2 = this// scope2 inherits from scope1}}
}
你可能会说CoroutineScope形式化了CoroutineContext的继承方式。
您可以看到CoroutineScope如何调解协程上下文的继承。如果您取消 中的作业scope1,这将传播到scope2并取消已启动的作业。
🔥 33、 如何覆盖 Kotlin 数据类的默认 getter?🔥
给定以下 Kotlin 类:
data class Test(val value: Int)
0如果值为负,我将如何覆盖 Int getter 以便它返回?
在使用错误值调用构造函数之前,让创建数据类的业务逻辑将值更改为 0 或更大。对于大多数情况,这可能是最好的方法。
不要使用data cla***常规的class.
class Test(value: Int) {val value: Int = valueget() = if (field < 0) 0 else fieldoverride fun equals(other: Any?): Boolean {if (this === other) return trueif (other !is Test) return falsereturn true}override fun hashCode(): Int {return javaClass.hashCode()}
}
在执行您想要的操作的对象上创建一个额外的安全属性,而不是拥有一个被有效覆盖的私有值。data class Test(val value: Int) {val safeValue: Intget() = if (value < 0) 0 else value
}
🔥 34、如何在 Kotlin 中为数据类创建空的构造函数?🔥
如果您为所有字段提供默认值- Kotlin 会自动生成空构造函数。data class User(var id: Long = -1, var uniqueIdentifier: String? = null) 你可以简单地调用:val user = User() 另一种选择是声明一个没有参数的辅助构造函数:data class User(var id: Long,var uniqueIdentifier: String?){constructor() : this(-1, null) }
🔥 35、简述什么是 Kotlin 中的对象表达式以及何时使用它们?🔥
有时我们需要创建一个稍加修改的类的对象,而不是显式地为它声明一个新的子类。Java 使用匿名内部类来处理这种情况。Kotlin 使用对象表达式来实现相同的功能。我们甚至可以通过实现它们的抽象方法来为接口或抽象类创建对象表达式。
它通常用作 Java 匿名类的替代品:
window.addMouseListener(object : MouseAdapter() {override fun mouseClicked(e: MouseEvent) {// ...
}override fun mouseEntered(e: MouseEvent) {// ...}
})
🔥 36、 以下Koltin 代码有什么问题?🔥
假设我想重写 Int getter,以便在数据类的值为负时返回 0。这种方法有什么不好?data class Test(private val _value: Int) {val value: Intget() = if (_value < 0) 0 else _value
}
这种方法的问题在于,数据类并不真正意味着像这样改变数据。
它们实际上只是用于保存数据。
像这样覆盖数据类的 getter 意味着Test(0)andTest(-1)不会彼此相等并且会有不同hashCodes的 ,但是当你调用 时.value,它们会得到相同的结果。
这是不一致的,虽然它可能对您有用,但您团队中的其他人看到这是一个数据类,可能会不小心误用它。
🔥 37、Kotlin 泛型中的“*”和“Any”有什么区别?🔥
List<*>可以包含任何类型的对象,但只能包含该类型,因此它可以包含Strings(但只能Strings)
whileList可以包含Strings和Integers诸如此类,都在同一个列表中
* 相当于java泛型的?,就是未知任意类型。Any 相当于Object类型
🔥 38、假设您将代码从 Java 迁移到 Kotlin。你会如何在 Kotlin 中重写这段代码?🔥
public class Foo {private static final Logger LOG = LoggerFactory.getLogger(Foo.class);
}
使用类静态方法:class MyClass {companion object {val LOG = Logger.getLogger(MyClass::class.java.name)}fun foo() {LOG.warning("Hello from MyClass")}
}
🔥 39、 Kotlin 协程在哪些方面优于 RxKotlin/RxJava?🔥
Kotlin 协程与 Rx 不同。两者都旨在解决异步编程的问题,但是它们的解决方法非常不同:
Rx 带有一种特殊的函数式编程风格,几乎可以在任何编程语言中实现,而无需语言本身的支持。当手头的问题很容易分解为一系列标准运算符时,它运行良好,否则效果不佳。
Kotlin 协程提供了一种语言特性,可以让库编写者实现各种异步编程风格,包括但不限于函数式反应风格 (Rx)。使用 Kotlin 协程,您还可以以命令式风格、基于 promise/futures 的风格、actor 风格等编写异步代码。
Kotlin 协程如何优于 RxKotlin?您只需编写顺序代码,一切都像编写同步代码一样简单,除了它是异步执行的。它更容易掌握。
协程更好地处理资源
在 RxJava 中,您可以将计算分配给调度程序,但subscribeOn()会ObserveOn()令人困惑。每个协程都被赋予一个线程上下文并返回父上下文。
对于一个通道,双方(生产者、消费者)都在自己的上下文中执行。协程在线程或线程池做作上更直观。
协程可以更好地控制这些计算何时发生。
例如,对于给定的计算,您可以传递手 ( yield)、优先级 ( select)、并行化 (multiple producer/ actoron channel) 或锁定资源 ( )。
Mutex在服务器上(RxJava 首先出现)可能无关紧要,但在资源有限的环境中,可能需要这种级别的控制。
由于它的反应性质,背压在 RxJava 中不太适合。
在通道的另一端send()是一个挂起函数,当达到通道容量时会挂起。
这是大自然赋予的开箱即用的背压。
您还offer()可以使用通道,在这种情况下,调用永远不会暂停,而是false在通道已满时返回,从而有效地onBackpressureDrop()从 RxJava 复制。
或者您可以编写自己的自定义背压逻辑,这对于协程来说并不困难,尤其是与使用 RxJava 做同样的事情相比。
🔥 40、 Kotlin 协程中的 launch/join 和 async/await 有什么区别?🔥
launch用于触发并忘记协程。
这就像开始一个新线程。
如果内部的代码因launch异常而终止,则将其视为线程中未捕获的异常——通常在后端 JVM 应用程序中打印到 stderr 并使 Android 应用程序崩溃。
join用于等待启动的协程完成,并且不会传播其异常。
但是,崩溃的子协程也会取消其父协程,并出现相应的异常。
async用于启动计算某些结果的协程。
结果由 的实例表示Deferred,您必须在其上使用await。
异步代码中未捕获的异常存储在结果Deferred中,不会传递到其他任何地方,除非处理,否则它将被静默丢弃。
你一定不要忘记你用异步启动的协程。