Java でマッピング・フィルタリング・畳み込み - FunctionalJava, totallylazy, Commons Collections, Guava, Java 8 Lambda
Java でコレクションのマッピング(map)・フィルタリング(filter)・畳み込み(foldLeft)のような処理を使いたいケースがありますが、今のところ標準で用意されていません。
そこで、以下のようなライブラリを使って試してみました。
ついでに、Java の次期バージョンである Java 8 の Early Access 版も試してみました。
サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20121027/
はじめに
下記 ProductItem クラスを使って以下の (1) 〜 (3) の処理を実装してみます。
- (1) name からなるコレクションを作成(マッピング)
- (2) price が 1500 以上のものを抽出(フィルタリング)
- (3) 合計金額(price × qty の合計)を算出(畳み込み)
ProductItem.java
import java.math.BigDecimal; public class ProductItem { private String id; private String name; private BigDecimal price; private int qty; public ProductItem(String id, String name, BigDecimal price, int qty) { this.id = id; this.name = name; this.price = price; this.qty = qty; } public String getId() { return id; } ・・・ 各種 getter メソッド ・・・ }
まず Groovy での実装は以下のようになります。(collect・findAll・inject を使用)
GroovySample.groovy
def items = [ new ProductItem("id1", "商品1", new BigDecimal("1000"), 1), new ProductItem("id2", "商品2", new BigDecimal("3000"), 2), new ProductItem("id3", "商品3", new BigDecimal("1500"), 3) ] // (1) マッピング def names = items.collect { it.name } names.each { println it } println "-----" // (2) フィルタリング def highItems = items.findAll { it.getPrice() >= 1500 } highItems.each { println it.name } println "-----" // (3) 畳み込み def total = items.inject(0) {a, b -> a + b.price * b.qty } println total
ScalaSample.scala
import scala.math.BigDecimal._ val items = List( new ProductItem("id1", "商品1", BigDecimal("1000").bigDecimal, 1), //以下でも可 //new ProductItem("id1", "商品1", 1000.bigDecimal, 1), new ProductItem("id2", "商品2", BigDecimal("3000").bigDecimal, 2), new ProductItem("id3", "商品3", BigDecimal("1500").bigDecimal, 3) ) // (1) マッピング val names = items.map { _.getName() } names.foreach { println } println("-----") // (2) フィルタリング val highItems = items.filter { _.getPrice() >= 1500 } highItems.foreach { it => println(it.getName()) } println("-----") // (3) 畳み込み def total = items.foldLeft(0: BigDecimal) {(a, b) => a + b.getPrice() * b.getQty() } println(total)
実行結果
> scala -cp .; ScalaSample.scala 商品1 商品2 商品3 ----- 商品2 商品3 ----- 11500
Functional Java
それでは Functional Java から実装してみます。
Functional Java では fj.data.List の map・filter・foldLeft メソッドが使えます。
ちなみに、要素の出力に foreach とか使ってますが普通に拡張 for 文を使えます。(Functional Java 以降のサンプルも同様)
FunctionalJavaSample.java
・・・ import fj.Effect; import fj.F; import fj.F2; import fj.data.List; import static fj.data.List.*; public class FunctionalJavaSample { public static void main(String[] args) { List<ProductItem> items = list( new ProductItem("id1", "商品1", new BigDecimal("1000"), 1), new ProductItem("id2", "商品2", new BigDecimal("3000"), 2), new ProductItem("id3", "商品3", new BigDecimal("1500"), 3) ); // (1) マッピング List<String> names = items.map(new F<ProductItem, String>() { public String f(ProductItem it) { return it.getName(); } }); names.foreach(new Effect<String>() { public void e(String n) { System.out.println(n); } }); //以下でも可 /* for (String n : names) { System.out.println(n); } */ System.out.println("-----"); // (2) フィルタリング List<ProductItem> highItems = items.filter(new F<ProductItem, Boolean>() { public Boolean f(ProductItem it) { return it.getPrice().compareTo(new BigDecimal("1500")) >= 0; } }); highItems.foreach(new Effect<ProductItem>() { public void e(ProductItem it) { System.out.println(it.getName()); } }); System.out.println("-----"); // (3) 畳み込み BigDecimal total = items.foldLeft( new F2<BigDecimal, ProductItem, BigDecimal>() { public BigDecimal f(BigDecimal a, ProductItem b) { return a.add(b.getPrice().multiply(new BigDecimal(b.getQty()))); } }, BigDecimal.ZERO ); System.out.println(total); } }
実行結果
> javac -cp .;functionaljava-3.1.jar FunctionalJavaSample.java > java -cp .;functionaljava-3.1.jar FunctionalJavaSample 商品1 商品2 商品3 ----- 商品2 商品3 ----- 11500
totallylazy
totallylazy では Sequence の map・filter・foldLeft で実装できます。インターフェース名などが異なるだけで Functional Java とほとんど同じです。
TotallylazySample.java
・・・ import com.googlecode.totallylazy.Callable1; import com.googlecode.totallylazy.Callable2; import com.googlecode.totallylazy.Predicate; import com.googlecode.totallylazy.Sequence; import static com.googlecode.totallylazy.Sequences.*; public class TotallylazySample { public static void main(String[] args) { Sequence<ProductItem> items = sequence( new ProductItem("id1", "商品1", new BigDecimal("1000"), 1), new ProductItem("id2", "商品2", new BigDecimal("3000"), 2), new ProductItem("id3", "商品3", new BigDecimal("1500"), 3) ); // (1) マッピング Sequence<String> names = items.map(new Callable1<ProductItem, String>() { public String call(ProductItem it) { return it.getName(); } }); names.each(new Callable1<String, Void>() { public Void call(String n) { System.out.println(n); return null; } }); //以下でも可 /* for (String n : names) { System.out.println(n); } */ System.out.println("-----"); // (2) フィルタリング Sequence<ProductItem> highItems = items.filter(new Predicate<ProductItem>() { public boolean matches(ProductItem it) { return it.getPrice().compareTo(new BigDecimal("1500")) >= 0; } }); highItems.each(new Callable1<ProductItem, Void>() { public Void call(ProductItem it) { System.out.println(it.getName()); return null; } }); System.out.println("-----"); // (3) 畳み込み BigDecimal total = items.foldLeft(BigDecimal.ZERO, new Callable2<BigDecimal, ProductItem, BigDecimal>() { public BigDecimal call(BigDecimal a, ProductItem b) { return a.add(b.getPrice().multiply(new BigDecimal(b.getQty()))); } } ); System.out.println(total); } }
実行結果
> javac -cp .;totallylazy-850.jar TotallylazySample.java > java -cp .;totallylazy-850.jar TotallylazySample 商品1 商品2 商品3 ----- 商品2 商品3 ----- 11500
Commons Collections
Commons Collections では CollectionUtils の collect・select を使ってマッピング・フィルタリングを実装できますが、畳み込みは用意されていないようです。
なお、ジェネリクスに対応していない点にも注意が必要です。
CommonsCollectionsSample.java
・・・ import org.apache.commons.collections.Closure; import org.apache.commons.collections.Predicate; import static org.apache.commons.collections.TransformerUtils.*; import static org.apache.commons.collections.CollectionUtils.*; public class CommonsCollectionsSample { public static void main(String[] args) { List<ProductItem> items = Arrays.asList( new ProductItem("id1", "商品1", new BigDecimal("1000"), 1), new ProductItem("id2", "商品2", new BigDecimal("3000"), 2), new ProductItem("id3", "商品3", new BigDecimal("1500"), 3) ); // (1) マッピング Collection names = collect(items, invokerTransformer("getName")); forAllDo(names, new Closure() { public void execute(Object n) { System.out.println(n); } }); // 以下でも可 /* for (Object n : names) { System.out.println(n); } */ System.out.println("-----"); // (2) フィルタリング Collection highItems = select(items, new Predicate() { public boolean evaluate(Object obj) { ProductItem it = (ProductItem)obj; return it.getPrice().compareTo(new BigDecimal("1500")) >= 0; } }); forAllDo(highItems, new Closure() { public void execute(Object obj) { ProductItem it = (ProductItem)obj; System.out.println(it.getName()); } }); System.out.println("-----"); ・・・ } ・・・ }
実行結果
> javac -cp .;commons-collections-3.2.1.jar CommonsCollectionsSample.java > java -cp .;commons-collections-3.2.1.jar CommonsCollectionsSample 商品1 商品2 商品3 ----- 商品2 商品3 -----
Guava
Guava では Collections2 の transform・filter を使ってマッピング・フィルタリングを実装できますが、Commons Collections と同様に畳み込みは用意されていないようです。
GuavaSample.java
・・・ import com.google.common.base.Function; import com.google.common.base.Predicate; import static com.google.common.collect.Collections2.*; public class GuavaSample { public static void main(String[] args) { List<ProductItem> items = Arrays.asList( new ProductItem("id1", "商品1", new BigDecimal("1000"), 1), new ProductItem("id2", "商品2", new BigDecimal("3000"), 2), new ProductItem("id3", "商品3", new BigDecimal("1500"), 3) ); // (1) マッピング Collection<String> names = transform(items, new Function<ProductItem, String>() { public String apply(ProductItem it) { return it.getName(); } }); for (String n : names) { System.out.println(n); } System.out.println("-----"); // (2) フィルタリング Collection<ProductItem> highItems = filter(items, new Predicate<ProductItem>() { public boolean apply(ProductItem it) { return it.getPrice().compareTo(new BigDecimal("1500")) >= 0; } }); for (ProductItem it : highItems) { System.out.println(it.getName()); } System.out.println("-----"); ・・・ } ・・・ }
実行結果
> javac -cp .;guava-13.0.1.jar GuavaSample.java > java -cp .;guava-13.0.1.jar GuavaSample 商品1 商品2 商品3 ----- 商品2 商品3 -----
Java SE 8 Early Access with Lambda Support (b62)
現時点の Java 8 では Stream の map・filter・fold を使う事ができ、ラムダ式によって実装がすっきりします。
ちなみに、Stream は今のところ Iterable では無いようです。*1
Java8Sample.java
・・・ import java.util.streams.Stream; public class Java8Sample { public static void main(String[] args) { List<ProductItem> items = Arrays.asList( new ProductItem("id1", "商品1", new BigDecimal("1000"), 1), new ProductItem("id2", "商品2", new BigDecimal("3000"), 2), new ProductItem("id3", "商品3", new BigDecimal("1500"), 3) ); // (1) マッピング Stream<String> names = items.stream().map(it -> it.getName()); names.forEach(n -> System.out.println(n)); System.out.println("-----"); // (2) フィルタリング Stream<ProductItem> highItems = items.stream().filter(it -> it.getPrice().compareTo(new BigDecimal("1500")) >= 0); highItems.forEach(it -> System.out.println(it.getName())); System.out.println("-----"); // (3) 畳み込み BigDecimal total = items.stream().fold( () -> BigDecimal.ZERO, (a, b) -> a.add(b.getPrice().multiply(new BigDecimal(b.getQty()))), null ); System.out.println(total); } }
実行結果
> javac Java8Sample.java > java Java8Sample 商品1 商品2 商品3 ----- 商品2 商品3 ----- 11500
*1:そのため、Stream を拡張 for 文に直接指定できません