目的
Javaの配列を扱うメソッドはいくつかのクラスに分散しているので、整理してみます。
各メソッドの詳細な説明はしません。
基本的なこと
- 配列はオブジェクト
- 多次元配列は、配列の配列であらわす
- 配列は共変(covariant)
- Iterableではないけど、拡張forループに使用可能
配列のシャローコピー
既にある配列に全体、あるい一部をコピー
java.lang.System#arraycopy()
同じ配列インスタンスを新しく生成
配列オブジェクト.clone()
- キャスト不要
- CloneNotSupportedExceptionもキャッチ不要
private int[] values;
public void setValues(int[] values) {
// 防御的コピー
this.values = values.clone();
}
サイズを指定して、あるいは部分配列を新しく生成
- java.util.Arrays#copyOf()
- java.util.Arrays#copyOfRange()
配列のリスト化
java.util.Arrays#asList()
- リストのサイズが変わる操作は不可(add,remove,clearなど)
- 要素の値が変わる操作はOK。
リストの要素を変えると、配列の要素も変わる - 要素がプリミティブ型の場合には、可変引数で渡すことはできるけど、配列として渡すことはできないので注意。
プリミティブ型配列のリスト化(案)
必要なプリミティブ型配列用の、以下のようなユーティリティメソッドを用意すればよいのかな。毎回オートボクシングがはしるけど。
public static List<Integer> asList(final int[] array) {
return new AbstractList<Integer>() {
@Override
public Integer get(int index) {
return array[index];
}
@Override
public Integer set(int index, Integer element) {
int oldValue = array[index];
array[index] = element;
return oldValue;
}
@Override
public int size() {
return array.length;
}
};
}
※本当は java.util.RandomAccess を実装するべきかな。
リストの配列化
- java.util.List#toArray()
- java.util.List#toArray(T[])
例1:
List<String> list = getList();
// 引数無しは Object[] を返す
Object[] array = list.toArray();
例2:
// 十分なサイズの配列を渡せば、その配列が返ってくる
String[] strings = list.toArray(new String[list.size()]);
例3:
private static final String[] EMPTY_STRINGS = new String[0];
// ...
// サイズが足りない場合には、新しい配列を生成して返してくれる
String[] strings = list.toArray(EMPTY_STRINGS);
配列とストリームの相互変換
- java.util.Arrays#stream() - 配列のストリーム化
- java.util.stream.Stream#toArray() - ストリームの配列化
プリミティブ型の対応もあり(IntStream#toArray()とか)。
リフレクション
配列オブジェクトのlengthフィールドやclone()メソッドは、Class#getDeclaredFields() や Class#getDeclaredMethods() 等では取得できないようです。
その代わりに、配列をリフレクションで扱うための java.lang.reflect.Array クラスがあります。
オブジェクトが配列かどうか判定
java.lang.Class#isArray()
配列の要素の型を取得
java.lang.Class#getComponentType()
配列の長さを取得
java.lang.reflect.Array#getLength()
配列の要素を取得、設定
- java.lang.reflect.Array#get()
- java.lang.reflect.Array#set()
プリミティブ型を get(), set() で扱うとオートボクシング/アンボクシングされます。
プリミティブ型用の getInt(), getLong(), setInt(), setLong() などもあります。
配列の生成
java.lang.reflect.Array#newInstance()
配列の文字列化
配列オブジェクトの toString() メソッドは、Objectクラスのものと同様です。
配列の内容を表示したい場合などは、java.util.Arraysのメソッドを利用すると便利です。
- 1次元配列
java.util.Arrays#toString() - 多次元配列
java.util.Arrays#deepToString()
ただし deepToString() に渡した配列オブジェクトが、自己循環参照をしている配列だった場合には、大変なことになります。
(スタックオーバーフローとか、無限ループとか)
ソート
ソートは、java.util.Arraysクラスのメソッドで可能です。
- java.util.Arrays#sort()
- java.util.Arrays#parallelSort()
シャッフル
ソートと同様に java.util.Arrays#asList() と java.util.Colllections#shuffle() を組み合わせて実現可能です。
List<String> list = Arrays.asList(array);
Collections.shuffle(list);
プリミティブ型の場合には、自力でがんばるしかないかも。
その他
その他のユーティリティ系メソッドは、基本的に java.util.Arrays クラスにあります。
- fill : 値埋め
- equals : 一致チェック
- deepEquals : ネスト配列の一致チェック
- binarySearch : バイナリサーチ(ソート済みである必要あり)
- hashCode : ハッシュ値計算
- deepHashCode : ネスト配列のハッシュ値計算
配列オブジェクトの equals() および hashCode() は Object クラスの実装と同じであるため、必要であれば上記 Arrays#equals() や Arrays#hashCode() などを利用してください。
また deep~() 系メソッドには、自己循環参照を渡さないようにする必要があります。
配列クラスの名前
配列クラスの各種名前を取得してみると、以下のような感じ。
Integer[][] array = new Integer[0][0];
Class<?> clazz = array.getClass();
System.out.println("Name : " + clazz.getName());
System.out.println("SimpleName : " + clazz.getSimpleName());
System.out.println("CanonicalName: " + clazz.getCanonicalName());
System.out.println("TypeName : " + clazz.getTypeName());
Name : [[Ljava.lang.Integer;
SimpleName : Integer[][]
CanonicalName: java.lang.Integer[][]
TypeName : java.lang.Integer[][]
0件の配列
一般的に配列は値の書き換えが可能なため、(仮に final にしても)修正可能オブジェクトになります。
ただし0件の配列は変更可能な部分が存在しないので、イミュータブルオブジェクトです。
そのため、定数として扱ったり、マルチスレッドで共有使用しても問題ありません。
private static final Object[] EMPTY_OBJS = new Object[0];
ビットあるいは真偽値の配列
java.util.BitSet のほうがメモリ効率は良いはず。