13. Android 面了50位Kotlin候选人,这36个语法坑90%的人答不全

Q1:Kotlin 中 var 和 val 的本质区别?Java 对应什么?

答案

  1. var可变变量,生成getter+setter,对应Java普通成员变量
  2. val只读变量 (并非不可变),仅生成getter,无setter,对应Java final 变量
  3. 编译期常量const val:编译时确定值,只能定义在顶层/object中,必须是基本类型/字符串

流程图

graph TD A[声明变量] --> B{var/val} B -->|var| C[可变, 有get+set] B -->|val| D[只读, 仅有get] D --> E{是否加const} E -->|是| F[编译期常量, 顶层/object] E -->|否| G[运行时常量, 类成员]

精简源码

kotlin 复制代码
var name: String = "Android"
name = "Kotlin"

val age: Int = 18
// age = 20 编译报错

const val TAG = "Main"

Q2:Kotlin 空安全机制的核心规则??.、!!、?: 的用法

答案

  1. Kotlin 默认非空 ,可空类型必须加?
  2. ?.:安全调用,对象为空则不执行,返回null
  3. !!:非空断言,强制认定对象非空,为空则抛NPE
  4. ?::Elvis 操作符,为空时使用默认值

流程图

graph TD A[可空对象调用] --> B{使用?.} B -->|非空| C[执行方法/属性] B -->|空| D[返回null] A --> E{使用!!} E -->|非空| F[正常执行] E -->|空| G[抛出NPE] A --> H{使用?:} H -->|空| I[返回右侧默认值]

精简源码

kotlin 复制代码
var str: String? = null
val len1 = str?.length // null
// val len2 = str!!.length // NPE
val len3 = str?.length ?: 0 // 0

Q3:Kotlin 延迟初始化 lateinit 和 lazy 的区别?

答案

特性 lateinit lazy
适用类型 var,引用类型 val,任意类型
初始化时机 手动赋值 首次访问时自动
线程安全 无(需自己保证) 默认线程安全
检查初始化 ::property.isInitialized 无需检查

流程图

graph TD A[延迟初始化] --> B[lateinit var] B --> C[可变, 手动赋值, 引用类型] C --> D[必须初始化后使用] A --> E[by lazy] E --> F[只读, 首次调用初始化] F --> G[线程安全, 仅val]

精简源码

kotlin 复制代码
lateinit var context: String
fun init() { context = "test" }

val data: String by lazy {
    println("初始化")
    "加载完成"
}

Q4:Kotlin 函数默认参数、具名参数的作用?Java 如何调用?

答案

  1. 默认参数:指定默认值,调用时可省略,解决Java方法重载冗余
  2. 具名参数:调用时指定参数名,无序传参,提升可读性
  3. Java调用:需加@JvmOverloads生成重载方法

流程图

graph TD A[函数定义] --> B[带默认参数] B --> C[调用可省略参数] C --> D[具名参数无序传参] B --> E[Java调用] E --> F[需@JvmOverloads]

精简源码

kotlin 复制代码
@JvmOverloads
fun showInfo(name: String, age: Int = 18, sex: String = "男") {
    println("$name $age $sex")
}
showInfo("Tom")
showInfo("Jerry", sex = "女")

Q5:Kotlin 顶层函数、属性是什么?和 Java 的区别?

答案

  1. 顶层声明:直接写在.kt文件中,不属于任何类
  2. 编译后:生成文件名Kt的Java类,顶层成员变为静态成员
  3. Java调用:类名Kt.方法名()

流程图

graph TD A[Kotlin文件] --> B[顶层函数/属性] B --> C[编译为Java静态成员] C --> D[Kotlin直接调用] C --> E[Java: 类名Kt.调用]

精简源码

kotlin 复制代码
// Utils.kt
val APP_NAME = "KotlinDemo"
fun toast(msg: String) = println(msg)

// Java调用: UtilsKt.toast("test")

Q6:Kotlin 数据类 data class 的作用及限制?

答案

  1. 自动生成:equals()hashCode()toString()copy()componentN()
  2. 限制:主构造至少一个参数;参数必须val/var;不能为abstract/open/sealed/inner

流程图

graph TD A[data class] --> B[主构造含属性] B --> C[自动生成方法] C --> D[equals/hashCode] C --> E[toString] C --> F[copy/解构]

精简源码

kotlin 复制代码
data class User(val id: Int, val name: String)
val user = User(1, "张三")
val copy = user.copy(name = "李四")
val (id, name) = user // 解构

Q7:Kotlin 枚举类 enum 和 密封类 sealed 的区别?

答案

  • 枚举:实例唯一,无状态
  • 密封类 :子类可多实例、可携带状态,when表达式无需else

流程图

graph TD A[有限类型] --> B[enum] B --> C[实例唯一, 无状态] A --> D[sealed class] D --> E[子类可多实例, 可带状态] D --> F[when穷尽判断]

精简源码

kotlin 复制代码
enum class Color { RED, GREEN }

sealed class Result {
    data class Success(val data: String) : Result()
    data class Error(val msg: String) : Result()
}

Q8:Kotlin 扩展函数/扩展属性的原理?能否访问私有成员?

答案

  1. 原理:编译为静态方法,第一个参数为接收者对象
  2. 不能访问类的私有/保护成员

流程图

graph TD A[扩展函数] --> B[编译为静态方法] B --> C[第一参数为目标对象] C --> D[对象.方法()调用] A --> E[无法访问私有成员]

精简源码

kotlin 复制代码
fun String.lastChar(): Char = this[length - 1]
val c = "Kotlin".lastChar() // 'n'

Q9:Kotlin 高阶函数是什么?Lambda 表达式的简化规则?

答案

  1. 高阶函数:以函数作为参数或返回值的函数
  2. Lambda简化规则:
    • 最后一个lambda参数可移出括号外
    • 无参数时可省略()
    • 单个参数默认用it
    • 最后一行作为返回值

流程图

graph TD A[高阶函数] --> B[函数作为参数] B --> C[传入Lambda] C --> D[简化: 括号外写lambda] C --> E[单参数用it]

精简源码

kotlin 复制代码
fun doAction(block: (String) -> Int) {
    block("test")
}
doAction { it.length }

Q10:Kotlin 内联函数 inline 的作用?原理?

答案

  1. 作用:消除Lambda对象创建和栈帧开销,提升性能
  2. 原理:编译时将函数体及lambda代码复制到调用处,无方法调用

流程图

graph TD A[inline函数] --> B[编译期] B --> C[代码复制到调用处] C --> D[无对象创建, 无方法调用]

精简源码

kotlin 复制代码
inline fun measureTime(block: () -> Unit) {
    val start = System.currentTimeMillis()
    block()
    println(System.currentTimeMillis() - start)
}

Q11:Kotlin 中 Any、Unit、Nothing 的区别?

答案

  • Any:所有非空类型的,对应Java Object
  • Unit:表示无返回值,单例,对应Java void
  • Nothing永远没有返回值(抛异常/死循环),是所有类型的子类型

流程图

graph TD A[Kotlin基础类型] --> B[Any: 根父类] A --> C[Unit: 无返回值] A --> D[Nothing: 永不返回]

精简源码

kotlin 复制代码
fun anyFun(): Any = 123
fun unitFun(): Unit {}
fun nothingFun(): Nothing = throw Exception()

Q12:Kotlin 伴生对象 companion object 是什么?Java 如何调用?

答案

  1. 类级别的单例对象,替代Java静态成员
  2. Java调用:默认使用类名.Companion.方法(),加@JvmStatic可生成真正的静态方法

流程图

graph TD A[companion object] --> B[类级别单例] B --> C[替代Java静态成员] C --> D[Kotlin直接调用] C --> E[Java: @JvmStatic]

精简源码

kotlin 复制代码
class User {
    companion object {
        @JvmStatic val TAG = "User"
        fun show() {}
    }
}
// Java: User.TAG

中高级核心题(Q13~Q27)

Q13:Kotlin 解构声明的原理?适用场景?

答案

  1. 原理:调用component1()、component2()等方法
  2. 数据类自动生成,普通类需手动添加operator函数
  3. 场景:批量取值、函数多返回值

流程图

graph TD A[解构声明] --> B[调用componentN方法] B --> C[数据类自动生成] C --> D[val (a,b) = 对象]

精简源码

kotlin 复制代码
data class Point(val x: Int, val y: Int)
val p = Point(10, 20)
val (x, y) = p

Q14:Kotlin 中 == 和 === 的区别?

答案

  • ==内容比较(equals),空安全
  • ===引用比较(地址),判断是否同一对象

流程图

graph TD A[比较操作] --> B[== 内容比较] A --> C[=== 引用比较]

精简源码

kotlin 复制代码
val a = "123"
val b = "123"
println(a == b)  // true
println(a === b) // true

val s1 = String("abc".toCharArray())
val s2 = String("abc".toCharArray())
println(s1 === s2) // false

Q15:Kotlin 函数中 vararg 关键字的作用?

答案

  1. 可变参数,一个函数只能有一个
  2. 传数组时需用*展开运算符

流程图

graph TD A[vararg] --> B[传入任意个数参数] B --> C[编译为数组] C --> D[展开: *数组]

精简源码

kotlin 复制代码
fun add(vararg nums: Int) = nums.sum()
add(1,2,3)
val arr = intArrayOf(1,2)
add(*arr)

Q16:Kotlin 接口中默认方法、抽象方法、属性的规则?

答案

  • 接口可包含:抽象方法、默认实现方法、抽象属性
  • 接口不能存储状态(无幕后字段)

流程图

graph TD A[Kotlin接口] --> B[抽象方法/属性] A --> C[默认实现方法] C --> D[子类可选重写] B --> E[子类必须重写]

精简源码

kotlin 复制代码
interface ITest {
    val name: String
    fun absFun()
    fun defFun() = println("默认实现")
}

Q17:Kotlin 中 init 代码块、主构造、次构造的执行顺序?

答案 : 执行顺序:主构造参数 → init代码块 → 次构造函数

流程图

graph TD A[类实例化] --> B[主构造参数] B --> C[init块] C --> D[次构造]

精简源码

kotlin 复制代码
class Person(name: String) {
    init { println("init: $name") }
    constructor() : this("张三") { println("次构造") }
}

Q18:Kotlin 中 also、let、apply、run 函数的核心区别?

答案

函数 接收者 返回值 核心场景
let it lambda结果 空安全处理、转换值
run this lambda结果 对象计算、逻辑代码块
apply this 对象本身 对象初始化、配置属性
also it 对象本身 附加操作(日志、副作用)

流程图

graph TD A[标准函数] --> B[apply/run: this] A --> C[let/also: it] B --> D[apply返回自身] B --> E[run返回最后一行] C --> F[also返回自身] C --> G[let返回最后一行]

精简源码

kotlin 复制代码
val user = User().apply { name = "test" }
val result = user.let { it.name }

Q19:Kotlin 泛型的 out 和 in 关键字(型变)?

答案

  • out T协变 ,生产者,只能读取,不能写入(对应Java ? extends T
  • in T逆变 ,消费者,只能写入,不能读取(对应Java ? super T

流程图

graph TD A[泛型型变] --> B[out: 协变, 只读] A --> C[in: 逆变, 只写]

精简源码

kotlin 复制代码
interface Producer<out T> { fun get(): T }
interface Consumer<in T> { fun set(t: T) }

Q20:Kotlin 中 reified 关键字的作用?

答案

  1. 必须配合inline使用,具体化泛型
  2. 解决泛型擦除问题,可直接获取泛型类型T::class.java
  3. 场景:泛型类型判断、反射

流程图

graph TD A[reified] --> B[inline函数] B --> C[泛型不擦除] C --> D[获取T::class]

精简源码

kotlin 复制代码
inline fun <reified T> isType(obj: Any) = obj is T
isType<String>("hello") // true

Q21:Kotlin 闭包捕获变量的原理?

答案

  • 基本类型包装为Ref对象,引用类型直接传递
  • Lambda可修改外部变量(Java无法修改)

流程图

graph TD A[闭包] --> B[捕获外部变量] B --> C[基本类型包装为Ref] C --> D[Lambda可修改]

精简源码

kotlin 复制代码
fun test(): () -> Unit {
    var count = 0
    return { count++ }
}

Q22:Kotlin 中 const 和 @JvmField 的区别?

答案

特性 const val @JvmField
时机 编译期常量 运行时常量
支持类型 基本类型/String 任意类型
字节码 直接替换,无getter 暴露为Java公开字段,无getter/setter

流程图

graph TD A[常量] --> B[const: 编译期替换] A --> C[@JvmField: 暴露Java字段]

精简源码

kotlin 复制代码
const val MAX = 100
class Config {
    companion object {
        @JvmField val URL = "https://"
    }
}

Q23:Kotlin 运算符重载的原理?哪些运算符可以重载?

答案

  1. 原理:通过定义固定名称的operator函数实现,编译器将运算符转换为函数调用
  2. 常用可重载运算符:+(plus)、-(minus)、*(times)、/(div)、%(rem)、[](get/set)、()(invoke)、+=(plusAssign)、==(equals)等

流程图

graph TD A[运算符表达式] --> B[查找operator函数] B --> C[转换为函数调用]

精简源码

kotlin 复制代码
data class Vector(val x: Int, val y: Int) {
    operator fun plus(other: Vector) = Vector(x + other.x, y + other.y)
    operator fun get(index: Int) = if (index == 0) x else y
}
val v = Vector(1,2) + Vector(3,4)
println(v[0]) // 4

Q24:Kotlin 尾递归优化 tailrec 的原理及限制?

答案

  1. 原理:编译器将递归转换为循环(while),避免栈溢出
  2. 限制:
    • 递归调用必须是函数体的最后一条语句
    • 递归调用不能在try/catch/finally块中
    • 仅支持自身递归,不支持相互递归

流程图

graph TD A[tailrec函数] --> B{递归在最后?} B -->|是| C[编译为循环] B -->|否| D[编译错误]

精简源码

kotlin 复制代码
tailrec fun factorial(n: Int, acc: Int = 1): Int =
    if (n <= 1) acc else factorial(n - 1, acc * n)

Q25:typealias 与 inline class 有什么区别?各自适用场景?

答案

特性 typealias inline class (value class)
类型安全 否(可互相赋值) 是(不同类型不能混用)
运行时开销 大多数情况无(内联为基础类型)
可定义方法 否(仅别名) 是(可定义成员函数、实现接口)
适用场景 简化长泛型、函数类型名 创建轻量级领域类型(如ID、邮箱)

流程图

graph TD A[typealias] --> B[编译期替换, 无类型安全] C[inline class] --> D[包装值, 内联优化] --> E[类型安全+自定义行为]

精简源码

kotlin 复制代码
typealias UserId = Int
@JvmInline
value class ProductId(val id: Int) {
    fun display() = "Product#$id"
}

Q26:Kotlin 中 sequence(序列)与 Iterable 的区别?何时使用序列?

答案

  • Iterable:立即执行,每个操作产生中间集合,适合数据量小
  • Sequence:惰性求值,每个元素依次经过所有操作,不创建中间集合,适合大数据量或无限序列

流程图

graph TD A[Iterable] --> B[立即执行, 产生中间集合] C[Sequence] --> D[惰性, 末端操作才求值]

精简源码

kotlin 复制代码
val seq = sequenceOf(1,2,3,4,5)
    .filter { it % 2 == 0 }
    .map { it * it }
seq.toList() // 触发计算

Q27:Kotlin 委托属性(by lazy、by observable)如何工作?如何自定义委托?

答案

  • 委托属性:属性访问逻辑委托给另一个对象(实现getValue/setValue
  • lazy:首次访问时初始化,线程安全(默认LazyThreadSafetyMode.SYNCHRONIZED
  • observable:属性变化时触发回调
  • 自定义委托:实现ReadOnlyPropertyReadWriteProperty接口,重写getValue/setValue

流程图

graph TD A[属性委托] --> B[编译器生成代理对象] B --> C[调用getValue/setValue] D[by lazy] --> E[首次访问初始化并缓存] F[by observable] --> G[值改变时回调]

精简源码

kotlin 复制代码
class Example {
    val lazyVal: String by lazy { "Hello" }
    var observed: String by Delegates.observable("init") { _, old, new ->
        println("$old -> $new")
    }
}

// 自定义委托
class MyDelegate<T>(initial: T) {
    private var value = initial
    operator fun getValue(thisRef: Any?, property: KProperty<*>) = value
    operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: T) {
        value = newValue
    }
}
相关推荐
Hello--_--World1 小时前
Vite:什么是bundleless?哪些要打包,哪些不要打包?依赖预构建是什么?依赖预构建如何减少网络请求的?esbuild 又是什么?
前端·javascript·webpack·vite
Rooting++1 小时前
vue2+webpack打包优化的相关问题
前端·webpack·node.js
alxraves1 小时前
超声图像前端信号处理的关键技术
前端·fpga开发·信号处理
东宇科技1 小时前
用CladueCode来玩tp8+swoole(常用案例)
后端·swoole
问心无愧05131 小时前
ctf show web入门47
前端·笔记
web守墓人1 小时前
【神经网络】js版本的Pytorch,estorch重磅发布
前端·javascript·人工智能·pytorch·深度学习·神经网络
贫民窟的勇敢爷们1 小时前
Vue的渐进式特性,让前端开发更具灵活性
前端·javascript·vue.js
Shadow(⊙o⊙)1 小时前
硬核手搓解析!进程-内核分析:命令行参数及环境变量,重构main()
linux·运维·服务器·开发语言·c++·后端·学习
问心无愧05131 小时前
ctf show web入门81
前端·笔记