疑問メモ: toArray(T[] t)はなぜ配列末尾にnullをパディングするのか

JavaのCollection周りのクラスを読み直していて感じたことのメモというか雑談です。

Javajava.util.Collectionインタフェースには、toArrayというメソッドがあります。toArrayメソッドは、コレクションの実装クラスのインスタンスを、配列に変換して返します。

toArrayには、引数を取るものと取らないものがあり、それぞれのメソッドは次のように宣言されています。

<T> T[] toArray(T[] a)
Object[] toArray();

引数を取る方のtoArrayメソッドは、引数で渡した配列に、変換後の配列要素を詰めて返してくれます。その際、引数の配列の長さが、要素を詰めてもなお余る場合は、nullを詰めて返します。たとえば、Collectionを実装したArrayListで、toArrayメソッドは、だいたい次のようになっています。

	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 (size < a.length) {
			a[size] = null;
		}
		return a;
	}

ところで、Collectionの実装クラスには、ArrayListのように、要素型にnullを許すものがありますよね。

ここで、ArrayListをtoArrayを使って配列に変換するケースを考えてみましょう。配列に詰め替えたいリストの要素数よりも、サイズの小さい配列を渡したとします。リストの途中の要素にnullがあるのは問題ないですが、末尾、あるいは末尾を含む連続した要素がnullの場合、後ろの余った部分にnullが詰められてしまうせいで、元のリストの要素がどこまでなのかが判別できなくなります。

つまり、こういうことになります。

package study.list;

import java.util.ArrayList;
import java.util.List;

public class ToArrayTest {
	public static void main(String[] args) {

		List<String> list = new ArrayList<String>();
		list.add("A");
		list.add("B");
		list.add(null);
		list.add("C");
		list.add("D");
		list.add("E");
		list.add("F");
		list.add(null);

		String[] sa = new String[10];
		String[] array = list.toArray(sa);
		for (int i = 0; i < array.length; i++) {
			System.out.println("[" + i + "] == " + array[i]);
		}
	}
}

実行結果。

[0] == A
[1] == B
[2] == null
[3] == C
[4] == D
[5] == E
[6] == F
[7] == null   // ここまでは正しくリストの要素であるが
[8] == null   // ここから先は渡した配列が長かったためにnullになっている
[9] == null

なぜこういう振る舞いをさせたのでしょう。

何となく、こういう結果が欲しい気がしませんか。そうでもないですか。

[0] == A
[1] == B
[2] == null
[3] == C
[4] == D
[5] == E
[6] == F
[7] == null   // 正しくリストの要素である部分までを配列変換し、
// 残りは配列を切り詰める

この場合、先ほどのtoArrayメソッドの実装は、こんな感じになるでしょうか。

	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 (size < a.length) {
			T[] copyOf = (T[]) Arrays.copyOf(a, size, a.getClass());
			a = copyOf;
		}
		return a;
	}

なぜ余った部分にnullを詰めるのかを考えてみましたが、よく分かりません。配列を得る側(toArrayを呼び出す側)としては、配列を渡したら、それと同じ固定長の配列が返ってくることを期待するのが、こういう場合の基本的な考え方なのでしょうか。しかし、渡した配列の長さがリストの要素を詰めるのに足りない場合には、toArrayは配列を十分な長さに拡張した上で、要素を詰めて返してくれるのです。この2つの振る舞いは、非対称な気がします。

気のせいなのか、考え違いをしているのか、そもそもnullが全部悪いのか(?) サテハテ。。。