EXP04-J. コレクションのパラメータの型と異なる型の引数を Java Collections Framework の一部のメソッドに渡さない
Java Collections Framework [JCF 2014] のインターフェースは、コレクションやマップにオブジェクトを挿入するために、 add(E e) や put(K key, V value) のような、ジェネリック型に型付けされパラメータ化されたメソッドを使っているが、contains()、remove()、get() などの他のメソッドは、パラメータ化された型ではなく Object 型の引数を受け取る。したがって、これらのメソッドは任意の型のオブジェクトを受け入れる。Collections Framework のインターフェースは、このように後方互換性を最大化して設計されているが、この設計はコーディングエラーにもつながる可能性がある。プログラマは、Map<K,V> の get() や Collection<E> の contains()、remove() などのメソッドに渡される引数の型が、対応するクラスインスタンスのパラメータ化された型と同じであることを確認しなければならない。
違反コード
以下の違反コード例の HashSet は、10個の要素を追加して削除した後、プログラマの意図に反して、0個ではなく10個の要素を持っている。Java の型チェックは、s に Short 型の値しか挿入できなくする。そのため、プログラマはコードがコンパイルされるように、short へのキャストを追加している。しかし、Collections<E>.remove() メソッドは、E 型ではなく Object 型の引数を受け取るため、プログラマは任意の型のオブジェクトを削除しようとすることができる。以下の違反コード例では、プログラマは変数 i を remove() メソッドに渡す前にキャストすることを忘れており、Short 型ではなく Integer 型のオブジェクトにオートボクシングされている。HashSet には Short 型の値しかないのに、このコードでは Integer 型のオブジェクトを削除しようとしているため、remove() メソッドは効果がない。
import java.util.HashSet;
public class ShortSet {
public static void main(String[] args) {
HashSet<Short> s = new HashSet<Short>();
for (int i = 0; i < 10; i++) {
s.add((short)i); // コードをコンパイルするためにキャストが必要
s.remove(i); // Integer を削除しようとしている
}
System.out.println(s.size());
}
}
remove() メソッドは成功を示す Boolean 値を返すため、この違反コード例は「EXP00-J. メソッドの返り値を無視しない」にも違反している。
適合コード
コレクションから削除されるオブジェクトの型はコレクションの要素の型と一致しなければならない。また、数値格上げとボクシングは予期せぬオブジェクト型を生成する可能性がある。以下の適合コードでは、明示的に short にキャストすることで、ボクシングの結果の型が想定どおりになるようにしている。
import java.util.HashSet;
public class ShortSet {
public static void main(String[] args) {
HashSet<Short> s = new HashSet<Short>();
for (int i = 0; i < 10; i++) {
s.add((short)i);
// Short を削除
if (s.remove((short)i) == false) {
System.err.println("Error removing " + i);
}
}
System.out.println(s.size());
}
}
例外
EXP04-J-EX1: Collections Framework の equals() メソッドも Object 型の引数を受け取るが、コレクションやマップの型と異なる型のオブジェクトを equals() メソッドに渡してもよい。異なるクラスのオブジェクトは決して等価にならないという契約があるため、混乱を招くことはない(MET08-J. equals() メソッドをオーバーライドする時は等価性に関する契約を守る)。
EXP04-J-EX2: 一部の Java プログラム、特にレガシープログラムは、コレクションのパラメータと同じ型のオブジェクトだけが操作されることを期待して、さまざまな型のオブジェクトをコレクションに格納している場合がある。操作が何もしない可能性を考慮しているなら、例外として許可される。
リスク評価
Java Collections Framework の一部のメソッドに、クラスインスタンスの型と異なる型の引数を渡すと、何のエラーも出さずに失敗する可能性がある。その結果、意図せぬオブジェクトの保持、メモリリーク、プログラムの誤った処理につながる可能性がある [Techtalk 2007]。
|
ルール |
深刻度 |
可能性 |
自動検出 |
自動修正 |
優先度 |
レベル |
|---|---|---|---|---|---|---|
|
EXP04-J |
低 |
中 |
可 |
不可 |
P4 |
L2 |
自動検出
Collection.remove() の呼出しにおいて、実引数の型がコレクションの要素の型と一致しないことを検出するのは容易である。ごくまれにはそのような呼出しを意図して行うことがあるかもしれないが、ほとんどの場合はコーディングエラーによるものであろう。他の API についても自動検出は可能である。
| ツール | バージョン | チェッカー | 説明 |
|---|---|---|---|
| PVS-Studio |
7.38 |
V6066 |
|
| SonarQube | 9.9 | S2175 | Inappropriate "Collection" calls should not be made |
参考文献
|
Chapter 5, "Inheritance" |
|
| [JCF 2014] | The Java Collections Framework |
|
[JLS 2015] |
|
| [Seacord 2015] | |
|
"The Joy of Sets" |
翻訳元
これは以下のページを翻訳したものです。
