Javaで配列を使用するのは、よっぽど性能が求められる場面に限られており、むしろ型安全が保障されない場合があるので基本的にListを使用した方がいいです。そもそも配列の標準ライブラリが貧弱で、Listでは簡単に実現できるような操作をするのに手間がかかります。ですが、配列を使うことを求められたのでまとめておきたいと思います。
一般操作
add
最後尾に一つ追加するには、以下のようにサイズが一つ大きい配列をコピーで作成して、新しい値を代入します。なお、Arrays#copyOfでは範囲外の部分については0で初期化されます。
int[] array = new int[]{1,2,3}; int[] newArray = Arrays.copyOf(array, array.length + 1); newArray[array.length] = 4;
addAll (join)
2つの配列を連結させたい場合は以下のようにします。
Streamを使わない場合
配列サイズが2つの合計値となるような新しい配列を作成し、そこに逐次コピーします。for分で不通に逐次コピーを書いても同様に実現できます。
int[] array = new int[]{1,2,3,4,5}; int[] array2 = new int[]{6,7,8,9,10}; int[] newArray = new int[array.length + array2.length]; Arrays.setAll(newArray, idx -> idx < array.length ? array[idx] : array2[idx - array.length]);
Streamを使う場合
XxxxStreamが用意されているint, double, longの場合に使用できます。
int[] newArray = IntStream.concat(Arrays.stream(array), Arrays.stream(array2)).toArray();
boolean, char, byteはStreamで標準でサポートされておらず、相性が悪いので使用できません。
remove
配列サイズをそのまま保ち、初期化する
以下のように記述できます。-1は初期化値です。
Arrays.setAll(newArray, idx -> array[idx] == target ? -1 : array[idx]);
配列サイズも縮小したいとき
Streamなし。この場合は簡潔には書けません。
//新しい配列サイズを決定する //新しい配列サイズの配列を作成する //除去したい値以外を新しい配列にコピーする
Streamあり
Arrays.stream(array).filter(value -> value != target).toArray();
toString
配列の中身を見たいときにtoStringを呼び出すと配列のメモリ番号しか返してくれません。
1次元配列の中身を見たいとき
int[] array; Arrays.toString(array)
2次元配列以上の中身を見たいとき
int[][] array; Arrays.deepToString(array);
サードライブラリ
Apache Commonsから提供されているorg.apache.commons.lang3のArrayUtilsには上記の汎用関数がすべて備わっています。
ライセンスはAPACHE LICENSE, VERSION 2.0ですので、ほとんどのプロジェクトで採用できると思いますのでこちらを導入するのが早いです。
primitiveの配列は使用するべき?
効率性が求められる部分では使用するべきです。例えば、intは8バイトですが、BoxingされたIntegerは80バイトであり、空間使用量は10倍となります。また、毎回Boxingする処理を挟む場合、処理速度についても歴然として差が発生します。何百万回と処理が求められる場合には積極的にprimitive配列を使用したほうがいいです。
int cycle = 100_000_000; Integer[] boxedArray = new Integer[cycle]; now = System.currentTimeMillis(); for(int i = 0; i < cycle; i++){ boxedArray[i] = 1; //ここで毎回Boxing } finish = System.currentTimeMillis(); System.out.println("array operation:" + (finish - now) + "ms"); int[] primitiveArray = new int[cycle]; now = System.currentTimeMillis(); for(int i = 0; i < cycle; i++){ primitiveArray[i] = 1; } finish = System.currentTimeMillis(); System.out.println("array operation:" + (finish - now) + "ms");
上記のように1億回Boxingのありなしで比較した場合、
- 572ms (Boxingあり)
- 49ms (Boxingなし)
なので10倍の差が生じます。
ListとArrayの差
各サイトで詳細に調べられていますが、自分の環境下では可変長のListを初期化する際には10倍の差が出るが、初期サイズを与えたListとArrayの差はありませんでした。すなわち、性能が気になるのであれば、Listの初期サイズは正しく与えることで、配列をわざわざ使用する必要はないです。
int cycle = 100_000_000; List<Integer> list = new ArrayList<>(); long now = System.currentTimeMillis(); for(int i = 0; i < cycle; i++){ list.add(1); } long finish = System.currentTimeMillis(); System.out.println("list operation:" + (finish - now) + "ms"); List<Integer> fixedList = new ArrayList<>(cycle); now = System.currentTimeMillis(); for(int i = 0; i < cycle; i++){ fixedList.add(1); } finish = System.currentTimeMillis(); System.out.println("fixed list operation:" + (finish - now) + "ms"); Integer[] boxedArray = new Integer[cycle]; now = System.currentTimeMillis(); for(int i = 0; i < cycle; i++){ boxedArray[i] = 1; } finish = System.currentTimeMillis(); System.out.println("array operation:" + (finish - now) + "ms");
- list operation:1865ms
- fixed list operation:554ms
- array operation:572ms