value class
、コンパイラがいろいろと頑張るのだろうなぁと思いつつ、いままでどんな感じになるのか調べていなかったので、サクッと javap
して雑に確認してみます。
※正確なところは仕様を追ったりしたほうが良いです。適当に試したメモみたいなものです。
とりあえず以下のような value class をつくって、
@JvmInline
value class Foo(val value: String) {
fun foo() = "value is $value"
}
コンパイル & javap してみる。
$ kotlinc Foo.kt
$ javap -p Foo
Compiled from "Foo.kt"
public final class Foo {
private final java.lang.String value;
public final java.lang.String getValue();
public static final java.lang.String foo-impl(java.lang.String);
public static java.lang.String toString-impl(java.lang.String);
public java.lang.String toString();
public static int hashCode-impl(java.lang.String);
public int hashCode();
public static boolean equals-impl(java.lang.String, java.lang.Object);
public boolean equals(java.lang.Object);
private Foo(java.lang.String);
public static java.lang.String constructor-impl(java.lang.String);
public static final Foo box-impl(java.lang.String);
public final java.lang.String unbox-impl();
public static final boolean equals-impl0(java.lang.String, java.lang.String);
}
さっくりみた感じ
-
value
をプロパティとしてもつFoo
クラスは作られる -
Foo
のインスタンスを作ること無く処理できるように、引数にvalue
の値を受け取るであろうstatic
なメソッド(-impl
の名称のもの)が生成される- コンストラクタ
-
toString
,hashCode
,equals
- 定義したメソッド
- boxing / unboxing 用のメソッドが生成される
という感じ。
value class 使用箇所で、コンパイラが頑張って -impl
なメソッド呼び出しのバイトコードを吐くのだろうなと推測がつく。
というわけで、試してみる
interface Bar {
fun bar(foo: Foo): String
}
value class をパラメータにとるメソッドのシグネチャはどうなるのか?
$ javap -p Bar
Compiled from "Bar.kt"
public interface Bar {
public abstract java.lang.String bar-GWb7d6U(java.lang.String);
}
メソッド名の後ろに -GWb7d6U
という、あからさまにランダムな値(もしかしたらハッシュ値とかかも?)が付いたものとなる。
value class をパラメータに含めるとメソッド名が変形される模様。
コンストラクタだとどうなるのか気になったので試してみた
class Baz(foo: Foo)
$ javap -p Baz
Compiled from "Baz.kt"
public final class Baz {
private Baz(java.lang.String);
public Baz(java.lang.String, kotlin.jvm.internal.DefaultConstructorMarker);
}
パラメータが追加されたコンストラクタが作成され、パラメータが一つのものは private
となっている。
(これ自体は、引数のデフォルト値のために使われていたりするものっぽい)
つづいて value class のメソッドを呼び出してみる。
class Baz(foo: Foo) {
init {
println(foo.foo())
}
}
$ javap -c -p Baz
Compiled from "Baz.kt"
public final class Baz {
private Baz(java.lang.String);
Code:
0: aload_0
1: invokespecial #9 // Method java/lang/Object."<init>":()V
4: nop
5: aload_1
6: invokestatic #15 // Method Foo."foo-impl":(Ljava/lang/String;)Ljava/lang/String;
9: getstatic #21 // Field java/lang/System.out:Ljava/io/PrintStream;
12: swap
13: invokevirtual #27 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
16: nop
17: return
public Baz(java.lang.String, kotlin.jvm.internal.DefaultConstructorMarker);
Code:
0: aload_0
1: aload_1
2: invokespecial #34 // Method "<init>":(Ljava/lang/String;)V
5: return
}
予想通り foo-impl
が呼び出されている (以下の部分)。
5: aload_1
6: invokestatic #15 // Method Foo."foo-impl":(Ljava/lang/String;)Ljava/lang/String;
あと、インタフェースや型パラメータが使用されたことにより value class であるかどうか分からなくなる場合は、box-impl
メソッドが使われます。まぁそりゃそうか、という感じ。
なので、entities.map { it.foo } ...
みたいにして油断して list に入れてしまうと、box-impl
が使われてインスタンスが生成されるので、いろいろと台無しになりそう。
このあたりは、もしかしたらそのうちに detekt とかで教えてくれるのかもしれないけど。
普通に使う分にはコンパイラが頑張ってくれているので、問題なく使えそうです。
一方でメソッド名が変形されたりするので、リフレクションでいろいろするライブラリと合わせて使うときは注意した方がよさそうです。