Array<Int> 和 IntArray 3个月前

编程语言
760
Array<Int> 和 IntArray

基本类型居然有特殊待遇

其实我们用数组的场景不太多。有一天在做demo的时候,我突然意识到有 Array<Int> 和 IntArray 这两种完全不同的存在,当时我感觉非常的奇怪,为什么要再搞一个 IntArray 呢?不仅 IntArray,还有 DoubleArray、LongArray 等等,所有基本类型都有一个专属的 Array 版本,这背后一定有什么原因的。

偏偏巧了,kotlin-runtime 这个 jar 包里面对应的 kotlin.kotlin_builtins 这个文件没有源码,所以直接跳进去看到的 IntArray 的声明什么都没有。。

public final class IntArray public constructor(size: kotlin.Int) : kotlin.Cloneable {
    public constructor(size: kotlin.Int, init: (kotlin.Int) -> kotlin.Int) { /* compiled code */ }

    public final val size: kotlin.Int /* compiled code */

    public open fun clone(): kotlin.IntArray { /* compiled code */ }

    public final operator fun get(index: kotlin.Int): kotlin.Int { /* compiled code */ }

    public final operator fun iterator(): kotlin.collections.IntIterator { /* compiled code */ }

    public final operator fun set(index: kotlin.Int, value: kotlin.Int): kotlin.Unit { /* compiled code */ }
}

当时我就很纳闷,想来想去可能也就 IntIterator 特殊一点儿,点进去看看吧。

/** An iterator over a sequence of values of type `Int`. */
public abstract class IntIterator : Iterator<Int> {
    override final fun next() = nextInt()

    /** Returns the next value in the sequence without boxing. */
    public abstract fun nextInt(): Int
}

注意看,“without boxing” 这一句,如果大家对 Java 基本类型的装箱有所了解,看到这个肯定会立马明白什么的——Kotlin 为基本类型定制 Array 的目的是为了避免装箱?

真相总是藏在字节码里面

如果真是这个理由,那么这明显非常说得过去,毕竟装箱是有开销的。为了证实这个答案,我们只需要看下字节码就好了。我们分别用 kotlin 和 Java 写下下面的等价代码:

val intArray = intArrayOf(1,3,4)
int[] array = new int[]{1,3,4};

使用 ASM Bytecode Outline 插件查看字节码,发现二者的结果几乎是完全一样的(当然除了行号之类):

LINENUMBER 7 L1
    ICONST_3
    NEWARRAY T_INT // 注意这里是构造int的数组
    DUP
    ICONST_0
    ICONST_1
    IASTORE
    DUP
    ICONST_1
    ICONST_3
    IASTORE
    DUP
    ICONST_2
    ICONST_4
    IASTORE
    ASTORE 1
    L2

而换个方式,如果我们用 Array<Int> ,会发生什么呢?

val arrayOfInt = arrayOf(1,3,4)
LINENUMBER 8 L2
    ICONST_3
    ANEWARRAY java/lang/Integer
    DUP
    ICONST_0
    ICONST_1
    INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
    AASTORE
    DUP
    ICONST_1
    ICONST_3
    INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
    AASTORE
    DUP
    ICONST_2
    ICONST_4
    INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
    AASTORE
    ASTORE 3
    NOP
    L3

这时候我们就能看到,其实是构造了一个 Integer[] 出来,每添加一个数字,就会调用 Integer.valueOf,这就产生了装箱的开销。

编译器的答案

故事本来发展到这儿就应该结束了吧,毕竟我们得到了一个说得过去的答案,可偏偏我是挖掘机专业毕业的,我就是很好奇 Kotlin 运行的时候把 IntArray 给搞成了什么样子,于是我打开了 Kotlin 的源码(1.0.5),并在 core/builtins/native/kotlin/Arrays.kt 当中找到了 IntArray 的真身:

/**
 * An array of ints. When targeting the JVM, instances of this class are represented as `int[]`.
 * @constructor Creates a new array of the specified [size], with all elements initialized to zero.
 */
public class IntArray(size: Int) : Cloneable {
    /**
     * Creates a new array of the specified [size], where each element is calculated by calling the specified
     * [init] function. The [init] function returns an array element given its index.
     */
    public inline constructor(size: Int, init: (Int) -> Int)

    /** Returns the array element at the given [index]. This method can be called using the index operator. */
    public operator fun get(index: Int): Int
    /** Sets the element at the given [index] to the given [value]. This method can be called using the index operator. */
    public operator fun set(index: Int, value: Int): Unit

    /** Returns the number of elements in the array. */
    public val size: Int

    /** Creates an iterator over the elements of the array. */
    public operator fun iterator(): IntIterator

    public override fun clone(): IntArray
}

果然,这里面的注释明确地告诉我们 IntArray 最终是要编译为 int[] 的。可是,现在骗子这么多,我怎么知道注释不是逗我玩呢?

于是我开始翻 Kotlin 的源码,主要是编译器相关的代码。关于这块儿呢,目前我还没有理解的很透彻,不过答案应该是有了的。

Kotlin 的编译器运行的时候,主要依靠一个叫做 FqName 的类来识别我们代码运行时的类型,所谓 FqName,其实就是 Full Qualified Name,简单的说就是代码元素(主要是类)的全名,比如 String -> java.lang.String。

下面是 Kotlin 映射 IntArray 的一段代码:

typeSignatureMapping.kt,mapBuiltInType方法

val arrayElementType = KotlinBuiltIns.getPrimitiveTypeByArrayClassFqName(fqName)
    if (arrayElementType != null) {
        return typeFactory.createFromString("[" + JvmPrimitiveType.get(arrayElementType).desc)
    }

在编译时,Kotlin 编译器如果遇到 IntArray,就会尝试映射这个类,运行到上面的代码时,fqName 的值是 "kotlin.IntArray",而 arrayElementType 是 PrimitiveType 的一个实例 INT,而这个 PrimitiveType 又是什么呢?

public enum PrimitiveType {
    BOOLEAN("Boolean"),
    CHAR("Char"),
    BYTE("Byte"),
    SHORT("Short"),
    INT("Int"),
    FLOAT("Float"),
    LONG("Long"),
    DOUBLE("Double"),
    ;
    ...
}

是 Kotlin 基本类型的一个枚举。

我们还是看刚才的映射代码,返回的究竟是什么呢?由于我们已经知道 arrayElementType 的值,也就不难得到 JvmPrimitiveType.get(arrayElementType).desc 实际上是 I,这么说来返回的类型应该是 [I——而这个,正是 int[] 的字节码类型。

小结

其实 Kotlin 中的不少类型都是对 Java 原有类型的扩展,当然有时候扩展不方便的话就直接在编译期做映射,类似的例子还有 kotlin.String -> Java.lang.String,kotlin.collections.MutableMap -> java.util.Map 等等。 如果跟高大上的设计模式、应用框架比起来,基本类型的数组问题,算不上一个“大”问题。不过,我希望通过本文传达给大家的一个信息是:“Kotlin 是开源哒,源码面前,了无秘密”。

image
EchoEcho官方
无论前方如何,请不要后悔与我相遇。
1377
发布数
439
关注者
2223121
累计阅读

热门教程文档

Javascript
24小节
Spring Cloud
8小节
MyBatis
19小节
C#
57小节
MySQL
34小节