我有以下两节课:
Person:

public class Person {

    private final Long id;
    private final String address;
    private final String phone;

    public Person(Long id, String address, String phone) {
        this.id = id;
        this.address = address;
        this.phone = phone;
    }

    public Long getId() {
        return id;
    }

    public String getAddress() {
        return address;
    }

    public String getPhone() {
        return phone;
    }

    @Override
    public String toString() {
        return "Person [id=" + id + ", address=" + address + ", phone=" + phone + "]";
    }
}
CollectivePerson:
import java.util.HashSet;
import java.util.Set;

public class CollectivePerson {

    private final Long id;
    private final Set<String> addresses;
    private final Set<String> phones;

    public CollectivePerson(Long id) {
        this.id = id;
        this.addresses = new HashSet<>();
        this.phones = new HashSet<>();
    }

    public Long getId() {
        return id;
    }

    public Set<String> getAddresses() {
        return addresses;
    }

    public Set<String> getPhones() {
        return phones;
    }

    @Override
    public String toString() {
        return "CollectivePerson [id=" + id + ", addresses=" + addresses + ", phones=" + phones + "]";
    }
}

我想进行流操作,以便:
  • 映射到PersonCollectivePerson
  • 对于所有具有相同addressphonePersonaddressesphones分别在CollectivePerson中合并为Personid
    为此,我编写了以下代码:
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.Objects;
    import java.util.stream.Collectors;
    
    public class Main {
    
        public static void main(String[] args) {
            Person person1 = new Person(1L, "Address 1", "Phone 1");
            Person person2 = new Person(2L, "Address 2", "Phone 2");
            Person person3 = new Person(3L, "Address 3", "Phone 3");
            Person person11 = new Person(1L, "Address 4", "Phone 4");
            Person person21 = new Person(2L, "Address 5", "Phone 5");
            Person person22 = new Person(2L, "Address 6", "Phone 6");
    
            List<Person> persons = new ArrayList<>();
            persons.add(person1);
            persons.add(person11);
            persons.add(person2);
            persons.add(person21);
            persons.add(person22);
            persons.add(person3);
    
            Map<Long, CollectivePerson> map = new HashMap<>();
            List<CollectivePerson> collectivePersons = persons.stream()
                    .map((Person person) -> {
                        CollectivePerson collectivePerson = map.get(person.getId());
    
                        if (Objects.isNull(collectivePerson)) {
                            collectivePerson = new CollectivePerson(person.getId());
                            map.put(person.getId(), collectivePerson);
    
                            collectivePerson.getAddresses().add(person.getAddress());
                            collectivePerson.getPhones().add(person.getPhone());
    
                            return collectivePerson;
                        } else {
                            collectivePerson.getAddresses().add(person.getAddress());
                            collectivePerson.getPhones().add(person.getPhone());
    
                            return null;
                        }
                    })
                    .filter(Objects::nonNull)
                    .collect(Collectors.<CollectivePerson>toList());
    
            collectivePersons.forEach(System.out::println);
        }
    }
    

    它完成工作并输出为:
    CollectivePerson [id=1, addresses=[Address 1, Address 4], phones=[Phone 1, Phone 4]]
    CollectivePerson [id=2, addresses=[Address 2, Address 6, Address 5], phones=[Phone 5, Phone 2, Phone 6]]
    CollectivePerson [id=3, addresses=[Address 3], phones=[Phone 3]]
    

    但我相信可能会有更好的方法,以流的方式实现相同的分组。任何指针都很棒。

    最佳答案

    而不是操纵外部Map,您应该使用收集器。有toMapgroupingBy,都可以解决问题,尽管由于类设计而有些冗长。主要的障碍是缺乏现有的方法来将Person合并为CollectivePerson或从给定的CollectivePerson实例构造Person,或者缺少用于合并两个CollectivePerson实例的方法。

    使用内置收集器的一种方法是

    List<CollectivePerson> collectivePersons = persons.stream()
        .map(p -> {
            CollectivePerson cp = new CollectivePerson(p.getId());
            cp.getAddresses().add(p.getAddress());
            cp.getPhones().add(p.getPhone());
            return cp;
        })
        .collect(Collectors.collectingAndThen(Collectors.toMap(
            CollectivePerson::getId, Function.identity(),
            (cp1, cp2) -> {
                cp1.getAddresses().addAll(cp2.getAddresses());
                cp1.getPhones().addAll(cp2.getPhones());
                return cp1;
            }),
          m -> new ArrayList<>(m.values())
        ));
    

    但是在这种情况下,自定义收集器可能会更简单:
    Collection<CollectivePerson> collectivePersons = persons.stream()
        .collect(
            HashMap<Long,CollectivePerson>::new,
            (m,p) -> {
                CollectivePerson cp=m.computeIfAbsent(p.getId(), CollectivePerson::new);
                cp.getAddresses().add(p.getAddress());
                cp.getPhones().add(p.getPhone());
            },
            (m1,m2) -> m2.forEach((l,cp) -> m1.merge(l, cp, (cp1,cp2) -> {
                cp1.getAddresses().addAll(cp2.getAddresses());
                cp1.getPhones().addAll(cp2.getPhones());
                return cp1;
            }))).values();
    

    两者都将受益于用于合并两个CollectivePerson实例的预定义方法,而第一个变体还将受益于CollectivePerson(Long id, Set<String> addresses, Set<String> phones)构造函数甚至更好的CollectivePerson(Person p)构造函数,而第二个变体将受益于CollectivePerson.add(Person p)方法……

    请注意,第二个变体返回Collection的值的Map视图,而不进行复制。如果您确实需要List,则可以像在finisher函数中的第一个变体一样使用new ArrayList<>( «map» .values())进行收缩。

  • 09-28 11:52