Help us understand the problem. What is going on with this article?

IntelliJでJavaをKotlinへ変換する 〜その1〜

はじめに

過去の記事で作ったものをkotlinへ変換してみました。(まだ途中)
この記事では、java(spirng-boot)からkotlinへ変換するステップやちょっとしたテクニックを紹介します。

なぜkotlin?

ぼくはjavaが結構好きですし、kotlinに精通しているわけではないです。
そして、javaは不滅だと思っています。

不滅と言うと言い過ぎですが、今javaで作られているアプリケーションは多く、javaのスキルを求められるケースも多いです。
そのため、javaエンジニアも多く、今後も使われ続ける言語だと思っています。

しかし、より生産性、安全性を高めるために「ラムダ式や関数型プログラミングの機能」や「non null/nullable」を搭載し、よりシンプルにかけるkotlinを学んでみようと思いました。

Source

こちらにコミットしています。
比較の際には、kotlinへ変換したリポジトリの履歴を見てもいいですし、kotlinとjavaのリポジトリを比較してもいいです。
(kotlinへ変換しているだけでロジックに手は入れていないです)

kotlin: https://github.com/ken-araki/line_bot_kotlin
java: https://github.com/ken-araki/line_bot

java to kotlin

変換機能が優秀なIntelliJを利用する前提で進めます。
変換後に少し手修正をしたので、その修正内容を紹介します。

1. build.gradleを直してkotlinプラグインを導入

build.gradleを以下のように直して、kotlinのプラグインを導入します。
(Main classも含まれていますが、あとで少し直しているのでbuild.gradleだけ見てください)

https://github.com/ken-araki/line_bot_kotlin/commit/ff8e2d9169633141ef0b0b3466711bb186460c04

2. MainClass(LineApiApplication)をkotlinへ変換

最終形はこんな感じです。

LineApiApplication.kt
@SpringBootApplication
@EnableScheduling
open class LineApiApplication {
    companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            SpringApplication.run(LineApiApplication::class.java, *args)
        }
    }
}

companion object@JvmStatic でjavaび static に該当します。

(kotlinは、デフォルトでfinal classとなるため、)open修飾子をつけて継承可能にしています。
というのも、openをつけないと以下エラーが発生しました。

@Configuration class 'LineApiApplication' may not be final. Remove the final modifier to continue.

どうやら、ConfigurationClassがjava(finalではない)で、MainClassがkotlin(finalである)と起きている模様です。
全てkotlinへ変換したら、openを消してあげましょう。

3. Lombokを使わないようにする

kotlinを利用しているかぎり、Lombokは不要になります。(kotlinに同じ機能が実装されている)
IntelliJの変換では、Lombokが残ってしまうので、手で直していきましょう。

AllArgsConstructorアノテーションはプライマリコンストラクタへ

このプロジェクトでは、基本的に Constructor Injection を利用しています。
なので、kotlin変換後に、メンバー変数をプライマリコンストラクタへ移動させてください。

QiitaController.kt
@RestController
@RequestMapping(path = ["/test/qiita"])
@Profile("local")
class QiitaController(
        private val client: QiitaClient,
        private val qiitaService: QiitaService
) {
    // 省略...

あと、nullableで変換されますが、コンストラクタであればnon-nullにできます。
なので、Filed Injectionを利用していても、このタイミングで Constructor Injection にしてしまう方がいいと思います。

Dataアノテーションはdata classへ

Dataアノテーションを利用しなくても、以下のようにdata classに利用することで同じことができます。

TrainDelay.kt
data class TrainDelay(
        var name: String? = null,
        var company: String? = null,
        var lastupdate_gmt: String? = null,
        var source: String? = null
)

privateにすると、getter/setterもprivateになるので除外してください。
valにするとfinalになるので、getterのみ提供されます。Dataアノテーションを利用している場合はvarを設定するのが良いと思います。
(Valueアノテーションなら、valで良いと思います。)

Builderを自作する

javaからの見え方を同じにしたい(javaを修正したくない)ので、kotlinに変換してもBuilderを提供する必要があります。
以下のように、data classにbuilerを実装しました。
(Builderアノテーションをつけてもダメです。)

BotUserQiita.kt
@Entity
@Table(name = "bot_user_qiita")
data class BotUserQiita(

        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "id")
        val id: Int? = null,

        @Column(name = "user_id")
        var userId: String? = null,

        @Column(name = "qiita_user_id")
        var qiitaUserId: String? = null,

        @Column(name = "deleted")
        var deleted: String = "0",

        @Column(name = "created_date")
        var createdDate: Date? = null
) {
    companion object {
        @JvmStatic
        fun builder(): BotUserQiitaBuilder = BotUserQiitaBuilder()
    }

    class BotUserQiitaBuilder {
        private var userId: String? = null
        private var qiitaUserId: String? = null
        private var deleted: String? = null
        private var createdDate: Date? = null

        fun build(): BotUserQiita = BotUserQiita(
                userId = userId,
                qiitaUserId = qiitaUserId,
                deleted = deleted ?: "0",
                createdDate = createdDate
        )

        fun userId(userId: String) = apply { this.userId = userId }
        fun qiitaUserId(qiitaUserId: String) = apply { this.qiitaUserId = qiitaUserId }
        fun deleted(deleted: String) = apply { this.deleted = deleted }
        fun createdDate(createdDate: Date) = apply { this.createdDate = createdDate }
    }
}

staticメソッドでbuilder()を提供し、各プロパティ名のメソッドで値を設定するBuilder Classを作成しました。
Builderは関係ないですが、デフォルト引数でオーバーロードができたり、nullだったら別の値を返すエルビス演算子(?:)だったり、applyだったり、kotlinはかなりいい感じにかけますね。

ちなみに、javaのBuilderも、kotlinからは見えません。
調べた感じ、delombokをしてあげれば良さげですが、model,entityをすべてkotlinへ変換したので今回は特別対応していません。

Functionを使わない

javaだとFunction Interfaceを実装したclassがあり、結構使います。
ただ、いつも覚えられず調べながら実装しています。(僕だけではないはず・・・)

kotlinでは、それをいい感じにかけます。

Utils.kt
@JvmStatic
fun uncheck(runnable: () -> Void) {
    try {
        runnable()
    } catch (e: Exception) {
        throw RuntimeException(e)
    }
}

@JvmStatic
fun <T> uncheck(supplier: () -> T): T {
    try {
        return supplier()
    } catch (e: Exception) {
        throw RuntimeException(e)
    }
}

@JvmStatic
fun <T, R> uncheck(t: T, function: (T) -> R): R {
    try {
        return function(t)
    } catch (e: Exception) {
        throw RuntimeException(e)
    }
}

どうでしょうか。
runnable: () -> Voidは引数なし、戻り値なしの関数で、
supplier: () -> Tは引数なし、戻り値Tの関数で、
function: (T) -> Rは引数Tの1つで、戻り値Rの関数です。

すべて、変数名()で実行できます。

ちなみに、これらはJavaから実行できません。
と言うと少し語弊がありますが、今回の使い方的にNGという意味です。

Utils.ktのコメントにも記載していますが、このメソッドの目的はチェックを例外をチェック例外として扱わない仕組みです。
kotlinにはチェック例外の仕組みがない(明示的にアノテーションをつける必要がある)ためチェック例外を明示的に拾っていません。
そのため、javaから見ると「メソッドの引数はチェック例外を起こさないのに、渡す引数はチェック例外を出す可能性がある」ため、コンパイルエラーが起きてしまいます。

なので、今回は↑の実装を新規作成しkotlinからは新しいほうを利用し、javaからは元々のメソッドを参照するようにしました。
(ジェネリクスを指定することで明示的に呼び出す必要があります。)

おわりに

どうでしょうか。
まだ、ロジック部分を直していないですが、個人的にはkotlinにするといい感じにかけそうな感じがしています。

次は、残りのモジュールたちをkotlinへ変換するのと、もっといい感じにかける方法を模索しようと思っています。
もっと、いい感じの書き方あるよ!というのがあれば、是非教えてください。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした