Q1:Kotlin 中 var 和 val 的本质区别?Java 对应什么?
答案:
var:可变变量,生成getter+setter,对应Java普通成员变量val:只读变量 (并非不可变),仅生成getter,无setter,对应Javafinal变量- 编译期常量
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 空安全机制的核心规则??.、!!、?: 的用法
答案:
- Kotlin 默认非空 ,可空类型必须加
? ?.:安全调用,对象为空则不执行,返回null!!:非空断言,强制认定对象非空,为空则抛NPE?::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 如何调用?
答案:
- 默认参数:指定默认值,调用时可省略,解决Java方法重载冗余
- 具名参数:调用时指定参数名,无序传参,提升可读性
- 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 的区别?
答案:
- 顶层声明:直接写在
.kt文件中,不属于任何类 - 编译后:生成
文件名Kt的Java类,顶层成员变为静态成员 - 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 的作用及限制?
答案:
- 自动生成:
equals()、hashCode()、toString()、copy()、componentN() - 限制:主构造至少一个参数;参数必须
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 扩展函数/扩展属性的原理?能否访问私有成员?
答案:
- 原理:编译为静态方法,第一个参数为接收者对象
- 不能访问类的私有/保护成员
流程图:
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 表达式的简化规则?
答案:
- 高阶函数:以函数作为参数或返回值的函数
- 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 的作用?原理?
答案:
- 作用:消除Lambda对象创建和栈帧开销,提升性能
- 原理:编译时将函数体及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 ObjectUnit:表示无返回值,单例,对应Java voidNothing:永远没有返回值(抛异常/死循环),是所有类型的子类型
流程图:
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 如何调用?
答案:
- 类级别的单例对象,替代Java静态成员
- 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 解构声明的原理?适用场景?
答案:
- 原理:调用
component1()、component2()等方法 - 数据类自动生成,普通类需手动添加
operator函数 - 场景:批量取值、函数多返回值
流程图:
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 关键字的作用?
答案:
- 可变参数,一个函数只能有一个
- 传数组时需用
*展开运算符
流程图:
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 关键字的作用?
答案:
- 必须配合
inline使用,具体化泛型 - 解决泛型擦除问题,可直接获取泛型类型
T::class.java - 场景:泛型类型判断、反射
流程图:
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 运算符重载的原理?哪些运算符可以重载?
答案:
- 原理:通过定义固定名称的
operator函数实现,编译器将运算符转换为函数调用 - 常用可重载运算符:
+(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 的原理及限制?
答案:
- 原理:编译器将递归转换为循环(while),避免栈溢出
- 限制:
- 递归调用必须是函数体的最后一条语句
- 递归调用不能在
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:属性变化时触发回调- 自定义委托:实现
ReadOnlyProperty或ReadWriteProperty接口,重写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
}
}