1 什么是 Kotlin,它有哪些特性?
Kotlin 和 Java 一样是 JVM 编程语言,它们的代码最终都会被编译成 .class 文件(JVM 字节码)。
Kotlin 的特性主要有以下几点:
- Lambda 表达式,函数式编程: Lambda 表达式并不是 Kotlin 特有的,Java 中也有,但是有限制
- Java 中的 Lambda 表达式主要用于简化单抽象方法的接口。
- 这是因为 Java 的系统类型需要明确的类型信息,Lambda 表达式要和特定的函数式接口(仅包含一个抽象方法的接口 SAM Single Abstract Method Interface)类型匹配;
 
 - Kotlin 中的 Lambda 表达式同样支持单抽象方法的接口,但更推荐使用闭包来实现。
- 闭包是一个可以捕获其所在上下文变量的代码块,在 Kotlin 中,闭包可以独立存在,不依赖特定的接口;
 
 
 - Java 中的 Lambda 表达式主要用于简化单抽象方法的接口。
 - 扩展
- 扩展属性: 允许开发者为现有的类添加新的属性,不过并不是真正的给类添加成员变量,而是提供了自定义的 
getter(val)和setter(var)方法。- 语法形式为:
val ClassName.属性名: 类型 
 - 语法形式为:
 - 扩展方法: 允许开发者为现有的类添加新的函数。
- 语法形式为:
fun ClassName.函数名(): 类型 
 - 语法形式为:
 
 - 扩展属性: 允许开发者为现有的类添加新的属性,不过并不是真正的给类添加成员变量,而是提供了自定义的 
 - 默认参数,减少方法重载。
 - 判空语法:
- 安全调用操作符 
?. - Elvis(埃尔维斯,猫王)操作符 
?: - 非空断言操作符 
!! 
 - 安全调用操作符 
 
Lambda 表达式:
            
            
              java
              
              
            
          
          // Java 示例:匿名内部类实现 OnClickListener
button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // 处理点击事件
    }
});
// 使用 Lambda 表达式简化
button.setOnClickListener(v -> {
    // 处理点击事件
});
// Kotlin 的转换 (OnClickListener 是 Java 定义的接口)
button.setOnClickListener {
  	// 处理点击事件
}
        扩展:
            
            
              kotlin
              
              
            
          
          // 扩展属性
val MutableList<Int>.sum: Int
    get() = this.sum()
val numbers = mutableListOf(1, 2, 3, 4, 5)
println(numbers.sum)
// 扩展函数
fun String.reverseString(): String {
    return this.reversed()
}
val str = "Hello"
val reversedStr = str.reverseString()
println(reversedStr)
        默认参数:
            
            
              kotlin
              
              
            
          
          // Kotlin 默认参数
fun showToast(
		message: String,
  	duration: Int = Toast.LENGTH_SHORT // 默认值
) {
  	
}
showToast("Hello") // 使用默认 duration
showToast("Hi", Toast.LENGTH_LONG) // 显式指定 duration
        判空语法:
            
            
              kotlin
              
              
            
          
          val length: Int? = nullableStr?.length // 若 nullableStr 为 null,length 也为 null
val lengthOrDefault: Int = nullableStr?.length ?:0 // 若为 null,返回 0
val forcedLength: Int = nullableStr!!.length // 慎用!可能引发崩溃
        2 Kotlin 中注解 @JvmOverloads 的作用
在有参数默认值的的方法上加上 @JvmOverloads 注解,Kotlin 就会暴露出多个重载方法。
这样 Java 代码就能像调用 Java 重载方法一样调用 Kotlin 函数,从而间接利用默认参数的功能。
            
            
              kotlin
              
              
            
          
          @JvmOverloads
fun foo(a: Int = 0, b: Boolean, c: String = "abc") {
  
}
        生成重载方法:
            
            
              java
              
              
            
          
          foo()
foo(a: Int)
foo(a: Int, b: Boolean)
foo(a: Int, b: Boolean, c: String)
        3 Kotlin 中的 List 和 MutableList 的区别
Kotlin 将集合分为可变集合(MutableList)和只读集合(List)。
MutableList:可变集合接口,允许对集合中的元素进行添加、删除、修改等操作。
List:只读集合接口(线程安全),一旦创建,其元素的数量和内容都不能被修改。
只读集合与可变集合之间的转变:
            
            
              kotlin
              
              
            
          
          // 只读 -> 可变
val list = listOf(1, 2, 3)
val mutableList = list.toMutableList() // 新对象
mutableList.add(4)
// 可变 -> 只读
val mutable = mutableListOf(1, 2, 3)
val readOnly = mutable.toList()
        只读集合可变的情况:
            
            
              kotlin
              
              
            
          
          val writeList: MutableList<Int> = mutableListOf(1, 2, 3, 4)
val readList: List<Int> = writeList
println(readList) // [1, 2, 3, 4]
writeList[0] = 0
println(readList) // [0, 2, 3, 4]
        4 Kotlin 中实现单例的几种常见方式
- 懒汉式,线程安全: 通过 私有构造函数 + 
companion object(伴生对象)和lazy实现懒加载,首次访问才进行初始化;lazy默认使用LazyThreadSafetyMode.SYNCHRONIZED([ˈsɪŋkrənaɪzd]),这是lazy()委托的一种线程安全模式,用于确保在多线程环境下延迟初始化只执行一次
 - 懒汉式,双重检验锁(Double - Checked Locking): 通过 私有构造函数 + 
companion object(伴生对象)、volatile、synchronized实现,首次访问时才进行初始化;- 通过 
@Volatile([ˈvɒlətaɪl]) 保证变量的可见性(对于变量的写操作会立即刷新到主内存中去),同时@Volatile禁止指令重排序; - 通过同步代码块(
synchronized)来确保线程的安全; 
 - 通过 
 - 静态内部类: 通过 私有构造函数 + 静态内部类 + 
companion object(伴生对象)+by lazy来实现。静态内部类只有在首次访问时才进行初始化,由 JVM 保证线程安全,确保静态变量 INSTANCE 只被初始化一次;- Kotlin 的类加载机制保证线程安全;
 
 - 饿汉式: 通过 
Object关键字来实现,像"饿汉"一样,在加载时就被创建(立即被创建)。是 Kotlin 中最简洁的单例模式;- Kotlin 编译器会保证单例的创建和线程安全;
 - 反编译成 Java 代码:静态变量在静态代码块中初始化
 
 
            
            
              kotlin
              
              
            
          
          // 懒汉式,线程安全:私有构造函数 + 伴生对象 + by lazy
class Singleton private constructor() {
    companion object {
        val singleton: Singleton by lazy { // LazyThreadSafetyMode.SYNCHRONIZED
            Singleton()
        }
    }
}
// 懒汉式,双重检验锁:私有构造函数 + 伴生对象 + @Volatile + synchronized
class Singleton private constructor() {
    companion object {
        // 变量可见性 + 防止指令重排
        @Volatile
        private var instance: Singleton? = null
        fun getInstance(): Singleton {
            return instance ?: synchronized(this) {
                return instance ?: Singleton().also { instance = it }
            }
        }
    }
}
// 私有构造函数 + 静态内部类 + 伴生对象 + by lazy:
class Singleton private constructor() {
    companion object {
        // LazyThreadSafetyMode.SYNCHRONIZED 保证线程安全
        val singleton: Singleton by lazy { Holder.INSTANCE }
    }
    // 静态内部类只有在使用的时候才被加载,从而实现延迟初始化
    private object Holder {
      	// 反编译成 Java 代码是静态变量
        val INSTANCE = Singleton()
    }
}
// 饿汉式:Kotlin 编译器会保证线程安全和单例的创建
object Singleton {
    
}
        饿汉式反编译成 Java 代码:
            
            
              java
              
              
            
          
          public final class Singleton {
   @NotNull
   public static final Singleton INSTANCE;
   private Singleton() {
   }
   static {
      Singleton var0 = new Singleton();
      INSTANCE = var0;
   }
}
        5 谈谈你对 Kotlin 中 data 关键字的理解?相比于普通类有哪些特点?
Kotlin 中 的 data 关键字用于声明数据类(Data Class),专门为简化数据模型而设计的。
特点:
- 主构造函数必须至少有一个参数,且参数标记为 
var或val;- 数据类的核心是属性,而非普通参数。如果不标记为 
var/val,参数仅作为构造参数存在,不会成为类的属性,导致:- 无法通过 
对象.属性名访问; - 编译器无法生成 
equals()/hashCode()等方法; 
 - 无法通过 
 
 - 数据类的核心是属性,而非普通参数。如果不标记为 
 - 自动生成标准方法:
toString()、componentN()、equals()和hashCode()、copy()等方法;toString():生成格式化的字符串表示,如User(name=John, age=30);componentN():函数:支持解构声明访问属性;equals()和hashCode():比较两个对象的内容是否相等(而非引用相等),仅基于主构造函数中声明的属性;copy():快速创建对象的副本,并可选择性修改部分属性(适用于不可变独享);
 - 数据类不能是 
abstract、open、sealed(密封类)或inner类- 不能是 
abstract(抽象类):数据类的主要目的是存储数据,需要能被直接实例化使用;- 抽象类不能直接实例化;
 - 数据类自动生成的 
copy()方法依赖具体实现; - 自动生成的 
componentN()函数要求属性必须实现; 
 - 不能是 
open(开放类):数据类的自动生成方法需要不可变性保证;- 子类添加新属性会导致 
equlas()行为错误; hashCode()在不同子类间可能产生冲突;copy()方法无法正确处理子类属性;
 - 子类添加新属性会导致 
 - 不能是 
sealed(密封类):密封类要求有子类,而数据类要求不能有子类 - 不能是 
inner(内部类):内部类隐式持有外部类引用;- 自动生成的 
equals()会包含外部类引用比较; hashCode()会包含不可控的外部信息类;copy()方法无法正确的处理外部类的绑定;
 - 自动生成的 
 
 - 不能是 
 
6 什么是委托属性?请简要说说其使用场景和原理?
在 Kotlin 中,委托是一种强大的设计模式,通过 by 关键字来实现,它允许对象将部分职责交给另一个辅助对象来完成。
Kotlin 支持的两种委托为:类委托(Class Delegation [ˌdelɪˈɡeɪʃn])和属性委托(Property Delegation)。
- 
类委托: 允许一个类将其公共接口的实现委托给另一个对象。
- 类似于继承,但更灵活,因为它是通过组合来实现的,避免了继承的一些限制(比如单继承问题)。遵循"组合优于继承"的原则;(多重继承:多个父类中有相同的方法或属性,导致冲突)
 - 当我们希望一个类实现某个接口,但不想自己实现所有的方法,而是想重用另一个类已有的实现时;
 
 - 
属性委托: 允许经属性的访问器(
getter/setter)逻辑委托给另一对象。这样可以将属性的读取和写入操作交给另一个辅助对象来处理,实现逻辑复用;- 
只读属性(
val):提供getValue() - 
可变属性(
var):提供getValue()和setValue() - 
使用场景:
- 惰性加载(lazy properties):属性第一次访问时才计算初始值;
 - 可观察属性(observable properties):属性变化时触发通知;
 - 属性存储在映射(map)中:适用于动态配置的属性,如 JSON 解析
 
 
 - 
 
类委托:
            
            
              kotlin
              
              
            
          
          interface Base {
    fun print()
}
class BaseImpl(val x: Int) : Base {
    override fun print() { println(x) }
}
// 委托给 BaseImpl 实例
class Derived(base: Base) : Base by base
// 使用
val base = BaseImpl(10)
val derived = Derived(base)
derived.print()  // 输出 10(实际由 BaseImpl 实现)
        Derived 类将 Base 接口的实现委托给 base 对象,编译器自动生成转换方法,无需手动实现接口。
属性委托:
            
            
              kotlin
              
              
            
          
          class Example {
    // 委托给 LazyImpl 实例
    val lazyValue: String by LazyImpl()
}
// 委托类需实现 ReadOnlyProperty 接口(val 属性)
class LazyImpl : ReadOnlyProperty<Example, String> {
    override fun getValue(thisRef: Example, property: KProperty<*>): String {
        return "计算结果"
    }
}
        标准库中的委托:
            
            
              kotlin
              
              
            
          
          // Lazy:延迟初始化
val heavyResource: Resource by lazy {
    println("Initializing...")
    Resource.load() // 首次访问时执行
}
// 使用
fun main() {
    println("Before access")
    heavyResource.use() // 此时初始化
    heavyResource.use() // 直接使用缓存
}
// observable:属性变更监听
var value: Int by Delegates.observable(0) { 
    prop, old, new ->
    println("${prop.name} changed: $old -> $new")
}
// 使用
fun main() {
    value = 10  // 输出: value changed: 0 -> 10
    value = 20  // 输出: value changed: 10 -> 20
}
// Map 委托:将属性存储到 Map 中,适用于动态属性场景(如 JSON 解析)
class User(val map: Map<String, Any?>) {
    val name: String by map
    val age: Int     by map
}
// 使用
fun main() {
    val user = User(mapOf(
        "name" to "Alice",
        "age"  to 25
    ))
    println(user.name) // Alice
    println(user.age)  // 25
}
        7 请举例说明 Kotlin 中的 with 函数和 apply 函数的应用场景和区别?

8 Kotlin 中 Unit 类型的作用以及与 Java 中 void 的区别
- Kotlin 中的 
Unit:和Int一样,Unit是一种数据类型,表示无返回值类型;- Kotlin 中引入 
Unit类型的很大原因是函数式编程; - 在 Kotlin 中,对象或函数都有类型,如果方法的返回类型是 
Unit,可以省略; 
 - Kotlin 中引入 
 - Java 中的 
void:是关键字,表示什么都不返回,void不能省略;- 这是 Java 中不能说函数调用都是表达式的原因,因为有些方法不具有返回值或类型信息,所以不能算作是表达式;
 
 - Kotlin 中的 
Nothing:表示这个函数永不返回;- 对于某些 Kotlin 函数来说,"返回类型"的概念没有任何意义,因为它们永远不会成功的结束。
 
 
            
            
              kotlin
              
              
            
          
          fun main() {
    fail("Error occurred")
}
fun fail(message: String): Nothing {
    throw java.lang.IllegalStateException(message)
}
// Exception in thread "main" java.lang.IllegalStateException: Error occurred
// 	at com.ixuea.test.TestKt.fail(Test.kt:158)
// 	at com.ixuea.test.TestKt.main(Test.kt:152)
// 	at com.ixuea.test.TestKt.main(Test.kt)
        9 Kotlin 中 infix 关键字的原理和使用场景
在 Kotlin 中,用 infix 关键字修饰的函数称为中缀函数。使用时可以省略 . 和 ()。让代码看起来更自然(类似自然语言)。
普通函数与中缀函数的语法:
- 普通函数:
a.function(b) - 中缀函数:
a function b 
示例:
            
            
              kotlin
              
              
            
          
          infix fun String.concatWith(another: String) = "$this$another"
// 链式中缀调用
val message = "Hello" concatWith "World" concatWith "!"
        定义一个中缀函数,必须满足以下条件:
- 该中缀函数必须是某个类的扩展函数或成员方法;
 - 该中缀函数只能有一个参数;
 - 该中缀函数的参数不能有默认值(否则,以上形式的 
b会缺失,从而对中缀表达式的语义造成破坏); 
标准库中的中缀函数:
to函数:用于创建Pair对象;until函数:用于生成区间;- 集合操作,如 
step和downTo; 
            
            
              kotlin
              
              
            
          
          // 源码
infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
val map = mapOf(
    "name" to "Eileen",
    "age" to 30
)
        中缀函数的底层实现(原理): Kotlin 编译器在语法层面给了支持,反编译成 Java 代码后就可以看到是普通函数。(Kotlin 的很多特性都是在语法和编译器上的优化)
示例:
            
            
              kotlin
              
              
            
          
          class Person(private val name: String) {
    // 成员中缀函数
    infix fun say(message: String) {
        println("$name says $message")
    }
}
// 扩展中缀函数
infix fun Int.multiply(factor: Int): Int = this * factor
        反编译中 Java 代码
            
            
              java
              
              
            
          
          public final class Person {
   private final String name;
   public final void say(@NotNull String message) {
      Intrinsics.checkNotNullParameter(message, "message");
      String var2 = this.name + " say " + message;
      System.out.println(var2);
   }
   public Person(@NotNull String name) {
      Intrinsics.checkNotNullParameter(name, "name");
      super();
      this.name = name;
   }
}
        10 Kotlin 中的可见修饰符有哪些?相比于 Java 有什么区别?

11 你觉得 Kotlin 和 Java 混合开发时需要注意哪些问题?
- 空安全: Kotlin 有严格的空安全机制,如非空类型 
String和 可空类型String?,但 Java 没有- Kotlin 代码调用 Java 代码时:Java 中的所有引用类型都认为是可空的,Kotlin 需要进行空判断,如使用安全调用操作符(
?.)、Elvis 操作符(?:)、非空断言(!!)。 - Java 在调用 Kotlin 代码时:可能会传入 
null到非空参数,因此,需要为调用的 Kotlin 方法参数添加@Nullable或@NotNull注解 
 - Kotlin 代码调用 Java 代码时:Java 中的所有引用类型都认为是可空的,Kotlin 需要进行空判断,如使用安全调用操作符(
 - 默认参数和方法重载: Kotlin 支持默认参数,但 Java 是不支持的,Java 调用带默认参数的 Kotlin 方法时必须传递所有的参数;
- 解决方案:使用 
@JvmOverloads注解生成多个重载方法; 
 - 解决方案:使用 
 - 注解: 
@JvmStatic和@JvmFiled@JvmStatic注解可以让 Kotlin 中伴生对象中的属性、方法在 Java 中成为真正的静态属性、方法;@JvmFiled注解可以让 Kotlin 中的private字段(通常为val)直接暴露为 Java 中的public final字段,禁止自动生成getter/setter方法;
 - 数据类: Kotlin 中的数据类可以自动生成 
equals()、hashCode()、toString、ComponentN()等方法,Java 需要手动实现; - 集合操作: Kotlin 中的集合分为只读集合(
List)和可变集合(MutableList),而 Java 中的集合均为可变集合 
            
            
              kotlin
              
              
            
          
          // Java 类
public class Person {
    private String name; // 可能为 null
    public String getName() {
        return name;
    }
}
// Kotlin 调用 Java 方法
val person = Person()
val name: String = person.name // 可能为空
val safeName: String = person.name ?: "" // 空安全处理
fun processString(@NotNull text: String) {
    
}
@JvmOverloads
fun greet(name: String, prefix: String = "Hello") {
    
}
        @JvmStatic 的使用:
            
            
              kotlin
              
              
            
          
          // Kotlin 无 @JvmStatic
class Utils {
    companion object {
        fun compute(a: Int, b: Int): Int = a + b
    }
}
// Java 调用(需通过 Companion 对象)
int result = Utils.Companion.compute(1, 2);
// Kotlin
class Utils {
    companion object {
        @JvmStatic
        fun compute(a: Int, b: Int): Int = a + b
    }
}
// Java 调用(直接静态访问)
int result = Utils.compute(1, 2);
        注解使用:
            
            
              kotlin
              
              
            
          
          class KotlinService {
    companion object {
        @JvmStatic fun create(): KotlinService = KotlinService()
    }
    
    @JvmField val VERSION = "1.0"
    
    @JvmOverloads
    fun process(data: String, timeout: Int = 1000) { ... }
    
    @Throws(IOException::class)
    fun loadResource() { ... }
}
        12 在 Kotlin 中,何为解构,该如何使用?
在 Kotlin 中,解构是一种语法糖,允许将一个对象分解成多个独立的变量。
解构声明:
            
            
              kotlin
              
              
            
          
          val (变量1, 变量2, ...) = 对象
        示例:
            
            
              kotlin
              
              
            
          
          data class User(val name: String, val age: Int)
// 创建对象
val user = User("Eileen", 34)
// 解构为多个变量
val (name, age) = user
println("name = $name, age = $age") // name = Eileen, age = 34
        原理: 解构实际上是调用对象 component1()、component2() 等函数
            
            
              kotlin
              
              
            
          
          val name = user.component1()
val age = user.component2()
        13 在 Kotlin 中,什么是内联函数?有什么作用?
13.1 内联函数
- 定义: 在 Kotlin 中,内联函数是一种通过 
inline关键字声明的函数; - 目的: 优化 Lambda 表达式所带来的开销;
 - 原理: 内联函数会在编译时直接将函数中的代码"嵌入"到调用处,从而避免函数调用所带来的开销;
- 当调用一个普通函数时,程序会跳转到函数体内去执行;
 - Java 方法的执行是基于 Java 虚拟机栈的,每一个方法从被调用到执行完成,都对应着一个栈帧的入栈和出栈过程,有一定的性能开销;
 
 
普通高阶函数:
            
            
              kotlin
              
              
            
          
          fun nonInlineFun(block: () -> Unit) {
    block()
}
fun main() {
    // 调用时,会生成一个 Function0 对象
    nonInlineFun { println("Hello") }
}
        反编译成 Java 代码:
            
            
              java
              
              
            
          
          public final class UserKt {
   public static final void nonInlineFun(@NotNull Function0 block) {
      Intrinsics.checkNotNullParameter(block, "block");
      block.invoke();
   }
   public static final void main() {
      nonInlineFun((Function0)null.INSTANCE);
   }
   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }
}
        内联函数:
            
            
              kotlin
              
              
            
          
          inline fun inlineFunc(block: () -> Unit) {
    block()
}
fun main() {
    // 调用时,会生成一个 Function0 对象
    inlineFunc { println("Hello") }
}
        反编译成 Java 代码:
            
            
              java
              
              
            
          
          public final class UserKt {
   public static final void inlineFunc(@NotNull Function0 block) {
      int $i$f$inlineFunc = 0;
      Intrinsics.checkNotNullParameter(block, "block");
      block.invoke();
   }
   public static final void main() {
      int $i$f$inlineFunc = false;
      int var1 = false;
      String var2 = "Hello";
      System.out.println(var2);
   }
   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }
}
        13.2 noinline 禁止内联
如果一个内联函数的参数里也包含 Lambda 表达式,也就是函数参数,那么该形参也是 inline 的,例如:
            
            
              kotlin
              
              
            
          
          inline fun inlineMethod(inBlock: () -> Unit) {
   
}
        需要注意的是:在这个内联函数的内部,函数参数被其他非内联函数调用,是会报错的:
            
            
              kotlin
              
              
            
          
          // 非内联函数
fun noinlineMethod(noBlock: () -> Unit) {
}
        报错:

想要解决这个问题,必须为内联函数的参数加上 noinline 修饰符,表示禁止内联,保留原有函数的特性。以上代码的正确写法是:
            
            
              kotlin
              
              
            
          
          inline fun inlineMethod(noinline inBlock: () -> Unit) {
    noinlineMethod(inBlock)
}
        13.3 局部返回与非局部返回
内联函数支持非局部返回(Non-local return): 是指从内联函数(inline function)的 lambda 表达式中直接退出退出外层函数(而非 Lambda 本身)的行为;
- 外层函数是内联的(使用 
inline修饰); - Lambda 直接使用 
return(无标签修饰); 

内联函数支持非局部返回:
            
            
              kotlin
              
              
            
          
          inline fun runCustom(action: () -> Unit) {
    action()
}
fun main() {
    runCustom {
        println("Before return")
        return // 非局部返回:直接退出 main 函数
    }
    println("This won't be printed") // 不会执行
}
// Before return
        普通函数不支持非局部返回:
            
            
              kotlin
              
              
            
          
          // 注意:未使用 inline 修饰符!
fun runNonInline(action: () -> Unit) {
    action()
}
fun main() {
    runNonInline {
        println("Before return")
        return // 编译错误:'return' 不允许在这里
    }
}
        局部返回(带标签的 return)
            
            
              kotlin
              
              
            
          
          fun main() {
    listOf(1, 2, 3).forEach {
        if (it == 2) return@forEach // 局部返回到 lambda,继续下一次迭代
        println(it)
    }
    println("Done") // 正常执行
}
// 1
// 3
// Done
        在 Kotlin 中,当一个普通函数接收 Lambda 表达式作为参数时,该 Lambda 表达式只支持局部返回(不支持非局部返回)。 这是因为:
- Lambda 表达式会被编译成一个独立的函数对象,有自己的作用域,与它的调用者处于不同的上下文;
 - 从编译器的角度来看,Lambda 表达式是一个独立的代码块,它并不知道外部函数的调用栈情况;
 - 在普通函数中,为了保证 Lambda 表达式能安全的返回,可以使用带标签的返回(如 
return@label),明确指定返回的目标; 
内联函数支持非局部返回的原因:
- 内联函数是通过将函数体"嵌入"到调用函数中(包括传递给它的 Lambda 表达式)来消除作用域的边界;
 - 这样,
return自然就作用在了外层函数 

示例:
            
            
              kotlin
              
              
            
          
          // 非内联普通函数
fun runNormal(block: () -> Unit) {
    block()
}
// 反编译成 Java 代码
public final class KTTestKt {
   public static final void runNormal(@NotNull Function0 block) {
      Intrinsics.checkNotNullParameter(block, "block");
      block.invoke();
   }
}
        在反编译的 Java 代码中:
- Lambda 会被编译成一个独立的 
Function对象(如Function0) - 相当于创建了一个匿名类实例;
 - 关键问题:
return语句子这个独立对象内部无法感知外层函数的上下文环境; 
作用域隔离:

Lambda 表达式有自己的作用域边界:
- 普通函数的 lambda 相当于一个嵌套函数;
 return只能作用于当前最内层的作用域
13.4 crossinline 禁止使用非局部返回
在内联函数中,Lambda 表达式参数是默认允许非局部返回。但有时,Lambda 表达式并不会在当前函数中执行,而是会传递给其他的函数。在这种情况下,非局部返回会导致逻辑错误,因为在执行 return 语句时,目标函数可能已经不存在了。
crossinline 是一个用于修饰内联函数中 Lambda 表达式参数的修饰符:
- 用于标记这个 Lambda 表达式参数,不允许使用非局部返回(
return); - 允许局部返回(带标签的返回,
return@label); - 代码仍然会被内联;
 - 编译器会在需要时强制开发者添加 
crossinline修饰符; 
非局部返回的潜在风险:

这里如果 action 中包含 return,它将试图返回到原始调用函数,但此时该函数可能已经执行完毕(因为代码在另一个线程执行)。
解决方案:
            
            
              kotlin
              
              
            
          
          // 正确使用 crossinline
inline fun runCustom(crossinline action: () -> Unit) {
    val runnable = Runnable {
        action()
    }
    Executors.newSingleThreadExecutor().submit(runnable)
}
// 正确使用
fun main() {
    runCustom {
        println("Running in background")
    }
    println("Main continues")
}
        非局部返回的潜在风险:

13.5 reified
"reified + 内联函数"的组合解决了泛型"类型擦除"带来的问题,允许在运行时访问类型信息。
Java 和 Kotlin 的泛型默认存在"类型擦除",泛型信息(如 List<String> 中的 String)在运行时会被擦除,仅剩原始类型(如 List)。这导致无法在运行时直接获取泛型的具体类型信息。
原理:内联函数让代码"嵌入"到调用处,reified 让"嵌入"的代码保存泛型的类型信息。
- 内联函数的特性是在编译时将函数体"嵌入"到调用处,而非普通函数那样通过调用栈执行;
 reified类型参数(通过reified关键字修饰)则利用内联函数的特性,在编译期保留泛型的具体类型信息,避免了类型擦除,可以在运行时直接反问泛型的实际类型;
例如,传统的泛型函数无法直接判断一个对象是否为泛型参数指定的类型:
            
            
              kotlin
              
              
            
          
          // 传统泛型函数:无法在运行时判断 T 的具体类型
fun <T> isType(value: Any): Boolean {
    // 编译错误:Cannot check for instance of erased type T
    return value is T 
}
        reified 类型参数的用法示例:
            
            
              kotlin
              
              
            
          
          // 内联函数 + reified 类型参数
inline fun <reified T> checkType(value: Any): Boolean {
    return value is T // 不再报错,可直接判断
}
// 调用
fun main() {
    println(checkType<String>("hello")) // true
    println(checkType<Int>("hello"))    // false
}
        14 谈谈 Kotlin 中的构造方法?有哪些注意事项?
14.1 构造方法
- Kotlin 中的构造方法分为主构造方法(Primary Constructor)和次构造方法(Secondary Constructor);
 - 主构造函数在类名之后声明:
- 可用注解或可见性修饰符( 
private、public)等修饰; - 如果主构造函数没有注解或可见性修饰符修饰,可以省略 
constructor关键字; 
 - 可用注解或可见性修饰符( 
 - 次构造函数在类体内声明,如果同时声明了主构造函数,次构造函数需要直接或间接的调用主构造函数;
- 如果一个类没有显式的声明主构造函数,而是显式声明了次构造函数,这个时候次构造函数无需依次调用主构造函数;
 
 - Kotlin 中的任何类(除 
data/object/companion object类)都默认有一个无参构造函数(主构造函数);- 但是,如果显式的声明了构造函数,默认的无参构造函数就失效了;
 
 
            
            
              kotlin
              
              
            
          
          class Person constructor(val name: String, val age: Int) {
    // 类体
}
// 没有注解或可见性修饰符修饰 constructor 可省略
class Person(val name: String, val age: Int) {
  	// 类体
}
// 有主构造函数的情况
class MyView(context: Context) : View(context) {
    // 所有次构造必须调用主构造
    constructor(context: Context, attrs: AttributeSet?) 
        : this(context) // 必须先调用主构造
    
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) 
        : this(context, attrs) // 链式调用
}
// 无主构造函数的情况
class MyView : View {
    // 每个构造直接调用父类对应构造
    constructor(context: Context) : super(context)
    
    constructor(context: Context, attrs: AttributeSet?) 
        : super(context, attrs) // 直接调用父类
    
    // 无链式调用要求
}
        14.2 属性声明
- 主构造函数的参数可以同 
val/var直接声明为类的属性;- 可以在类的任何地方(包括 
init代码块、方法等)访问; 
 - 可以在类的任何地方(包括 
 - 如果主构造函数的参数没有用 
val/var修饰,那么它不是类的属性,仅在以下场景下可见:- 类体中的 
init代码块(初始化块); - 类的属性初始化器(声明类属性时直接赋值的表达式);
 
 - 类体中的 
 

            
            
              kotlin
              
              
            
          
          class User(fullName: String) {  // 未用 val/var 修饰的参数
    // 属性初始化器中使用 fullName
    val firstName = fullName.split(" ")[0]
    
    // init 代码块中使用 fullName
    init {
        println("完整名称:$fullName")
    }
    
    fun printFirstName() {
        println("名:$firstName")
        // println(fullName)  // 编译错误:fullName 在此处不可见
    }
}
fun main() {
    val user = User("Alice Smith")
    user.printFirstName()  // 输出:名:Alice
}
        14.3 init 初始化代码块
- 在 Kotlin 中,主构造函数只能有参数声明,不能有可执行代码,
init代码块的核心作用就是弥补这一限制,它允许在主构造函数执行时(即类的初始化阶段)运行初始化逻辑,相当于主构造函数的"代码体"; init代码块的特性:- 执行时机:与主构造函数同步执行,在类实例创建时(即调用主构造函数时)自动执行;
 - 执行次数:每个类实例创建时执行一次;
 - 执行顺序:主构造函数参数初始化 ---> 属性初始化器(按声明顺序)---> 
init代码块(按声明顺序) 
- **
init代码块和次构造函数: **init代码块的执行顺序优先于次构造函数中的代码执行- 这是因为,次构造函数必须直接或间接调用主构造函数,而 
init代码块是主构造函数初始化逻辑的一部分,会在主构造函数参数解析后立即执行; 
 - 这是因为,次构造函数必须直接或间接调用主构造函数,而 
 - 在继承关系中: 
init代码块的执行顺序遵循"先父后子、先init后构造"的规则:- 父类的主构造函数参数解析;
 - 父类的 
init代码块(按声明顺序); - 父类的次构造函数
 - 子类的主构造函数参数解析
 - 子类的 
init代码块(按声明顺序) - 子类的次构造函数
 
 
执行顺序:
            
            
              kotlin
              
              
            
          
          class User(val name: String) { // 主构造函数
  	// 属性初始化器
    val greeting = "Hello, $name!".also { println("属性初始化器执行") }
  	// init 代码块
    init {
        println("init 代码块执行")
    }
  	// 次构造函数
    constructor(name: String, age: Int) : this(name) {
        println("次构造函数:name = $name, age = $age")
    }
}
fun main() {
    val user = User("Eileen", 33)
}
// 属性初始化器执行
// init 代码块执行
// 次构造函数:name = Eileen, age = 33
        继承关系:
            
            
              kotlin
              
              
            
          
          open class User(name: String) { // 主构造函数
    // 属性初始化器
    val greeting = "Hello, $name!".also { println("(父类)属性初始化器执行") }
    // init 代码块
    init {
        println("(父类)init 代码块执行")
    }
    // 次构造函数
    constructor(name: String, age: Int) : this(name) {
        println("(父类)次构造函数:name = $name, age = $age")
    }
}
class Student(name: String) : User(name) {
    // 属性初始化器
    val sGreeting = "Hello, $name!".also { println("(子类)属性初始化器执行") }
    // init 代码块
    init {
        println("(子类)init 代码块执行")
    }
    // 次构造函数
    constructor(name: String, age: Int) : this(name) {
        println("(子类)次构造函数:name = $name, age = $age")
    }
}
fun main() {
    val student = Student("Eileen", 33)
}
//(父类)属性初始化器执行
//(父类)init 代码块执行
//(子类)属性初始化器执行
//(子类)init 代码块执行
//(子类)次构造函数:name = Eileen, age = 33
        14.4 特殊类
- 
object/companion object是对象示例,作为单例类或伴生对象,没有构造函数 - 
data class必须有一个含有至少一个成员属性的主构造函数; - 
密封类(
sealed class):是一种特殊的抽象类, 其子类必须密封类的内部或同一文件中声明(限制继承);- 
密封类的构造函数默认为
protected,也可以显式声明为private; - 
不允许声明为
public或internal; 
 - 
 
15 谈谈 Kotlin 中的 Sequence,为什么它处理集合操作更加高效?
Sequence(序列)和普通集合的核心区别:
- 普通集合(如 
List)的急切求值: 每调用一个中间操作(如map、filter)都会立即执行并生成一个新的集合,最终导致多次遍历和中间集合的内存开销; - Sequence 采用惰性求值: 中间操作(如 
map、filter)不会立即执行生成新的集合,只会记录操作逻辑;直到调用终端操作(如toList、count、forEach)时,才会生成最终结果,且仅遍历一次; 
Sequence 更高效的原因:
- 惰性求值: Sequence 避免中间集合的创建,仅仅记录操作逻辑,节省内存;
- 普通集合的链式操作会产生多个中间集合,占用额外内存;
 
 - 单次遍历: Sequence 仅需要单次遍历即可完成所有操作;
- 普通集合的链式操作需要多次遍历(每个操作一次);
 - 普通集合:
map遍历 100 万次 ---> 生成中间集合 --->filter再遍历 100 万次 ---> 总共遍历 200 万次; - Sequence:终端操作时,遍历 100 万次,每次遍历时同时执行 
map和filter---> 总共遍历 100 万次; 
 - 优化短路操作: 对于包含短路逻辑的操作(如 
find、any、take),Sequence 可以在满足条件时立即终止遍历,避免不必要的计算; 
示例代码:
            
            
              kotlin
              
              
            
          
          val list = listOf(1, 2, 3, 4, 5)
// 普通集合:生成 2 个中间集合(map 结果、filter 结果)
val result = list
    .map { it * 2 }   // 立即执行,生成 [2,4,6,8,10]
    .filter { it > 5 } // 立即执行,生成 [6,8,10]
val sequence = list.asSequence()
// Sequence:不生成中间集合,仅记录 map 和 filter 的逻辑
val result = sequence
    .map { it * 2 }   // 不执行,仅记录
    .filter { it > 5 } // 不执行,仅记录
    .toList()         // 终端操作:触发执行,一次性计算结果
        反编译成 Java 代码:
            
            
              kotlin
              
              
            
          
          public final class KTTestKt {
   @NotNull
   private static final List list = CollectionsKt.listOf(new Integer[]{1, 2, 3, 4, 5});
   @NotNull
   private static final List result0;
   @NotNull
   private static final Sequence sequence;
   @NotNull
   private static final List result1;
   @NotNull
   public static final List getList() {
      return list;
   }
   @NotNull
   public static final List getResult0() {
      return result0;
   }
   @NotNull
   public static final Sequence getSequence() {
      return sequence;
   }
   @NotNull
   public static final List getResult1() {
      return result1;
   }
   static {
      Iterable $this$filter$iv = (Iterable)list;
      int $i$f$filter = false;
      Collection destination$iv$iv = (Collection)(new ArrayList(CollectionsKt.collectionSizeOrDefault($this$filter$iv, 10)));
      int $i$f$filterTo = false;
      Iterator var5 = $this$filter$iv.iterator();
      Object element$iv$iv;
      int it;
      boolean var8;
      while(var5.hasNext()) { // 普通集合的第1次遍历
         element$iv$iv = var5.next();
         it = ((Number)element$iv$iv).intValue();
         var8 = false;
         Integer var10 = it * 2;
         destination$iv$iv.add(var10);
      }
      $this$filter$iv = (Iterable)((List)destination$iv$iv);
      $i$f$filter = false;
      destination$iv$iv = (Collection)(new ArrayList());
      $i$f$filterTo = false;
      var5 = $this$filter$iv.iterator();
      while(var5.hasNext()) {// 普通集合的第2次遍历
         element$iv$iv = var5.next();
         it = ((Number)element$iv$iv).intValue();
         var8 = false;
         if (it > 5) {
            destination$iv$iv.add(element$iv$iv);
         }
      }
      result0 = (List)destination$iv$iv;
      sequence = CollectionsKt.asSequence((Iterable)list);
      result1 = SequencesKt.toList(SequencesKt.filter(SequencesKt.map(sequence, (Function1)null.INSTANCE), (Function1)null.INSTANCE));
   }
}
        优化的短路操作:
            
            
              kotlin
              
              
            
          
          // 普通集合:先 map 所有元素(生成中间集合),再 filter 查找 → 处理所有元素
list.map { it * 2 }.find { it > 5 } 
// Sequence:遍历到第 3 个元素(1*2=2→不满足,2*2=4→不满足,3*2=6→满足)时,立即终止
list.asSequence().map { it * 2 }.find { it > 5 } 
        16 谈谈 Kotlin 中的 Coroutines,它与线程有什么区别?有哪些优点?
在 Kotlin 中,协程(Coroutines /kəʊrəʊˈtiːnz/)是一种轻量级的并发编程模型,用于处理异步、并发和非阻塞代码。

结构化并发:通过作用域自动管理协程的生命周期,减少泄漏的风险。
协程常见作用域:
viewModelScope:与ViewModel生命周期绑定;lifecycleScope:与Activity/Fragment生命周期绑定;
阻塞、挂起、睡眠 在主动/被动,内存/外存、cpu、锁、线程等 方面有什么不同?
阻塞、挂起、睡眠:

17 Kotlin 中该如何安全地处理可空类型?
- 安全调用操作符 
?.: 当我们不确定某个可空变量是否为null时,可以使用安全调用操作符;- 如果该变量不为 
null,则执行操作;否则,不执行并返回null; 
 - 如果该变量不为 
 Elvis(埃尔维斯)运算符?:: 用于提供当表达式的结果为null时的默认值;- 非空断言 
!!: 当我们确定某个可空变量不为null时,可以使用非空断言操作符;- 但是,如果变量为 
null,则会抛出NullPointerException(NPE),需谨慎使用; 
 - 但是,如果变量为 
 - 安全类型转换 
as: 当我们尝试将对象转换为目标类型时,如果转换失败,通常会导致ClassCastException。使用安全类型转换as,即使转换失败,也会返回null; - **
let函数: ** 允许我们对可空变量执行一个代码块,如果变量不为null,则执行代码块(避免额外的if判断);- 常见用途:集中处理非空逻辑,替代 
if (user != null) { ... } - 使用 
run、apply、also等作用域函数也可以处理可空类型 
 - 常见用途:集中处理非空逻辑,替代 
 
安全调用操作符:
            
            
              kotlin
              
              
            
          
          data class User(val address: Address?)
data class Address(val city: String?)
fun getCity(user: User?): String? {
    // 链式安全调用:任何环节为 null,整个表达式返回 null
    return user?.address?.city 
}
// 调用
val user: User? = null
println(getCity(user)) // 输出 null,无异常
        Elvis 运算符:
            
            
              kotlin
              
              
            
          
          fun getUserName(user: User?): String {
    // 若 user 为 null,返回默认值 "Unknown"
    return user?.name ?: "Unknown"
}
// 复杂场景:结合安全调用返回非空类型
val length = user?.name?.length ?: 0 // 若 name 为 null,长度默认为 0
        非空断言:
            
            
              kotlin
              
              
            
          
          fun printName(user: User?) {
    // 断言 user 不为 null,否则抛 NPE
    println(user!!.name) 
}
        安全类型转换 as
            
            
              kotlin
              
              
            
          
          fun safeCast(obj: Any?): String? {
    // 若转换失败,返回 null
    return obj as? String 
}
val str: Any? = 123
println(safeCast(str)) // 输出 null,无异常
        let 函数
            
            
              kotlin
              
              
            
          
          fun processUser(user: User?) {
    // 仅当 user 不为 null 时,才执行 lambda 中的逻辑
    user?.let { 
        println("用户名:${it.name}")
        saveUser(it) // 安全调用非空对象的方法
    }
}
        18 说说 Kotlin 中的 Any 和 Java 中的 Object 有何异同?
相同点:
- 根类:
- 在 Java 中,
Object类是所有类(基本类型除外)的根类- 但基本类型也有对应的包装类,这些包装类都继承自 
Object; 
 - 但基本类型也有对应的包装类,这些包装类都继承自 
 - 在 Kotlin 中,
Any是所有非空类型的根类(包括基本类型如Int、Double等);- 注意:Kotlin 中的可空类型(如 
String?)的根类是Any? 
 - 注意:Kotlin 中的可空类型(如 
 
 - 在 Java 中,
 - 基础方法: 都提供了面向对象的核心方法
equals():判断对象的相等性;hashCode():获取对象的哈希值;toString():返回对象的字符串表示;
 
不同点:

Kotlin 中的默认继承规则:任何没有显式声明父类的类,都会默认继承 Any。这一规则适用于所有的类。
            
            
              kotlin
              
              
            
          
          println(Int::class.supertypes) // [kotlin.Number, kotlin.Comparable<kotlin.Int>, java.io.Serializable]
println(Double::class.supertypes) // [kotlin.Number, kotlin.Comparable<kotlin.Double>, java.io.Serializable]
println(Boolean::class.supertypes) // [kotlin.Comparable<kotlin.Boolean>, java.io.Serializable, kotlin.Any]
println(Number::class.supertypes) // [kotlin.Any, java.io.Serializable]
        根类、顶级父类、超类:


19 Kotlin 中的数据类型有隐式转换吗?为什么?
在 Kotlin 中,基本数据类型(如 Int、Long、Double 等)之间没有隐式转换,必须通过显式转换函数(如 toLong、toDouble)进行类型转换:
- 避免精度损失风险:如 
Long转Int时的数据截断; - 消除类型混淆错误,保持类型一致性;
 
Kotlin 为所有的基本类型提供了完整的显式转换函数:
toByte()、toShort()、toInt()、toLong()toFloat()、toDoubletoChar()

正确的写法:
            
            
              kotlin
              
              
            
          
          val a: Int = 1
val b: Long = 2
println(a.toLong() == b) // false
        20 Kotlin 中遍历集合有哪几种方式?
for-in循环;forEach高阶函数:通过 Lambda 表达式遍历,简介且支持函数式编程风格:- 迭代器(Iterator):显示使用 
iterator()获取迭代器,手动控制遍历过程; - 通过索引遍历(仅适用于有序集合);
 - 范围遍历;
 - 序列(Sequence)遍历;
 
            
            
              kotlin
              
              
            
          
          // for-in 循环
val fruits = listOf("Apple", "Banana", "Cherry")
for (fruit in fruits) {
    println("$fruit  ")
}
for ((index, fruit) in fruits.withIndex()) {
    println("$index: $fruit  ")
}
// forEach 高阶函数
val numbers = listOf(1, 2, 3, 4, 5)
numbers.forEach { num ->
    println("${num * 2} ")
}
numbers.forEachIndexed { index, num ->
    println("$index: $num  ")
}
// 迭代器
val set = setOf("Red", "Green", "Blue")
val iterator = set.iterator()
while (iterator.hasNext()) {
    val color = iterator.next()
    if (color == "Green") {
        break // 中途终止遍历
    }
    println(color)
}
// 通过索引遍历
val animals = listOf("Dog", "Cat", "Bird")
for (i in animals.indices) {
    println("动物 ${i + 1}: ${animals[i]}")
}
for (i in 0 until animals.size) {
    println(animals[i])
}
// 序列
val largeList = (1..1000000).toList().asSequence()
largeList.filter { it % 2 == 0 }
    .map { it * 2 }
    .take(5)
    .forEach { println(it) }