简体   繁体   English

如何在Java 8中使用concat获取不同的对象列表

[英]How to get distinct list of object with concat in Java 8

I have 2 Java Classes. 我有2个Java类。

class A {
 String name;
 List<B> numbers;
}

class B {
 Integer number;
}

I want to get the distinct of Class A and concat the List of B in it. 我希望得到A类的独特之处并将其中的B列表连接起来。

eg Suppose I have a List with the following objects. 例如,假设我有一个包含以下对象的List。

List<A>{
 name = "abc"
 List<B>{1,2}

 name= "xyz"
 List<B>{3,4}

 name = "abc"
 List<B>{3,5}
}

The result should be: 结果应该是:

List<A>{
 name = "abc"
 List<B>{1,2,3,5}

 name="xyz"
 List<B>{3,4}
}

Any help would be appreciated. 任何帮助,将不胜感激。

Note: I want to achieve this functionality using Java 8 streams. 注意:我想使用Java 8流实现此功能。

Thanks 谢谢

You may use toMap collector: 您可以使用toMap collector:

Collection<A> result = list.stream()
         .collect(Collectors.toMap(a -> a.name, a -> a, 
                      (a, b) -> {a.numbers.addAll(b.numbers); return a;}))
         .values();

You may copy the result after that into List (like new ArrayList<>(result) ), but as we don't preserve any particular order, having List is not very useful. 您可以将结果复制到List (如new ArrayList<>(result) ),但由于我们不保留任何特定的顺序,因此使用List不是很有用。 In the most of scenarios having Collection as a result is fine. 在大多数情况下, Collection结果很好。

I don't think there is a way to get around the map. 我认为没有办法绕过地图。 You can however use groupingBy to hide it, but it is effectively the same code and performance as Paul Boddington suggested. 但是,您可以使用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());
}

There is no mutation of original list and you can easily change the behaviour of merging the lists - for example if you want to get rid of duplicates just add .distinct() after flatMap(list -> list.numbers.stream()) (remember about adding equals to B ) or in a similar way you can sort them by just adding .sorted() (you have to make B implement Comparable interface or just use .sorted(Comparator<B>) ). 原始列表没有变异,您可以轻松更改合并列表的行为 - 例如,如果您想要删除重复项,只需在flatMap(list -> list.numbers.stream())之后添加.distinct() flatMap(list -> list.numbers.stream()) (记住向B )添加equals或者以类似的方式你可以通过添加.sorted()来对它们进行排序(你必须使B实现Comparable接口或者只使用.sorted(Comparator<B>) )。

Here is full code with tests and imports: 以下是测试和导入的完整代码:

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)))
        )));
    }

}

EDIT: 编辑:

Following your questions in the comments, if you want to add multiple fields into groupingBy clause, you would need to create a class that represents a key in the map. 在注释中提出问题后,如果要在groupingBy子句中添加多个字段,则需要创建一个表示地图中键的类。 If you have fields that you don't want to include in the key, then you have to define how to merge two different values - similarly to what you do with numbers. 如果您有不希望包含在键中的字段,则必须定义如何合并两个不同的值 - 类似于您对数字执行的操作。 Depending on what the fields are, merging behaviour can be just choosing the first value and discarding the other (what I have done with numbers in the code below). 根据字段的不同,合并行为可以只选择第一个值并丢弃另一个(我在下面的代码中使用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());
}

Here's my answer. 这是我的答案。 I've added a constructor for A to make it slightly easier. 我为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());
    }
}

There is almost certainly a simple way to replace the first 3 lines of the merge method with something beginning list.stream()... making the entire method a one-liner. 几乎可以肯定有一种简单的方法可以用list.stream()...开头的方法替换merge方法的前3行list.stream()...使整个方法成为一个单行。 I was not able to work it out though. 虽然我无法解决这个问题。 Perhaps somebody else can edit this answer showing how? 也许其他人可以编辑这个答案显示如何?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM