美文网首页js css html
Stack Overflow上最热门的8个Kotlin问题

Stack Overflow上最热门的8个Kotlin问题

作者: android不是安卓 | 来源:发表于2022-05-14 19:25 被阅读0次

    这是 Stack Overflow 上最热门的几个 Kotlin 问题,每个问题如果更深入的分析,都可以单独写一篇文章,后面我会针对这些问题,在进一步的分析。

    通过这篇文章你将学习到以下内容:
    • Array<Int> 和 IntArray 的区别,以及如何选择。
    • Iterable 和 Sequence 的区别,以及如何选择。
    • 常用的 8 种 For 循环遍历的方法。
    • 在 Kotlin 中如何使用 SAM 转换。
    • 如何声明一个静态成员,Java 和 Koltin 进行互操作。
    • 为什么 kotlin 中的智能转换不能用于可变属性,如何才能解决这个问题。
    • 当重写 Java 函数时,如何决定参数的可空性。
    • 如何在一个文件中使用多个具有相同名称的扩展函数和类。

    1. Array<Int> 和 IntArray 的区别

    Array<Int>

    Array<T> 可以为任何 T 类型存储固定数量的元素。它和 Int 类型参数一起使用, 例如 Array<Int>,编译成 Java 代码,会生成 Integer[] 实例。我们可以通过 arrayOf 方法创建数组。

    val arrayOfInts: Array<Int> = arrayOf(1, 2, 3, 4, 5)
    
    IntArray

    IntArray 可以让我们使用基础数据类型的数组,编译成 Java 代码,会生成 int[] (其它的基础类型的数组还有 ByteArray , CharArray 等等), 我们可以通过 intArrayOf 工厂方法创建数组。

    val intArray: IntArray = intArrayOf(1, 2, 3, 4, 5)
    
    什么时候使用 Array<Int> 或者 IntArray

    默认使用 IntArray,因为它的性能更好,不需要对每个元素进行装箱。IntArray 进行初始化的时候,默认将每个索引的值初始化为 0,代码如下所示。

    val intArray = IntArray(10)
    val arrayOfInts = Array<Int>(5) { i -> i * 2 }
    

    而 Array<Int> 的性能比较差,会对每个元素进行装箱,如果你需要创建包含 null 值的数组,Kotlin 也提供了 arrayOfNulls方法,帮助我们进行创建。

    val notActualPeople: Array<Person?> = arrayOfNulls<Person>(13)
    

    2. Iterable 和 Sequence 的区别

    Iterable

    Iterable 对应 Java 的 java.lang.Iterable, Iterable 会立即处理输入的元素,并返回一个包含结果的新集合。我们来举一个简单的例子 返回年龄 > 21 前 5 个人的集合。

    val people: List<Person> = getPeople()
    val allowedEntrance = people
            .filter { it.age >= 21 }
            .map { it.name }
            .take(5)
    

    • 首先通过 filter 函数检查每个人的年龄,将结果放入到一个新的结果集中。
    • 通过 map 函数对上一步得到的结果进行名字映射,然后生成一个新的列表 list<String>。
    • 通过 take 函数返回前 5 个元素,得到最终的结果集。

    Sequence

    Sequence 是 Kotlin 中一个新的概念,用来表示一个延迟计算的集合。Sequence 只存储操作过程,并不处理任何元素,直到遇到终端操作符才开始处理元素,我们也可以通过 asSequence 扩展函数,将现有的集合转换为 Sequence ,代码如下所示。

    val people: List<Person> = getPeople()
    val allowedEntrance = people.asSequence()
    .filter { it.age >= 21 }
    .map { it.name }
    .take(5)
    .toList()
    

    在这个例子中, toList() 表示终端操作符,filter 、 map 、 take 都是中间操作符,返回 Sequence 实例,当 Sequence 遇到中间操作符时,只是存储操作过程,并不参与计算,直到遇到 toList()。

    Sequence 的好处它不会生成中间结果集,直接对原始列表中的每一个人重复这个步骤,直到找到 5 个人,返回最终的结果集。

    应该如何选择

    如果数据量比较小,可以使用 Iterable。虽然会创建中间结果集,在数据不大的情况下,对性能的影响不会很严重。

    如果处理的数据量比较大,Sequence 是最好的选择,因为不会创建中间结果集,内存开销更小。

    3. 常用的 8 种 For 循环遍历方法

    我们经常会使用以下方法进行遍历。

    for (i in 0..args.size - 1) {
    println(args[i])
    }
    

    但是 Array 有一个可读性更强的扩展属性 lastIndex。

    for (i in 0..args.lastIndex) {
    println(args[i])
    }
    

    但是实际上我们不需要知道最后一个索引,有一个更加简单的写法。

    for (i in 0 until args.size) {
    println(args[i])
    }
    

    当然你也可以使用下标扩展属性 indices 得到它的范围。

    for (i in args.indices) {
    println(args[i])
    }
    

    还有一个更加直接的写法,通过下面的方式直接迭代集合。

    for (arg in args) {
    println(arg)
    }
    

    您也可以使用 forEach 函数,传递一个 lambda 表达式来处理每个元素。

    args.forEach { arg ->
    println(arg)
    }
    

    它们生成的 Java 代码都非常的相似,在这些例子中,都增加一个索引变量,并在循环中通过索引获取元素。但是如果我们迭代的是 List,最后两个例子底层使用 Iterator,而其他的例子仍是通过索引获取元素。另外还有两个遍历的方法:

    • withIndex 函数,它返回一个 Iterable 对象,该对象可以被解构为当前索引和元素。

    for ((index, arg) in args.withIndex()) {
    
        println("$index: $arg")
    }
    

    • forEachIndexed 函数,它为每个索引和参数提供了一个 lambda 表达式。

    args.forEachIndexed { index, arg ->
        println("$index: $arg")
    }
    

    4. 如何使用 SAM 转换

    可以通过 lambda 表达式实现 SAM 转换,从而使代码更简洁,可读性更强,我们来看一个例子。

    在 Java 中定义一个 OnClickListener 接口,并声明一个 onClick 的方法。

    public interface OnClickListener {
        void onClick(Button button);
    }
    

    我们给 Button 添加 OnClickListener 监听器,每次点击的时候都会被调用。

    public class Button {
        public void setListener(OnClickListener listener) { ... }
    }
    

    在 Kotlin 中常见的写法,创建匿名类,实现 OnClickListener 接口。

    button.setListener(object: OnClickListener {
        override fun onClick(button: Button) {
            println("Clicked!")
        }
    })
    

    如果我们使用 SAM 转换,将使代码更简洁,可读性更强。

    button.setListener {
    
        fun onClick(button: Button) {
            println("Clicked!")
        }
    }
    

    5. 如何声明一个静态成员,Java 和 Koltin 进行互操作

    一个普通的类可以有静态成员和非静态成员,在 Kotlin 中我们常把静态成员放到 companion object 中。

    class Foo {
        companion object {
            fun x() { ... }
        }
        fun y() { ... }
    }
    

    我们也可以用 object 来声明一个单例,替换 companion object。

    object Foo {
        fun x() { ... }
    }
    

    如果你不想总是通过类名去调用 Foo.x(), 而只是想使用 x(),可以声明为顶级函数。

    fun x() { ... }
    

    另外想在 Java 中调用 Kotlin 中静态方法,需要添加 @JvmStatic 和 @JvmName 注解。用一张表格汇总一下,如何在 Java 中调用 Kotlin 中的代码。

    同样的规则也适用于变量,@JvmField 注解用于变量上,加上 const 关键字,编译时可以将常量值内联到调用处。

    6. 为什么 kotlin 中的智能转换不能用于可变属性

    我们先来看一段有问题的代码。

    class Dog(var toy: Toy? = null) {
        fun play() {
            if (toy != null) {
                toy.chew()
            }
        }
    }
    

    上面的代码在编译时无法通过,异常信息如下所示:

    Kotlin: Smart cast to 'Toy' is impossible, because 'toy' is a mutable property that could have been changed by this time
    

    出现这个问题的原因在于,执行完 toy != null 之后和 toy.chew() 方法被调用之间,这个 Dog 的实例可能被另外一个线程修改,这可能会出现 NullPointerException 异常。

    如何才能解决这个问题呢?

    只需要将变量设置为不可变的,即用 val 声明,那么上面的问题就不存在,默认情况将所有的变量都用 val 声明,除非有必要的时候,才将它们设置为 var。

    如果一定要声明为 var ,那么可以使用局部不可变的副本来解决这个问题。修改一下上面的代码,如下所示。

    class Dog(var toy: Toy? = null) {
        fun play() {
            val _toy = toy
            if (_toy != null) {
                _toy.chew()
            }
        }
    }
    

    但是还有一个更简洁的写法:

    class Dog(var toy: Toy? = null) {
        fun play() {
            toy?.length
        }
    }
    

    7. 当重写 Java 函数时,如何决定参数的可空性

    在 Java 中定义一个 OnClickListener 接口,并声明一个 onClick 的方法。

    public interface OnClickListener {
        void onClick(Button button);
    }
    

    在 Kotlin 中实现这个接口,并通过 IDEA 自动生成 onClick 方法,将会得到下面的方法签名,onClick 方法参数默认为可空类型。

    class KtListener: OnClickListener {
        override fun onClick(button: Button?): Unit {
            val name = button?.name ?: "Unknown button"
            println("Clicked $name")
        }
    }
    

    由于 Java 平台没有可空类型,而 Kotlin 中有,在这个例子中 Button 是否为空由我们来决定。默认情况下,对所有参数使用可空类型更安全,编译器会强制我们处理这些参数。

    对于已知的永远不会空的参数,可以使用非空类型,空和非空都可以正常编译,但是如果将方法参数声明为非空,那么 Kotlin 编译器会自动注入一个空的检查,可能会抛出 IllegalArgumentException 异常,潜在的风险很大。当然使用非空参数,代码将会更加简洁。

    class KtListener: OnClickListener {
        override fun onClick(button: Button): Unit {
            val name = button.name
            println("Clicked $name")
        }
    }
    

    8. 如何在一个文件中使用多个具有相同名称的扩展函数和类

    假设在不同的包中对 String 类实现了两个相同名字的扩展函数,如果是一个普通函数,你可以使用完全限定包名来调用它,但是扩展函数不行。所以我们可以在 import 语句中使用as 关键字对其重命名,代码如下所示。

    import com.example.code.indent as indent4
    import com.example.square.indent as indent2
    
    "hello world".indent4()
    

    另外一个案例,想在同一个文件中使用来自不同包中两个具有相同名称的类(例如 java.util.Date 和 java.sql.Date ),并且您不希望通过完全限定包名来调用它们。我们也可以在 import 语句中使用 as 关键字对其重命名。

    import java.util.Date as UtilDate
    import java.sql.Date as SqlDate
    

    现在我们就可以在这个类中,使用通过 as 关键字声明的别名来引用这些类。

    作者:安卓开发精选
    原文地址:https://mp.weixin.qq.com/s/IypY7Qd5Tg-9m1JxZDvRlA

    相关文章

      网友评论

        本文标题:Stack Overflow上最热门的8个Kotlin问题

        本文链接:https://www.haomeiwen.com/subject/kdpeurtx.html