【问题标题】:Kotlin inlined extension propertyKotlin 内联扩展属性
【发布时间】:2017-09-21 08:26:24
【问题描述】:

我知道 inline 关键字意味着避免调用函数的调用开销。但我无法弄清楚扩展属性的内联是做什么用的?

假设我们有两个名为 foo 的扩展属性和另一个名为 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 中的等价物,或者对此有什么用处?

编辑

鉴于编译器会替换内联属性的内容,是否可以方便地内联每个没有繁重操作的扩展属性?

谢谢

【问题讨论】:

    标签: kotlin inline kotlin-extension jvm-bytecode


    【解决方案1】:

    来自Kotlin's doc

    请注意,由于扩展实际上并不将成员插入到类中,因此没有有效的方法让扩展属性具有 backing field

    also

    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 对我来说,大多数时候,函数调用开销并不是什么大问题。当我需要具体类型参数时,我会考虑使用inline。您可以看看这个question 以供参考。
    • 我发现了一个用途:inline val &lt;reified T&gt; T.TAG : String get() = T::class.java.simpleName :)
    • 是的,这是对具体类型参数的有效且良好的使用。为了使它更好,我会使用T::class.qualifiedName 建议here 因为这个issue
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-04-08
    • 1970-01-01
    • 2014-09-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多