聞こえないJavaエンジニアが適当に書き連ねていく

つらつらとメモしたり日頃の溜まっている想いを吐き出す場所です。

java.util.ObjectsクラスにcheckIndex()なるメソッドが増えてた話 #java9

序章

Java1.8とJava9のArrayListのソースを比較していたところ、set(int index, E element)/get(int)メソッドの中身が変わっていることに気づきました。

setメソッド

  • Java1.8
public E set(int index, E element) {
    rangeCheck(index);

    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}
  • Java9
public E set(int index, E element) {
    Objects.checkIndex(index, size);
    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

getメソッド

  • Java1.8
public E get(int index) {
    rangeCheck(index);

    return elementData(index);
}
  • Java9
public E get(int index) {
    Objects.checkIndex(index, size);
    return elementData(index);
}

Objectsクラスを見てみる

ObjectsクラスにcheckIndex()なるメソッドが追加されたようです。中身を見てみます。

/**
    * Checks if the {@code index} is within the bounds of the range from
    * {@code 0} (inclusive) to {@code length} (exclusive).
    *
    * <p>The {@code index} is defined to be out-of-bounds if any of the
    * following inequalities is true:
    * <ul>
    *  <li>{@code index < 0}</li>
    *  <li>{@code index >= length}</li>
    *  <li>{@code length < 0}, which is implied from the former inequalities</li>
    * </ul>
    *
    * @param index the index
    * @param length the upper-bound (exclusive) of the range
    * @return {@code index} if it is within bounds of the range
    * @throws IndexOutOfBoundsException if the {@code index} is out-of-bounds
    * @since 9
    */
@ForceInline
public static
int checkIndex(int index, int length) {
    return Preconditions.checkIndex(index, length, null);
}

Preconditionsなる慣れないクラスが出てきました。
どうやら、Java9で新たに追加されたクラスの様で、パッケージは jdk.internal.util です。
なんとなく、一般開発者はあまり直で触らない方がよさそうな雰囲気のパッケージ名です。深入りせずに、戻ることにします。

Objectsクラスに戻ってみると、checkIndex()メソッド以外にcheckFromToIndex()や、checkFromIndexSize()というメソッドも追加されています。

/**
    * Checks if the sub-range from {@code fromIndex} (inclusive) to
    * {@code toIndex} (exclusive) is within the bounds of range from {@code 0}
    * (inclusive) to {@code length} (exclusive).
    *
    * <p>The sub-range is defined to be out-of-bounds if any of the following
    * inequalities is true:
    * <ul>
    *  <li>{@code fromIndex < 0}</li>
    *  <li>{@code fromIndex > toIndex}</li>
    *  <li>{@code toIndex > length}</li>
    *  <li>{@code length < 0}, which is implied from the former inequalities</li>
    * </ul>
    *
    * @param fromIndex the lower-bound (inclusive) of the sub-range
    * @param toIndex the upper-bound (exclusive) of the sub-range
    * @param length the upper-bound (exclusive) the range
    * @return {@code fromIndex} if the sub-range within bounds of the range
    * @throws IndexOutOfBoundsException if the sub-range is out-of-bounds
    * @since 9
    */
public static
int checkFromToIndex(int fromIndex, int toIndex, int length) {
    return Preconditions.checkFromToIndex(fromIndex, toIndex, length, null);
}

/**
    * Checks if the sub-range from {@code fromIndex} (inclusive) to
    * {@code fromIndex + size} (exclusive) is within the bounds of range from
    * {@code 0} (inclusive) to {@code length} (exclusive).
    *
    * <p>The sub-range is defined to be out-of-bounds if any of the following
    * inequalities is true:
    * <ul>
    *  <li>{@code fromIndex < 0}</li>
    *  <li>{@code size < 0}</li>
    *  <li>{@code fromIndex + size > length}, taking into account integer overflow</li>
    *  <li>{@code length < 0}, which is implied from the former inequalities</li>
    * </ul>
    *
    * @param fromIndex the lower-bound (inclusive) of the sub-interval
    * @param size the size of the sub-range
    * @param length the upper-bound (exclusive) of the range
    * @return {@code fromIndex} if the sub-range within bounds of the range
    * @throws IndexOutOfBoundsException if the sub-range is out-of-bounds
    * @since 9
    */
public static
int checkFromIndexSize(int fromIndex, int size, int length) {
    return Preconditions.checkFromIndexSize(fromIndex, size, length, null);
}

新たなメソッドを使ってみる

とりあえず、使ってみることにします。

import java.util.Objects;

public class CheckSize {

    public static void main(String... args) {

        checkIndex();
        checkFromToIndex();
        checkFromIndexSize();

    }

    private static void checkIndex() {
        int a = Objects.checkIndex(2, 8);
        System.out.println("checkIndex:" + a);
    }

    private static void checkFromToIndex() {

        int a = Objects.checkFromToIndex(2, 8, 10);
        System.out.println("checkFromToIndex:" + a);

    }

    private static void checkFromIndexSize() {

        int a = Objects.checkFromIndexSize(2, 8, 10);
        System.out.println("checkFromIndexSize:" + a);

    }

}

実行結果はこうなりました。

checkIndex:2
checkFromToIndex:2
checkFromIndexSize:2

JavaDocにも書かれているように、引数に問題が無ければ一番目の引数をそのまま返すようです。 では、不正な値を渡したらどうなるのでしょう?

private static void checkIndex() {
    int a = Objects.checkIndex(8, 2);
    System.out.println("checkIndex:" + a);
}

こんなエラーを吐きました。

Exception in thread "main" java.lang.IndexOutOfBoundsException: Index 8 out-of-bounds for length 2
    at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
    at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:70)
    at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:248)
    at java.base/java.util.Objects.checkIndex(Objects.java:372)
    at test.CheckSize.checkIndex(CheckSize.java:16)
    at test.CheckSize.main(CheckSize.java:9)

他のメソッドだと以下の通りです。

  • checkFromToIndex()メソッドとそのエラー内容
private static void checkFromToIndex() {
    int a = Objects.checkFromToIndex(8, 1, 10);
    System.out.println("checkFromToIndex:" + a);
}
Exception in thread "main" java.lang.IndexOutOfBoundsException: Range [8, 1) out-of-bounds for length 10
    at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
    at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckFromToIndex(Preconditions.java:76)
    at java.base/jdk.internal.util.Preconditions.checkFromToIndex(Preconditions.java:295)
    at java.base/java.util.Objects.checkFromToIndex(Objects.java:398)
    at test.CheckSize.checkFromToIndex(CheckSize.java:22)
    at test.CheckSize.main(CheckSize.java:10)
  • checkFromIndexSize()メソッドとそのエラー内容
private static void checkFromIndexSize() {
    int a = Objects.checkFromIndexSize(2, 1, 1);
    System.out.println("checkFromIndexSize:" + a);
}
Exception in thread "main" java.lang.IndexOutOfBoundsException: Range [2, 2 + 1) out-of-bounds for length 1
    at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
    at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckFromIndexSize(Preconditions.java:82)
    at java.base/jdk.internal.util.Preconditions.checkFromIndexSize(Preconditions.java:343)
    at java.base/java.util.Objects.checkFromIndexSize(Objects.java:424)
    at test.CheckSize.checkFromIndexSize(CheckSize.java:29)
    at test.CheckSize.main(CheckSize.java:11)

まとめ

通常のロジックで使うにはちょっとエラーメッセージが不親切、という印象です。自分で丁寧に判定ロジックを書いて、状況に合わせてわかりやすいメッセージを作った方がいいかもしれません。

そもそも、使い道がほとんどなさそうです。フレームワーク側で使うかも?くらいでしょうか。何かこんなパターンの時使えるのでは、という閃きがありましたら、教えてください。

10/5追記

Objects.checkIndexメソッドはArrayList.java以外にどこから呼ばれてるのかなとソースコードgrep検索したところ、java.util.ImmutableCollections.javajava.util.AbstractList.java からも呼ばれていました。とはいえ、この二つは開発者は意識しないと思うクラスなので本当に「へー」程度ですね。