Groovy3 Tips セーフインデックス

まえがき

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 で動作確認しています。

コメントを残す