Kotlin 创建数组的正确方式

数组在 Java 中构建十分的简单,可是到了 Kotlin 这里,很多人(包括我)在初期却出现了不少问题。

我来总结一下,顺便写一下我之前犯过的错误。

本文暂时没写协变逆变的不同,先写点比较基础的,如果有时间会再补。

本文结合 Kotlin 和 Java,方便大家区分其中的区别。

基本类型数组

在 Java 中,创建一个基本类型数组非常的简单:

int[] arr1 = new int[2]; // [0, 0]
char[] arr2 = new char[] {'a', 'b', 'c'}; // ['a', 'b', 'c']
boolean[] arr3 = {true}; // [true]

了解 Kotlin 的一般都知道,在 Kotlin 中,数组创建不采用T[],而是Array<T>

Int数组为例,假设你需要创建一个对标 Java 中int[]的数组

你一想,Java 的非空的Integerint在 Kotlin 中不都统一成Int了吗?我直接

val arr = Array<Int>(2)

怎么报错了,一看Array类的构建参数有两个

public class Array<T> {
    public inline constructor(size: Int, init: (Int) -> T)

这不简单?Java 里不是创建int数组初始化自动补 0 吗?我给他填满 0 不就行了?

val arr = Array<Int>(2) { 0 }

Kotlin 提供了arrayOf<T>(vararg T)方法来构建数组

public inline fun <reified @PureReifiable T> arrayOf(vararg elements: T): Array<T>
val arr = arrayOf(1, 2, 3)

之后测试了一番,发现这两种方法确实不会报错,而且数组也能正常使用。但这增加了非常多的开销。

分析一下这段代码

val arr = Array<Int>(2) { 0 }

把这反编译成 Java 再看看

Integer[] var2 = new Integer[2];
for (int var3 = 0; var3 < 2; ++var3) {
    Integer var8 = 0;
    var2[var3] = var8;

显而易见,用这种方法创建的是Integer[]而不是int[],而且后面补 0 的操作更是多此一举。

第二个方法也是错误的,arrayOf<T>(vararg T)方法返回值是Array<T>,本质和上者的反编译是差不多的,用这种方法创建的是Integer[]而不是int[]

用这种方法确实能起到假性int[]的作用,因为由于 Kotlin 严格的判空,所以你不能往这个数组添加非空元素。但是你若要用真正的int[],一定不要采用这种方法。

以基本类型int为例,别的也都一样,改个名就是了

在 Kotlin 中,提供了以下方法用来构建基本类型数组

public class IntArray(size: Int)
public class IntArray(size: Int, init: (Int) -> Int)
public fun intArrayOf(vararg elements: Int): IntArray
val arr1 = IntArray(2)
val arr2 = intArrayOf(1, 2, 3)

对应 Java

int[] arr1 = new int[2];
int[] arr2 = new int[] {1, 2, 3};

这方法才叫真正的不拖泥带水,给你真正的基本类型数组。

要是想构建元素可空的Integer数组怎么办,别急,这就是Array<T>的范畴了,一会就会说。

以 String 为例

在 Java 中,创建一个对象数组

String[] arr = new String[2]; // 1
String[] arr1 = new String[] {"Ace Taffy"}; // 2

从上文可知,Kotlin 提供了arrayOf<T>(vararg T)方法来构建对象数组,如果创建像上者 2 这样的数组,因为 String 是一个类,不是基本数据类型,直接这样即可。

val arr1 = arrayOf("Ace Taffy")

但如果你想要上者 1 这样的数组,你该如何去做呢?

一般肯定又会想到之前提到的 Array 类,我们知道 Java 对象数组初始化自动补 null,然后这样构建

val arr = Array<String?>(2) { null }

Array 类的构造器后的init: (Int) -> T参数会导致一轮不必要的循环,让我们再看一下反编译的结果

String[] var2 = new String[2]; // 本身就已经是 [null, null]
// 再给它循环赋值 null,完全没必要
for (int var3 = 0; var3 < 2; ++var3) {
    Object var8 = null;
    var2[var3] = (String)var8;

其实官方也早有对策,有专门的方法来创建上者 1 这样的数组

public fun <reified @PureReifiable T> arrayOfNulls(size: Int): Array<T?>
val arr = arrayOfNulls<String>(2)
String[] var1 = new String[2];

完美符合需求。

上文说的构建元素可空的Integer数组,用该方法就可以实现。

val arr = arrayOfNulls<Int>(2)

如果你需要一个长度为 0 的元素非空数组,官方也给你提供了一个方法

public inline fun <reified @PureReifiable T> emptyArray(): Array<T> =
        @Suppress("UNCHECKED_CAST")
        (arrayOfNulls<T>(0) as Array<T>)

以一个大的例子来总结一下,先用 Java 描述基本要求,再用 Kotlin 改写

// 1, [' ', ' ']
char[] arr1 = new char[2];
// 2, ['a', 'a']
char[] arr2 = new char[] {'a', 'a'};
// 3, [0, 2, 4]
int[] arr3 = new int[3];
for (int i = 0; i < arr3.length; ++i) {
    arr3[i] = i * 2;
// 4, [null, null]
String[] arr4 = new String[2];
// 5, ["Ace Taffy"]
String[] arr5 = new String[] {"Ace Taffy"};
// 6,"!"意思是永不为null,Java中没这个概念也不能实现, []
String![] arr6 = new String![0];
// 7, ["0", "2"]
String[] arr7 = new String[2];
for (int i = 0; i < arr7.length; ++i) {
    arr7[i] = String.valueOf(i * 2);
// 8, ["Ace Taffy", "Ace Taffy", "Ace Taffy"]
String[] arr8 = new String[3];
for (int i = 0; i < arr8.length; ++i) {
    arr8[i] = "Ace Taffy";
val arr1 = CharArray(2)
val arr2 = charArrayOf('a', 'a')
val arr3 = IntArray(3) { i -> i * 2 }
val arr4 = arrayOfNulls<String>(2)
// 5,也可以用 8 的方式。
val arr5 = arrayOf<String or String?>("Ace Taffy")
val arr6 = emptyArray<String>()
// 7,如需要该数组为元素非空数组,String? 也可以改成 String,
// 保证数组内的元素不为 null 是 Kotlin 特有,Java 不能实现。
// Array<String> 和 Array<String?> 在 Java 中都是 String[].
val arr7 = Array<String or String?>(2) { i -> (i * 2).toString() }
// 8,同 7
val arr8 = Array<String or String?>(3) { "Ace Taffy" }
复制代码
分类:
Android
  • GitLqr Kotlin
  •