背景
- レガシー環境では、ミドルウェアの関係でJava8が使えずStreamが利用できない
- コレクションを整形するときはfor文でぶん回している。
そこでJava7でも、StreamやLinQのようにスマートにコレクション操作できるものを探しました。
(ほかにfor文のぶん回し回避方法をご存じでしたら教えてください)
CQEngineについて
Awesome Java : 素晴しい Java フレームワーク・ライブラリ・ソフトウェアの数々
上記記事で紹介されていました。
CQEngine
公式リポジトリ Github : CQengin
- CQEngineはモデルに対し、Indexを設定することで超高速なCollection操作を提供するライブラリのようです
- 今回はStreamの代わりとしてJava7で使えるかを検証していきます
- 主題とずれるためIndexについては検証しません
License
「CQEngine」のライセンスは「Apache License 2.0」です。
CQEngineでのコレクション操作の記述方法
Queryの利用
- メリット
- タイプセーフに書ける
- for文でぶん回すより可読性が高い
- Indexを設定することによりJava8のStreamより早くなるみたいです ※ [question] Performance difference in Query vs standard collection lambda
- デメリット
- POJOそのままには使えない
- Attributeの設定方法の学習コストがかかりそう
- CQEngine用の記述方法が必要
// already defined Attribute in Car Class
// search brand-new AAACar
uery<Car> query1 = and(
endsWith(Car.NAME, "AAACar"),
equal(Car.DISCREPRION, "brandNew")
);
ResultSet<car> resultSet = cars.retrieve(query1));
CQN dialectsの利用
- メリット
- POJOに適用できる
- for文でぶん回すより可読性が高い・・・?
- デメリット
- タイプセーフでない
- CQEngine用の記述方法が必要
import static com.googlecode.cqengine.codegen.AttributeBytecodeGenerator.*;
// generate Attribute
CQNParser<Car> parser = CQNParser.forPojoWithAttributes(Car.class, createAttributes(Car.class));
// search brand-new AAACar
ResultSet<Car> results = parser.retrieve(cars,
"and("
+ "equal(\"name\",\"AAACar\"),"
+ "equal(\"discription\", \"brandNew\")"
+ " )");
SQL dialectsの利用
- メリット
- POJOに適用できる
- SQLがわかる人であれば使える(ちょっと異なるルールもありそう)
- デメリット
- タイプセーフでない
// generate Attribute
SQLParser<Car> parser = SQLParser.forPojoWithAttributes(Car.class, createAttributes(Car.class));
// search brand-new AAACar
ResultSet<Car> results = parser.retrieve(cars,
"SELECT * FROM cars " +
"WHERE (" +
" name = 'AAACar' " +
" AND discription = 'brandNew')"
);
for (Car car : results ) {
System.out.println(car.toString());
}
検証
Maven
現時点での最新版を入れています
Java7で使える「2.12.4」を入れます
<dependency>
<groupId>com.googlecode.cqengine</groupId>
<artifactId>cqengine</artifactId>
<version>2.12.4</version>
</dependency>
リリースノートを見ると、Ver3からJava7以下は対象外となったみたいです。
CQEngine Release Notes
比較用のデータの準備
コレクション用のモデルの用意
Carを作りました。※ @ Data はlombokを使っているため記述しています
@Data
public class Car {
private int carId;
private String name;
private String discription;
private List<String> features;
}
比較
Java7のfor文ぶん回し
// create car collection
Set<Car> cars = new HashSet<>();
cars.add( Car.builder().carId(0).name("AAACar").discription("brandNew").features( Arrays.asList("spare tyre", "sunroof")).build() );
cars.add( Car.builder().carId(1).name("BBBCar").discription("used").features( Arrays.asList("spare tyre", "radio")).build() );
cars.add( Car.builder().carId(2).name("CCCCar").discription("used").features( Arrays.asList("radio", "sunroof")).build() );
cars.add( Car.builder().carId(3).name("DDDCar").discription("brandNew").features( Arrays.asList("spare tyre", "sunroof")).build() );
cars.add( Car.builder().carId(4).name("AAACar").discription("used").features( Arrays.asList("radio", "sunroof")).build() );
cars.add( Car.builder().carId(5).name("BBBCar").discription("brandNew").features( Arrays.asList("spare tyre", "radio")).build() );
cars.add( Car.builder().carId(6).name("CCCCar").discription("brandNew").features( Arrays.asList("spare tyre", "sunroof")).build() );
cars.add( Car.builder().carId(7).name("DDDCar").discription("used").features( Arrays.asList("radio", "sunroof")).build() );
cars.add( Car.builder().carId(8).name("AAACar").discription("used").features( Arrays.asList("spare tyre", "sunroof")).build() );
cars.add( Car.builder().carId(9).name("BBBCar").discription("used").features( Arrays.asList("spare tyre", "radio")).build() );
cars.add( Car.builder().carId(10).name("CCCCar").discription("brandNew").features( Arrays.asList("spare tyre", "sunroof")).build() );
// search brand-new AAACar
Set<Car> results = new HashSet<>();
for (Car car : cars) {
if ( Objects.equals(car.getName(), "AAACar")
&& Objects.equals(car.getDiscription(), "brandNew") ) {
results.add(car);
}
}
for (Car car : results ) {
System.out.println(car.toString()); // => Car(carId=0, name=AAACar, discription=brandNew, features=[spare tyre, sunroof])
}
CQN(CQEngine Native)を用いる
Streamの代替品として利用することを目的としているため、POJOにAttributeを加えない方針で検証します。
Attributeを用いないCQN記法で試してみます。
Generate attributes from private class fields
POJOのprivateなフィールドを取得することは難しいそうなので、getterを用いる設定をcreateAttributesに設定しています。
// generate Attribute
CQNParser<Car> parser = CQNParser.forPojoWithAttributes(Car.class, createAttributes(Car.class, MemberFilters.GETTER_METHODS_ONLY));
// create car collection
IndexedCollection<Car> cars = new ConcurrentIndexedCollection<>();
cars.add( Car.builder().carId(0).name("AAACar").discription("brandNew").features( Arrays.asList("spare tyre", "sunroof")).build() );
//(省略)
cars.add( Car.builder().carId(10).name("CCCCar").discription("brandNew").features( Arrays.asList("spare tyre", "sunroof")).build() );
// search brand-new AAACar
ResultSet<Car> results = parser.retrieve(cars,
"and("
+ "equal(\"getName\",\"AAACar\"),"
+ "equal(\"getDiscription\", \"brandNew\")"
+ " )");
for (Car car : results ) {
System.out.println(car.toString()); // => Car(carId=0, name=AAACar, discription=brandNew, features=[spare tyre, sunroof])
}
Java8のStream
// create car collection
Set<Car> cars = new HashSet<>();
cars.add( Car.builder().carId(0).name("AAACar").discription("brandNew").features( Arrays.asList("spare tyre", "sunroof")).build() );
//(省略)
cars.add( Car.builder().carId(10).name("CCCCar").discription("brandNew").features( Arrays.asList("spare tyre", "sunroof")).build() );
// search brand-new AAACar
Set<Car> results = cars.stream()
.filter(car -> Objects.equals(car.getName(), "AAACar")
&& Objects.equals(car.getDiscription(), "brandNew") )
.collect(Collectors.toSet());
for (Car car : results ) {
System.out.println(car.toString());
}
CQEngineをJava7以下で使う方法
CQEngineのVer3以上をはJava7対応していないので、下記エラーが出ます。
Unsupported major.minor version 52.0
結論
- タイプセーフにこだわらないのであれば、可読性は向上できそうです
- タイプセーフに使うためにはひと手間付け加える必要あり(POJOにAttributeを設定する)
- 最新版はJava7では使えない
- Java7でStreamの代用として利用するという点ではそこまで魅力を感じなかったので(もともとそういうライブラリでない)
大量データを高速に処理したいときの候補として覚えておきます
もっと素直にStreamを Java7- で使う
Lightweight-Stream-API - Java6・7でもStream APIを使いたい
こちらはそのままStreamのように利用できるライブラリみたいです
Java8に移行するときは、移行前はこのライブラリで移行後のコードはJava8のStreamといった
カオスな状況にならないように気を付けないといけないとおもったので
今回CQEngineがJava8以降についても標準ライブラリを超えられるかという観点で調査しようと思いました。
なので、今後は大量データ処理でのCQEngineの性能を評価していきたいと思います
大量データでの利用
(別記事で書きます)
データを用意するのは大変なので、多めのデータが取れるAPIを探します。
個人でも使える!おすすめAPI一覧