Javaのオートボクシングの弊害?

先日、玉木研究室の方々がICPCに向けた学内セミナーを開いていただいて、そこに参加させていただいた時に先輩から
Javaで、Integer型のオブジェクトを格納するListを作って、List#removeを呼び出した時に、List#remove(int)が呼び出されるのかList#remove(Integer)が呼び出されるのかわかりにくい場合がある、というお話を聞いて、なるほどなと思ったので書いておきます。

オートボクシングって?

こういうことです。
本来ならば、

List<Integer> list = new ArrayList<Integer>();
list.add(Integer.valueOf(1));

としなければならないところを、

List<Integer> list = new ArrayList<Integer>();
list.add(1);

と書くことができるものです。
これは、int型の値がlistに追加されるときに、自動的にIntegerオブジェクトにラップされてから追加されるためにできることです。
多くの場合は、これによりコードの見た目がすっきりするので便利だと思います。

でも…

java.Util.Listの例ですが、Listにはremoveメソッドというのがふたつあり、それぞれ

  • E remove(int index)
    • リスト内の指定された位置にある要素を削除
  • boolean remove(Object o)
    • 指定された要素がこのリストにあれば、その最初のものをリストから削除

という機能を持っています。
Integer型のオブジェクトを格納するListを生成して、

List<Integer> list = new ArrayList<Integer>();
list.remove(0);

というようなことをすると
List#remove(int)が呼ばれているのか、int型の数値がオートボクシングされてInteger型のオブジェクトとして扱われてList#remove(Object)が呼ばれているのかわかりづらくなってしまいます。

実際にどうなるのかやってみる

ソース
import java.util.ArrayList;
import java.util.List;

public class Sample {

	public static void main(String[] args) {
		List<Integer> listA = new ArrayList<Integer>();
		List<Integer> listB;
		List<Integer> listC;

		// リストに値を格納
		listA.add(3);
		listA.add(2);
		listA.add(1);
		listA.add(0);

		// listAの内容をlistB、listCにもコピー
		listB = new ArrayList<Integer>(listA);
		listC = new ArrayList<Integer>(listA);

		// それぞれの別の方法でremoveメソッドを呼び出す
		listA.remove((int) 0);
		listB.remove((Integer) 0);
		listC.remove(0);

		// 結果出力
		System.out.println("listA:");
		showList(listA);
		System.out.println("listB:");
		showList(listB);
		System.out.println("listC:");
		showList(listC);
	}

	static void showList(List<Integer> list) {
		for (int i = 0; i < list.size(); i++) {
			System.out.println(i + ":" + list.get(i));
		}
	}

}
結果

listA:
0:2
1:1
2:0
listB:
0:3
1:2
2:1
listC:
0:2
1:1
2:0

まぁ

こういう結果になるのは予測できることだと思いますし、
オーバーロードされた2つのメソッドhoge(String)とhoge(Object)があったとき、hoge("str")というコードを実行するとどちらが呼び出されるか?ということを考えても前者が呼び出されるのが自然でしょう。
ただ、オートボクシングに慣れて、オートボクシングされることを期待して上記のメソッドを使うと想定外の動作をしてしまい、バグのもとになるかもしれませんし、やっぱりまぎらわしい面があるよね、という話です。

ちなみに

ここ?でしょうか。同じ名前で引数の違うメソッドがあった場合、具体的な型の指定がある方が選ばれて呼び出されるというようなことが書いてあると思います。