简体   繁体   中英

Java: filter 2 collections of different objects by an attribute value

Given 2 Lists containing 2 kinds of different objects (as if you are getting a collection from an API to create a client or update if it exists):

public static void main(String[] args) {
        List<ClientA> clientsA = new ArrayList<>();
        List<ClientB> clientsB = new ArrayList<>();
        for (int i = 1; i <=5; i++) {
            clientsA.add(new ClientA("JohnA-" + i, "DoeA-" + i, "A-" + i));
            clientsB.add(new ClientB("JohnB-" + i, "DoeB-" + i, "B-" + i));
        }
    }

    @Getter
    @Setter
    @AllArgsConstructor
    static class ClientA {
        private String firstName;
        private String lastName;
        private String ssNumber;

    }

    @Getter
    @Setter
    @AllArgsConstructor
    static class ClientB {
        private String firstName;
        private String lastName;
        private String security;
    }

The purpose is to build a new list of ClientA objects:

  • if there is an entry in clientsA list with the same ssNumber value equal to the security value of the clients in ClientB list, update the found entry firstName and lastName attributes;
  • otherwise, create a new ClientA object with the same attributes/values from the clientsB list, assigning firstName -> firstName , lastName -> lastName , securityNumber -> ssNumber .

I was going to use contains or retainAll methods, but it requires overriding equals and hashCode of the above classes what I can't do.

I expecting to have something like this:

public void process() {
        List<ClientA> clientsA = new ArrayList<>();
        List<ClientB> clientsB = new ArrayList<>();
        for (int i = 1; i <=5; i++) {
            clientsA.add(new ClientA("John-" + i, "Doe-" + i, "A-" + i));
            clientsB.add(new ClientB("JohnB-" + i, "DoeB-" + i, "B-" + i));
        }

        clientsA.add(new ClientA("Samantha", "Smith", "123456789"));
        clientsB.add(new ClientB("Michael", "Smith", "123456789"));

        findExistingEClientsA(clientsA, clientsB);
        findNewClientsB(clientsA, clientsB);
    }

    private void findNewClientsB(List<ClientA> clientsA, List<ClientB> clientsB) {
        Set resultSet = new HashSet();
        for (ClientA clientA : clientsA) {
            List<ClientB> collect = clientsB.stream().filter(c -> !c.getSecurity().equals(clientA.getSsNumber())).collect(Collectors.toList());
            resultSet.addAll(collect);
        }
        System.out.println("+++++++ New clients B +++++++");
        System.out.println(resultSet);
    }

    private void findExistingEClientsA(List<ClientA> clientsA, List<ClientB> clientsB) {
        Set resultSet = new HashSet();
        for (ClientA clientA : clientsA) {
            List<ClientB> collect = clientsB.stream().filter(c -> c.getSecurity().equals(clientA.getSsNumber())).collect(Collectors.toList());
            resultSet.addAll(collect);
        }

        System.out.println("++++++ existing clients B +++++++ ");
        System.out.println(resultSet);
    }        

What returns the below result:

++++++ existing clients B +++++++ 
[ClientB{firstName='Michael', lastName='Smith', security='123456789'}]
+++++++ New clients B +++++++
[ClientB{firstName='JohnB-4', lastName='DoeB-4', security='B-4'}, ClientB{firstName='JohnB-2', lastName='DoeB-2', security='B-2'}, ClientB{firstName='JohnB-5', lastName='DoeB-5', security='B-5'}, ClientB{firstName='JohnB-3', lastName='DoeB-3', security='B-3'}, ClientB{firstName='JohnB-1', lastName='DoeB-1', security='B-1'}, ClientB{firstName='Michael', lastName='Smith', security='123456789'}]

Is it a good solution or there is a better one?

But still no success.

It seems that for both cases an instance of ClientA should be created from ClientB instances and collected into a new list, thus, the following constructor should be added to ClientA :

static class ClientA {
    public ClientA(ClientB b) {
        this(b.getFirstName(), b.getLastName(), b.getSecurity());
    }
}

So the conversion would be a simple remapping:

List<ClientA> newA = clientsB.stream().map(ClientA::new).collect(Collectors.toList());

If the "update" means that the change needs to be promoted to clientsA list, a map of ClientA by ssNumber can be built first:

Map<String, ClientA> map = clientsA.stream()
        .collect(Collectors.toMap(
            ClientA::getSsNumber, clientA -> clientA, 
            (c1, c2) -> c1  // select first clientB if duplicate entries are detected
        ));

Then a new list can be created like this:

List<ClientA> newA = new ArrayList<>();

clientsB.forEach(b -> {
    ClientA a = map.getOrDefault(b.getSecurity(), new ClientA(b));
    a.setFirstName(b.getFirstName());
    a.setLastName(b.getLastName());
    newA.add(a);
});

Or a helper method should be implemented (possibly added to ClientA ) to copy the values from ClientB :

public static ClientA copyToA(ClientA a, ClientB b) {
    Objects.requireNonNull(a);
    Objects.requireNonNull(b);
    a.setFirstName(b.getFirstName());
    a.setLastName(b.getLastName());
    return a;
}

Then the new list of ClientA may be built in a more streamed way:

List<ClientA> newClientsA = clientsB.stream()
        .map(b -> map.containsKey(b.getSecurity()) 
            ? copyToA(map.get(b.getSecurity()), b)
            : new ClientA(b)
        )
        .collect(Collectors.toList());

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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