Kotlin 内联扩展属性

14

我知道 inline 关键字的意思是避免调用函数时产生开销。但我不确定将一个扩展属性标记为 inline 会起什么作用?

假设我们有两个扩展属性,分别命名为 foo 和另一个标记为 inline 的属性 bar

val Any.foo : Long
    get() = Date().time

inline val Any.bar : Long
    get() = Date().time
执行它们中的任何一个,我们都可以得到预期的输出,即当前时间。
这个文件的字节码如下:
public final class InlinedExtensionPropertyKt {

  public final static getFoo(Ljava/lang/Object;)J
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
   L0
    ALOAD 0
    LDC "$receiver"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
   L1
    LINENUMBER 9 L1
    NEW java/util/Date
    DUP
    INVOKESPECIAL java/util/Date.<init> ()V
    INVOKEVIRTUAL java/util/Date.getTime ()J
    LRETURN
   L2
    LOCALVARIABLE $receiver Ljava/lang/Object; L0 L2 0
    MAXSTACK = 2
    MAXLOCALS = 1

  public final static getBar(Ljava/lang/Object;)J
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
   L0
    ALOAD 0
    LDC "$receiver"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
   L1
    LINENUMBER 12 L1
    NEW java/util/Date
    DUP
    INVOKESPECIAL java/util/Date.<init> ()V
    INVOKEVIRTUAL java/util/Date.getTime ()J
    LRETURN
   L2
    LOCALVARIABLE $receiver Ljava/lang/Object; L0 L2 0
    LOCALVARIABLE $i$f$getBar I L0 L2 1
    MAXSTACK = 2
    MAXLOCALS = 2

  @Lkotlin/Metadata;(mv={1, 1, 7}, bv={1, 0, 2}, k=2, d1={"\u0000\u000e\n\u0000\n\u0002\u0010\u0009\n\u0002\u0010\u0000\n\u0002\u0008\u0005\"\u0016\u0010\u0000\u001a\u00020\u0001*\u00020\u00028\u00c6\u0002\u00a2\u0006\u0006\u001a\u0004\u0008\u0003\u0010\u0004\"\u0015\u0010\u0005\u001a\u00020\u0001*\u00020\u00028F\u00a2\u0006\u0006\u001a\u0004\u0008\u0006\u0010\u0004\u00a8\u0006\u0007"}, d2={"bar", "", "", "getBar", "(Ljava/lang/Object;)J", "foo", "getFoo", "test sources for module app"})
  // compiled from: InlinedExtensionPropertyKt.kt
}
我们可以看到两者相似但只在以下几个方面有所不同: foo 提取:
    LOCALVARIABLE $receiver Ljava/lang/Object; L0 L2 0
    MAXSTACK = 2
    MAXLOCALS = 1

酒吧提取:

    LOCALVARIABLE $receiver Ljava/lang/Object; L0 L2 0
    LOCALVARIABLE $i$f$getBar I L0 L2 1
    MAXSTACK = 2
    MAXLOCALS = 2

我真的不明白这里发生了什么事情。有人能指点我看看这种行为,在Java中的相当之处,或对此有什么用处吗?

编辑

考虑到编译器将替换内联属性的内容,是否将每个扩展属性内联化并且没有繁重操作会更方便呢?

谢谢

1个回答

11

来自 Kotlin 文档

请注意,由于扩展实际上未将成员插入类中,因此扩展属性没有有效的方法可以具有支持字段

以及 也是如此

inline 可以用于没有支持字段的属性访问器。

如上所述,内联扩展属性没有支持字段。您可以将扩展属性视为静态 getter/setter 对,如下所示:

//In Kotlin
var Any.foo : Long
    get() = Date().time
    set(value) {
        //Cannot access field here since extension property cannot have backing field
        //Do something with `obj`
    }

//In Java
public static long getFoo(Object obj) {
    return new Date().getTime();
}
public static void setFoo(Object obj) {
    //Do something with `obj`
}

因此,内联属性意味着在访问该属性时,getter / setter函数的代码将内联到调用站点中(与常规内联函数相同)。

//In Kotlin
val x = "".foo
val y = "".bar

//Generated code
val x = InlinedExtensionPropertyKt.getFoo("")
val y = Date().time

对于你在问题中发布的字节码,很抱歉我无法解释发生了什么。但是你可以尝试查看以下代码的字节码:

fun get() {
    val x = "".foo
    val y = "".bar
}

在这里,"".foo将调用getter函数,但"".bar则不会。


谢谢你的回答,我认为字节码比你所建议的要多得多。你认为内联每个扩展属性是好的吗? - crgarridos
@crgarridos 对我来说,大多数情况下,函数调用开销并不是什么大问题。当我需要具体化类型参数时,我会考虑使用 inline。你可以参考这个问题 - BakaWaii
2
我发现了一个用法:inline val <reified T> T.TAG : String get() = T::class.java.simpleName :) - crgarridos
1
是的,这是使用具体化类型参数的有效且良好的用法。为了使它更好,建议使用T::class.qualifiedName,如此处所建议,因为存在这个问题 - BakaWaii

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接