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

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

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

```java
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 メソッドを定義して、各階でどのようなメニューが出されるのかを見てみましょう。

```java
Arrays.stream(Floor.values())
      .map(Floor::serve)
      .forEach(System.out::println);
```

```:実行結果
Noodle
Hamburg steak
Steak
```

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

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

```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](https://try.kotlinlang.org) の v1.1.3 で記述・動作確認しています。

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

```kotlin
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 の処理を書くことができます。

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

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

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

```kotlin
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 を受け付けるよう追記をし、デフォルトパラメータを指定します。

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

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

```kotlin
fun serve(): String = server()
```

全体は下記の通りです。

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

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

    fun serve(): String = server()
}
```

実行してみましょう。

```:実行結果
Water
Water
Steak
```

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

----

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

## 参考
- [Kotlin文法 - クラス、継承、プロパティ](http://qiita.com/k5n/items/35e76d79ee9de4effb89#_reference-2771a4a2238247ec2bee)
- [Kotlin文法 - ネストされたクラス、Enumクラス、オブジェクト、委譲、委譲されたプロパティ](http://qiita.com/k5n/items/57d362295c2299090d8d)
- [Kotlinのクラスの継承とメソッドのオーバーライド、あとポエム](http://qiita.com/RyotaMurohoshi/items/738b6dff835bb2325897)