これからJavaで書かれたAndroidアプリケーションのソースコードをKotlinに書き換える際に気をつける、やるべきこと2点

Kotlin歴2ヶ月程度で勉強中なのですが、現在プロジェクトのソースコードを少しずつですがKotlinに置き換えていて、本日11/7時点でKotlin率は40%弱です。

この記事の内容は、そんな自分がKotlin置き換え始めた頃の失敗、問題点を雑に振り返り、

JavaソースコードをKotlinに書き換える際には下記2つを行なうのが良いのではないかという提案です:

  1. 自動変換コードは常に疑う
  2. デコンパイルされたバイトコードのレビューをする

尚、内容はYahoo JAPAN!様で開催されたBonfire#2にて発表したものから抜粋しています。(発表資料はこの記事の下部にあります)

Kotlin置き換え始めた頃の自分の問題点

1. 自動変換を信用しすぎていたこと

Android Studioを用いたAndroidアプリケーション開発において、JavaソースコードをKotlinに置き換える際には、自動変換のツールが用意されています。

Macの場合は、

⌘ Option Shift K

のショートカットを利用することで、IDE側で.javaファイルを.ktファイルに置き換えてくれます。素晴らしい!

KotlinはNull安全な言語です。Kotlin言語を使った開発では、値がnullになりえるのか、そうでないのかを明示的に記述する必要があります。

もちろん上記の自動変換によってある程度動くコードには変換されるのですが、手直しは必ず必要です。自動変換されたコードが問題なく動作するコードであっても、そのコードがKotlinらしい文法のコード、パフォーマンス性の高いコードであるという保証はどこにもありません。「自動変換のコードは絶対に使わず自分の手できれいなコードを書け!」というわけではありません。「そんなの当たり前じゃん」と言われればそれまでですが、ここで伝えたいことは、自動生成されたコードが動くからと言ってそれを無防備に信用はせず、どのようなコードに変換されているのか、もしKotlinを最近書き始めた方であれば、自動生成されたコードを自分で読んでもっと良い書き方はないかと模索するのが良いかもということです。

ということで、自動変換は常に疑いましょう。手直しは必ず必要です。また、一見問題なさそうなコードであっても入念にチェックしておくに越したことはありません。

たとえばonActivityResultoverrideしているActivityクラスをJavaからKotlinに置き換えるとしましょう。

変換後のonActivityResultのKotlinのソースコードがこのような形になったとしましょう。一見問題ないようにみえますが、どうでしょうか?

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
  super.onActivityResult(requestCode, resultCode, data)
  ...
}

onActivityResultの第三引数のdataの値はnullに成り得ます。この場合、KotlinではNull許容であるということを明示的に示す?マークを付与する必要があります。?マークがついていない変数等にnullが動的に代入された場合NullPointerExceptionが発生しアプリが終了してしまいます。

ということでこのように記述する必要があります:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
  super.onActivityResult(requestCode, resultCode, data)
  ...
}

自動変換が便利で、ある程度信用しきっていた部分があったため、自分の場合この問題に出くわして( ゚д゚)ハッ!としました。何度も書きますが、自動変換は常に疑いましょう。手直しは必ず必要です。

2. (デコンパイルされたバイト)コードレビューをしていなかったこと

Javaにおけるstaticな定数をKotlinで定義したい場合にはcompanion objectを利用できます。

companion object {
  val SOME_VALUE = 1
}
  
println(“$SOME_VALUE")

このようなコードは一件問題ないように見えるのですが、このKotlinのソースコードが生成するバイトコードデコンパイルしてみると、

public static final int SOME_VALUE = 1;
public static final class Companion {
  public final int getSOME_VALUE() {
    return MainActivity.SOME_VALUE;
  }
}
…

String var2 = "" + Companion.getSOME_VALUE();
System.out.println(var2);

このようになっています。定数に対するgetterが定義されているしメソッド名もヮ(゚д゚)ォ!な感じになっていて、Javaで書く際には決して書かないようなコードが生成されています。

Kotlinでは、プロパティに対してデフォルトでsetter / getterが定義可能であれば定義される仕組みとなっています。

たとえばあるクラス内にvar someValue = 1というプロパティを定義した場合、KotlinではsetSomeValue()getSomeValue()メソッドが新たに生成されます。

ここでは詳しくは記載しませんが、varは可変なプロパティを宣言する際に利用します。なのでsetterが定義されます。対してvalは不変なプロパティ宣言の際に利用され、valによって定義されたプロパティにはデフォルトでgetterが生成されます。

先程のコードを改めて確認すると、今回の場合は定数定義を行いたいだけなので、Kotlinには新たにgetterの生成は行ってほしくありません、そのような場合には、

  1. String型もしくはプリミティブ型のプロパティ: const val定義
  2. 1.以外のプロパティ: @JvmFieldアノテーション付与

を行なうことで解決できます。

1. String型もしくはプリミティブ型のプロパティ: const val定義

constキーワードは、object内のトップレベルもしくはメンバーとして定義されているString型、プリミティブ型の変数に対してのみ付与できます。const定義されたプロパティはコンパイル時定数(=Compile-Time-Constants)として定義され、getter生成が省略されます。

companion object {
  const val SOME_VALUE = 1
}
  
println(“$SOME_VALUE")

このようなコードを記述して生成されたバイトコードデコンパイルすると、

public static final int SOME_VALUE = 1;
public static final class Companion {
  …
}

String var2 = "1";
System.out.println(var2);

先程のバイトコードとは違い、getterがなくなっていることがわかります。(getterがなくなったことで、コードが幾分か効率化されたこともわかります)

2. @JvmFieldアノテーション付与

@JvmFieldアノテーションconstが利用できないプロパティに対して付与でき、Kotlinに対してgetterを生成せず、Javaにおけるフィールドとして公開することを伝えるためのものです。

Kotlinのバイトコードデコンパイルすることで、自分のKotlinのソースコードをもっと効率化できないか、無駄なメソッドが生成されていないか等を確認することができます。

まとめ

世の中では「Kotlinかわいい😍」というツイートをよく見かけますが、Null許容を表す一文字の?をつけるのか、そうでないのかによって大きな違いが生まれるのがKotlinです。怖いでしょ!?Javaでよくない!?とKotlinを否定しているのではありません。僕も思います!Kotlinかわいいです😍 !

ただ、JavaソースコードをKotlinに置き換える際には特に、変換後のKotlinのコードに不備や問題はないか、チェックすることが必要なのかもと思います。(自分の場合は、デコンパイルされたバイトコードを意識して確認するようにしています。)

Kotlinの良さに気づいた時、「Kotlinかわいい😍 」と目が♡になってしまい盲目になりKotlinに噛まれることのないよう、特にこれからJavaソースコードをKotlinに置き換えていく方に向けて、下記の二点をオススメします:

  1. 自動変換を行う際には常に変換後のコードをチェック
  2. 可能であればバイトコードデコンパイルしてレビュー

最後に、下記は、Yahoo! JAPAN Bonfire#2での登壇資料です。上記内容以外に自分がハマったこと等についていくつか紹介しています:

おまけ: 来年2月に開催されるDroidKaigiにて、fluxについて発表します。初の30分という長い時間と英語のプレゼンなので緊張しますが、選ばれたからにはしっかりやろうと思います!

以上です!