Edited at

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

More than 3 years have passed since last update.


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