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> = map
map
を使う側では 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
式はすべての分岐を網羅していない場合にコンパイルエラーになるので、コンパイルが通っていれば記述に漏れがないことが確定し、実行時エラーの可能性を排除できる。
実用するかはともかくとして、イメージしたものを作れて満足した。