Kotlin歴2ヶ月程度で勉強中なのですが、現在プロジェクトのソースコードを少しずつですがKotlinに置き換えていて、本日11/7時点でKotlin率は40%弱です。
この記事の内容は、そんな自分がKotlin置き換え始めた頃の失敗、問題点を雑に振り返り、
JavaのソースコードをKotlinに書き換える際には下記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を最近書き始めた方であれば、自動生成されたコードを自分で読んでもっと良い書き方はないかと模索するのが良いかもということです。
ということで、自動変換は常に疑いましょう。手直しは**必ず**必要です。また、一見問題なさそうなコードであっても入念にチェックしておくに越したことはありません。
たとえば`onActivityResult`を`override`している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分という長い時間と英語のプレゼンなので緊張しますが、選ばれたからにはしっかりやろうと思います!
以上です!