- Contents -
まえがき
Groovy3からは、セーフインデックス ?[idx]
という記述ができるようになっています。
(Safe Index Based Access)
配列、List、Mapなどにアクセスする際に、
NullPointerExceptionを発生させないようにできるシンタックスシュガーです。
この記事では、Map, List, 配列にたいして、
セーフインデックスでアクセスした場合の挙動について紹介します。
Map/List/配列の変数自体が null状態の場合
インスタンス化がされてない状態の変数に対して、
セーフインデックスでアクセスした場合を見てみましょう。
Map<String, String> map = null
List<Integer> list = null
int[] arr = null
getter
セーフインデックスにて、要素を取得した場合、
NullPointerExceptionは発生せず、nullを返してくるようになります。
when:
String ret1 = map ?["a"]
Integer ret2 = list ?[0]
Integer ret3 = arr ?[0] // nullが返るので、もしintで宣言するとCastExceptionが発生します。
then:
// NullPointerExceptionが発生する代わりにnullが返される
ret1 == null
ret2 == null
ret3 == null
setter
インスタンス化されていない変数に対しては、操作が無視されます。
NullPointerExceptionは発生しません。
when:
map ?["a"] = "hello"
list ?[0] = 1
arr ?[0] = 1
then:
// NullPointerExceptionが発生する代わりに操作が無視される
map == null
list == null
arr == null
多次元のMap/List/配列で途中の要素がnullを返した場合
多次元のMap/List/配列で、添字を連続して利用した際、
途中でnullオブジェクトが返ると、NullPointerExceptionが発生してしまいます。
そういったときにも、セーフインデックスは利用できます。
多次元定義した変数で、インスタンス化した場合を見ていきましょう。
// ネストしたMap
def map = [a: [b: "hello"]] as Map<String, Map<String, String>>
// ListのList
def list = [[1, 2, 3], [4, 5, 6]] as List<List<Integer>>
// 2次元配列
def arr = new int[][]{new int[]{1, 2, 3}, new int[]{4, 5, 6}}
getter for Map, List
?[idx]
を連続して記述することができます。
途中で、nullになっても、NullPointerExceptionが発生することはありません。
when:
String ret1 = map ?["c"] ?["d"] // 1つ目のキーでnullを返した場合
Integer ret2 = list ?[2] ?[0] // 1つ目の添字でnullを返した場合
then:
// NullPointerExceptionが発生する代わりにnullが返される
ret1 == null
ret2 == null
setter for Map, List
セーフインデックスでアクセスし途中でnullになった場合は、
NullPointerExceptionは発生しませんが、操作が無視されます。
when:
map ?["c"] ?["d"] = "aaa" // 1つ目のキーでnullを返した場合
list ?[2] ?[0] = 7 // 1つ目の添字でnullを返した場合
then:
// 途中の要素がnullのため、変化がないこと
// (代入操作が無視されている)
map == [a: [b: "hello"]]
list == [[1, 2, 3], [4, 5, 6]]
getter for int[]
ひとつ注意しないといけないのが、int[]
などの配列です。(Listじゃない)
Listに対して存在しないインデックスでアクセスしたとしても、nullが返るだけですが、
配列の場合は、ArrayIndexOutOfBoundsExceptionという例外が発生します。
これはセーフインデックスを利用しても、例外が発生します。
when:
Integer ret3 = arr ?[2] ?[0] // 1つ目の添字アクセス時に例外になる。
then:
// 配列は、存在しない添字でアクセスすると、
// ArrayIndexOutOfBoundsExceptionが発生する
thrown(ArrayIndexOutOfBoundsException)
nullオブジェクトに対して、セーフインデックスでアクセスした時は、
NullPointerExceptionを抑止できますが、
ArrayIndexOutOfBoundsExceptionは抑止できません。
setter for int[]
セーフインデックスでアクセスしたとしても、存在しないインデックスでアクセスした場合は、
ArrayIndexOutOfBoundsExceptionが発生します。
when:
arr ?[2] ?[0] = 4 // 1つ目の添字アクセス時に例外になる。
then:
// 配列は、存在しない添字でアクセスすると、
// ArrayIndexOutOfBoundsExceptionが発生する
thrown(ArrayIndexOutOfBoundsException)
まとめ
-
セーフインデックスでアクセスすると、NullPointerExceptionを抑止することができます。
特にネストしたMapや、Listではnullチェックをわざわざ書かなくてよくなるので、便利です。 -
ただ、NullPointerExceptionを抑止できるだけで、
配列のArrayIndexOutOfBoundsExceptionは発生するので注意が必要です。
Listの場合は間違ったインデックスでアクセスしてもnullが返るだけですが、
int[]
の場合は ArrayIndexOutOfBoundsExceptionが発生する。
(これはセーフインデックスを利用した場合でも抑止できない) -
あと、Groovy2の場合や、セーフインデックスを使わなくても、
getAtメソッドに対して、セーフナビゲーションで代用することもできます。// list ?[0] ?[0] ?[0] と同じように使える Integer ret1 = list.getAt(0)?.getAt(0)?.getAt(0)
サンプルコードは、Groovy 3.0.5 で動作確認しています。