LoginSignup
87

More than 5 years have passed since last update.

JavaにC#のLINQを移植してみた - jLinqer

Last updated at Posted at 2015-06-21

JavaでLINQが使いたい

 Javaで使えるLINQライブラリを作成しました。C#のLINQなら流暢に表現できるのに,Java8のStreamでは上手く表現できない悩みをラップします。

jLinqer

 jLinqerはJavaでLINQ (Language INtegrated Query: 統合言語クエリ) が使えるライブラリです。C#のLINQに存在してJava8のStreamに不足している機能(selectMany,Union, Intersect, Except等)も追加しています。

使用方法

 Gradleで使用する場合は,以下を登録してください。

'com.github.jlinqer:jlinqer:1.0.0'

 Mavenで使用する場合は,以下のpom.xmlを登録してください。


<dependency>
    <groupId>com.github.jlinqer</groupId>
    <artifactId>jlinqer</artifactId>
    <version>1.0.0</version>
</dependency>

 mavenを使用せず,jLinqerのjarを直接使用する場合は,以下よりダウンロードして下さい。

  ‡Maven, "jLinqer", https://oss.sonatype.org/content/groups/public/com/github/jlinqer/jlinqer/1.0.0/jlinqer-1.0.0.jar
 

LINQ - jLinqer 対応マトリクス

機能 LINQ(C#) jLinqer(Java) Stream(Java)
【基本】
抽出 Where where filter
射影 Select select map
並べ替え(昇順) OrderBy orderBy sorted
並べ替え(降順) OrderByDescending orderByDescending n/a
後続を並べ替え(昇順) ThenBy thenBy n/a
後続を並べ替え(降順) ThenByDescending thenByDescending n/a
平坦化して射影 SelectMany selectMany flatMap
【抽出系】
n件飛ばす Skip skip skip
条件を満たすまで飛ばす SkipWhile skipWhile n/a
n件まで流す Take take limit
条件を満たすまで流す TakeWhile takeWhile n/a
【合成系】
連結 Concat concat concat
積集合 Intersect intersect n/a
和集合 Union union n/a
差集合 Except except n/a
内部結合 Join join n/a
外部結合 GroupJoin groupJoin n/a
並びを逆にする Reverse reverse n/a
2つの値を揃えて流す Zip zip n/a
【グループ化、集計系】
重複を無くす Distinct distinct distinct
畳み込み Aggregate aggregate reduce
グループ化 GroupBy groupBy Collectors.groupingBy
平均 Average averageXXX Collectors.summarizingXXX
件数 Count count n/a
件数 LongCount longCount count
最大 Max max max
最小 Min min min
合計 Sum sumXXX Collectors.summarizingXXX
先頭 First first findFirst
先頭 FirstOrDefault firstOrDefault n/a
終端 Last last n/a
終端 LastOrDefault lastOrDefault n/a
1件の値を得る Single single n/a
1件の値を得る SingleOrDefault singleOrDefault n/a
空なら既定値を返す DefaultIfEmpty defaultIfEmpty n/a
指定番目の要素取得 ElementAt elementAt n/a
指定番目の要素取得 ElementAtOrDefault elementAtOrDefault n/a
全データが条件にマッチするか? All all allMatch
いずれかのデータが条件にマッチするか? Any any anyMatch
【生成系】
Empty empty n/a
範囲を生成 Range range n/a
繰り返す Repeat repeat n/a
【その他】
全要素一致 SequenceEqual sequenceEqual n/a
キャスト Cast cast n/a
一致する型のみ抽出 OfType ofType n/a

使い方

java.util.Listをcom.github.jlinqer.collections.Listに置き換えて使用します。

Where

List<Integer> list = new List<>(1, 2, 3);

List<Integer> actual = list.where(x -> x == 1 || x == 3).toList();

assertEquals(true , actual.contains(1));
assertEquals(false, actual.contains(2));
assertEquals(true , actual.contains(3));

Select

List<Person> list = new List<>(
        new Person("React"   , 1),
        new Person("Angular" , 3),
        new Person("Backbone", 5)
);

List<String> actual = list.select(x -> x.name).toList();

assertEquals("React"   , actual.get(0));
assertEquals("Angular" , actual.get(1));
assertEquals("Backbone", actual.get(2));

OrderBy

List<String> list = new List<>("Backbone", "Angular", "React");

List<String> actual = list.orderBy(x -> x).toList();

assertEquals("Angular" , actual.get(0));
assertEquals("Backbone", actual.get(1));
assertEquals("React"   , actual.get(2));

OrderByDescending

List<String> list = new List<>("Backbone", "Angular", "React");

List<String> actual = list.orderByDescending(x -> x).toList();

assertEquals("React"   , actual.get(0));
assertEquals("Backbone", actual.get(1));
assertEquals("Angular" , actual.get(2));

ThenBy

List<Person> list = new List<>(
        new Person("Angular2", 2),
        new Person("Angular1", 2),
        new Person("React"   , 1)
);

List<String> actual = list.orderBy(x -> x.age).thenBy(x -> x.name).toList();

assertEquals("React" , actual.get(0).name);
assertEquals("Angular1", actual.get(1).name);
assertEquals("Angular2"   , actual.get(2).name);

ThenByDescending

List<Person> list = new List<>(
        new Person("Angular2", 2),
        new Person("Angular1", 2),
        new Person("React"   , 1)
);

List<String> actual = list.orderBy(x -> x.age).thenByDescending(x -> x.name).toList();

assertEquals("React" , actual.get(0).name);
assertEquals("Angular2", actual.get(1).name);
assertEquals("Angular1"   , actual.get(2).name);

SelectMany

List<Person> list = new List<>(
        new Person("Angular", 3, new List("1.0.1", "1.0.2")),
        new Person("React"  , 1, new List("2.0.1", "2.0.2"))
);

List<String> actual = list.selectMany(x -> x.versionHistory).toList();

assertEquals("1.0.1", actual.get(0));
assertEquals("1.0.2", actual.get(1));
assertEquals("2.0.1", actual.get(2));
assertEquals("2.0.2", actual.get(3));

Skip

List<Integer> list = new List<>(1, 2, 3);

List<Integer> actual = list.skip(2).toList();

assertEquals(3, actual.get(0).intValue());

SkipWhile

List<Integer> list = new List<>(1, 2, 3, 4, 5);

List<Integer> actual = list.skipWhile(x -> x <= 3).toList();

assertEquals(4, actual.get(0).intValue());
assertEquals(5, actual.get(1).intValue());

Take

List<String> list = new List<>("Backbone", "Angular", "React");

List<String> actual = list.take(2).toList();

assertEquals(2, actual.size());
assertEquals("Backbone", actual.get(0));
assertEquals("Angular" , actual.get(1));

TakeWhile

List<String> list = new List<>("Backbone", "Angular", "React");

List<String> actual = list.takeWhile(x -> x.equals("Backbone") || x.equals("Angular")).toList();

assertEquals(2, actual.size());
assertEquals("Backbone", actual.get(0));
assertEquals("Angular" , actual.get(1));

Concat

List<Integer> first  = new List<>(1, 2);
List<Integer> second = new List<>(2, 3);

List<Integer> actual = first.concat(second).toList();

assertEquals(1, actual.get(0).intValue());
assertEquals(2, actual.get(1).intValue());
assertEquals(2, actual.get(2).intValue());
assertEquals(3, actual.get(3).intValue());

Intersect

List<Integer> first  = new List<>(1, 2, 3);
List<Integer> second = new List<>(1, 3);

List<Integer> actual = first.intersect(second).toList();

assertEquals(1, actual.get(0).intValue());
assertEquals(3, actual.get(1).intValue());

Union

List<Integer> first = new List<>(1, 2, 3);
List<Integer> second = new List<>(0, 1, 3, 4);

List<Integer> actual = first.union(second).toList();

assertEquals(5, actual.size());
assertEquals(1, actual.get(0).intValue());
assertEquals(2, actual.get(1).intValue());
assertEquals(3, actual.get(2).intValue());
assertEquals(0, actual.get(3).intValue());
assertEquals(4, actual.get(4).intValue());

Except

List<Integer> first  = new List<>(1, 2, 3);
List<Integer> second = new List<>(1, 3);

List<Integer> actual = first.except(second).toList();

assertEquals(2, actual.get(0).intValue());

Join

List<Javascript> outer = new List<>(
        new Javascript("Angular", 1),
        new Javascript("React"  , 4),
        new Javascript("ES2016" , 5)
);
List<Javascript> inner = new List<>(
        new Javascript("Angular", 2),
        new Javascript("Angular", 3),
        new Javascript("ES2016" , 6),
        new Javascript("ES7"    , 7)
);

Function<Javascript, String> outerKey = (x) -> x.name;
Function<Javascript, String> innerKey = (y) -> y.name;
BiFunction<Javascript, Javascript, Javascript> selector = (x, y) -> new Javascript(x.name, y.age);
List<Javascript> actual = outer.join(inner, outerKey, innerKey, selector).toList();

assertEquals(3, actual.size());
assertEquals("Angular", actual.get(0).name);
assertEquals("Angular", actual.get(1).name);
assertEquals("ES2016" , actual.get(2).name);
assertEquals(2, actual.get(0).age);
assertEquals(3, actual.get(1).age);
assertEquals(6, actual.get(2).age);

GroupJoin

List<Javascript> outer = new List<>(
        new Javascript("Angular", 1),
        new Javascript("React"  , 4),
        new Javascript("ES2016" , 5)
);
List<Javascript> inner = new List<>(
        new Javascript("Angular", 2),
        new Javascript("Angular", 3),
        new Javascript("ES2016" , 6),
        new Javascript("ES7"    , 7)
);

Function<Javascript, String> outerKey = (x) -> x.name;
Function<Javascript, String> innerKey = (y) -> y.name;
BiFunction<Javascript, IEnumerable<Javascript>, Javascript> selector = (x, y) -> new Javascript(x.name, y.select(z -> z.age));
List<Javascript> actual = outer.groupJoin(inner, outerKey, innerKey, selector).toList();

assertEquals(3, actual.size());
assertEquals("Angular", actual.get(0).name);
assertEquals("React"  , actual.get(1).name);
assertEquals("ES2016" , actual.get(2).name);
assertEquals(2, actual.get(0).ages.elementAt(0));
assertEquals(3, actual.get(0).ages.elementAt(1));
assertEquals(0, actual.get(1).ages.count());
assertEquals(6, actual.get(2).ages.elementAt(0));

Reverse

List<Integer> list = new List<>(1, 2, 3);

List<Integer> actual = list.reverse().toList();

assertEquals(3, actual.get(0).intValue());
assertEquals(2, actual.get(1).intValue());
assertEquals(1, actual.get(2).intValue());

Zip

List<Integer> first = new List<>(1, 2, 3);
List<String> second = new List<>("Angular", "React", "Backbone");

List<Integer> actual = first.zip(second, (x, y) -> String.format("%s %d", x, y)).toList();

assertEquals("1 Angular" , actual.get(0));
assertEquals("2 React"   , actual.get(1));
assertEquals("3 Backbone", actual.get(2));

Distinct

List<Integer> list =
        new List<>(
                1, 2, 3,
                1, 2, 3, 4
        );

List<Integer> actual = list.distinct().toList();

assertEquals(1, actual.get(0).intValue());
assertEquals(2, actual.get(1).intValue());
assertEquals(3, actual.get(2).intValue());
assertEquals(4, actual.get(3).intValue());

Aggregate

List<Integer> list = new List<>(1, 2, 3);

int actual = list.aggregate((sum, elem) -> sum + elem);

assertEquals(6, actual);

GroupBy

List<Person> list = new List<>(
        new Person("React"   , 1),
        new Person("Angular" , 1),
        new Person("Backbone", 5)
);

Map<Integer, IEnumerable<Person>> actual = list.groupBy(x -> x.age);

assertEquals(true, actual.get(1).any(x -> x.name.equals("React")));
assertEquals(true, actual.get(1).any(x -> x.name.equals("Angular")));
assertEquals(true, actual.get(5).any(x -> x.name.equals("Backbone")));

Average

List<Long> listLong = new List<>(1l, 2l, 3l, 4l);

double actualLong = listLong.averageLong(x -> x);

assertEquals(2.5d, actualLong, 0);

Count

List<String> list = new List<>("Backbone", "Angular", "React");

long actual = list.longCount();
int actualNone = list.count(x -> x.equals("jquery"));

assertEquals(3, actual);
assertEquals(0, actualNone);

Max

List<Double> listDouble = new List<>(1d, 2d, 3d);

double actualDouble = listDouble.max(x -> x);

assertEquals(3d, actualDouble, 0);

Min

List<BigDecimal> listBigDecimal = new List<>(
        new BigDecimal(1d),
        new BigDecimal(2d),
        new BigDecimal(3d)
);

BigDecimal actualBigDecimal = listBigDecimal.min(x -> x);

assertEquals(1d, actualBigDecimal.doubleValue(), 0);

Sum

List<Integer> listInt = new List<>(1, 2, 3);

int actualInt = listInt.sumInt(x -> x);

assertEquals(6, actualInt);

FirstOrDefault

List<String> list = new List<>("Backbone", "Angular", "React");

String actualFirst   = list.firstOrDefault();
String actualMatch   = list.firstOrDefault(x -> x.equals("Angular"));
String actualUnMatch = list.firstOrDefault(x -> x.equals("jquery"));

assertEquals("Backbone", actualFirst);
assertEquals("Angular" , actualMatch);
assertEquals(null      , actualUnMatch);

LastOrDefault

List<Integer> list = new List<>(1, 2, 3);
List<Integer> listEmpty = new List<>();

int actual = list.lastOrDefault();
Integer actualDefaultNone = listEmpty.lastOrDefault(x -> x == 0);

assertEquals(3, actual);
assertEquals(null, actualDefaultNone);

SingleOrDefault

List<Integer> listMany = new List<>(1, 2, 3);
List<Integer> listEmpty = new List<>();

int actualFilter = listMany.singleOrDefault(x -> x == 3);
Integer actualUnMatch = listEmpty.singleOrDefault(x -> x == 0);

assertEquals(3, actualFilter);
assertEquals(null, actualUnMatch);

DefaultIfEmpty

List<String> listEmpty = new List<>();

List<String> actualDefault = listEmpty.defaultIfEmpty("ES7").toList();

assertEquals("ES7", actualDefault.get(0));

ElementAtOrDefault

List<Integer> list = new List<>(1, 2, 3);

int actual = list.elementAtOrDefault(2);
Integer actualDefault = list.elementAtOrDefault(3);

assertEquals(3, actual);
assertEquals(null, actualDefault);

All

List<String> list = new List<>("Backbone", "Angular", "React");

boolean actual = list.all(x -> x.equals("Angular") || x.equals("Backbone") || x.equals("React"));
boolean actualNotFound = list.all(x -> x.equals("Angular") || x.equals("React"));

assertEquals(true, actual);
assertEquals(false, actualNotFound);

Any

List<String> list = new List<>("Backbone", "Angular", "React");

boolean actual = list.any(x -> x.equals("Angular"));
boolean actualNotFound = list.any(x -> x.equals("jquery"));

assertEquals(true, actual);
assertEquals(false, actualNotFound);

Empty

List<Double> actual = IEnumerable.empty(Double.class);

assertEquals(0, actual.count());

Range

List<Integer> actual = IEnumerable.range(-2, 3);

assertEquals(-2, actual.get(0).intValue());
assertEquals(-1, actual.get(1).intValue());
assertEquals(0 , actual.get(2).intValue());

Repeat

List<String> actual = IEnumerable.repeat(String.class, "Law of Cycles", 10);

assertEquals(10, actual.count());
assertEquals("Law of Cycles", actual.get(9));

SequenceEqual

List<Integer> first = new List<>(1, 2, 3);
List<Integer> secondMatch = new List<>(1, 2, 3);
List<Integer> secondUnMatchElem = new List<>(1, 2, 4);

boolean actualMatch = first.sequenceEqual(secondMatch);
boolean actualUnMatchElm = first.sequenceEqual(secondUnMatchElem);

assertEquals(true, actualMatch);
assertEquals(false, actualUnMatchElm);

Cast

List<Object> list = new List<>(1, 2, 3);

List<Integer> actual = list.cast(Integer.class).toList();

assertEquals(1, actual.get(0).intValue());
assertEquals(2, actual.get(1).intValue());
assertEquals(3, actual.get(2).intValue());

OfType

List<Object> list = new List<>(1, "2", 3, "4");

List<String>  actualStr = list.ofType(String.class).toList();
List<Integer> actualInt = list.ofType(Integer.class).toList();

assertEquals("2", actualStr.get(0));
assertEquals("4", actualStr.get(1));
assertEquals(1  , actualInt.get(0).intValue());
assertEquals(3  , actualInt.get(1).intValue());

課題

 LINQの標準動作を再現出来ていない箇所があります。

  • 遅延評価になっていない
    • toList等で評価するように変更中 (変更済:2015/06/22)
  • XXXorDefault
    • Integer,Long,Doubleのdefault値がnullとなってしまいます。C#はプリミティブ型でジェネリクスが可能なのでデフォルト値が0が返却できます
  • GroupBy
    • 返り値がMap<Key, IEnumerable<TResult>>となっています。C#はIGroupingです。IEnumerableのMapが必要です。(検討中)
  • sumXXX,averageXXX,minXXXmaxXXX
    • ジェネリクスでオーバーロードができないため,メソッド名に型を含ませています。Javaのジェネリクスでオーバーロードする議論はstackoverflowでしました
  • Listがjava.lang.Listと重複する
    • Listを継承して既存のListを完全に置き換えるように作っています。java.util.Listをcom.github.jlinqer.collections.Listに変更する必要があります。正統派Iterable継承版は参考文献の"javaLinq"を参照ください
  • ThenBy/ThenByDescendingができない
    • IOrderedEnumerableの実装が必要です。(検討中) (実装済:2015/06/27)
  • JoinGroupJoinZipができない
    • (検討中)(Zip実装済:2015/07/05)(Join実装済:2015/10/26)(GroupJoin実装済:2015/10/26)

ソースコード

 ソースコードはGitHubに公開しています。機能追加,動作障害等はPull Requestをぜひお願いします。
 
 ‡GitHub, "jLinqer", https://github.com/k--kato/jLinqer
 

バージョンについて

参考文献

  1. Microsoft Reference Source, "Enumerable.cs", http://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs
  2. GitHub, "javaLinq", https://github.com/sircodesalotOfTheRound/javaLinq
  3. Qiita, "LINQ to Objects と Java8-Stream API の対応表", http://qiita.com/amay077/items/9d2941283c4a5f61f302
  4. stackoverflow, "Generic method to perform a map-reduce operation. (Java-8)", http://stackoverflow.com/questions/30826674/generic-method-to-perform-a-map-reduce-operation-java-8

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
87