libGDXとはWindows、Linux、Mac、Android、iPhone、HTMLに対応したクロスプラットフォームゲームライブラリです。有名なIngressでも利用されています。欧米ではわりとメジャーどころです。
Listつかってますか?
Javaではコレクションクラスとして、Listインターフェース、実装としてArrayListを使うことが多いと思います。
ですが、このクラス、そこまで単純なクラスではないです。思っているより複雑だったりします。
また、コレクションクラスはデータの保持のみで、便利な機能は別途用意する必要があります。
LibGDXでは便利なコレクションクラスとしてArrayListの代わりに使えるArrayというクラスが存在します。今回はこちらを紹介します。
Arrayはコレクションフレームワークを実装しない
ArrayとはArrayListを置き換えて使えるような可変長配列的に使えるクラスです。ただし、Listインターフェースやシリアライズインターフェース/メソッドを実装していません。したがってListインターフェース等に依存する場合は利用できないことになります。
JSONやXMLとマッピングするライブラリや他とデータのやり取り等がインターフェースで決められているところでは使えないということです。
そのライブラリやアプリで完結しているところにArrayを使えばよいということです。
Arrayは軽量
ソースを見比べてください。まずソースコードの量が違います。
libGDX 1.6.2では590行。Java8のArrayListは1461行。ArrayListは継承されており、実装の一部はAbstractListやAbstractCollectionにもあります。Arrayは継承していません。
また、見比べるとArrayのほうはパフォーマンスが出るような設定になっている場合が多いです。特にスマホのようなパフォーマンスがPCとくらべて大幅に低いもの、JavaVMじゃないものなどでは差が出ます。ちなみにPCで使ってもマイクロベンチマークをしたところ差は出ているようです。
使い方
ArrayListでは以下のように書いたとします。
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
for(Integer value : list){
System.out.println(value);
}
Arrayでは以下のようになります。
Array<Integer> array = new Array<>();
array.add(1);
array.add(2);
array.add(3);
array.add(4);
for(Integer value : array){
System.out.println(value);
}
Iterableインターフェースを実装しているのでこれくらいなら同じです。
単純ミスを防ぐ
Listインターフェースのremoveメソッドですが、これJava5からかなりややこしいものになりました。Javaのはまりあるあるですが、オートボクシングなどによって想定していないものを選択してしまいます。
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
Integer index = 2;
list.remove(index);
for(Integer value : list){
System.out.println(value);
}
これの実行結果は
インデックスとして2(3番目)が消えるのではなく、2番目のInteger.valueOf(2)の項目が削除されます。
変数の型がInteger型ではなくプリミティブのint型だと想定した通りになります。
ななめ読みしているとこのバグに気が付かない可能性もありますね。
ではArrayはどうなっているでしょうか。
Arrayにはremoveメソッドは存在しません。
代わりにあるのが、removeIndexメソッドとremoveValueメソッドです。つまり、間違わないようにメソッド名を分けたのです。
そしてremoveValueは引数が1つではなく2つに拡張されています。1つ目は削除するObjectなのはList#remove(Object)と同じですが、2つ目はbooleanです。
この2つ目の引数がfalseの場合、List#remove(Object)と同様の動きになります。
つまり、equalsメソッドで比較です。
trueの場合、インスタンスが同一の場合(==で比較)削除します。もちろん、こちらのほうが早いですし、使う場面も結構あります。
そのほかの便利な命令
便利そうなのを並べてみます。
スタック系
array.push(123);
boolean peek = array.peek();
Integer pop = array.pop();
ランダムに1件選択
Integer pop = array.random();
逆順に並べる
array.reverse();
値の入れ替え
index0と1の値を交換する。
array.swap(0,1);
シャッフル
array.shuffle();
配列化
Integer[] integers = array.toArray(Integer.class);
もしくはコンストラクタで型をクラスをわたせばそのままいけます。
ArrayList<Integer> list = new ArrayList<>(Integer.class);
Integer[] integers = array.toArray();
コンストラクタの代わりにArray.of(class)を利用することもできますが、上記のコンストラクタの単なるラッパーなので、ループの内側でインスタンスを大量に生成する場合など、少しでも負荷を減らしたいと考えるならば使わないほうがよいかもしれません。
可変長引数でadd
array.addAll(5,6,7,8);
条件選択
Iterable<Integer> ite = array.select(new Predicate<Integer>() {
@Override
public boolean evaluate(Integer arg) {
return (arg % 2 == 0);//偶数のみ取得
}
});
for(Integer value:ite){
System.out.println(value);
}
Arrayの亜種
プリミティブ用に以下の3種類が用意されています。もちろんこちらのほうがパフォーマンス的に有利なので今まで書いてきたサンプルはIntArrayにするべきでしょう。
IntArray intArray = new IntArray();
FloatArray floatArray = new FloatArray();
LongArray longArray = new LongArray();
libGDXのソースは非常にシンプルなので一度見てみたほうがよいです。まずはこの短いソースのArrayから追ってみるとよいでしょう。libGDX内部でもこのパフォーマンスの良いArrayは多用されています。