2024年Android面试总结

2024年Android面试总结

1.kotlin与Java的区别和优点?

语法简洁性

Kotlin的语法比Java更为简洁,减少了冗余代码。例如,Kotlin支持自动类型推断,避免了大量的样板代码。Kotlin的函数表达式和扩展函数也使得代码更加简洁‌12。

空指针安全

Kotlin内置空指针安全特性,通过区分可为空和不可为空的类型,避免了空指针异常。Java则需要手动进行空值检查,容易引发空指针异常‌12。

扩展函数

Kotlin支持扩展函数,可以向现有类添加新的功能而无需修改原有代码。Java不支持扩展函数,需要通过继承或其他设计模式来实现类似功能‌23。

协程

Kotlin支持协程,这是一种用于并发编程的工具,使得异步代码更加高效和易读。Java则使用传统的线程和回调处理异步编程,代码较为复杂且难以维护‌24。

互操作性

Kotlin与Java具有良好的互操作性,Kotlin代码可以与Java代码无缝集成。Kotlin编译器将代码编译为JVM字节码,可以在Java虚拟机上运行‌12。

编译时间

Kotlin的编译时间比Java更快,这意味着开发过程中可以更快地看到代码效果‌5。

优点总结

  • 简洁性‌:Kotlin的简洁语法减少了样板代码,提高了开发效率。
  • 空指针安全‌:内置的空安全机制避免了空指针异常,提高了程序的稳定性。
  • 扩展函数‌:允许向现有类添加新功能,无需修改原始代码,增强了代码的可维护性。
  • 协程‌:提供了更高效、更易读的异步编程方式。
  • 编译时间‌:更快的编译速度意味着更快的开发反馈。

2.kotlin中内联函数的作用?

内联函数

  • 使用inline关键字修饰的[函数]称为内联函数
    内联函数主要用于减少高阶函数的使用开销
    我们在使用高阶函数的时候,我们传入的lambda都会被编译成一个Function对象,这个过程会带来一定的性能开销。
  • 在编译时期,把调用这个函数的地方用这个函数的方法体进行替换。

3.kotlin中高阶函数apply、let、also、run的区别?

1.let 函数

  • let的函数参数block中, 对应的上下文this和第一个run函数是一致的
  • let和第二个run函数一样是一个extension函数, 但是它的block参数支持一个参数, 就是任意类型T的实例, 在extension函数定义时候, “this”就对应扩展类型实例.

2. also函数

2.1 作用 & 应用场景

类似let函数,但区别在于返回值:

  • let函数:返回值 = 最后一行 / return的表达式
  • also函数:返回值 = 传入的对象的本身

3. with函数

3.1 作用

调用同一个对象的多个方法 / 属性时,可以省去对象名重复,直接调用方法名 / 属性即可

3.2 应用场景

需要调用同一个对象的多个方法 / 属性

4. run函数

4.1 作用 & 应用场景

结合了let、with两个函数的作用,即:

  1. 调用同一个对象的多个方法 / 属性时,可以省去对象名重复,直接调用方法名 / 属性即可
  2. 定义一个变量在特定作用域内
  3. 统一做判空处理

5. apply函数

5.1 作用 & 应用场景

与run函数类似,但区别在于返回值:

  • run函数返回最后一行的值 / 表达式
  • apply函数返回传入的对象的本身
5.2 应用场景

对象实例初始化时需要对对象中的属性进行赋值 & 返回该对象

img

4.kotlin中Lambda与Java中Lambda区别?

编译后的表现形式

  • Kotlin‌:Kotlin的Lambda表达式在编译后会生成匿名类,这些类继承自Lambda类并实现了Function接口。每个Lambda表达式都对应一个Function类型的类‌。
  • Java‌:Java的Lambda表达式依赖于LambdaMetafactory动态生成匿名类,这些类通常是通过InvokeDynamic指令实现的。Java的Lambda表达式要求引用的变量为final或等效于final,而Kotlin允许非final变量,并通过编译时优化处理‌。

语法差异

  • Kotlin‌:Kotlin的Lambda表达式总是括在花括号中,参数声明放在花括号内,函数体跟在一个->符号之后。如果推断出的该lambda的返回类型不是Unit,那么该lambda主体中的最后一个表达式会被视为返回值‌3。
  • Java‌:Java的Lambda表达式使用(argument) -> {body}语法书写,参数类型可以明确声明,也可以根据上下文推断。如果Lambda表达式的主体只有一条语句,花括号可以省略‌。

应用场景和好处

  • 应用场景‌:在Kotlin和Java中,Lambda表达式都可以作为函数参数传递,使代码结构更加紧凑。它们常用于集合操作、过滤、映射等函数式编程场景‌4。
  • 好处‌:Lambda表达式语法简洁,避免了冗长的函数声明调用过程,使得代码更加简洁易读‌。

5.kotlin的泛型和Java泛型区别?

类型擦除

  • Java‌:泛型在编译时进行类型检查,但在运行时会被擦除,因此不能直接访问泛型参数的类型信息。
  • Kotlin‌:泛型在编译时进行类型检查,但不会擦除类型信息,因此在运行时可以保留泛型参数的类型信息‌。

类型约束

  • Java‌:使用:ml-search[extends]关键字来限制泛型类型的上界。
  • Kotlin‌:使用:ml-search[符号]来限制泛型类型的上界,并且支持多重界限‌。

星号投影

  • Java‌:使用通配符?来表示未知类型,但只能用于读取操作,不能用于写入操作。
  • Kotlin‌:使用星号投影*来表示未知类型,可以用于读取和写入操作‌。

内联类

  • Java‌:没有内联类的机制。
  • Kotlin‌:引入了内联类的概念,允许将数据类型直接嵌入到值中,以提高性能‌。

协变和逆变

  • Java‌:只支持有限的协变和逆变,需要使用通配符来实现。
  • Kotlin‌:支持完全的协变和逆变,不需要使用通配符‌。

扩展函数和扩展属性

  • Java‌:不支持扩展函数和扩展属性。
  • Kotlin‌:支持扩展函数和扩展属性,可以在已有的类上添加新的方法或属性‌。

泛型类的定义和使用

  • Kotlin‌:泛型类中的类型参数默认是不可变的,如果需要支持协变或逆变,需要使用:ml-search[out]:ml-search[in]关键字来指定。泛型类中的类型参数如果可以被推断出来,可以省略类型参数‌。

泛型在编译时的行为

  • ‌Kotlin和‌Java的泛型在编译时都会进行类型检查,但‌Kotlin在运行时保留了泛型参数的类型信息,而‌Java则会擦除这些信息‌。

6.kotlin中密封类和枚举特点?

在Kotlin中,密封类(sealed class)和枚举(enum class)都是为了表示有限的类型。

1.密封类:

  • 密封类是当你需要为多种类型定义一个基类型时很有用。
  • 密封类不能被直接实例化,只能是其子类的实例。
  • 你可以使用when表达式来匹配密封类的不同子类。

2.枚举类:

  • 当你想要定义一组固定的对象时,可以使用枚举类。
  • 枚举常量是枚举的实例,不能有方法。
  • 可以为枚举类添加属性和方法。

3 . 密封类作用 :

定义一个密封类 , 该类只能有有限个指定的子类 , 不能在其它文件定义其它类型子类 ;

4. 密封类与枚举类 :

相同点 ( 类型限制 ) : 从类型种类角度对比 , 类与枚举类类似 , 枚举类的值的集合是受限制的 , 不能随意扩展 ;

** 不同点 ( 对象个数限制 ) 😗* 从每个类型对象个数对比 , 枚举类的每个类型只能存在一个实例 , 而密封类的每个类型可以创建无数个实例 ;

7.koltin中val和var区别?

‌**Kotlin中的val和var的主要区别在于变量的可变性。**‌

  • val‌:用于声明一个只读变量(immutable variable),即一旦赋值就不能再修改。val声明的变量相当于Java中的final变量,只能被赋值一次。例如:val name = "John",之后尝试将name重新赋值为"Alice"会导致编译错误‌。
  • var‌:用于声明一个可变变量(mutable variable),可以随时被重新赋值。var声明的变量可以多次赋值。例如:var age = 25,之后可以将age重新赋值为30,这是合法的操作‌。

具体使用场景

在实际编程中,应根据变量是否需要被修改来选择使用val还是var:

  • 如果变量在初始化后不需要修改,使用val。
  • 如果变量在程序运行过程中需要多次修改,使用var。

对象和类的使用场景

对于对象和类的实例,val声明的对象实例一旦创建后不能重新实例化,但对象的属性可以修改

8.kotlin中伴生对象作用?

声明:

Kotlin 中,在类中定义的对象(object)声明,可使用 companion 修饰,这样此对象(object)就是伴生对象了

调用:

使用 companion 关键字修改的对象之后,伴生对象就相当于是外部类的对象,我们可以使用类直接调用

伴生对象的作用:

通过上面的 NumberTest.plus(1, 2) 和 NumberTest.flag 代码不难看出来,类似于 Java 中使用类访问静态成员的语法。因为 Kotlin 取消了 static 关键字,所以 Kotlin 引入伴生对象来弥补没有静态成员的不足。可见,伴生对象的主要作用就是为其所在的外部类模拟静态成员。

总结:

  • 每个类可以最多有一个半生对象;
  • 伴生对象的成员类似于 Java 的静态成员;
  • 使用 const 关键字修饰常量,类似于 Java 中的 static final修饰。
  • 可以使用 @JvmField 和 @JvmStatic 类似于 Java 中调用静态属性和静态方法;
  • 伴生对象可以扩展属性和扩展方法。

9.kotlin中函数如何声明?构造函数类型?区别是什么?

类的声明

使用class+类名来声明,若类体中无内容,类体后的花括号可以省略

构造函数类型‌:

Kotlin中的类可以有三种构造函数类型:主构造函数、带init块的构造函数和次构造函数

主构造函数‌:

在类名后直接定义参数列表,没有花括号包围的函数体。主构造函数用于初始化类的成员变量

init块的构造函数‌:

在主构造函数之后定义一个init块,用于执行初始化代码。

次构造函数‌:

在类体内部定义,使用constructor关键字。次构造函数可以调用主构造函数或另一个次构造函数进行初始化

区别‌:

  • 主构造函数‌是最简洁的方式,适用于简单的初始化操作。它不允许在函数体中包含任何代码。
  • init块的构造函数‌适用于需要在构造函数中执行一些初始化代码的情况。它允许在主构造函数之后添加初始化代码块。
  • 次构造函数‌提供了最大的灵活性,可以调用其他构造函数,适用于复杂的初始化逻辑。它允许在类体内部定义,并且可以包含多个次构造函数。

10.kotlin中协程的启动方式有哪些?有啥区别?

协程的三种启动方式:launch、runBlocking、async

launch:

launch是最常用的启动协程方式,它会立即返回一个Job对象,后台执行协程任务 如果在启动协程时使用了try-catch,那么异常会被try-catch块捕获。如果没有捕获,异常会传递给未捕获异常处理器进行处理
应用场景:如果需要启动一个独立协程,并且不关心其结果或异常,最好使用launch。

runBlocking:

runBlocking是一个阻塞式函数,用于测试和调试协程代码 它会启动一个新的协程,并等待该协程执行完毕后才会返回。在调用runBlocking时,当前线程将被阻塞,直到协程执行完毕。
应用场景:如果需要在测试和调试环境中启动协程,最好使用runBlocking。

async:

async用于启动一个独立协程,并返回Deferred对象,通过该对象可以获取协程的执行结果 如果在启动协程时使用了try-catch,那么异常会延迟到await函数调用时抛出。
应用场景:如果需要启动一个独立协程,并获取其执行结果,最好使用async。

协程的启动模式

Kotlin协程的启动模式包括以下几种:

  • ‌**[DEFAULT]**‌:创建后立即调度执行。如果协程在调度前被取消,将直接进入取消状态‌。
  • ‌**[ATOMIC]**‌:创建后立即调度执行,但在挂起点前不可取消。如果协程在挂起点前被取消,将直接进入异常状态‌。
  • ‌**[LAZY]**‌:只有在需要时(通过start、join或await等方法)才开始调度。如果协程在调度前被取消,将直接进入异常状态‌。
  • ‌**[UNDISPATCHED]**‌:在当前函数调用栈中执行,直到遇到第一个挂起点。如果协程在挂起点前被取消,将直接进入取消状态‌。

11.kotlin中协程原理及优点?

1. 协程的定义

协程是一种轻量级的线程,它们在用户代码中运行,可以挂起和恢复执行。与传统线程相比,协程更为轻量,不需要昂贵的上下文切换开销。Kotlin 中的协程允许开发者以同步的方式编写异步代码,从而使代码更加简洁和易读。

1.1 协程的特性

  • 挂起和恢复:协程可以在执行过程中被挂起,稍后再恢复执行。这使得协程能够高效地处理耗时的操作,比如网络请求或文件读写。
  • 轻量级:协程的创建和销毁开销非常小,通常可以在同一线程中创建成千上万的协程,而不必担心内存和性能问题。
  • 可组合性:协程可以与其他协程进行组合,方便地构建复杂的异步操作。

2. Kotlin 中的协程工作原理

Kotlin 的协程是通过 suspend 函数和 CoroutineScope 来实现的。协程的基本构建块是挂起函数,这些函数可以在协程中调用,并且可以被挂起。

2.1 suspend 函数

suspend 函数是可以在协程中挂起的函数。它们的调用不会阻塞线程,而是将协程挂起,直到操作完成。

2.2 CoroutineScope

CoroutineScope 是协程的上下文,它定义了协程的生命周期和调度器。Kotlin 提供了多种协程作用域,如 GlobalScope 和自定义作用域。

2.3 协程的调度器

Kotlin 的协程调度器负责决定协程的执行线程。常见的调度器有:

  • Dispatchers.Main:用于在主线程中执行协程,适用于 UI 更新。
  • Dispatchers.IO:用于进行 I/O 操作的协程,适用于网络请求或文件操作。
  • Dispatchers.Default:用于执行 CPU 密集型任务的协程。

3.特点:

创建协程:

可以使用 launchasync 函数来创建协程。

  • launch:启动一个协程,不返回结果。
  • async:启动一个协程并返回 Deferred 对象,可以通过该对象获取结果。

协程本质上可以认为是运行在线程上的代码块,协程提供的 挂起 操作会使协程暂停执行,而不会导致线程阻塞。

协程是一种轻量级资源,如上图所示,即使创建了上千个协程,对于系统来说也不是一种很大的负担。

img

包含关系上看,协程跟线程的关系,有点像“线程与进程的关系”,毕竟,协程不可能脱离线程运行;协程虽然不能脱离线程而运行,但可以在不同的线程之间切换,如上图所示。

协程的核心竞争力在于:简化异步并发任务(同步的写法实现异步操作)。

线程:

  • 线程是操作系统级别的概念
  • 我们开发者通过编程语言(Thread.java)创建的线程,本质还是操作系统内核线程的映射
  • JVM 中的线程与内核线程的存在映射关系,有“一对一”,“一对多”,“M对N”。JVM 在不同操作系统中的具体实现会有差别,“一对一”是主流
  • 一般情况下,我们说的线程,都是内核线程,线程之间的切换,调度,都由操作系统负责
  • 线程也会消耗操作系统资源,但比进程轻量得多
  • 线程,是抢占式的,它们之间能共享内存资源,进程不行
  • 线程共享资源导致了多线程同步问题
  • 有的编程语言会自己实现一套线程库,从而能在一个内核线程中实现多线程效果,早期 JVM 的“绿色线程” 就是这么做的,这种线程被称为“用户线程”

协程:

  • Kotlin 协程,不是操作系统级别的概念,无需操作系统支持
  • Kotlin 协程,有点像上面提到的“绿色线程”,一个线程上可以运行成千上万个协程
  • Kotlin 协程,是用户态的(userlevel),内核对协程无感知
  • Kotlin 协程,是协作式的,由开发者管理,不需要操作系统进行调度和切换,也没有抢占式的消耗,因此它更加高效
  • Kotlin 协程,它底层基于状态机实现,多协程之间共用一个实例,资源开销极小,因此它更加轻量
  • Kotlin 协程,本质还是运行于线程之上,它通过协程调度器,可以运行到不同的线程上

4. 协程的优势与挑战

4.1 协程的优势

  • 简化异步代码:使用协程,开发者可以以同步的方式编写异步代码,使代码更易于理解和维护。
  • 减少回调地狱:传统的异步编程通常依赖于回调函数,容易导致“回调地狱”,而协程通过挂起函数解决了这个问题。
  • 轻量级和高效:协程的上下文切换开销非常小,可以在同一线程中高效管理多个协程。

4.2 协程的挑战

  • 学习曲线:对于初学者,协程的概念和使用方式可能需要一些时间来掌握。
  • 调试:协程的调试可能会更复杂,因为它们在后台异步执行,跟踪执行路径可能不如同步代码直观。
  • 内存管理:尽管协程是轻量级的,但仍然需要注意协程的生命周期和资源管理,以避免内存泄漏。

12.kotlin协程调度器原理?

分类:

在Kotlin中,主要有以下几种Dispatcher

  1. Default - 默认的调度器,使用了默认的executor(通常是全局的后台线程池)。
  2. Main - 专门用于与界面交互的调度器,在Android中表示UI主线程。
  3. IO - 用于I/O密集型任务的调度器,执行环境是后台线程池。
  4. Unconfined - 不带上下文的调度器,它在其他调度器之上提供了无阻塞的调用。
  5. NewSingleThread - 创建单个线程的调度器,保证任务在同一线程中顺序执行。

作用:

协程就是通过Dispatchers调度器来控制线程切换的

什么是调度器?

从使用上来讲,调度器就是我们使用的Dispatchers.Main,Dispatchers.Default,Dispatcher.IO等

从作用上来讲,调度器的作用是控制协程运行的线程

从结构上来讲,Dispatchers的父类是ContinuationInterceptor,然后再继承于CoroutineContext

调度器的具体实现

我们首先明确下,CoroutineDispatcher是通过CoroutineContext取出来的,这也是协程上下文作用的体现

nternal class HandlerContext private constructor(
    private val handler: Handler,
    private val name: String?,
    private val invokeImmediately: Boolean
) : HandlerDispatcher(), Delay {
   
   
    public constructor(
        handler: Handler,
        name: String? = null
    ) : this(handler, name, false)//...override fun dispatch(context: CoroutineContext, block: Runnable) {
   
   // 利用主线程的 Handler 执行任务
​        handler.post(block)}
}

Kotlin协程到底是怎么切换线程的?你是否知晓?_android_04

如上所示,其实就是一个装饰模式

1.调用CoroutinDispatcher.dispatch方法切换线程

2.切换完成后调用DispatchedTask.run方法,执行真正的协程体

delay是怎样切换线程的?

public suspend fun delay(timeMillis: Long) {
   
   
    if (timeMillis <= 0) return // don't delay
    return suspendCancellableCoroutine sc@ {
   
    cont: CancellableContinuation<Unit> ->
        // if timeMillis == Long.MAX_VALUE then just wait forever like awaitCancellation, don't schedule.
        if (timeMillis < Long.MAX_VALUE) {
   
   
            cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
        }
    }
}

internal val CoroutineContext.delay: Delay get() = get(ContinuationInterceptor) as? Delay ?: DefaultDelay

internal class HandlerContext private constructor(
    private val handler: Handler,
    private val name: String?,
    private val invokeImmediately: Boolean
) : HandlerDispatcher(), Delay {
   
   
    override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
   
   
        // 利用主线程的 Handler 延迟执行任务,将完成的 continuation 放在任务中执行
        val block = Runnable {
   
   
            with(continuation) {
   
    resumeUndispatched(Unit) }
        }
        handler.postDelayed(block, timeMillis.coerceAtMost(MAX_DELAY))
        continuation.invokeOnCancellation {
   
    handler.removeCallbacks(block) }
    }

    //..
}

1.可以看出,其实也是通过handler.postDelayed实现延时效果的

2.时间到了之后,再通过resumeUndispatched方法恢复协程

3.如果我们用的是Dispatcher.IO,效果也是一样的,不同的就是延时效果是通过切换线程实现的

withContext是怎样切换线程的?

fun test(){
   
   
    viewModelScope.launch(Dispatchers.Main) {
   
   
        print("1:" + Thread.currentThread().name)
        withContext(Dispatchers.IO){
   
   
            delay(1000)
            print("2:" + Thread.currentThread().name)
        }
        print("3:" + Thread.currentThread().name)
    }
}
//1,2,3处分别输出main,DefaultDispatcher-worker-1,main
public suspend fun <T> withContext(
    context: CoroutineContext,
    block: suspend CoroutineScope.() -> T
): T {
   
     
    return suspendCoroutineUninterceptedOrReturn sc@ {
   
    uCont ->
        // 创建新的context
        val oldContext = uCont.context
        val newContext = oldContext + context
        ....
        //使用新的Dispatcher,覆盖外层
        val coroutine = DispatchedCoroutine(newContext, uCont)
        coroutine.initParentJob()
        //DispatchedCoroutine作为了complete传入
        block.startCoroutineCancellable(coroutine, coroutine)
        coroutine.getResult()
    }
}

private class DispatchedCoroutine<in T>(
    context: CoroutineContext,
    uCont: Continuation<T>
) : ScopeCoroutine<T>(context, uCont) {
   
   
    //在complete时会会回调
    override fun afterCompletion(state: Any?) {
   
   
        afterResume(state)
    }

    override fun afterResume(state: Any?) {
   
   
        //uCont就是父协程,context仍是老版context,因此可以切换回原来的线程上
        uCont.intercepted().resumeCancellableWith(recoverResult(state, uCont))
    }
}
  • 1.withContext其实就是一层Api封装,最后调用到了startCoroutineCancellable,这就跟launch后面的流程一样了,我们就不继续跟了

  • 2.传入的context会覆盖外层的拦截器并生成一个newContext,因此可以实现线程的切换

  • 3.DispatchedCoroutine作为complete传入协程体的创建函数中,因此协程体执行完成后会回调到afterCompletion中

  • 4.DispatchedCoroutine中传入的uCont是父协程,它的拦截器仍是外层的拦截器,因此会切换回原来的线程中

13.kotlin协程作用域有哪些?有啥区别?

‌**Kotlin协程作用域的类型和区别:

  1. ‌**[GlobalScope]**‌:这是一个单例对象,不绑定到任何Job上,无法取消。通过它启动的子协程不会阻塞其所在线程,可以一直运行到应用程序停止。GlobalScope适用于全局性的协程,但需要注意,由于它不绑定到任何Job上,可能会导致内存泄漏和CPU资源浪费‌。
  2. ‌**[ContextScope]**‌:通过上下文创建的协程作用域,包括[MainScope]等。MainScope默认使用[SupervisorJob],适用于Android开发中管理UI线程的协程生命周期。CoroutineScope则允许传入自定义的上下文对象‌。
  3. ‌**[coroutineScope]**‌:创建一个新的协程作用域,所有在该作用域内启动的协程会在作用域结束时被取消。如果作用域内的协程失败,异常会向上传递到父协程‌。
  4. ‌**[supervisorScope]**‌:与coroutineScope类似,但异常不会向上传递到父协程。这意味着在一个supervisorScope作用域内的协程失败时,不会影响其他协程的执行‌。

协程作用域的使用场景和区别:

  • GlobalScope‌:适用于全局性的、长时间运行的协程,但需要小心管理以避免内存泄漏。
  • ContextScope‌:适用于需要特定上下文的场景,如Android开发中的UI线程管理。
  • coroutineScope‌:适用于需要确保所有子协程在作用域结束时被取消的场景。
  • supervisorScope‌:适用于需要并发执行多个任务,但希望某个任务的失败不影响其他任务的场景。

协程作用域的优缺点:

  • 优点‌:
    • 结构化并发‌:协程作用域帮助管理并发任务的生命周期,确保资源正确释放。
    • 异常处理‌:在supervisorScope中,异常不会向上传递,减少了异常对其他任务的影响。
    • 内存管理‌:协程是轻量级的,不会像线程那样占用大量资源。
  • 缺点‌:
    • 滥用GlobalScope‌:如果不正确管理,可能会导致内存泄漏和资源浪费。
    • 学习曲线‌:对于初学者来说,理解和正确使用协程作用域可能需要一定的时间

14.kotlin中静态方法和Java静态方法有啥区别?

Java&Kotlin–静态属性和静态方法

静态方法和属性是什么:

  • Java:用 static 修饰符修饰的属性(成员变量)、常量和成员方法称为静态变量、常量和方法,它们统称为静态成员,归整个类所有,不依赖于类的特定实例,被类的所有实例共享。
  • Kotlin:kotlin并没有static关键词,但仍然有静态成员的概念。kotlin会通过companion object生成一个内部静态类,和@JvmStatic注解搭配使用,可以将变量和函数声明为真正的静态成员。

使用静态方法和对象方法的时机:

  • 需要一个数据对象为整个类而非某个对象服务,同时又力求不破坏类的封装性,即要求此成员隐藏在类的内部,对外不可见
  • 当一个方法不访问这个类的数据和对象方法
  • 在项目的多模块,多个类中不希望创建具体对象就能使用某个类的方法时
  • 这个方法需要访问这个类的数据 和对象方法,例如需要对某个数据进行存储时
  • 需要通过一个实例的对象进行某些操作时

使用静态成员函数和静态数据成员的优缺点:

优点:

  • 节省内存:被所有对象公用,即使有多个对象也只存储一处
  • 提高时间效率:静态函数会自动分配在一个一直使用的存储区,直到退出应用实例,避免了调用函数时压栈出栈
  • 避免重名冲突:静态函数表示是内部函数,函数作用域仅局限于本文本,避免与其他文件重名冲突

缺点:

  • 静态成员不能访问非静态成员函数和非静态数据成员
  • 初始化时便加载到内存,若后续未被引用加大了内存负担和程序运行负担。

15.kotlin中when和Switch的区别?

功能和用法的区别

  1. 类型支持‌:
    • Kotlin的when表达式‌:可以处理任意类型的数据,包括枚举类型、字符、字符串等。它不仅支持基本数据类型,还可以处理复杂的对象类型‌12。
    • Java的switch语句‌:只能处理整数、字符和枚举类型,不支持复杂的对象类型‌1。
  2. 条件表达式‌:
    • Kotlin的when表达式‌:可以使用表达式作为分支条件,使代码更加简洁和易读。它还支持范围、类型判断、集合等条件‌12。
    • Java的switch语句‌:只支持常量值作为分支条件,不支持表达式‌1。
  3. 语法和灵活性‌:
    • Kotlin的when表达式‌:功能更加强大和灵活,可以替代if-else语句,支持多种条件判断和自动转型‌12。
    • Java的switch语句‌:语法相对固定,不支持复杂的条件表达式和自动转型‌1

16.kotlin中空安全的实现原理?

要点:

  • Kotlin中通过「非空类型」与「可空类型」来规避 NPE
  • 可空操作符「?」、安全调用操作符「?.」、非空断言运算符「!!
  • **Elvis操作符「?:」**如果不为空返回它,否则返回另一个值;
  • 安全的类型转换「as?
  • ?.let{}
  • 不是绝对的空指针安全:Kotlin调用返回空的Java代码;

Kotlin空安全的实现原理:

img

总结:

  • 1、非空类型的属性编译器添加**@NotNull注解,可空类型添加@Nullable**注解;
  • 2、非空类型直接对参数进行判空,如果为空直接抛出异常;
  • 3、可空类型,如果是?.判空,不空才执行后续代码,否则返回null;如果是!!,空的话直接抛出NPE异常。
  • 4、as操作符会判空,空的话直接抛出异常,不为空才执行后续操作,没做类型判断!运行时可能会报错!
  • 5、as?则是新建一个变量,参数赋值给它,然后判断是否为特定类型,赋值为null,接着把这个变量的值赋值给另一个新的变量,这里有一点注意:as?处理后的参数可能为空!!!所以调用as?转换后的对象还需要添加安全调用操作符(?.)

17.@JvmStatic、@JvmOverloads、@JvmFiled 在 Kotlin 中有什么作用?

@JvmOverloads

Kotlin的方法里有多个默认参数时,如果在Java中直接调用,只能调用一个包含完整参数的方法,如果想暴露更多的重载函数给Java,可以使用@JvmOverloads 用于生成重载。对于每一个有默认值的参数,生成的重载会把当前有默认值的参数及其右边的参数都去掉,所以如果方法中所有的参数都有默认值,生成的重载函数中还会有一个无参的重载函数。

@JvmOverloads 主要用于构造函数、方法中,同时不能用于抽象方法、接口中的方法等。

@JvmStatic

@JvmStatic用于声明静

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值