我有一个班级:
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框架中的A
和names
,但toMap
会抛出groupingBy
,因为有重复的密钥,而toMap
会创建一个地图形式为IllegalStateException
,而我想要groupingBy
。
答案 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;
}));