Kotlin inlined extension property

2020-08-23 01:12发布

问题:

I know inline keyword means to avoid the call overhead calling a funtion. But I can't figure out what inline a extension property work for?

Let say we have two extension property named foo and another with is inlined named bar

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

inline val Any.bar : Long
    get() = Date().time

Executing any of them, we gent the expected output, the current time.

The bytecode for this file is this below:

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
}

We can see both are similar but differents only on these lines:

foo extract:

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

bar extract:

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

I really don't understand what is happennig here. Can someone point me to see what is the behaviour, or the equivalent in java, or some use for this?

Edit

Given the compiler will replace the content of inlined property, it may convenient to inline every extension property having not heavy operations ?

Thank you

回答1:

From Kotlin's doc,

Note that, since extensions do not actually insert members into classes, there's no efficient way for an extension property to have a backing field.

and also,

The inline modifier can be used on accessors of properties that don't have a backing field.

As mentioned above, an inline extension property does not have a backing field. You may treat an extension property as a pair of static getter/setter, like this:

//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`
}

So, inline property means that the code of the getter/setter function will be inlined into the call site when accessing the property (same as regular inline functions).

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

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

For the bytecode that you post in the question, sorry that I am not able explain what is happening. But you may try to take a look at the bytecode of the following code:

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

, where "".foo will invoke the getter function but "".bar will not.