SharedPreferences の @Nullable問題
問題の発端は SharedPreferences の getString の処理はこのように定義されていることにある。
@Nullable
String getString(String key, @Nullable String defValue);
戻り値に @Nullable のアノテーションが指定されているため、getString で取得した値をnullチェックせずに使用するとチェックツールに怒られる。
ところが、時には設計上絶対に null にはならない処理もある。nullチェックを書けばそれはデッドコードになるだろう。しかし書かなければチェックツールに怒られる。
書けば デッドコード、書かなければ 規約違反、さてどうしよう。
というわけで実験
全部で4x3パターンしかないので、どんなときに null が返却されるのか見てみよう。
SharedPreferences pref = getSharedPreferences("🤔", Context.MODE_PRIVATE);
String key0 = "key0";
String key1 = "key1";
String key2 = "key2";
String key3 = "key3";
// key0はputStringしない
pref.edit().putString(key1, null).commit();
pref.edit().putString(key2, "").commit();
pref.edit().putString(key3, "🎁").commit();
String result00 = pref.getString(key0, "");
String result01 = pref.getString(key0, null);
String result02 = pref.getString(key0, "🎁");
.... 省略 ....
String result11 = pref.getString(key3, "🎁");
このプログラムの結果を表にすると以下となる。
↓デフォルト値/設定値→ | なし | null | "" (空文字) | 🎁 |
---|---|---|---|---|
"" (空文字) | "" | "" | "" | 🎁 |
null | null | null | "" | 🎁 |
🎁 | 🎁 | 🎁 | "" | 🎁 |
おわかりいただけただろうか.... |
結論
デフォルト値に null を指定して、ぬるぽの危険性をわざわざ仕込む必要もないので、
第2引数のデフォルト値に null を指定しない限り @NonNull と考えて問題ない
SharedPreferences の get〇〇 メソッドを包むラッパーがいてくれるといいなって。
@NonNull
public String getString(String key, @NonNull String defValue) {
.... 省略 ....
}
おまけ
Q. そもそも実験するまでもなくソース見れば一目瞭然ですよね?
A. 全くもってその通りで SharedPreferencesImpl の処理を最初に見ればよかったのです
@Nullable
public String getString(String key, @Nullable String defValue) {
synchronized (this) {
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
Q. SharedPreferencesってinterfaceですよね?
A. はい、そうです。
そのため、自分で実装クラスを書いていて、引数のデフォルト値以外からnullを返すような実装をしている場合はこの限りではありません。
まぁ、自分で SharedPreferences を実装している人なんて圧倒的少数だと思いますが....