【问题标题】:Google Guava "zip" two lists谷歌番石榴“压缩”两个列表
【发布时间】:2025-11-27 05:25:01
【问题描述】:

使用 Google Guava (Google Commons),有没有办法将两个大小相同的列表合并为一个列表,新列表包含两个输入列表的复合对象?

例子:

public class Person {
    public final String name;
    public final int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String toString() {
        return "(" + name + ", " + age + ")";
    }
}

List<String> names = Lists.newArrayList("Alice", "Bob", "Charles");
List<Integer> ages = Lists.newArrayList(42, 27, 31);

List<Person> persons =
    transform with a function that converts (String, Integer) to Person
System.out.println(persons);

会输出:

[(Alice, 42), (Bob, 27), (Charles, 31)]

【问题讨论】:

  • 为什么需要专门的函数,为什么不自己写一个简单的呢?
  • Guava 可以提供的任何东西都比直接的 for 循环使用起来更复杂,无论如何,这很简单。
  • 您有什么没有告诉我们的吗?您引用的示例,期望外部库了解您的 Person 类的任何信息有点荒谬...做您想做的事情所需的反射量会比给出的相当明显的答案减慢得多下面。
  • @dcsohl 请参阅code.google.com/p/guava-libraries/issues/detail?id=677 了解这可能会有什么用处
  • @arshajii 因为它是标准的函数式东西,而 Guava 支持一些函数式构造。

标签: java guava


【解决方案1】:

从 Guava 21 开始,这可以通过Streams.zip()

List<Person> persons = Streams.zip(names.stream(), ages.stream(), Person::new)
                              .collect(Collectors.toList());

【讨论】:

    【解决方案2】:

    目前 Guava 中似乎没有此功能,但它是一个理想的功能。请参阅this github issue,尤其是Iterators.zip()

    【讨论】:

      【解决方案3】:

      假装这是 Guava 方法:

      for (int i = 0; i < names.size(); i++) {
          persons.add(new Person(names.get(i), ages.get(i)));
      }
      

      【讨论】:

      • 这个答案没有抓住重点。当然我可以写一个for循环。我正在寻找将“zip”模式应用于两个集合的通用方法。如果明天我需要将Xs 和Ys 合并到Zs 的列表中,我必须复制上面的代码并修改它。
      • @SteveKuo:Guava 可能提供什么 API 会导致比这更短的解决方案?即使 Guava 添加了BiFunction 接口或其他东西,以及zipWith 方法,生成的匿名类也会比这个简单的for 循环更长。
      • 有关使用转换的概念,请参阅“Guava 中的功能习语,解释”code.google.com/p/guava-libraries/wiki/FunctionalExplained
      • @SteveKuo 如果您的集合中的元素没有索引怎么办?套装浮现在脑海中。
      • @Max 或Iterables。 (您通常不想压缩套装,因为它们没有订单。不过交叉产品会很有用)
      【解决方案4】:

      您可以参考underscore-java library

      Underscore-javaUnderscore.js对于Java的一个端口,zip方法可以达到目的。

      以下是示例代码和输出:

      $.zip(Arrays.asList("moe", "larry", "curly"), Arrays.asList("30", "40", "50"));
      

      => [[moe, 30], [larry, 40], [curly, 50]]

      【讨论】:

      • 那是javascript而不是java。
      • > Underscore-java 是 Underscore.js for Java 的一个端口
      【解决方案5】:

      这是使用普通 Java 压缩列表的通用方法。由于缺少元组,我选择使用映射条目列表(如果您不喜欢使用映射条目,请引入额外的类 ZipEntry 或其他东西)。

      public static <T1,T2> List<Map.Entry<T1,T2>> zip(List<T1> zipLeft, List<T2> zipRight) {
          List<Map.Entry<T1,T2>> zipped = new ArrayList<>();
          for (int i = 0; i < zipLeft.size(); i++) {
              zipped.add(new AbstractMap.SimpleEntry<>(zipLeft.get(i), zipRight.get(i)));
          }
          return zipped;
      }
      

      也支持数组:

      @SuppressWarnings("unchecked")
      public static <T1,T2> Map.Entry<T1,T2>[] zip(T1[] zipLeft, T2[] zipRight) {
          return zip(asList(zipLeft), asList(zipRight)).toArray(new Map.Entry[] {});
      }
      

      为了使其更健壮,添加对列表大小等的前置条件检查,或引入类似于 SQL 查询的 left join / right join 语义。

      【讨论】:

        【解决方案6】:

        这是一个没有显式迭代的版本,但它变得非常丑陋。

        List<Person> persons = ImmutableList.copyOf(Iterables.transform(
            ContiguousSet.create(Range.closedOpen(0, names.size()),
                DiscreteDomain.integers()),
            new Function<Integer, Person>() {
              @Override
              public Person(Integer index) {
                return new Person(names.get(index), ages.get(index));
              }
            }));
        

        这实际上并不比显式迭代好多少,您可能需要某种程度的边界检查以确保两个输入确实具有相同的大小。

        【讨论】: