简体   繁体   English

什么是LINQ Join的Java 8 Stream API等价物?

[英]What is the Java 8 Stream API equivalent for LINQ Join?

In C#/.Net it is possible to join IEnumerable sequences with the extension method Enumerable.Join in a SQL 'JOIN ... ON' way. 在C#/ .Net中,可以使用扩展方法Enumerable.Join以SQL'JOIN ... ON'方式连接IEnumerable序列。

Is there something similar in Java 8 (Stream API)? Java 8(Stream API)中有类似的东西吗? Or what is the best way to simulate Enumerable.Join? 或者模拟Enumerable.Join的最佳方法是什么?

See: https://msdn.microsoft.com/en-us/library/bb534675%28v=vs.100%29.aspx 请参阅: https//msdn.microsoft.com/en-us/library/bb534675%28v=vs.100%29.aspx

join is just syntactic sugar for Stream.flatMap() as explained in this article . join只是Stream.flatMap()语法糖,如本文所述 Consider this example: 考虑这个例子:

List<Integer> l1 = Arrays.asList(1, 2, 3, 4);
List<Integer> l2 = Arrays.asList(2, 2, 4, 7);

l1.stream()
  .flatMap(i1 -> l2.stream()
                   .filter(i2 -> i1.equals(i2)))
  .forEach(System.out::println);

The result is: 结果是:

2
2
4

In the above example, flatMap() corresponds to (INNER) JOIN whereas the filter() operation of the nested stream corresponds to the ON clause. 在上面的示例中, flatMap()对应于(INNER) JOIN而嵌套流的filter()操作对应于ON子句。

jOOλ is a library that implements innerJoin() and other join types to abstract over this, eg also to buffer stream contents in case you want to join two Stream instances, instead of two Collection instances. jOOλ是一个实现innerJoin()和其他连接类型的库,用于innerJoin()进行抽象,例如,如果要连接两个Stream实例而不是两个Collection实例,还要缓冲流内容。 With jOOλ, you would then write: 有了jOOλ,你会写:

Seq<Integer> s1 = Seq.of(1, 2, 3, 4);
Seq<Integer> s2 = Seq.of(2, 2, 4, 7);

s1.innerJoin(s2, (i1, i2) -> i1.equals(i2))
  .forEach(System.out::println);

... which prints (the output are tuples, which is more like SQL's semantics semantics): ...打印(输出是元组,更像是SQL的语义语义):

(2, 2)
(2, 2)
(4, 4)

(disclaimer, I work for the company behind jOOλ) (免责声明,我为jOOλ背后的公司工作)

I haven't found any existing equivalent, but the below method should work: 我还没有找到任何现有的等价物,但下面的方法应该有效:

public static <Outer, Inner, Key, Result> Stream<Result> join(
        Stream<Outer> outer, Stream<Inner> inner,
        Function<Outer, Key> outerKeyFunc,
        Function<Inner, Key> innerKeyFunc,
        BiFunction<Outer, Inner, Result> resultFunc) {

    //Collect the Inner values into a list as we'll need them repeatedly
    List<Inner> innerList = inner.collect(Collectors.toList());

    //matches will store the matches between inner and outer
    final Map<Outer, List<Inner>> matches = new HashMap<>();

    //results will be used to collect the results in
    final List<Result> results = new ArrayList<>();


    outer.forEach(o -> innerList
            .stream()
            //Filter to get those Inners for which the Key equals the Key of this Outer
            .filter(i -> innerKeyFunc.apply(i).equals(outerKeyFunc.apply(o)))
            .forEach(i -> {
                if (matches.containsKey(o)) {
                    //This Outer already had matches, so add this Inner to the List
                    matches.get(o).add(i);
                } else {
                    //This is the first Inner to match this Outer, so create a List
                    List<Inner> list = new ArrayList<>();
                    list.add(i);
                    matches.put(o, list);
                }
            }));

    matches.forEach((out, in) -> in.stream()
            //Map each (Outer, Inner) pair to the appropriate Result...
            .map(i -> resultFunc.apply(out, i))
            //...and collect them
            .forEach(res -> results.add(res)));

    //Return the result as a Stream, like the .NET method does (IEnumerable)
    return results.stream();
}

I only did a brief test of the code using the following inputs: 我只使用以下输入对代码进行了简短测试:

public static void main(String[] args) {
    Stream<String> strings = Arrays.asList("a", "b", "c", "e", "f", "d").stream();
    Stream<Integer> ints = Arrays.asList(1, 2, 3, 6, 5, 4).stream();
    Stream<String> results = join(strings, ints, 
            Function.identity(),
            str    -> Integer.parseInt(str, 16) - 9, 
            (o, i) -> "Outer: " + o + ", Inner: " + i);
    results.forEach(r -> System.out.println(r));
}
  • The int s are their own keys, so no transformation int是他们自己的键,所以没有转换
  • The Strings are mapped to int s according to their hex value - 9 Strings根据其十六进制值-9映射到int
  • (The elements match if the int values are equal, as per default) (如果int值相等,则元素匹配,默认情况下)
  • Matching pairs are put into a String 匹配对被放入String

The following (correct) results are printed: 打印以下(正确)结果:

Outer: a, Inner: 1
Outer: b, Inner: 2
Outer: c, Inner: 3
Outer: d, Inner: 4
Outer: e, Inner: 5
Outer: f, Inner: 6

More in-depth testing will be needed, of course, but I believe this implementation to be correct. 当然,还需要进行更深入的测试,但我认为这种实施是正确的。 It could probably be more efficient as well, I'm open to suggestions. 它也可能更有效率,我愿意接受建议。

I also came from C# and missed that feature. 我也来自C#并错过了这个功能。 One big advantage would be to have readable code by expressing intention. 一个很大的优点是通过表达意图来获得可读代码。 So I wrote my own streamjoin which works like C# Enumerable.Join(). 所以我编写了自己的streamjoin ,它的工作方式类似于C#Enumerable.Join()。 Plus: it tolerates null-keys. 另外:它容忍空键。

Stream<BestFriends> bestFriends = 
 join(listOfPersons.stream())
  .withKey(Person::getName)
  .on(listOfDogs.stream())
  .withKey(Dog::getOwnerName)
  .combine((person, dog) -> new BestFriends(person, dog))
  .asStream();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM