key としてすべての enum を網羅した Map
プログラムを書いていると、enum class をキーとして、何らかの値を保持する Map を作ることがよくある。
enum class MyEnum { KEY1, KEY2,}
val map = mapOf( MyEnum.KEY1 to "VALUE1", MyEnum.KEY2 to "VALUE2",)key としてすべての enum を網羅していた場合、Map#get が null を返すことはないはずなのだが、戻り値は nullable であるため、checkNotNull を挟むなどしなければならないのが面倒。
val v: String = checkNotNull(map[MyEnum.KEY1])そこで、すべての enum を網羅していることを前提に、non-nullable な Map#get ができる仕組みを考えてみた。
import java.util.EnumMap
class CoveredEnumMap<K : Enum<K>, out V> @PublishedApi internal constructor( private val enumMap: EnumMap<K, V>,) : Map<K, V> by enumMap { override operator fun get(key: K): V { return checkNotNull(enumMap.get(key)) }
override fun toString(): String { return "CoveredEnumMap($enumMap)" }
override fun equals(other: Any?): Boolean { return enumMap.equals(other) }
override fun hashCode(): Int { return enumMap.hashCode() }}
inline fun <reified K : Enum<K>, V> coveredEnumMapOf(map: Map<K, V>): CoveredEnumMap<K, V> { require(map.size == enumValues<K>().size) return CoveredEnumMap(EnumMap(map))}基本的には enumMap に処理を移譲しつつ、Map#get の戻り値を non-nullable にしている。構築時にすべての enum を網羅していることを確認済みなので、Map#get したタイミングで例外が飛ぶことはない。
まあ、checkNotNull を wrap しただけといえばそうなのだが。
val map = coveredEnumMapOf( mapOf( MyEnum.KEY1 to "VALUE1", MyEnum.KEY2 to "VALUE2", ))
val v: String = map[MyEnum.KEY1]val map2: Map<MyEnum, String> = mapmap を使う側では checkNotNull を使うことなく non-nullable な値を取得できる。Map の実装としてもそのまま使える。
これで Map#get については解決できたが、これだと enum を網羅していないことを実行時にしか検知できないのが惜しい。ぜひともコンパイル時に解決したいところ。
そこで考えたのが以下のコード。
inline fun <reified K : Enum<K>, V> coveredEnumMapOf(transform: (K) -> V): CoveredEnumMap<K, V> { return CoveredEnumMap(enumValues<K>().associateWithTo(EnumMap(K::class.java), transform))}
val map = coveredEnumMapOf<MyEnum, String> { when (it) { MyEnum.KEY1 -> "VALUE1" MyEnum.KEY2 -> "VALUE2" }}
val v: String = map[MyEnum.KEY1]まず、各要素の key となる enum に対して値を決めさせるインターフェイスにすることで、強制的にすべての enum を key に持つ Map しか作れないようにした。値を決める部分は利用者次第だが、なにか共通の規則で導出するのでなければ、when 式が使える。when 式はすべての分岐を網羅していない場合にコンパイルエラーになるので、コンパイルが通っていれば記述に漏れがないことが確定し、実行時エラーの可能性を排除できる。
実用するかはともかくとして、イメージしたものを作れて満足した。