【问题标题】:What is the Java 8 Stream API equivalent for LINQ Join?LINQ Join 的 Java 8 Stream API 等效项是什么?
【发布时间】:2025-12-06 08:40:01
【问题描述】:

在 C#/.Net 中,可以使用扩展方法 Enumerable.Join 以 SQL 'JOIN ... ON' 方式连接 IEnumerable 序列。

Java 8 (Stream API) 中有类似的东西吗?或者模拟 Enumerable.Join 的最佳方法是什么?

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

【问题讨论】:

  • 几乎可以肯定,您必须将两个流转储到指定键上的映射中,然后直接加入映射。 Java 流并非真正设计为以任何方式组合,而是串联。

标签: java c# .net linq join


【解决方案1】:

join is just syntactic sugar for Stream.flatMap() as explained in this article。考虑这个例子:

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);

结果是:

2
2
4

在上面的例子中,flatMap() 对应于(INNER) JOIN 而嵌套流的filter() 操作对应于ON 子句。

jOOλ 是一个库,它实现了innerJoin() 和其他连接类型以对其进行抽象,例如如果您想加入两个 Stream 实例,而不是两个 Collection 实例,还可以缓冲流内容。使用 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);

... 打印(输出是元组,更像是 SQL 的语义语义):

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

(免责声明,我在 jOOλ 背后的公司工作)

【讨论】:

    【解决方案2】:

    我还没有找到任何现有的等价物,但下面的方法应该可以工作:

    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();
    }
    

    我只使用以下输入对代码进行了简要测试:

    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));
    }
    
    • ints 是它们自己的键,因此无需转换
    • Strings 根据其十六进制值映射到 ints - 9
    • (默认情况下,如果int 值相等,则元素匹配)
    • 匹配对被放入String

    打印以下(正确的)结果:

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

    当然,需要进行更深入的测试,但我相信这个实现是正确的。它也可能更有效,我愿意接受建议。

    【讨论】:

      【解决方案3】:

      我也来自 C# 并且错过了该功能。一大优势是通过表达意图来获得可读的代码。所以我写了自己的streamjoin,它的工作方式类似于 C# Enumerable.Join()。另外:它可以容忍空键。

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

      【讨论】:

        最近更新 更多