Android笔试面试题AI答之Kotlin(5)

19. Kotlin 中的“open”和“public”有什么区别?

Kotlin 中的“open”和“public”是两个不同的关键字,它们各自在类的成员(包括类本身、方法、属性等)的可见性和可继承性方面扮演着不同的角色。

open 关键字

  1. 定义与用途

    • “open”关键字在Kotlin中用于标记一个类、方法或属性为“开放”的,即它们可以被其他类继承或重写。默认情况下,Kotlin中的类和方法都是final的,意味着它们不能被继承或重写。通过使用“open”关键字,可以显式地允许这种继承或重写行为。
    • 示例:open class MyClass {} 表示MyClass可以被其他类继承。
  2. 作用域

    • 主要用于控制类的继承性和成员的重写性。

public 关键字

  1. 定义与用途

    • “public”关键字在Kotlin(以及许多其他编程语言中)用于指定一个类、方法或属性的访问级别为公开的,即它们可以在任何地方被访问,只要它们是在可访问的范围内(如同一个包内或通过导入)。在Kotlin中,如果没有明确指定访问修饰符,类的成员默认为public。
    • 示例:public class MyClass {}(但在Kotlin中,这通常是隐式的,因为Kotlin默认使用public)。
  2. 作用域

    • 控制类、方法或属性的可见性,确保它们可以在不同的作用域内被访问。

open 和 public 的区别

  • 目的不同

    • “open”是为了允许类被继承或方法被重写。
    • “public”是为了控制类、方法或属性的可见性,使其能够在更广泛的范围内被访问。
  • 默认值不同

    • 在Kotlin中,类和方法默认是final的(即不能被继承或重写),而成员(除非特别指定)默认是public的(即可在任何地方被访问)。
  • 使用场景不同

    • 当需要设计一个类,希望它的某些子类能够扩展其功能时,会使用“open”。
    • 当需要控制类、方法或属性的访问范围时,会根据需要选择适当的访问修饰符,包括“public”。

综上所述,“open”和“public”在Kotlin中扮演着不同的角色,分别用于控制类的继承性和成员的可见性。它们的目的、默认值和使用场景都有所不同。

20. Kotin “const”和“val”有什么区别?

Kotlin中的constval关键字都用于声明不可变的属性,但它们在使用场景、编译时特性以及应用方式上存在一些关键区别。

1. 使用场景

  • const

    • const用于声明编译时常量,其值必须在编译时确定且不可更改。
    • 它通常用于顶级对象、对象的成员或伴随对象的成员中。
    • const修饰的变量只能是字符串(String)或基本数据类型(如IntFloat等)。
    • const修饰的变量没有自定义的getter方法,因为它们在编译时会被内联到使用它们的地方。
  • val

    • val用于声明只读变量,一旦赋值后就不能再被重新赋值。
    • 它可以在运行时初始化,适用于各种情况。
    • val可以声明为任何类型的属性,包括复杂对象或集合。
    • val变量可以有自定义的getter方法,尽管大多数情况下不需要。

2. 编译时特性

  • const

    • const变量的值必须在编译时已知,且不能被修改。
    • 在编译过程中,const变量会被内联到使用它们的地方,这有助于减少运行时的开销,因为不需要通过getter方法来访问它们的值。
    • 当Kotlin代码被编译成Java代码时,const val会被编译成public static final字段。
  • val

    • val变量的值可以在运行时确定,但一旦赋值后就不能更改。
    • val变量在编译时不会像const那样被内联,而是通过getter方法(如果有的话)或直接在内存中访问它们的值。
    • 当Kotlin代码被编译成Java代码时,val变量会根据其声明位置被编译成相应的Java字段,但不一定是static final

3. 应用方式

  • const

    • 由于const变量的值在编译时已知且不可更改,因此它们特别适用于那些在整个应用程序中都不会改变的值,如配置参数、错误代码等。
    • 使用const可以提高代码的可读性和可维护性,因为它强制开发者在编译时确定常量的值。
  • val

    • val变量适用于需要在运行时确定值但之后不会更改的场景。
    • 它们可以用于类的属性、局部变量等。
    • 使用val可以编写更安全、更易于理解的代码,因为它防止了变量在赋值后被意外修改。

总结

constval在Kotlin中都用于声明不可变的属性,但const更侧重于编译时常量,而val则更灵活,适用于各种需要在运行时确定但之后不会更改的场景。在选择使用哪个关键字时,应根据具体的应用场景和需求来决定。

21. Kotlin List 和 Array 类型有什么区别?

Kotlin中的List和Array类型在多个方面存在区别,主要包括以下几个方面:

1. 可变性与大小

  • Array:数组(Array)在Kotlin中是一个具有固定大小的集合,一旦创建,其大小就不能改变。尽管数组中的元素本身可以是可变的(比如,如果数组的元素是可变对象),但数组本身的大小是固定的。
  • List:列表(List)则是一个大小可以动态变化的集合。Kotlin中的List接口有多个实现,如ArrayList和LinkedList,它们允许在运行时添加、删除或替换元素。

2. 泛型与类型

  • Array:Array是具体类型的集合,它需要一个明确的类型参数来指定数组中元素的类型。例如,Array<Int>表示一个整型数组。Kotlin还提供了特定于基本数据类型的数组类,如IntArrayDoubleArray等,这些类直接映射到Java的原始数组类型(如int[]double[]),以提高性能和减少装箱/拆箱的开销。
  • List:List是一个泛型接口,它允许你指定列表中元素的类型。例如,List<String>表示一个字符串列表。List的实现(如ArrayList和LinkedList)在内部处理元素的存储和检索,但具体的实现细节对外部是隐藏的。

3. 初始化与访问

  • Array:数组可以使用arrayOf函数或Array构造函数来初始化。访问数组元素通常使用索引操作符[],如arr[index]。对于特定于基本数据类型的数组(如IntArray),也可以使用类似Java的语法来访问元素。
  • List:List的初始化通常通过调用listOf函数或其他集合转换函数来完成。访问List元素也使用索引操作符[](对于MutableList,还可以使用set方法来修改元素),但List的访问可能会比数组慢,因为List的实现可能需要遍历内部数据结构来找到元素。

4. 特性与功能

  • Array:数组提供了一系列基本的集合操作,如遍历、搜索、排序等。但由于其固定大小,它不支持直接添加或删除元素的操作。不过,你可以通过创建一个新数组并复制旧数组的元素(可能还包括新元素或已删除元素的位置的空位)来间接实现这些操作。
  • List:List提供了比数组更丰富的集合操作,包括添加、删除、替换元素以及更复杂的遍历和搜索操作。List的实现(如ArrayList)通常提供了高效的随机访问性能,但某些操作(如插入或删除元素)可能会比数组慢,因为它们可能需要移动列表中的其他元素。

5. 用途与选择

  • 在选择使用List还是Array时,你应该考虑你的具体需求。如果你需要一个固定大小的集合,并且关心性能(特别是对于基本数据类型的集合),那么数组可能是一个更好的选择。如果你需要一个大小可以变化的集合,或者需要利用List提供的丰富集合操作,那么List可能更适合你的需求。

综上所述,Kotlin中的List和Array类型在可变性与大小、泛型与类型、初始化与访问、特性与功能以及用途与选择等方面存在显著的区别。在选择使用哪种类型时,你应该根据你的具体需求和上下文来做出决策。

22. 简述Kotlin 中的 Elvis 运算符?

Kotlin 中的 Elvis 运算符是一个非常有用的特性,它用 ?: 表示。这个运算符用于处理空值(null)的情况,提供一种简洁的方式来为可能为 null 的表达式提供一个默认值。Elvis 运算符的名称来源于它在一些编程社区中的流行称呼,灵感来源于猫王(Elvis Presley)的昵称“The King”(国王),因为它为表达式提供了一种“备选国王”(即默认值)的优雅方式。

基本用法如下:

val b: String? = null // b 是一个可能为 null 的 String
val result = b ?: "default" // 如果 b 是 null,则 result 会是 "default"

在这个例子中,如果 bnull,则 result 会被赋值为 "default";如果 b 不是 null,则 result 会被赋值为 b 的值。

Elvis 运算符的一个关键特性是它可以非常方便地链式调用,即可以嵌套使用:

val a: String? = "Hello"
val b: String? = null
val c: String? = null

val result = a ?: b ?: c ?: "default"
// 这里,如果 a 不是 null,result 将是 a 的值;
// 如果 a 是 null,则检查 b;
// 如果 b 不是 null,result 将是 b 的值;
// 以此类推,直到找到第一个非 null 的值,或者如果没有找到,就使用 "default"

通过这种方式,Elvis 运算符可以极大地简化处理可能为 null 的多个表达式时的代码逻辑,使得代码更加简洁、易读。

在 Kotlin 的最新版本中,还引入了安全的调用操作符 ?. 与 Elvis 运算符的组合使用,允许你在进行安全调用后再提供一个默认值,进一步增强了处理 null 值的能力。例如:

val a: String? = "Hello"
val b: String? = null

val lengthOrDefault = a?.length ?: 0 // 如果 a 不是 null,计算并返回其长度;否则返回 0

这种结合使用的方式使得 Kotlin 在处理可能为 null 的值时变得更加灵活和强大。

23. 下述Java代码的功能是什么?存在什么问题?请用 Kotlin 重写这段代码 ?

public class Singleton {
private static Singleton instance = null;
private Singleton(){
}
private synchronized static void createInstance() {
if (instance == null) {
instance = new Singleton();
}
}
public static Singleton getInstance() {
if (instance == null) createInstance();
return instance;
}

这段Java代码的目的是实现单例模式,但存在一个问题:getInstance() 方法不是同步的,这可能导致在多线程环境下创建多个 Singleton 实例,从而违背了单例模式的原则。

具体来说,如果两个线程同时调用 getInstance() 方法,并且 instance 还未被初始化(即为 null),那么两个线程都可能进入 if (instance == null) 条件,并都调用 createInstance() 方法,导致创建了两个 Singleton 实例。

为了修复这个问题,我们需要将 getInstance() 方法也标记为 synchronized,但这会降低性能,因为每次调用 getInstance() 时都需要进行线程同步。更好的做法是使用双重检查锁定(Double-Checked Locking)或者利用 Kotlin 的语言特性来避免这个问题。

以下是使用 Kotlin 重写的代码,利用了 Kotlin 的 by lazy 委托和线程安全初始化器,它自动处理了线程安全问题,并且实现了懒加载:

class Singleton private constructor() {
    companion object {
        // 使用 by lazy 实现线程安全的单例模式,并且自动处理懒加载
        private val instance: Singleton by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
            Singleton()
        }

        @JvmStatic
        fun getInstance(): Singleton = instance
    }

    // 示例方法
    fun doSomething() {
        println("Doing something in Singleton")
    }
}

fun main() {
    Singleton.getInstance().doSomething()
}

在这个 Kotlin 版本中,Singleton 类有一个私有构造函数,防止从类的外部直接创建实例。companion object 类似于 Java 中的静态内部类,但它更简洁。在 companion object 中,我们定义了一个 instance 属性,它使用 by lazy 委托来确保只有在第一次调用 getInstance() 方法时才会创建 Singleton 的实例,并且这个创建过程是线程安全的(由于我们使用了 LazyThreadSafetyMode.SYNCHRONIZED)。

@JvmStatic 注解允许我们从 Java 代码中静态地访问 getInstance() 方法,就像它是一个 Java 静态方法一样。如果你不打算从 Java 代码中访问这个单例,那么可以省略这个注解。

现在,无论多少个线程同时调用 getInstance() 方法,都只会创建一个 Singleton 实例,并且这个实例的创建是线程安全的。

答案来自文心一言,仅供参考

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

工程师老罗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值