enum の定数値ごとに振る舞いを変えるのを Kotlin でやってみる

  • 5
    Like
  • 0
    Comment

概要

Kotlin に慣れている人からしたら「何そんな当たり前のことで悩んでるの?」という感じだと思います。Kotlin を始めようと思って Java のコード(Retrolambda 導入済み)を一気にコンバートしたら大量にコンパイルエラーの検出されるダメコードの山ができてしまい、直すのに Kotlin の知識が必要になったので、少し調べてみた次第です。

Java の enum で定数値ごとに振る舞いを変える

例えば、あるビルを示す enum があり、各階で異なる食事を提供しているのを enum だけで表現してみます。
何かしらの interface を用意して enum のコンストラクタ引数として要求し Lambda expression で実装する、というやり方が考えられます。

public enum Floor {

    FIRST(() ->  "Noodle"),
    SECOND(() -> "Hamburg steak"),
    THIRD(() ->  "Steak");

    private MealServer server;

    Floor(final MealServer s) {
        server = s;
    }

    interface MealServer {
        String serve();
    }

    public String serve() {
        return server.serve();
    }
}

下記の処理を実行する main メソッドを定義して、各階でどのようなメニューが出されるのかを見てみましょう。

Arrays.stream(Floor.values())
      .map(Floor::serve)
      .forEach(System.out::println);
実行結果
Noodle
Hamburg steak
Steak

こういったコードを Kotlin で実装するにはどうしたらよいか?という話です。

Java -> Kotlin 変換

前述の Java コードを IntelliJ の Java -> Kotlin 変換にかけると下記の動作しないコードが得られました。

動かないコード
enum class Floor private constructor(private val server: Floor.MealServer) {

    FIRST({ "Noodle" }),
    SECOND({ "Hamburg steak" }),
    THIRD({ "Steak" });

    internal interface MealServer {
        fun serve(): String
    }

    fun serve(): String {
        return server.serve()
    }
}

どうやら自力で書き直すしかないようです。Kotlin の書き方がわからないので途方にくれます。


Kotlin で書き直す

以降のコードは Try Kotlin の v1.1.3 で記述・動作確認しています。

abstract メソッドを用意してそれぞれ override する

Kotlin の enum のメソッドは override が可能なので直接上書きすることができるようです。まずここの知識が私にありませんでした。

enum class Floor {

    FIRST{  override fun serve(): String = "Noodle"  },
    SECOND{ override fun serve(): String = "Hamburg steak" },
    THIRD{  override fun serve(): String = "Steak" };

    abstract fun serve(): String
}

せっかくなので呼び出し側も Kotlin にしてみましょう。Kotlin の Array 型は内部イテレーションが使えます。そのまま Lambda expression で map や forEach の処理を書くことができます。

fun main(args: Array<String>) {
    Floor.values()
         .map { it.serve() }
         .forEach { println(it) }
}

デフォルトの実装を用意する

このままだと、例えば1階と2階で同じメニューを出すことになった際に、いちいち同じ内容を実装しなければいけません。特に何もない時は共通のメニューを出す、という時にはデフォルトの実装を用意しておくと多少親切になります。

Kotlin では override を許可するメソッドは open を明示的に付ける必要があります。

enum class Floor {

    FIRST,
    SECOND,
    THIRD{  override fun serve(): String = "Steak" };

    open fun serve(): String = "Water"
}

3階だけステーキを出すようにしました。実行してみましょう。

実行結果
Water
Water
Steak

ご覧の通り、1階と2階は経費節減のため水しか提供されなくなりました。

Lambda

もうちょっと短く書きたい、と思ったら Kotlin で Lambda の使い方を調べていました。 Java の Lambda expression から出て行って、 Kotlin の Lambda expression に帰ってきました。

コンストラクタに Lambda を受け付けるよう追記をし、デフォルトパラメータを指定します。

enum class Floor(val server: () -> String = { "Water" } ) {

serve メソッドは server を実行するだけに書き換え、override が不要になったので open を外しましょう。

fun serve(): String = server()

全体は下記の通りです。

enum class Floor(val server: () -> String = { "Water" } ) {

    FIRST,
    SECOND,
    THIRD( { "Steak" });

    fun serve(): String = server()
}

実行してみましょう。

実行結果
Water
Water
Steak

先ほどと同じ出力が得られました。


終わりに

勉強し始めたばかりなので、より良い方法があるかと思います。よろしければお教えくださいますと幸いです。

参考