使用lambda合并不同的对象

时间:2016-12-14 12:51:53

标签: java lambda java-8

我有一个班级:

public class A {
     String type;
     List<String> names;
}

鉴于此类的对象列表(List<A>),我如何创建Map<String, A'>

  • 关键是type String
  • A'是{strong 1}类型的 new 对象,通过合并具有相同{{1}的输入列表的所有A个实例而形成} - 该新对象的A type将包含合并的names个实例'List列表的所有名称。

我尝试使用Java Collectors框架中的Anames,但toMap会抛出groupingBy,因为有重复的密钥,而toMap会创建一个地图形式为IllegalStateException,而我想要groupingBy

4 个答案:

答案 0 :(得分:4)

我不认为它可以在单个Stream管道中完成(尽管我认为可能在Java 9中)。

groupingBy会将您带到正确的方向,但您无法将其与将Collector元素映射到单个List<A>实例(包含所有内容)的其他A结合使用名字)。

您可以使用两个Stream管道:

Map<String, A> result =
    listOfA.stream()
           .collect(Collectors.groupingBy(a -> a.type)) // Map<String,List<A>>
           .entrySet()
           .stream()
           .collectors.toMap(e -> e.getKey (),
                             e -> new A (e.getKey(),
                                         e.getValue()
                                          .stream()
                                          .flatMap(a->a.names.stream()) 
                                          .collect(Collectors.toList())));

这假定A有一个A (String type, List<String> names)构造函数。

输出names实例的List A可能包含重复项。如果您不希望这样,可以将名称收集到Set,然后创建由List初始化的Set

答案 1 :(得分:3)

假设A的构造函数使用可变names初始化List,您可以使用

Map<String, A> result=list.stream()
    .collect(Collectors.groupingBy(a -> a.type, Collector.of(A::new,
        (a,t)  ->{ a.type=t.type; a.names.addAll(t.names); },
        (a1,a2)->{ a1.names.addAll(a2.names); return a1; })));

否则,您必须使用

Map<String, A> result=list.stream()
    .collect(Collectors.groupingBy(a -> a.type, Collector.of(
        ()     ->{ A a = new A(); a.names=new ArrayList<>(); return a; },
        (a,t)  ->{ a.type=t.type; a.names.addAll(t.names); },
        (a1,a2)->{ a1.names.addAll(a2.names); return a1; })));

当然,代码会更简单,如果你有一个真正的类有可用的方法而不是只包含两个字段的A草图。

答案 2 :(得分:1)

您可以将Map的值替换为toMap Collector的合并值。

如果您的班级A看起来像这样:

class A {
  private final String type;
  private final List<String> names;

  private A(String type, List<String> names) {
    this.type = type;
    this.names = names;
  }

  public String getType() {
    return type;
  }

  public List<String> getNames() {
    return names;
  }

  public static A merge(A a, A b) {
    if (!Objects.equals(a.type, b.type)) {
      throw new IllegalArgumentException();
    }
    return of(a.type, Stream.concat(a.names.stream(), b.names.stream()).toArray(String[]::new));
  }

  public static A of(String type, String... names) {
    return new A(type, Arrays.asList(names));
  }

  @Override
  public String toString() {
    return "A [type=" + type + ", names=" + names + "]";
  }

}

您可以使用以下方法轻松创建所需的输出:

List<A> list = Arrays.asList(A.of("a", "a", "b", "c"), A.of("a", "d", "e"), A.of("b"), A.of("c", "x", "y", "z"));

Map<String, A> collect = list.stream().collect(Collectors.toMap(A::getType, Function.identity(), A::merge));

结果:

{a=A [type=a, names=[a, b, c, d, e]], b=A [type=b, names=[]], c=A [type=c, names=[x, y, z]]}

答案 3 :(得分:0)

您未能使用toMap的原因是您没有提供合并功能来处理重复键。 (有关Collectors.toMap的三参数版本,请参阅javadoc,其中包含合并地址的一个很好的示例。)

这是我的解决方案,类似于Flown提供的答案,但没有对原始类进行任何更改(我的代码不假设它是不可变类型)。

    Map<String, A> result = list.stream().collect(
            Collectors.toMap(
                a -> a.type, // keyMapper
                a -> { // valueMapper
                    A value = new A();
                    value.type = a.type;
                    value.names = new ArrayList<>(a.names);
                    return value;
                },
                (A value1, A value2) -> { // mergeFunction
                    value1.names.addAll(value2.names);
                    return value1;
                }));