如何在 Java 8 中使用 concat 获取不同的对象列表
How to get distinct list of object with concat in Java 8
我有 2 Java Classes.
class A {
String name;
List<B> numbers;
}
class B {
Integer number;
}
我想获得 Class A 的不同并在其中连接 B 的列表。
例如假设我有一个包含以下对象的列表。
List<A>{
name = "abc"
List<B>{1,2}
name= "xyz"
List<B>{3,4}
name = "abc"
List<B>{3,5}
}
结果应该是:
List<A>{
name = "abc"
List<B>{1,2,3,5}
name="xyz"
List<B>{3,4}
}
如有任何帮助,我们将不胜感激。
注意:我想使用 Java 8 个流来实现此功能。
谢谢
这是我的答案。我为 A
添加了一个构造函数以使其更容易一些。
public class Main {
static class A {
String name;
List<B> numbers;
A(String name, List<B> numbers) {
this.name = name;
this.numbers = new ArrayList<>(numbers);
}
}
static class B {
Integer number;
}
static List<A> merge(List<A> list) {
Map<String, List<B>> map = new LinkedHashMap<>();
for (A a : list)
map.computeIfAbsent(a.name, k -> new ArrayList<>()).addAll(a.numbers);
return map.entrySet()
.stream()
.map(e -> new A(e.getKey(), e.getValue()))
.collect(Collectors.toList());
}
}
几乎肯定有一种简单的方法可以用 list.stream()...
开头的内容替换 merge
方法的前 3 行,从而使整个方法成为一行。我无法解决这个问题。也许其他人可以编辑此答案以说明如何操作?
我认为没有办法绕过地图。但是,您可以使用 groupingBy
来隐藏它,但它实际上与 Paul Boddington 建议的代码和性能相同。
List<A> merge(List<A> input) {
return input.stream()
.collect(groupingBy(a -> a.name)) // map created here
.entrySet()
.stream()
.map(entry -> new A(
entry.getKey(),
entry.getValue().stream()
.flatMap(list -> list.numbers.stream())
.collect(toList()) // merging behaviour
)).collect(toList());
}
原始列表没有变化,您可以轻松更改合并列表的行为 - 例如,如果您想删除重复项,只需在 flatMap(list -> list.numbers.stream())
之后添加 .distinct()
(记住添加 equals
到 B
) 或者以类似的方式你可以通过添加 .sorted()
对它们进行排序(你必须让 B 实现 Comparable
接口或者只使用 .sorted(Comparator<B>)
).
这是包含测试和导入的完整代码:
import org.junit.Test;
import java.util.List;
import static com.shazam.shazamcrest.MatcherAssert.assertThat;
import static com.shazam.shazamcrest.matcher.Matchers.sameBeanAs;
import static java.util.Arrays.asList;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
public class Main {
class A {
final String name;
final List<B> numbers;
A(String name, List<B> numbers) {
this.name = name;
this.numbers = numbers;
}
}
class B {
final Integer number;
B(Integer number) {
this.number = number;
}
}
List<A> merge(List<A> input) {
return input.stream()
.collect(groupingBy(a -> a.name))
.entrySet()
.stream()
.map(entry -> new A(
entry.getKey(),
entry.getValue().stream()
.flatMap(list -> list.numbers.stream())
.collect(toList())
)).collect(toList());
}
@Test
public void test() {
List<A> input = asList(
new A("abc", asList(new B(1), new B(2))),
new A("xyz", asList(new B(3), new B(4))),
new A("abc", asList(new B(3), new B(5)))
);
List<A> list = merge(input);
assertThat(list, sameBeanAs(asList(
new A("abc", asList(new B(1), new B(2), new B(3), new B(5))),
new A("xyz", asList(new B(3), new B(4)))
)));
}
}
编辑:
根据您在评论中提出的问题,如果您想将多个字段添加到 groupingBy
子句中,则需要创建一个 class 来表示地图中的一个键。如果您有不想包含在键中的字段,那么您必须定义如何合并两个不同的值——类似于您对数字所做的操作。根据字段的不同,合并行为可以只是选择第一个值并丢弃另一个值(我在下面的代码中使用 numbers
所做的)。
class A {
final String name;
final String type;
final List<B> numbers;
A(String name, String type, List<B> numbers) {
this.name = name;
this.type = type;
this.numbers = numbers;
}
}
class B {
final Integer number;
B(Integer number) {
this.number = number;
}
}
class Group {
final String name;
final String type;
Group(String name, String type) {
this.name = name;
this.type = type;
}
// this needs to have equals and hashCode methods as we use it as a key in a map
}
List<A> merge(List<A> input) {
return input.stream()
.collect(groupingBy(a -> new Group(a.name, a.type)))
.entrySet()
.stream()
.map(entry -> new A(
entry.getKey().name, // entry.getKey() is now Group, not String
entry.getKey().type,
entry.getValue().get(0).numbers // no merging, just take first
)).collect(toList());
}
您可以使用 toMap
收集器:
Collection<A> result = list.stream()
.collect(Collectors.toMap(a -> a.name, a -> a,
(a, b) -> {a.numbers.addAll(b.numbers); return a;}))
.values();
您可以将之后的结果复制到 List
(如 new ArrayList<>(result)
),但由于我们不保留任何特定顺序,因此 List
不是很有用。在大多数情况下 Collection
结果是好的。
我有 2 Java Classes.
class A {
String name;
List<B> numbers;
}
class B {
Integer number;
}
我想获得 Class A 的不同并在其中连接 B 的列表。
例如假设我有一个包含以下对象的列表。
List<A>{
name = "abc"
List<B>{1,2}
name= "xyz"
List<B>{3,4}
name = "abc"
List<B>{3,5}
}
结果应该是:
List<A>{
name = "abc"
List<B>{1,2,3,5}
name="xyz"
List<B>{3,4}
}
如有任何帮助,我们将不胜感激。
注意:我想使用 Java 8 个流来实现此功能。
谢谢
这是我的答案。我为 A
添加了一个构造函数以使其更容易一些。
public class Main {
static class A {
String name;
List<B> numbers;
A(String name, List<B> numbers) {
this.name = name;
this.numbers = new ArrayList<>(numbers);
}
}
static class B {
Integer number;
}
static List<A> merge(List<A> list) {
Map<String, List<B>> map = new LinkedHashMap<>();
for (A a : list)
map.computeIfAbsent(a.name, k -> new ArrayList<>()).addAll(a.numbers);
return map.entrySet()
.stream()
.map(e -> new A(e.getKey(), e.getValue()))
.collect(Collectors.toList());
}
}
几乎肯定有一种简单的方法可以用 list.stream()...
开头的内容替换 merge
方法的前 3 行,从而使整个方法成为一行。我无法解决这个问题。也许其他人可以编辑此答案以说明如何操作?
我认为没有办法绕过地图。但是,您可以使用 groupingBy
来隐藏它,但它实际上与 Paul Boddington 建议的代码和性能相同。
List<A> merge(List<A> input) {
return input.stream()
.collect(groupingBy(a -> a.name)) // map created here
.entrySet()
.stream()
.map(entry -> new A(
entry.getKey(),
entry.getValue().stream()
.flatMap(list -> list.numbers.stream())
.collect(toList()) // merging behaviour
)).collect(toList());
}
原始列表没有变化,您可以轻松更改合并列表的行为 - 例如,如果您想删除重复项,只需在 flatMap(list -> list.numbers.stream())
之后添加 .distinct()
(记住添加 equals
到 B
) 或者以类似的方式你可以通过添加 .sorted()
对它们进行排序(你必须让 B 实现 Comparable
接口或者只使用 .sorted(Comparator<B>)
).
这是包含测试和导入的完整代码:
import org.junit.Test;
import java.util.List;
import static com.shazam.shazamcrest.MatcherAssert.assertThat;
import static com.shazam.shazamcrest.matcher.Matchers.sameBeanAs;
import static java.util.Arrays.asList;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
public class Main {
class A {
final String name;
final List<B> numbers;
A(String name, List<B> numbers) {
this.name = name;
this.numbers = numbers;
}
}
class B {
final Integer number;
B(Integer number) {
this.number = number;
}
}
List<A> merge(List<A> input) {
return input.stream()
.collect(groupingBy(a -> a.name))
.entrySet()
.stream()
.map(entry -> new A(
entry.getKey(),
entry.getValue().stream()
.flatMap(list -> list.numbers.stream())
.collect(toList())
)).collect(toList());
}
@Test
public void test() {
List<A> input = asList(
new A("abc", asList(new B(1), new B(2))),
new A("xyz", asList(new B(3), new B(4))),
new A("abc", asList(new B(3), new B(5)))
);
List<A> list = merge(input);
assertThat(list, sameBeanAs(asList(
new A("abc", asList(new B(1), new B(2), new B(3), new B(5))),
new A("xyz", asList(new B(3), new B(4)))
)));
}
}
编辑:
根据您在评论中提出的问题,如果您想将多个字段添加到 groupingBy
子句中,则需要创建一个 class 来表示地图中的一个键。如果您有不想包含在键中的字段,那么您必须定义如何合并两个不同的值——类似于您对数字所做的操作。根据字段的不同,合并行为可以只是选择第一个值并丢弃另一个值(我在下面的代码中使用 numbers
所做的)。
class A {
final String name;
final String type;
final List<B> numbers;
A(String name, String type, List<B> numbers) {
this.name = name;
this.type = type;
this.numbers = numbers;
}
}
class B {
final Integer number;
B(Integer number) {
this.number = number;
}
}
class Group {
final String name;
final String type;
Group(String name, String type) {
this.name = name;
this.type = type;
}
// this needs to have equals and hashCode methods as we use it as a key in a map
}
List<A> merge(List<A> input) {
return input.stream()
.collect(groupingBy(a -> new Group(a.name, a.type)))
.entrySet()
.stream()
.map(entry -> new A(
entry.getKey().name, // entry.getKey() is now Group, not String
entry.getKey().type,
entry.getValue().get(0).numbers // no merging, just take first
)).collect(toList());
}
您可以使用 toMap
收集器:
Collection<A> result = list.stream()
.collect(Collectors.toMap(a -> a.name, a -> a,
(a, b) -> {a.numbers.addAll(b.numbers); return a;}))
.values();
您可以将之后的结果复制到 List
(如 new ArrayList<>(result)
),但由于我们不保留任何特定顺序,因此 List
不是很有用。在大多数情况下 Collection
结果是好的。