<-- mermaid -->

既存のJavaクラスの一部をKotlinへ移行するにはどうする??

はじめに

みなさんこんにちは!NewsPicks Growthチームの アダチ (@dikxs118)です。

NewsPicksではサーバーサイドの開発にKotlinの導入を推進しており、新規で書くコードは全てKotlin、既存のコードでもJavaで書いてあるものを随時Kotlinに書き換えています。

Kotlinを使うからには、「日本一使いこなせている!」と言えるまでに高められるよう、日々勉強会や、外部カンファレンスへの登壇を積極的に行なっています。

その成果もあって、社内での知見も溜まってきておりKotlinをはじめたての方でも迷うことなく書ける環境が整ってきました。 Kotlin化に対するPRレビューもチームを横断して盛んに行われており、今回はそのレビューの中で得た知見を共有しようと思います。

本題

さて、私は Java -> Kotlin という移行勢なのですが、久方ぶりにJavaを書くと

  • Stream API じゃなくてKotlinの標準関数で書きてえ・・
  • Kotlinだったら全体的にもっと良い感じに書ける・・
  • とにかくKotlinで書きてえ・・

という気持ちになることが多いです。
新規クラスであればKotlinにすれば良いですし、小さなクラスであれば簡単にリファクタできるのですぐKotlinにしてしまうのですが、 肥大なServiceクラスや歴史のある大規模なクラスをKotlin化するのは労力がかかります。

あるタスクで、上記のような大規模なクラスに新規メソッドを追加、もしくは既存メソッドの改修をすることになったとしましょう。
本来のタスクの工数を大幅に超過してまで、大規模なクラスをKotlin化してその動作を保証することは望まれませんし、 Kotlin化は手段に過ぎないので、気持ち的にも中々辛いものがあります。

だけど、、

それでも、、

Kotlinで書きたいんじゃ!

そういったケースではどうするのが良いでしょうか??

NewsPicksではそのようなケースでは、対象メソッドのみを別のKotlinクラスに書き出し、残りのメソッドをJavaクラスへ委譲または継承することで開発者の体験を高めています。
今回はその委譲について詳しく解説していきます。

Javaクラスの一部をKotlinで書く

ここからは、どのように委譲するか、一見勘違いしてしまうハマりポイントがあるので技術的な解説をしていきます。 前提知識としては、委譲DIです。フレームワークは Spring Bootを利用していますが、他のフレームワークを使っている方や独自のDIコンテナを使っている方は適宜読み換えてください。

今回の使うコードは下記です。

public interface SampleServiceInterface {

    String sampleMethodA();

    String sampleMethodB();
}
@Service
public class SampleServiceImplJava implements SampleServiceInterface {

    @Override
    public String sampleMethodA() {
        return "A";
    }

    @Override
    public String sampleMethodB() {
        return "B";
    }
}

このクラスに新たなsampleMethodCを作り、それをKotlinで実装できるようにしましょう。
先に実装後のコードを記載します。

public interface SampleServiceInterface {

    String sampleMethodA();

    String sampleMethodB();

    String sampleMethodC();
}
@Service
public class SampleServiceImplJava implements SampleServiceInterface {

    @Override
    public String sampleMethodA() {
        return "A";
    }

    @Override
    public String sampleMethodB() {
        return "B";
    }

    @Override
    @Deprecated()
    public String sampleMethodC() {
        throw new UnsupportedOperationException("Not Implemented");
    }
}
@Service
@Primary
class SampleServiceImplKt(private val b: SampleServiceInterface) : SampleServiceInterface by b {
    override fun sampleMethodC(): String {
        return "C"
    }
}

ポイントは下記2つです。

  • SampleServiceImplKt@Primaryで優先的にDIして、SampleServiceImplJavaに委譲する
  • SampleServiceImplJava側には Not Implementedということを書いておく(他のファイルで実装してある旨を書いても良いですね)

一つ目のポイントについて@Primaryで優先的にDIしてJavaクラスに委譲するところまでは特に問題ないと思います。
ただ、その先が少しハマりポイントになっていて、SampleServiceImplJavaに委譲すると言いながら、SampleServiceInterfaceに委譲しているのでは?SampleServiceInterfaceは優先的にSampleServiceImplKtが解決されるのでは??と思う方もいるかもしれません。

どちらが解決される?

しかしこのクラスが SampleServiceInterfaceを解決する段階では、SampleServiceImplKtはまだ完成されていないので、自動的にSampleServiceImplJavaとして解決されて正しく委譲されます。

もちろんSampleServiceImplJava2みたいな他にも Interfaceを実装しているクラスがある場合は Spring BootはどのクラスをDIするべきか解決できないので@Qualifier("sampleServiceImplJava2") private val b: SampleServiceInterfaceといった方法を一例に解決するクラスを明示的に指定してあげてください。

この方法では呼び出し元に一切影響することなく、Kotlinを導入できるのでそういった面でも体験が良いです。

おわりに

今回は、委譲を使ってJavaのコードの一部をKotlinで書く方法についてまとめさせていただきました!

NewsPicksでは 継承を使って今回と同様の課題を解決している場面もありますが、その際はコーディングルールなどを定めて実装者が迷わないようにする工夫をしています。その辺りもまたの機会にまとめられたらと思います。

それでは。

Page top