# Android 原生面试题精选
覆盖语言基础、系统原理、
UI、并发、架构、性能、安全、测试与发布。每题都有 30 秒速记、代码示例、详细解析和面试官追问,流程类题目配了时序图。
# 1 Java 与 JVM 基础
# 1. Java 泛型擦除是什么?Kotlin 里怎么在运行时拿到泛型类型?
⚡ 30 秒速记
- 擦除 = 泛型只在编译期检查,编译完
List<String>和List<Int>都是同一个List类 - 后果:不能
is List<String>、不能T::class、不能new T(),as List<String>只给警告,真错在取元素时才炸 - 类、字段、方法签名上的泛型会留在
Signature属性里,Gson的TypeToken就是靠匿名子类读父类签名 Kotlin用inline+reified把类型在调用处「抄」进去,本质是编译器替你传了Class- 开
R8混淆要保留Signature,不然TypeToken拿到的是原始类型,解析出LinkedTreeMap然后类型转换崩溃
泛型擦除就是编译器做完类型检查后,把类型参数擦成上界(默认 Object),运行时 List<String> 和 List<User> 是同一个东西。 这么设计是为了兼容 Java 5 以前没有泛型的字节码。日常最常碰到的坑是 Gson 解析列表:传 List::class.java 进去,Gson 不知道元素是什么,只能给你一堆 LinkedTreeMap,到取元素当 User 用的时候才抛 ClassCastException。解决办法要么用 TypeToken 匿名子类把类型留在签名里,要么在 Kotlin 里写 inline fun <reified T>,让编译器在调用处把具体类型填进去。
# 擦掉的是什么,留下的是什么
打个比方,泛型像快递单上的「易碎品」标签,分拣中心(编译器)看一眼就撕了,货车上(运行时)的箱子都长一个样。所以运行时你问一个对象「你是不是 List<String>」,它答不上来,Kotlin 直接不让你这么写:
fun check(x: Any) {
// if (x is List<String>) {} // 编译报错:Cannot check for instance of erased type
if (x is List<*>) println("是个 List,元素类型不知道")
}
fun main() {
val raw: Any = listOf(1, 2, 3)
@Suppress("UNCHECKED_CAST")
val strs = raw as List<String> // 只有警告,这行不会崩
println(strs.size) // 输出 3,也不会崩
val s: String = strs[0] // 这里才抛 ClassCastException:Integer cannot be cast to String
}