乐趣区

关于android:Stack-Overflow-上最热门的-10-个-Kotlin-问题

这是 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 关键字申明的别名来援用这些类。

退出移动版