• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

Stack-Overflow-上最热门的-10-个-Kotlin-问题

android 搞代码 3年前 (2022-05-09) 25次浏览 已收录 0个评论
文章目录[隐藏]

这是 Stack Overflow 上最热门的几个 Kotlin 问题,每个问题如果更深刻的剖析,都能够独自写一篇文章,前面我会针对这些问题,在进一步的剖析。

通过这篇文章你将学习到以下内容:

  • Array<Int>IntArray 的区别,以及如何抉择
  • IterableSequence 的区别,以及如何抉择
  • 罕用的 8 种 For 循环遍历的办法
  • 在 Kotlin 中如何应用 SAM 转换
  • 如何申明一个动态成员,Java 和 Koltin 进行互操作
  • 为什么 kotlin 中的智能转换不能用于可变属性,如何能力解决这个问题
  • 当重写 Java 函数时,如何决定参数的可空性
  • 如何在一个文件中应用多个具备雷同名称的扩大函数和类

译文

Array 和 IntArray 的区别

Array

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)

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() 示意终端操作符,filtermaptake 都是两头操作符,返回 Sequence 实例,当 Sequence 遇到两头操作符时,只是存储操作过程,并不参加计算,直到遇到 toList()

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

应该如何抉择

如果数据量比拟小,能够应用 Iterable。尽管会创立两头后果集,在数据不大的状况下,对性能的影响不会很重大。

如果解决的数据量比拟大,Sequence 是最好的抉择,因为不会创立两头后果集,内存开销更小。

罕用的 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")
}

如何应用 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!")
    }
}

如何申明一个动态成员,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 中的代码。

Function declaration Kotlin usage Java usage
Companion object Foo. F () Foo. Companion. F ();
Companion object with @JvmStatic Foo. F () Foo. F ();
Object Foo. F () Foo. INSTANCE. F ();
Object with @JvmStatic Foo. F () Foo. F ();
Top level function f () UtilKt. F ();
Top level function with @JvmName f () Util. F ();

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

Variable declaration Kotlin usage Java usage
Companion object X. X X. Companion. GetX ();
Companion object with @JvmStatic X. X X. GetX ();
Companion object with @JvmField X. X X. X;
Companion object with const X. X X. X;
Object X. X X. INSTANCE. GetX ();
Object with @JvmStatic X. X X. GetX ();
Object with @JvmField X. X X. X
Object with const X. X X. X;
Top level variable X. X ConstKt. GetX ();
Top level variable with @JvmField X. X ConstKt. X;
Top level variable with const x ConstKt. X;
Top level variable with @JvmName x Const. GetX ();
Top level variable with @JvmName and @JvmField x Const. X;
Top level variable with @JvmName and const x Const. X;

为什么 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
    }
}

当重写 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")
    }
}

如何在一个文件中应用多个具备雷同名称的扩大函数和类

假如在不同的包中对 String 类实现了两个雷同名字的扩大函数,如果是一个一般函数,你能够应用齐全限定包名来调用它,然而扩大函数不行。所以咱们能够在 import 语句中应用 as 关键字对其重命名,代码如下所示。

import com.example.code.indent as indent4
import com.example.square.indent as indent2

"hello world".indent4()

另外一个案例,想在同一个文件中应用来自不同包中两个具备雷同名称的类(例如 java.util.Datejava.sql.Date ),并且您不心愿通过齐全限定包名来调用它们。咱们也能够在 import 语句中应用 as 关键字对其重命名。

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

当初咱们就能够在这个类中,应用通过 as 关键字申明的别名来援用这些类。

译者

全文到这里就完结了,这篇文章每个问题,都是一个知识点,前面我会针对每个问题,独自写一篇文章,进行更加深刻的剖析。

如果有帮忙点个赞就是对我最大的激励


搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:Stack-Overflow-上最热门的-10-个-Kotlin-问题

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址