Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

01 Kotlin 语言核心

Kotlin 是现代 Android 的第一语言。你会用,但要把“会用“升级到“讲得清原理“。本篇覆盖中级面试高频语法点的底层机制

一、空安全(Null Safety)

Kotlin 把 null 检查提前到编译期。

  • val a: String 不可空;val b: String? 可空。
  • 安全调用 b?.length:b 为 null 时整体返回 null。
  • Elvis b?.length ?: 0:为 null 时给默认值。
  • 非空断言 b!!:为 null 时抛 NPE(慎用)。
  • 平台类型 String!:来自 Java 的类型,Kotlin 不知其可空性,调用方负责

易错:lateinit var 用于非空且延迟初始化(只能用于 var、非基本类型),访问前未初始化抛 UninitializedPropertyAccessException,可用 ::x.isInitialized 判断。by lazy 用于 val,线程安全延迟初始化。

二、扩展函数 / 扩展属性

fun String.lastChar(): Char = this[length - 1]

原理(高频追问):扩展函数编译成静态方法,接收者作为第一个参数传入。所以:

  • 扩展函数是静态分发,不是多态——调用哪个由声明类型决定,不是运行时类型。
  • 不能真正修改类、不能访问 private 成员。
  • 扩展属性没有 backing field,只能定义 get/set。

三、高阶函数与 inline

inline fun <T> measure(block: () -> T): T { ... }
  • Lambda 默认会被编译成 Function 对象,有对象创建开销。
  • inline 把函数体和 lambda 直接内联到调用处,消除对象分配,还能让 lambda 内的 return 直接返回外层函数(非局部返回)。
  • noinline:某个 lambda 参数不内联。
  • crossinline:禁止该 lambda 非局部返回(用于会在别处调用的场景)。
  • reified:配合 inline,让泛型类型在运行时可见(T::class),解决泛型擦除。
  • 边界:reified 只让内联函数调用点能拿到 T 的运行时类型,不能恢复集合元素的完整泛型实参;例如仍无法把 List<String>List<Int> 的元素类型当作普通运行时类型安全区分。

四、作用域函数(let/run/with/apply/also)

函数引用对象返回值典型用途
letitlambda 结果非空判断后操作 x?.let { }
runthislambda 结果配置对象并计算结果
withthislambda 结果对一个对象多次操作(非扩展)
applythis对象本身初始化配置 Paint().apply { }
alsoit对象本身副作用(打日志)不改链式

记忆法:返回结果用 let/run/with,返回自身用 apply/also;用 it 是 let/also,用 this 是 run/with/apply。

五、class 家族

  • data class:自动生成 equals/hashCode/toString/copy/componentN。注意 copy 是浅拷贝;只有主构造参数参与生成。
  • sealed class / sealed interface:密封类型,子类受限在同一模块。配合 when 可穷尽分支(无需 else),适合表达状态(Loading/Success/Error)。
  • object:单例;companion object 伴生对象(类级别成员,可实现接口、可命名)。
  • enum:枚举,可带属性和方法。
  • 嵌套 vs 内部类:Kotlin 嵌套类默认是静态的;加 inner 才持有外部类引用。

六、委托(Delegation)

  • 类委托:class B(b: Base) : Base by b,把接口实现委托给成员,组合优于继承。
  • 属性委托:
    • by lazy { }:首次访问时计算,默认 SYNCHRONIZED 线程安全。
    • Delegates.observable:值变化时回调。
    • Delegates.notNull():非空但延迟赋值。
    • by map:从 Map 读取属性。
    • 自定义委托需实现 getValue / setValue

七、泛型型变

  • out T(协变):只能作为输出(生产者),List<out T>,List<String> 可赋给 List<Any>
  • in T(逆变):只能作为输入(消费者),Comparator<in T>
  • * 星投影:不关心具体类型时使用。
  • PECS:Producer-Extends(out),Consumer-Super(in)。面试可用一句话落地:只从容器里读 Tout,只往容器里写 Tin;既要读又要写具体 T 时通常不要加型变,否则编译器会限制不安全操作。

高频面试题

Q1:===== 的区别? == 比较值(调用 equals),=== 比较引用。Java 的 == 对应 Kotlin 的 ===

Q2:lateinitby lazy的区别? lateinit 用于 var、非空、可多次赋值、不能用于基本类型、由开发者负责初始化时机;lazy 用于 val、首次访问自动初始化、线程安全可配置。

Q3:扩展函数能被重写吗?为什么? 不能。扩展函数是静态分发,编译成静态方法,调用哪个由声明类型决定而非运行时类型,所以没有多态。

Q4:inline 一定能提升性能吗? 不一定。inline 消除 lambda 对象分配,适合高阶函数;但内联会增大字节码,对大函数滥用反而增加体积、降低性能。Kotlin 编译器会对大 inline 函数告警。

Q5:Kotlin 的 UnitNothingAny 区别? Any 是所有非空类型的根(类比 Object);Unit 表示无返回值(类比 void,但是真实对象);Nothing 表示永不返回(抛异常或死循环),是所有类型的子类型。

Q6:data class 用作 HashMap 的 key 安全吗? 安全(自动生成了 hashCode/equals),但若字段可变,作为 key 后修改字段会导致查找失败 —— 应保证 key 不可变。