【问题标题】:Java 8 stream join and return multiple valuesJava 8 流连接并返回多个值
【发布时间】:2016-07-09 19:19:04
【问题描述】:

我正在将一段代码从 .NET 移植到 Java,并偶然发现了一个我想使用流来映射和减少的场景。

class Content
{
  private String propA, propB, propC;
  Content(String a, String b, String c)
  {
    propA = a; propB = b; propC = c;
  }
  public String getA() { return propA; }
  public String getB() { return propB; }
  public String getC() { return propC; }
}

List<Content> contentList = new ArrayList();
contentList.add(new Content("A1", "B1", "C1"));
contentList.add(new Content("A2", "B2", "C2"));
contentList.add(new Content("A3", "B3", "C3"));

我想写一个函数,可以通过 contentlist 的内容进行流式传输,并返回一个带有结果的类

content { propA = "A1, A2, A3", propB = "B1, B2, B3", propC = "C1, C2, C3" }

我对 Java 还很陌生,所以您可能会发现一些代码更像 C# 而不是 java

【问题讨论】:

  • 很好的问题描述。但是现在,你尝试过什么? StackOverflow 不是一个让其他人为您编写代码的社区。相反,您需要发布minimal reproducible example,展示您尝试过的、得到的和期望的,StackOverflow 社区可能会尝试帮助确定您出错的地方并指出解决方案。
  • @AJNeufeld 感谢您的建议。这也是我第一次尝试在 SO 上发布问题,尽管每天都使用它。我让它使用带有变量的基本 for 循环来附加内容。我尝试在流上使用 forEach 然后意识到 Java 匿名函数不允许修改 foreach 块内的变量。我还考虑为达到一个目标写一个收集,但意识到它也不会有效。

标签: java dictionary java-8 java-stream collect


【解决方案1】:

您可以在 reduce 函数中为 BinaryOperator 使用适当的 lambda。

Content c = contentList
            .stream()
            .reduce((t, u) -> new Content(
                                  t.getA() + ',' + u.getA(),
                                  t.getB() + ',' + u.getB(), 
                                  t.getC() + ',' + u.getC())
                   ).get();

【讨论】:

  • @AJNeufeld 同意,但这是空间和时间之间的选择。
  • 只有 3 个属性,我偏爱我的实现。但在更一般的情况下——具有更多属性、更复杂的属性、私有属性等——最好的可能是reduce((t, u) -&gt; new Content(t, u); 使用专门的构造函数将合并委托给Content 对象本身,或者实际上编写一个@ 987654325@,高效积累信息。
【解决方案2】:
static Content merge(List<Content> list) {
    return new Content(
            list.stream().map(Content::getA).collect(Collectors.joining(", ")),
            list.stream().map(Content::getB).collect(Collectors.joining(", ")),
            list.stream().map(Content::getC).collect(Collectors.joining(", ")));
}

编辑:扩展 Federico 的内联收集器,这是一个专门用于合并 Content 对象的具体类:

class Merge {

    public static Collector<Content, ?, Content> collector() {
        return Collector.of(Merge::new, Merge::accept, Merge::combiner, Merge::finisher);
    }

    private StringJoiner a = new StringJoiner(", ");
    private StringJoiner b = new StringJoiner(", ");
    private StringJoiner c = new StringJoiner(", ");

    private void accept(Content content) {
        a.add(content.getA());
        b.add(content.getB());
        c.add(content.getC());
    }

    private Merge combiner(Merge second) {
        a.merge(second.a);
        b.merge(second.b);
        c.merge(second.c);
        return this;
    }

    private Content finisher() {
        return new Content(a.toString(), b.toString(), c.toString());
    }
}

用作:

Content merged = contentList.stream().collect(Merge.collector());

【讨论】:

    【解决方案3】:

    如果您不想在列表上迭代 3 次,或者不想创建太多 Content 中间对象,那么您需要使用自己的实现来收集流:

    public static Content collectToContent(Stream<Content> stream) {
        return stream.collect(
            Collector.of(
                () -> new StringBuilder[] {
                        new StringBuilder(),
                        new StringBuilder(),
                        new StringBuilder() },
                (StringBuilder[] arr, Content elem) -> {
                    arr[0].append(arr[0].length() == 0 ? 
                            elem.getA() : 
                            ", " + elem.getA());
                    arr[1].append(arr[1].length() == 0 ? 
                            elem.getB() : 
                            ", " + elem.getB());
                    arr[2].append(arr[2].length() == 0 ? 
                            elem.getC() : 
                            ", " + elem.getC());
                },
                (arr1, arr2) -> {
                    arr1[0].append(arr1[0].length() == 0 ?
                            arr2[0].toString() :
                            arr2[0].length() == 0 ?
                                    "" :
                                    ", " + arr2[0].toString());
                    arr1[1].append(arr1[1].length() == 0 ?
                            arr2[1].toString() :
                            arr2[1].length() == 0 ?
                                    "" :
                                    ", " + arr2[1].toString());
                    arr1[2].append(arr1[2].length() == 0 ?
                            arr2[2].toString() :
                            arr2[2].length() == 0 ?
                                    "" :
                                    ", " + arr2[2].toString());
                    return arr1;
                },
                arr -> new Content(
                        arr[0].toString(), 
                        arr[1].toString(), 
                        arr[2].toString())));
    }
    

    此收集器首先创建一个包含 3 个空 StringBuilder 对象的数组。然后定义一个累加器,将每个Contentelement 的属性附加到对应的StringBuilder。然后它定义了一个仅在并行处理流时使用的合并函数,它将两个先前累积的部分结果合并。最后,它还定义了一个finisher函数,将3个StringBuilder对象转换为Content的一个新实例,每个属性对应于前面步骤的累积字符串。

    请查看Stream.collect()Collector.of() javadocs 以获得进一步参考。

    【讨论】:

      【解决方案4】:

      处理此类任务的最通用方法是将多个收集器的结果合并为一个。

      使用jOOL 库,您可以拥有以下内容:

      Content content = 
          Seq.seq(contentList)
             .collect(
               Collectors.mapping(Content::getA, Collectors.joining(", ")),
               Collectors.mapping(Content::getB, Collectors.joining(", ")),
               Collectors.mapping(Content::getC, Collectors.joining(", "))
             ).map(Content::new);
      

      这会从输入列表中创建一个Seq,并结合3 个给定的收集器来创建一个Tuple3,它只是3 个值的持有者。然后使用构造函数 new Content(a, b, c) 将这 3 个值映射到 Content。收集器本身只是将每个Content 映射到其abc 值,并将结果连接在一起,并用", " 分隔。


      没有第三方帮助,我们可以像这样创建自己的组合器收集器(这是基于StreamExpairing 收集器,它对 2 个收集器做同样的事情)。它将 3 个收集器作为参数,并对收集到的 3 个值的结果执行整理器操作。

      public interface TriFunction<T, U, V, R> {
          R apply(T t, U u, V v);
      }
      
      public static <T, A1, A2, A3, R1, R2, R3, R> Collector<T, ?, R> combining(Collector<? super T, A1, R1> c1, Collector<? super T, A2, R2> c2, Collector<? super T, A3, R3> c3, TriFunction<? super R1, ? super R2, ? super R3, ? extends R> finisher) {
      
          final class Box<A, B, C> {
              A a; B b; C c;
              Box(A a, B b, C c) {
                  this.a = a;
                  this.b = b;
                  this.c = c;
              }
          }
      
          EnumSet<Characteristics> c = EnumSet.noneOf(Characteristics.class);
          c.addAll(c1.characteristics());
          c.retainAll(c2.characteristics());
          c.retainAll(c3.characteristics());
          c.remove(Characteristics.IDENTITY_FINISH);
      
          return Collector.of(
                  () -> new Box<>(c1.supplier().get(), c2.supplier().get(), c3.supplier().get()),
                  (acc, v) -> {
                      c1.accumulator().accept(acc.a, v);
                      c2.accumulator().accept(acc.b, v);
                      c3.accumulator().accept(acc.c, v);
                  },
                  (acc1, acc2) -> {
                      acc1.a = c1.combiner().apply(acc1.a, acc2.a);
                      acc1.b = c2.combiner().apply(acc1.b, acc2.b);
                      acc1.c = c3.combiner().apply(acc1.c, acc2.c);
                      return acc1;
                  },
                  acc -> finisher.apply(c1.finisher().apply(acc.a), c2.finisher().apply(acc.b), c3.finisher().apply(acc.c)),
                  c.toArray(new Characteristics[c.size()])
                 );
      }
      

      最后使用它

      Content content = contentList.stream().collect(combining(
          Collectors.mapping(Content::getA, Collectors.joining(", ")),
          Collectors.mapping(Content::getB, Collectors.joining(", ")),
          Collectors.mapping(Content::getC, Collectors.joining(", ")), 
          Content::new
      ));
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-01-13
        • 1970-01-01
        • 1970-01-01
        • 2018-11-20
        • 1970-01-01
        • 2014-04-08
        • 2017-05-11
        相关资源
        最近更新 更多