はじめに
JavaのListで処理を実装しているとき「リストを100件ずつ処理したいー」とかあったりすると思います。DBでBulkインサートする時とかでないですか?なかったら私の頭がおかしいです。
所謂そんなBulk的な処理をJavaで実装したやつと実装してちょっとハマって同じ過ちを犯しそうなので忘れ無いようのメモです。
参考
以前に参考させていただいた投稿があって、ほぼそれを使わせていただいているのですが、調べてもなかなか出てこず。。、いいねもしていなかったようで。、。、。、。、
もし「このコード見たことある」「というか私のこの投稿だ」とかあれば教えて頂けると幸いです。
実装したもの
public interface BulkHelper<T> extends Iterable<T> {
/**
* 第一引数で設定されているサイズのListをIterateします。
* @param size 一括で処理するリストのサイズ
* @param list 処理するリスト
*/
default void doMethod(int size, Consumer<? super List<T>> list) {
final List<T> _list = new ArrayList<T>(size);
for (T t : this) {
if(_list.size() == size) {
list.accept(_list);
_list.clear();
}
_list.add(t);
}
if(_list.size() > 0) list.accept(_list);
}
/**
* Iterateするリストをセットします。
* このメソッドは呼び出し必須です。
* @param itr
* @return
*/
public static <T> BulkHelper<T> setList(Iterable<T> itr){
return () -> itr.iterator();
}
}
使うとき
public static void main(String[] args) {
List<String> list = Arrays.asList("AAA", "BBB", "CCC", "DDD", "EEE", "FFF","GGG");
BulkHelper.setList(list).doMethod(2, _list -> {
System.out.println(_list);
});
}
結果
[AAA, BBB]
[CCC, DDD]
[EEE, FFF]
[GGG]
ハマったこと
一括処理分をListに詰め直してそれを並列処理したいと思ったときこんなことをしました。
public static void main(String[] args) {
List<String> list = Arrays.asList("AAA", "BBB", "CCC", "DDD", "EEE", "FFF","GGG");
List<List<String>> copyList = new ArrayList<>();
BulkHelper.setList(list).doMethod(2, _list -> {
copyList.add(_list);
});
copyList.parallelStream().forEach(System.out::println);
}
結果
[[GGG], [GGG], [GGG], [GGG]]
いやん。、
原因
BulkHelperインターフェースのここの処理。
if(_list.size() == size) {
list.accept(_list);
_list.clear();
}
_list.add(t);
参照先は常に_listになっており、且つclear(), add()を繰り返していたためにListで最後に処理された[GGG]を全ての要素が参照してしまったというところです。凡ミスです。
解決
BulkHelper.setList(list).doMethod(2, _list -> {
ArrayList<String> clone = new ArrayList<>(_list);
copyList.add(clone);
});
本当はインターフェース側で何とかしたいのですがパッと思いついたのは処理内部でnewしてあげるというところになりました。
追記
@Kilisameさんと@swordoneさんのご指摘のおかげでインターフェースを以下のようにスッキリ解決できました!
ありがとうございます!
public interface BulkHelper<T> extends Iterable<T> {
/**
* 第一引数のListを第二引数のサイズごとに処理を行います。
* @param list 処理するリスト
* @param size 一括処理のサイズ
* @param bulkList 処理内容
*/
public static <T> void extract(List<T> list, int size, Consumer<? super List<T>> bulkList) {
for (int i = 0; i < list.size(); i += size) {
List<T> _list = new ArrayList<>(list.subList(i, Integer.min(i + size, list.size())));
bulkList.accept(_list);
}
}
}
使うとき
public static void main(String[] args) {
List<String> list = Arrays.asList("AAA", "BBB", "CCC", "DDD", "EEE", "FFF","GGG");
List<List<String>> copyList = new ArrayList<>();
BulkHelper.extract(list, 2, _list -> {
copyList.add(_list);
});
copyList.parallelStream().forEach(System.out::println);
}
スッキリだ~