Android 开发者应该知道的39个Kotlin面试问题
由于文章太长我就不做太多说明了,看题目就知道这是啥了,ok,废话不多说,看下面整理出来的题,希望可以对想从事Android开发的兄弟姐妹们有所帮助,下面的题整理出来的,并不全面,欢迎各位提问和补充!Android面试题和答案已按照规范已整理完成,大家可看文末或评论/私信,一起交流技术、进阶提升~
Q1:如何在 Kotlin 中用值初始化一个数组?
在 Java 中,可以初始化数组,例如:
int numbers[] = new int[] {10, 20, 30, 40, 50}
Kotlin 的数组初始化是什么样子的?
val numbers: IntArray = intArrayOf(10, 20, 30, 40, 50)
Q2:Kotlin 中 fold 和 reduce 的基本区别是什么?什么时候用哪个?
- fold接受一个初始值,传递给它的 lambda 的第一次调用将接收该初始值和集合的第一个元素作为参数。
listOf(1, 2, 3).fold(0) { sum, element -> sum + element }
对 lambda 的第一次调用将使用参数0和1。
如果您必须为您的操作提供某种默认值或参数,那么能够传入初始值是很有用的。
- reduce不采用初始值,而是从集合的第一个元素作为累加器开始(sum在以下示例中调用)
listOf(1, 2, 3).reduce { sum, element -> sum + element }
此处对 lambda 的第一次调用将使用参数1和2。
Q3:Kotlin 中的 var 和 val 有什么区别?
- var类似于general变量,在 kotlin 中称为可变变量,可以多次赋值。
- val就像Final变量,在 Kotlin 中被称为不可变,并且只能被初始化一次。
+----------------+-----------------------------+---------------------------+ | | val | var | +----------------+-----------------------------+---------------------------+ | Reference type | Immutable(once initialized | Mutable(can able to change| | | can't be reassigned) | value) | +----------------+-----------------------------+---------------------------+ | Example | val n = 20 | var n = 20 | +----------------+-----------------------------+---------------------------+ | In Java | final int n = 20; | int n = 20; | +----------------+-----------------------------+---------------------------+
Q4:我应该在哪里使用 var 和 where val?
在值经常变化的地方使用var。例如,在获取 android 设备的位置时:
var integerVariable : Int? = null
在整个班级的价值没有变化的情况下使用val 。例如,您想以编程方式设置 textview 或按钮的文本。
val stringVariables : String = "Button's Constant or final Text"
Q5:Kotlin 中的数据类是什么?
我们经常创建主要目的是保存数据的类。在 Kotlin 中,这称为数据类并标记为data:
data class User(val name: String, val age: Int)
为了确保生成代码的一致性和有意义的行为,数据类必须满足以下要求:
- 主构造函数至少需要一个参数;
- 所有主要的构造函数参数都需要标记为 val 或 var;
- 数据类不能是抽象的、开放的、密封的或内部的;
Q6:如何在 Kotlin 中创建单例?
只需使用object.
object SomeSingleton
上面的 Kotlin 对象将被编译为以下等效的 Java 代码:
public final class SomeSingleton { public static final SomeSingleton INSTANCE; private SomeSingleton() { INSTANCE = (SomeSingleton)this; System.out.println("init complete"); } static { new SomeSingleton(); } }
这是在 JVM 上实现单例的首选方法,因为它可以实现线程安全的延迟初始化,而不必依赖像复杂的双重检查锁定这样的锁定算法。
Q7:以下代码执行的结果是什么?
输出会是什么?
val aVar by lazy { println("I am computing this value") "Hola" } fun main(args: Array<String>) { println(aVar) println(aVar) }
第lazy一次访问 Lazy 属性时,会进行初始化(lazy()函数调用)。第二次,这个值被记住并返回:
I am computing this value Hola Hola
Q8:解释 Kotlin 中的 Null 安全性
Kotlin 的类型系统旨在消除代码中空引用的危险,也称为十亿美元错误。
许多编程语言(包括 Java)中最常见的缺陷之一是访问空引用的成员将导致空引用异常。在 Java 中,这相当于 aNullPointerException或简称 NPE。
在 Kotlin 中,类型系统区分可以保存的null引用(可空引用)和不能保存的引用(non-null引用)。例如,String 类型的常规变量不能保存 null:
var a: String = "abc" a = null // compilation error
为了允许空值,我们可以将变量声明为可空字符串,写成String?:
var b: String? = "abc" b = null // ok print(b)
Q9:解释一下那个代码有什么问题?
为什么这段代码是错误的?
class Student (var name: String) { init() { println("Student has got a name as $name") } constructor(sectionName: String, var id: Int) this(sectionName) { } }
类的属性不能在二级构造函数中声明。这会报错,因为这里我们id在二级构造函数中声明了类的属性,这是不允许的。
如果要在辅助构造函数中使用某些属性,则在类中声明该属性并在辅助构造函数中使用它:
class Student (var name: String) { var id: Int = -1 init() { println("Student has got a name as $name") } constructor(secname: String, id: Int) this(secname) { this.id = id } }
Q10:Kotlin 中伴随对象的用途是什么?
与 Java 或 C# 不同,Kotlin 没有static成员或成员函数。如果您需要编写一个可以在没有类实例的情况下调用但需要访问类内部的函数,您可以将其编写为该类中伴随对象声明的成员。
class EventManager { companion object FirebaseManager { } } val firebaseManager = EventManager.FirebaseManager
伴随对象是单例。伴生对象本身就是一个合适的对象,并且可以有自己的超类型 - 您可以将它分配给一个变量并传递它。如果您正在与 Java 代码集成并需要一个真正的静态成员,您可以使用@JvmStatic.
Q11:Kotlin 中的 Lateinit 是什么,你会在什么时候使用它?
lateinit表示后期初始化。如果您不想在构造函数中初始化变量,而是希望稍后对其进行初始化,并且如果您可以保证在使用它之前进行初始化,则使用 lateinit 关键字声明该变量。在初始化之前它不会分配内存。您不能将 lateinit 用于 Int、Long 等原始类型属性。
lateinit var test: String fun doSomething() { test = "Some value" println("Length of string is "+test.length) test = "change value" }
有一些用例非常有用,例如:
- Android:在生命周期方法中初始化的变量;
- 使用 Dagger 进行 DI:注入的类变量在外部初始化并且独立于构造函数;
- 单元测试的设置:测试环境变量在一个带@Before注释的方法中初始化;
- Spring Boot 注释(例如@Autowired)。
Q12:什么时候在 Kotlin 中使用 lateinit 而不是延迟初始化?
有一些简单的规则可以确定是否应该使用其中一个来进行属性初始化:
- 如果属性是可变的(即可能在稍后阶段更改)使用lateInit
- 如果属性是在外部设置的(例如需要传入一些外部变量来设置它),请使用lateinit。仍然有使用惰性但不那么直接的解决方法。
- 如果它们只打算初始化一次并由所有人共享,并且它更多地在内部设置(取决于类内部的变量),那么使用lazy。从战术上讲,您仍然可以使用 lateinit,但使用lazy 会更好地封装您的初始化代码。
还比较:
lateinit var | lazy var |
---|---|
可以从看到对象的任何地方初始化。 | 只能从初始化程序 lambda 初始化。 |
可以进行多次初始化。 | 只初始化一次。 |
非线程安全。在多线程环境中正确初始化取决于用户。 | 默认情况下是线程安全的,并保证初始化程序被调用一次。 |
只能用于 var。 | 只能用于 val。 |
不符合非空属性的条件。 | 不符合非空属性的条件。 |
添加了一个 isInitialized 方法,用于检查该值之前是否已初始化。 | 属性永远无法取消初始化。 |
不允许在原始类型的属性上使用。 | 允许在原始类型的属性上。 |
Q13:Kotlin 中的协程是什么?
与许多其他具有类似功能的语言不同,async 和 await 不是 Kotlin 中的关键字,甚至不是其标准库的一部分。
kotlinx.coroutines是由 JetBrains 开发的丰富的协程库。它包含许多支持协程的高级原语,包括launch等async。Kotlin Coroutines 为您提供了一个 API 来按顺序编写异步代码。
文档说 Kotlin 协程就像轻量级线程。它们是轻量级的,因为创建协程不会分配新线程。相反,它们使用预定义的线程池和智能调度。调度是确定您接下来将执行哪一项工作的过程。
此外,协程可以在执行过程中暂停和恢复。这意味着您可以有一个长时间运行的任务,您可以一点一点地执行它。您可以暂停它任意次数,并在您再次准备好时恢复它。
Q14:暂停和阻塞有什么区别?
对函数的阻塞调用意味着从同一线程调用任何其他函数将停止父函数的执行。接下来,这意味着如果您在主线程的执行上进行阻塞调用,您将有效地冻结 UI。在阻止呼叫完成之前,用户将看到一个静态屏幕,这不是一件好事。
暂停不一定会阻止您的父函数的执行。如果您在某个线程中调用挂起函数,您可以轻松地将该函数推送到不同的线程。如果是繁重的操作,它不会阻塞主线程。如果挂起函数必须挂起,它将简单地暂停其执行。这样你就可以释放它的线程来做其他工作。一旦完成挂起,它将从池中获取下一个空闲线程,以完成其工作。
Q15:Kotlin 中的 Java 静态方法等价物是什么?
将函数放在伴随对象中。
class Foo { public static int a() { return 1; } }
会变成:
class Foo { companion object { fun a() : Int = 1 } } // to run Foo.a();
另一种方法是用包级函数解决大部分静态函数的需求。它们只是在源代码文件中的类之外声明。可以在文件的开头使用 package 关键字指定文件的包。在引擎盖下,这些“顶级”或“包”函数实际上被编译到它们自己的类中。在上面的示例中,编译器将创建一个具有所有顶级属性和函数的类 FooPackage,并适当地将所有引用路由到它们。
考虑:
package foo fun bar() = {}
用法:
import foo.bar
Q16:解释 Kotlin 中“when”与“switch”的优势
在 Java 中,我们使用 switch,但在 Kotlin 中,该 switch 被转换为when。什么时候有更好的设计。它比传统的开关更加简洁和强大。when既可以用作表达式,也可以用作语句。
使用时的一些示例:
- 两个或多个选择
when(number) { 1 -> println("One") 2, 3 -> println("Two or Three") 4 -> println("Four") else -> println("Number is not between 1 and 4") }
- 没有参数的“when”
when { number < 1 -> print("Number is less than 1") number > 1 -> print("Number is greater than 1") }
- “when”中传入的任何类型
fun describe(obj: Any): String = when (obj) { 1 -> "One" "Hello" -> "Greeting" is Long -> "Long" !is String -> "Not a string" else -> "Unknown" }
- Smart casting
when (x) { is Int -> print("X is integer") is String -> print("X is string") }
- Ranges
when(number) { 1 -> println("One") //statement 1 2 -> println("Two") //statement 2 3 -> println("Three") //statement 3 in 4..8 -> println("Number between 4 and 8") //statement 4 !in 9..12 -> println("Number not in between 9 and 12") //statement 5 else -> println("Number is not between 1 and 8") //statement 6 }
Q17:Kotlin 相对于 Java 的优势是什么?
基本上对我来说,编写与大多数 java 代码等效的 kotlin 所需的思考更少:data class
- java:你必须为每件事编写getter和setter,你必须hashCode正确编写(或让IDE自动生成,每次更改类时都必须再次执行),toString(与 相同的问题hashcode)和equals(与 相同的问题hashCode) . 或者你可以使用 lombok,但这会带来一些奇怪的问题。record类型有望在路上。*kotlin:data class为你做这一切。
getter 和 setter 模式
java:为您使用它的每个变量重写getter和setter
kotlin:不必编写 getter 和 setter,如果您愿意,自定义 getter 和 setter 可以减少在 kotlin 中的输入。相同的 getter\setter 也存在代表
abstract与open类
java:你必须做一个抽象类实现
kotlin:open class让你创建一个可继承的类,同时它本身也可以使用。界面和普通类imo的完美结合
扩展功能
java: 不存在
kotlin:确实存在,让功能在使用上更清晰,感觉更自然。
无效的
java:除了原语之外的任何东西都可以随时为空。
kotlin:你可以决定什么可以为空,什么不能为空。允许美好的事情,比如inline class
singleton
java: 记住单例模式
kotlin:object代替class
generics
java: 他们还好,没什么特别的
kotlin:具体化泛型(您可以访问实际类型),in并out用于协变
命名参数
java: 不存在,一不小心就容易破坏 api 的向后兼容性。
kotlin:确实存在,易于保持 api 向后兼容。
主构造函数
java:本身没有,您仍然必须在类中定义所有内容
kotlin:非常高兴能够快速编写构造函数而无需任何构造函数或额外的不必要的声明
Q18:Kotlin 有哪些缺点?
有些人认为 Kotlin 是一堆额外的语法和关键字。以下是一些含义不明显的关键字:internal、crossinline、expect、reified、sealed、inner、open。Java 没有这些。Kotlin 的关键字也很有趣地不一致:一个函数是用 'fun' 声明的,但一个接口是用 'interface' 声明的(不是 'inter'?)。Kotlin 也没有检查异常。已检查的异常已经过时,但许多人(包括我)发现它们是确保您的代码健壮的有效方法。最后,Kotlin 隐藏了很多发生的事情。在 Java 中,您几乎可以跟踪程序逻辑的每一步。这对于寻找错误至关重要。在 Kotlin 中,如果你定义了一个数据类,那么 getter、setter、相等测试、to string 和 hash code 都会为你无形地添加。这可能是个坏主意。
同样根据文档,Java 有 Kotlin 没有的:
- 已检查的异常
- 不是类的原始类型
- 静态成员
- 非私有领域
- 通配符类型
- 三元运算符 a ? b:c
Q19:Kotlin 中的“open”和“public”有什么区别?
- open关键字的意思是“为扩展而开放” 。类上的 open 注释与 Java 的相反final:它允许其他人从该类继承。
- 如果您不指定任何可见性修饰符,则默认使用public,这意味着您的声明将在任何地方可见。如果没有明确指定其他内容,则public是默认值。
Q20:“const”和“val”有什么区别?
consts 是编译时常量。这意味着它们的值必须在编译时分配,不像vals,它可以在运行时完成。
这意味着,const永远不能将 s 分配给函数或任何类构造函数,而只能分配给 aString或原语。
例如:
const val foo = complexFunctionCall() //Not okay val fooVal = complexFunctionCall() //Okay const val bar = "Hello world" //Also okay
Q21:List 和 Array 类型有什么区别?
与使用方面的主要区别在于Arrays具有固定大小,而(Mutable)List可以动态调整其大小。此外Array是可变的,而List不是。
此外,还有kotlin.collections.List一个由 实现的接口java.util.ArrayList。kotlin.collections.MutableList当需要允许修改项目的集合时,它也被扩展为使用。
在 jvm 级别Array上由数组表示。List另一方面,java.util.List因为在 Java 中没有可用的不可变集合等价物,所以由 表示。
Q22:你什么时候会在 Kotlin 中使用 Elvis 运算符?
Elvis 运算符是许多编程语言的一部分,例如 Kotlin 以及 Groovy 或 C#。Elvis 运算符是省略了第二个操作数的三元运算符。
x ?: y // yields `x` if `x` is not null, `y` otherwise.
如果x不为空,则返回。如果为 null,则将y返回 。
Q23:用 Kotlin 重写这段代码
你能用 Kotlin 重写这段 Java 代码吗?
public class Singleton { private static Singleton instance = null; private Singleton(){ } private synchronized static void createInstance() { if (instance == null) { instance = new Singleton(); } } public static Singleton getInstance() { if (instance == null) createInstance(); return instance; }
使用 Kotlin:
object Singleton
Q24:如何建议在 Kotlin 中创建常量?
在 Kotlin 中,如果你想创建应该在类中使用的局部常量,那么你可以像下面这样创建它:
val MY_CONSTANT_1 = "Constants1" // or const val MY_CONSTANT_2 = "Constants2"
像,用关键字val定义的变量是不可变的。const这里的区别在于const 用于在编译时已知的变量。
还要避免使用伴随对象。在后台,为可访问的字段创建了 getter 和 setter 实例方法。调用实例方法在技术上比调用静态方法更昂贵。而是在 中定义常量object:
object DbConstants { const val TABLE_USER_ATTRIBUTE_EMPID = "_id" const val TABLE_USER_ATTRIBUTE_DATA = "data" }
Q25:你可以互换使用 IntArray 和 Kotlin 中的 Array 吗?
Array<int>是一个Integer[]在引擎盖下,IntArray而是一个int[]。</int>
这意味着当您将 an 放入Int中时Array<int>,它将始终被装箱(特别是,带有Integer.valueOf()调用)。在 的情况下IntArray,不会发生装箱,因为它会转换为 Java 原始数组。</int>
所以不,我们不能互换使用它们。
Q26:什么是 Kotlin double-bang (!!) 运算符?
非空断言运算符 !! 将任何值转换为非 null 类型,KotlinNullPointerException如果值为 null,则引发异常。
考虑:
fun main(args: Array<String>) { var email: String? email = null println(email!!) }
此运算符应在开发人员保证的情况下使用 - 它允许您 100% 确定其值不为空。
Q27:函数中Unit-return的目的是什么?为什么 VALUE 在那里?这个价值是多少?
fun printHello(name : String?) : Unit { if (name != null) print("Hello, $name!") else print("Hi there!") // We don't need to write 'return Unit.VALUE' or 'return', although we could }
目的与 C 或 Java 相同void。只有 Unit 是正确的类型,所以它可以作为泛型参数等传递。
为什么我们不称它为“Void”:因为“void”这个词的意思是“无”,还有另一种类型,Nothing即“根本没有价值”,即计算没有正常完成(永远循环或抛出一个例外)。我们无法承受意义的冲突。
为什么 Unit 有值(即与 Nothing 不同):因为通用代码可以顺利运行。如果您为通用参数 T 传递 Unit,则为任何 T 编写的代码都需要一个对象,并且必须有一个对象,即 Unit 的唯一值。
如何访问 Unit 的值:因为它是一个单例对象,所以说Unit
UNIT实际上包含有价值的信息,它基本上只是意味着“完成”。它只是将信息返回给调用者,即方法已完成。
Q28:你会如何使用“apply”重构这段代码?
考虑:
class Message(message: String, signature: String) { val body = MessageBody() init { body.text = message + "\n" + signature } }
您是否看到任何可以进行的重构?
你可以写:
class Message(message: String, signature: String) { val body = MessageBody().apply { text = message + "\n" + signature } }
Q29:什么是 Kotlin 中的内联类,我们什么时候需要一个?举个例子。
有时,业务逻辑需要围绕某种类型创建包装器。但是,由于额外的堆分配,它会引入运行时开销。此外,如果包装的类型是原始类型,那么性能损失会很严重,因为原始类型通常由运行时进行大量优化。
内联类为我们提供了一种包装类型的方法,从而添加功能并自行创建新类型。与常规(非内联)包装器相反,它们将受益于改进的性能。发生这种情况是因为数据被内联到其用法中,并且在生成的编译代码中跳过了对象实例化。
inline class Name(val s: String) { val length: Int get() = s.length fun greet() { println("Hello, $s") } } fun main() { val name = Name("Kotlin") name.greet() // method `greet` is called as a static method println(name.length) // property getter is called as a static method }
关于内联类的一些注意事项:
- 在主构造函数中初始化的单个属性是内联类的基本要求
- 内联类允许我们像普通类一样定义属性和函数
- 不允许初始化块、内部类和支持字段
- 内联类只能从接口继承
- 内联类也是有效的最终类
Q30:什么是 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并取消已启动的作业。
Q31:你会如何覆盖 Kotlin 数据类的默认 getter?
给定以下 Kotlin 类:
data class Test(val value: Int)
0如果值为负,我将如何覆盖 Int getter 以便它返回?
在使用错误值调用构造函数之前,让创建数据类的业务逻辑将值更改为 0 或更大。对于大多数情况,这可能是最好的方法。
不要使用data cla***常规的class.
class Test(value: Int) { val value: Int = value get() = if (field < 0) 0 else field override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is Test) return false return true } override fun hashCode(): Int { return javaClass.hashCode() } }
在执行您想要的操作的对象上创建一个额外的安全属性,而不是拥有一个被有效覆盖的私有值。
data class Test(val value: Int) { val safeValue: Int get() = if (value < 0) 0 else value }
Q32:如何在 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) }
Q33:什么是 Kotlin 中的对象表达式以及何时使用它们?
有时我们需要创建一个稍加修改的类的对象,而不是显式地为它声明一个新的子类。Java 使用匿名内部类来处理这种情况。Kotlin 使用对象表达式来实现相同的功能。我们甚至可以通过实现它们的抽象方法来为接口或抽象类创建对象表达式。
它通常用作 Java 匿名类的替代品:
window.addMouseListener(object : MouseAdapter() { override fun mouseClicked(e: MouseEvent) { // ... } override fun mouseEntered(e: MouseEvent) { // ... } })
Q34:那个代码有什么问题?
假设我想重写 Int getter,以便在数据类的值为负时返回 0。这种方法有什么不好?
data class Test(private val _value: Int) { val value: Int get() = if (_value < 0) 0 else _value }
这种方法的问题在于,数据类并不真正意味着像这样改变数据。它们实际上只是用于保存数据。像这样覆盖数据类的 getter 意味着Test(0)andTest(-1)不会彼此相等并且会有不同hashCodes的 ,但是当你调用 时.value,它们会得到相同的结果。这是不一致的,虽然它可能对您有用,但您团队中的其他人看到这是一个数据类,可能会不小心误用它。
Q35:Kotlin 泛型中的“*”和“Any”有什么区别?
List<*>
可以包含任何类型的对象,但只能包含该类型,因此它可以包含Strings(但只能Strings)whileList<Any>
可以包含Strings和Integers诸如此类,都在同一个列表中
Q36:假设您将代码从 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") } }
Q37: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 做同样的事情相比。
Q38:Kotlin 协程中的 launch/join 和 async/await 有什么区别?
launch用于触发并忘记协程。这就像开始一个新线程。如果内部的代码因launch异常而终止,则将其视为线程中未捕获的异常——通常在后端 JVM 应用程序中打印到 stderr 并使 Android 应用程序崩溃。join用于等待启动的协程完成,并且不会传播其异常。但是,崩溃的子协程也会取消其父协程,并出现相应的异常。
async用于启动计算某些结果的协程。结果由 的实例表示Deferred,您必须在其上使用await。异步代码中未捕获的异常存储在结果Deferred中,不会传递到其他任何地方,除非处理,否则它将被静默丢弃。你一定不要忘记你用异步启动的协程。
Q39:如何在 Kotlin 中实现 Builder 模式?
首先,在大多数情况下,您不需要在 Kotlin 中使用构建器,因为我们有默认和命名参数,但如果您需要使用:
class Car( //add private constructor if necessary val model: String?, val year: Int ) { private constructor(builder: Builder) : this(builder.model, builder.year) class Builder { var model: String? = null private set var year: Int = 0 private set fun model(model: String) = apply { this.model = model } fun year(year: Int) = apply { this.year = year } fun build() = Car(this) } }
用法:
val car = Car.Builder().model("X").build()
感谢🙌阅读并祝你面试好运!
公众号:Android Jasper 专注分享面试题|面试技巧|Android学习资料。
#Android##面试##面试题#