【Effective Java】項目24:無検査警告を取り除く

今回は「項目24:無検査警告を取り除く」。

ポイント

  • 無検査警告は必ず取り除く
  • 方法1:コードを変更して警告を取り除く
  • 方法2:注意して @SuppressWarnigns アノテーションで警告を抑制する

ジェネリックスに対するコンパイル警告

ジェネリックスを利用してコードを書くと、無検査警告と呼ばれるコンパイラ警告が発生する場合があります。種類に応じてさまざまな無検査警告があります。

  • 無検査キャスト警告
  • 無検査メソッド呼び出し警告
  • 無検査ジェネリック配列生成警告
  • 無検査変換警告

たとえば、以下のようなコードでは無検査変換警告が発生します。

List<Integer> numbers = new ArrayList();
// ↑ 無検査変換警告の発生
// Information:java: Main.java の操作は、未チェックまたは安全ではありません。

警告を取り除くためにはコードを以下のように変更します。

List<Integer> numbers = new ArrayList<Integer>();

このように、多くの無検査警告は簡単に取り除くことができます。なるべくコードを変更して無検査警告を取り除くべきです。

無検査警告がなければ実行時に ClassCastException が発生しないことを保障できるので、プログラムの安全性を高めることができます。

アノテーションで警告を抑制する

一方、コードの変更によって無検査警告を取り除くことができない場合があります。

この場合、警告を起こしているコードが型安全であることを論理的に示すことができるならば @SuprpressWarnings アノテーションで警告を抑制するべきです。

@SuppressWarnings は個々のローカル変数宣言からクラス全体にまで指定することができます。 しかし、このアノテーションはできる限り最小のスコープで使用するべきです。

例えば、ArrayList の toArray メソッドは以下のように実装されています。 この実装では、新たな配列を生成する箇所(3行目)で無検査キャスト警告が発生します。

public <T> T[] toArray(T[] a) {
    if (a.length < size)
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        // ↑ 無検査キャスト警告の発生
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

このとき、メソッド全体に @SuppressWarnings アノテーションをつけてはいけません。 新たなローカル変数を宣言し、その変数に対して @SuppressWarnings アノテーションをつけることで、スコープを最小限にすることができます。

加えて、無検査警告を抑制する根拠となる理由をコメントとして残してください。

public <T> T[] toArray(T[] a) {
    if (a.length < size) {
        // T[] として渡されたものと同じ型の配列を生成するので、このキャストは常に安全
        @SuppressWarnigns("unchecked") 
        T[] result = (T[]) Arrays.copyOf(elements, size, a.getClass());
        return resutl
    }
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

@SupressWarnings(“unchecked”) アノテーションは論理的に型安全であることが分かる場合にだけ利用してください。 むやみに @SuppressWarnings アノテーションを利用すると自分自身に間違った安心感を与えてしまいます。

まとめ

コンパイル時の無検査警告は必ずすべて取り除くようにしましょう。

取り除く方法には「コードを変更する方法」、「SupressWarnings をつける方法」がありますが、SupressWarnings は中止して利用しましょう。

そうでない場合にはジェネリックスの恩恵がうけられません。

感想

コンパイル警告はちゃんと見ようね、という話。

ちなみに OpenJDK 7 の ArrayList の実装では、悪手とされてる「メソッド全体に @SupressWarnings」になっている。

http://hg.openjdk.java.net/jdk7/jdk7/jdk/file/00cd9dc3c2b5/src/share/classes/java/util/ArrayList.java#l351

何か理由があるのだろうか?

(2017/2/17 追記) 仕事で似たようなところ触っていたら、ふと、

「return 文には @SupressWarnings 付けられないからメソッド全体に付けている」

という理由に気づいた。