Kotlin中@JvmInline标记的value class未内联?求排查原因
Kotlin Value Class反编译后未内联的疑问
我最近使用value class开发工具以减少额外开销,但将其反编译为Java后,发现它似乎未被内联,不确定是否存在问题。操作流程是:在Android Studio中通过Tools->Kotlin->Show Kotlin Bytecode路径打开字节码界面,再点击Decompile按钮得到Java代码。
原始Kotlin类
@JvmInline value class StringKey(override val key: String) : DefaultTypedBundle<String>, TypedIntent<String> { override fun put(bundle: Bundle, value: String) { bundle.putString(key, value) } override fun get(bundle: Bundle, defaultValue: String): String = bundle.getString(key, defaultValue) override fun get(bundle: Bundle): String? = bundle.getString(key) override fun put(intent: Intent, value: String) { intent.putExtra(key, value) } override fun get(intent: Intent): String? { return intent.getStringExtra(key) } }
反编译后的Java类
@JvmInline @Metadata( mv = {1, 8, 0}, k = 1, d1 = {"\u0000B\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\u0010\u000e\n\u0002\u0018\u0002\n\u0002\b\u0006\n\u0002\u0010\u000b\n\u0000\n\u0002\u0010\u0000\n\u0002\b\u0004\n\u0002\u0018\u0002\n\u0002\b\u0003\n\u0002\u0018\u0002\n\u0002\b\u0004\n\u0002\u0010\b\n\u0002\b\u0003\n\u0002\u0010\u0002\n\u0002\b\u0007\b\u0087@\u0018\u00002\b\u0012\u0004\u0012\u00020\u00020\u00012\b\u0012\u0004\u0012\u00020\u00020\u0003B\u0012\u0012\u0006\u0010\u0004\u001a\u00020\u0002ø\u0001\u0000¢\u0006\u0004\b\u0005\u0010\u0006J\u001a\u0010\t\u001a\u00020\n2\b\u0010\u000b\u001a\u0004\u0018\u00010\fHÖ\u0003¢\u0006\u0004\b\r\u0010\u000eJ\u0019\u0010\u000f\u001a\u0004\u0018\u00010\u00022\u0006\u0010\u0010\u001a\u00020\u0011H\u0016¢\u0006\u0004\b\u0012\u0010\u0013J\u0019\u0010\u000f\u001a\u0004\u0018\u00010\u00022\u0006\u0010\u0014\u001a\u00020\u0015H\u0016¢\u0006\u0004\b\u0012\u0010\u0016J\u001f\u0010\u000f\u001a\u00020\u00022\u0006\u0010\u0014\u001a\u00020\u00152\u0006\u0010\u0017\u001a\u00020\u0002H\u0016¢\u0006\u0004\b\u0012\u0010\u0018J\u0010\u0010\u0019\u001a\u00020\u001aHÖ\u0001¢\u0006\u0004\b\u001b\u0010\u001cJ\u001f\u0010\u001d\u001a\u00020\u001e2\u0006\u0010\u0010\u001a\u00020\u00112\u0006\u0010\u001f\u001a\u00020\u0002H\u0016¢\u0006\u0004\b \u0010!J\u001f\u0010\u001d\u001a\u00020\u001e2\u0006\u0010\u0014\u001a\u00020\u00152\u0006\u0010\u001f\u001a\u00020\u0002H\u0016¢\u0006\u0004\b \u0010\"J\u0010\u0010#\u001a\u00020\u0002HÖ\u0001¢\u0006\u0004\b$\u0010\u0006R\u0014\u0010\u0004\u001a\u00020\u0002X\u0096\u0004¢\u0006\b\n\u0000\u001a\u0004\b\u0007\u0010\b\u0088\u0001\u0004ø\u0001\u0000\u0082\u0002\u0004\n\u0002\b\u0019¨\u0006%"}, d2 = {"Lcom/jintin/bundle/key/StringKey;", "Lcom/jintin/bundle/key/DefaultTypedBundle;", "", "Lcom/jintin/bundle/key/TypedIntent;", "key", "constructor-impl", "(Ljava/lang/String;)Ljava/lang/String;", "getKey", "()Ljava/lang/String;", "equals", "", "other", "", "equals-impl", "(Ljava/lang/String;Ljava/lang/Object;)Z", "get", "intent", "Landroid/content/Intent;", "get-impl", "(Ljava/lang/String;Landroid/content/Intent;)Ljava/lang/String;", "bundle", "Landroid/os/Bundle;", "(Ljava/lang/String;Landroid/os/Bundle;)Ljava/lang/String;", "defaultValue", "(Ljava/lang/String;Landroid/os/Bundle;Ljava/lang/String;)Ljava/lang/String;", "hashCode", "", "hashCode-impl", "(Ljava/lang/String;)I", "put", "", "value", "put-impl", "(Ljava/lang/String;Landroid/content/Intent;Ljava/lang/String;)V", "(Ljava/lang/String;Landroid/os/Bundle;Ljava/lang/String;)V", "toString", "toString-impl", "sources for library Gradle: io.github.jintin:typed-bundle:0.3.0@aar"} ) public final class StringKey implements DefaultTypedBundle, TypedIntent { @NotNull private final String key; public void put(@NotNull Bundle var1, @NotNull String var2) { put-impl(this.key, var1, var2); } // $FF: synthetic method // $FF: bridge method public void put(Bundle var1, Object var2) { this.put(var1, (String)var2); } @NotNull public String get(@NotNull Bundle var1, @NotNull String var2) { return get-impl(this.key, var1, var2); } // $FF: synthetic method // $FF: bridge method public Object get(Bundle var1, Object var2) { return this.get(var1, (String)var2); } @Nullable public String get(@NotNull Bundle var1) { return get-impl(this.key, var1); } // $FF: synthetic method // $FF: bridge method public Object get(Bundle var1) { return this.get(var1); } public void put(@NotNull Intent var1, @NotNull String var2) { put-impl(this.key, var1, var2); } // $FF: synthetic method // $FF: bridge method public void put(Intent var1, Object var2) { this.put(var1, (String)var2); } @Nullable public String get(@NotNull Intent var1) { return get-impl(this.key, var1); } // $FF: synthetic method // $FF: bridge method public Object get(Intent var1) { return this.get(var1); } @NotNull public String getKey() { return this.key; } // $FF: synthetic method private StringKey(String key) { Intrinsics.checkNotNullParameter(key, "key"); super(); this.key = key; } public static void put_impl/* $FF was: put-impl*/(String $this, @NotNull Bundle bundle, @NotNull String value) { Intrinsics.checkNotNullParameter(bundle, "bundle"); Intrinsics.checkNotNullParameter(value, "value"); bundle.putString($this, value); } @NotNull public static String get_impl/* $FF was: get-impl*/(String $this, @NotNull Bundle bundle, @NotNull String defaultValue) { Intrinsics.checkNotNullParameter(bundle, "bundle"); Intrinsics.checkNotNullParameter(defaultValue, "defaultValue"); String var10000 = bundle.getString($this, defaultValue); Intrinsics.checkNotNullExpressionValue(var10000, "bundle.getString(key, defaultValue)"); return var10000; } @Nullable public static String get_impl/* $FF was: get-impl*/(String $this, @NotNull Bundle bundle) { Intrinsics.checkNotNullParameter(bundle, "bundle"); return bundle.getString($this); } public static void put_impl/* $FF was: put-impl*/(String $this, @NotNull Intent intent, @NotNull String value) { Intrinsics.checkNotNullParameter(intent, "intent"); Intrinsics.checkNotNullParameter(value, "value"); intent.putExtra($this, value); } @Nullable public static String get_impl/* $FF was: get-impl*/(String $this, @NotNull Intent intent) { Intrinsics.checkNotNullParameter(intent, "intent"); return intent.getStringExtra($this); } @NotNull public static String constructor_impl/* $FF was: constructor-impl*/(@NotNull String key) { Intrinsics.checkNotNullParameter(key, "key"); return key; } // $FF: synthetic method public static final StringKey box_impl/* $FF was: box-impl*/(String v) { Intrinsics.checkNotNullParameter(v, "v"); return new StringKey(v); } public static String toString_impl/* $FF was: toString-impl*/(String var0) { return "StringKey(key=" + var0 + ")"; } public static int hashCode_impl/* $FF was: hashCode-impl*/(String var0) { return var0 != null ? var0.hashCode() : 0; } public static boolean equals_impl/* $FF was: equals-impl*/(String var0, Object var1) { if (var1 instanceof StringKey) { String var2 = ((StringKey)var1).unbox-impl(); if (Intrinsics.areEqual(var0, var2)) { return true; } } return false; } public static final boolean equals_impl0/* $FF was: equals-impl0*/(String p1, String p2) { return Intrinsics.areEqual(p1, p2); } // $FF: synthetic method public final String unbox_impl/* $FF was: unbox-impl*/() { return this.key; } public String toString() { return toString-impl(this.key); } public int hashCode() { return hashCode-impl(this.key); } public boolean equals(Object var1) { return equals-impl(this.key, var1); } }
原因说明
这种情况是正常的,不存在问题。核心原因在于你的StringKey实现了DefaultTypedBundle<String>和TypedIntent<String>这两个接口。
Kotlin的value class在大多数场景下会被编译器内联,直接使用其底层值(这里就是String)来避免对象创建开销。但当value class需要作为接口类型使用时,编译器必须生成实际的包装类(也就是你反编译看到的StringKey类),因为接口需要实例来实现多态特性。
具体来说:
- 当你直接以
StringKey类型使用这个值类时,编译器仍然会内联,不会创建包装对象; - 只有当你把它赋值给接口类型变量(比如
val bundleKey: DefaultTypedBundle<String> = StringKey("key")),或者通过接口调用方法时,才会触发装箱操作,创建StringKey实例。
反编译出来的代码只是编译器生成的必要框架,实际运行时的开销只存在于需要装箱的场景,直接使用value class的地方依然能享受到无额外开销的优势。
内容的提问来源于stack exchange,提问作者Jintin
相关产品推荐
相关产品推荐

