LoginSignup
0
0

いまさらだけど Kotlin の value class を簡単に調べてみる

Posted at

value class、コンパイラがいろいろと頑張るのだろうなぁと思いつつ、いままでどんな感じになるのか調べていなかったので、サクッと javap して雑に確認してみます。
※正確なところは仕様を追ったりしたほうが良いです。適当に試したメモみたいなものです。

とりあえず以下のような value class をつくって、

Foo.kt
@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 なメソッド呼び出しのバイトコードを吐くのだろうなと推測がつく。

というわけで、試してみる

Bar.kt
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 をパラメータに含めるとメソッド名が変形される模様。

コンストラクタだとどうなるのか気になったので試してみた

Baz.kt
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 のメソッドを呼び出してみる。

Baz.kt
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 とかで教えてくれるのかもしれないけど。

普通に使う分にはコンパイラが頑張ってくれているので、問題なく使えそうです。
一方でメソッド名が変形されたりするので、リフレクションでいろいろするライブラリと合わせて使うときは注意した方がよさそうです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0