ticktakclockの日記

技術ポエムを綴ったりします。GitHub idも同じです (@ticktakclock)

KotlinプロパティとカスタムゲッターのBytecodeから見る違い

こんにちは、tkyです。

今日はKotlinのプロパティとゲッターの種類と違いを深堀りしてみようと思います。

はじめにプロパティとフィールドについておさらい程度に単語を整理します。

プロパティとフィールド

Kotlinのプロパティについて復習です。

dogwood008.github.io

Kotlinにおいて以下のように記述することはプロパティを宣言することであって、暗黙的にゲッターセッターを持っています。

Javaにするとフィールドとゲッターセッターを記述する必要があるのに対してボイラーコードが減っていいですね。

// kotlin
var name: String = "This is a property"
// convert to java
private String name = "This is a field.";

public String getName() {
  return name;
}

public void setName(String var1){
  name = var1;
}

さらにKotlinのプロパティは明示的にゲッターを記述することもできます。

val isEmpty: Boolean
  get() = this.size == 0

そして fun getName() のように宣言しようとするとコンパイルエラーとなります。プロパティ宣言によって暗黙的にgetName()メソッドが宣言されていることが確認できます。

f:id:ticktakclock:20200614151229p:plain
エラー

ここまではある程度みなさんも理解していることでしょう。

ゲッター書き方色々

1つのプロパティにアクセスする方法がいくつか存在します。特に暗黙的に作成されたゲッターと明示的に作成されたゲッターは何が違うのでしょうか?

  • プロパティ(暗黙的なゲッターを利用)
  • カスタムゲッター(明示的に記述する)
  • メソッド(暗黙的なゲッターとは異なる命名で作成)

この謎を解明すべくByteCodeという名の秘境の奥地に足を踏み入れてみたのでした。

サンプルコード

適当に作ってみました。全部valで定義しているのでセッターはありません。

class User(val firstName: String, val lastName: String, val age: Int) {

    // プロパティ宣言
    val fullName: String = "$firstName $lastName"

    // カスタムゲッター
    val fullNameCustom: String
        get() = "$firstName $lastName"

    // メソッドでゲッター
    fun getFullNameMethod(): String {
        return "$firstName $lastName"
    }
}

バイトコード

Android Studioの機能で Tools > Kotlin > Show Kotlin Bytecode を選択することでBytecodeが見れるようになります。

f:id:ticktakclock:20200614163317p:plain
Tools > Kotlin > Show Kotlin Bytecode

長すぎたので要所だけ切り取ります。

    // プロパティ宣言
  public final getFullName()Ljava/lang/String;
  @Lorg/jetbrains/annotations/NotNull;() // invisible
   L0
    LINENUMBER 6 L0
    ALOAD 0
    GETFIELD com/github/ticktakclock/myapplication/User.fullName : Ljava/lang/String;
    ARETURN
   L1

    // カスタムゲッター
  public final getFullNameCustom()Ljava/lang/String;
  @Lorg/jetbrains/annotations/NotNull;() // invisible
   L0
    LINENUMBER 10 L0
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    ALOAD 0
    GETFIELD com/github/ticktakclock/myapplication/User.firstName : Ljava/lang/String;
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    BIPUSH 32
    INVOKEVIRTUAL java/lang/StringBuilder.append (C)Ljava/lang/StringBuilder;
    ALOAD 0
    GETFIELD com/github/ticktakclock/myapplication/User.lastName : Ljava/lang/String;
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    ARETURN
   L1

    // メソッドでゲッター
  public final getFullNameMethod()Ljava/lang/String;
  @Lorg/jetbrains/annotations/NotNull;() // invisible
   L0
    LINENUMBER 14 L0
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    ALOAD 0
    GETFIELD com/github/ticktakclock/myapplication/User.firstName : Ljava/lang/String;
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    BIPUSH 32
    INVOKEVIRTUAL java/lang/StringBuilder.append (C)Ljava/lang/StringBuilder;
    ALOAD 0
    GETFIELD com/github/ticktakclock/myapplication/User.lastName : Ljava/lang/String;
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    ARETURN
   L1

プロパティ宣言

プロパティ宣言のものについてはフィールド fullNamegetFullName() メソッドが作成されていて、読みだしたフィールドの値を返却していることがわかります。 fullName もコンストラクタで文字列の連結が行われて、ゲッターでは計算済みの文字列を返却しています。

カスタムゲッターとメソッドのゲッターは特に違いなし

カスタムゲッターとメソッドのゲッターではフィールドは作られずメソッドだけが作成されていました。

バイトコードちゃんと読めないのですが、中に書かれていることも都度文字列の連結をして返却していることが雰囲気でわかります。

カスタムゲッターとメソッドによるゲッターはどちらも同じ振る舞いをするのですが、Kotlinっぽさが出るのはプロパティ宣言にしてカスタムゲッターを使うやり方かなと思います。(多分好みの問題

ゲッター内のコードの計算量に注意

例えば何度も getFullNameCustom() を呼び出す場合、プロパティ宣言方式のほうが計算済みの値を返すだけなので効率が良いかもしれないなと思いました。これがfor文とかが入ってO(n)などになるとパフォーマンスに影響するかもですね

まとめ

おそらくKotlinのコードの書き方によって生成されるバイトコードは最も良い形に最適化されて出力されると思うので必ずしもこの通りになることはないかもしれません。

しかし自らが書いたプログラムがどう動こうとしているのかを把握しておくことで「なぜこう書いたのか?」の理由づけや意思決定の材料になったり、困ったときにバイトコードを見てみるといった調査の幅も広げられるのではないかなと思います。