# 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
}
webapp
公众号
开发者导航
切换夜间模式
点击侧边栏上一篇
点击侧边栏下一篇
折叠侧边栏
收起全部