Map#computeIfAbsentはJava8で追加されたメソッドで、かれこれ5年も経ってるのに、知ったのは1年ほど前でした。
V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction)
keyに該当する値が存在しない場合は、mappingFunctionが呼び出され、その戻り値がMapに格納されて返却してくれます。
ConcurrentHashMap
あるWebシステムでデータをキャッシュする処理を実装するにあたり疑問が湧きました。
private Map<String, String> cache = new ConcurrentHashMap<>()
public String get(String id) {
return cache.computeIfAbsent(key, key -> {
if (/* キーに該当する外部ファイルが存在する */) {
// ファイルを読み込んで返却
return readData;
}
// 該当するファイルがない
return null;
});
}
ConcurrentHashMapは値にnullが持てないのに、nullを返却して大丈夫なんだろうか?
ということでテストコードで実験してみました。
Map<String, String> map = new ConcurrentHashMap<>();
String ret = map.computeIfAbsent("test", key -> null);
System.out.println(ret);
実行結果は例外が発生することもなく、戻り値はnullでした。
これはJavadocにもちゃんと書いてありましたね。。。
指定されたキーがまだ値に関連付けられていない場合、指定されたマッピング関数を使用してその値の計算を試行し、
nullでない場合はそれをこのマップに入力します。
Kotlin
見ての通り上記はJavaで書いてますが、システム開発も序盤なのでKotlinにコードを書き換えたところコンパイルエラーになりました。Kotlinのバージョンは1.3.31です。
val cache = ConcurrentHashMap<String, String>()
fun get(id: String): String? {
return cache.computeIfAbsent(id) {
if (/* キーに該当する外部ファイルが存在する */) {
// ファイルを読み込んで返却
return readData;
}
return null; // エラー! Kotlin: Null can not be a value of a non-null type String
}
}
KotlinのAPIリファレンスによるとMap.getは
operator fun get(key: K): V?
とVがnull許容にしてくれているけど、mappingFunctionの戻り値はVのままのようです。
回避策として
val ret: String? = map.computeIfAbsent("test") { null!! }
のように!!を付ければコンパイルエラーは解決するものの、実行時にkotlin.KotlinNullPointerExceptionが発生する始末。。。orz
解決策としては
val map = ConcurrentHashMap<String, String?>()
val ret: String? = map.computeIfAbsent("test") { null }
Vを最初からnull許容で宣言するぐらいしか判りませんでした。
でも、これだとgetの戻り値がString??なんて変な型にならないかと心配したら流石にそんなこともなく、String?にしたことで今の所不便も見つかってないので、これで正解なのかなぁ。