I have two list ListA listA = new ArrayList() and ListB listB = new ArrayList() both contain object of type Position object and Position contain these variables.
Position {
String account;
String Date;
String Cycle;
String Status;
}
and if for example my lists has values like this
ListA = ["ACC1","20-Jan-23","1","open"],
["ACC1","20-Jan-23","2","closing"],
["ACC2","20-Jan-23","1","open"],
["ACC2","20-Jan-23","2","closing"],
["ACC3","20-Jan-23","1","open"],
["ACC3","20-Jan-23","2","closing"]
ListB = ["ACC1","20-Jan-23","1","open"],
["ACC1","20-Jan-23","2","closing"],
["ACC2","20-Jan-23","1","open"],
["ACC2","20-Jan-23","2","closed"],
["ACC3","20-Jan-23","1","open"]
now my requirement is from the above both lists, I need to find out and extract all accounts that exactly matches in the other list but uniquely, meaning
"ACC1" having two objects in listA and same exists in ListB so this the right candidate that i needed to extract
"ACC2" having two objects in both lists but only one matching exactly same with listB, but other record doesnt match because the status values differs ('closing' and 'closed') so i need to exclude ACC2
"ACC3" having two objects in listA but not in list B, so i need to exclude this ACC3 as well
so ACC1 is what i'm interested in
Is there any way we can achieve this efficiently using java streams or usual standard way
Thanks
Assuming you have all the necessary getters and have overriden the equals and hashCode methods, for example like this:
class Position {
String account;
String Date;
String Cycle;
String Status;
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final Position position = (Position) o;
return Objects.equals(account, position.account) && Objects.equals(Date, position.Date)
&& Objects.equals(Cycle, position.Cycle) && Objects.equals(Status, position.Status);
}
@Override
public int hashCode() {
return Objects.hash(account, Date, Cycle, Status);
}
}
You could stream over both lists, order them in an identical way and group them by account
and use the resulting maps to filter accounts having the same list of Position objects. Example code:
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
public class Example2 {
public static void main(String[] args) {
List<Position> listA = List.of(new Position("ACC1", "20-Jan-23", "1", "open"),
new Position("ACC1", "20-Jan-23", "2", "closing"),
new Position("ACC2", "20-Jan-23", "1", "open"),
new Position("ACC2", "20-Jan-23", "2", "closing"),
new Position("ACC3", "20-Jan-23", "1", "open"),
new Position("ACC3", "20-Jan-23", "2", "closing"));
List<Position> listB = List.of(new Position("ACC1", "20-Jan-23", "1", "open"),
new Position("ACC1", "20-Jan-23", "2", "closing"),
new Position("ACC2", "20-Jan-23", "1", "open"),
new Position("ACC2", "20-Jan-23", "2", "closed"),
new Position("ACC3", "20-Jan-23", "1", "open"));
Comparator<Position> comparator = Comparator.comparing(Position::getAccount)
.thenComparing(Position::getDate)
.thenComparing(Position::getCycle)
.thenComparing(Position::getStatus);
Map<String, List<Position>> mapA = listA.stream().sorted(comparator).collect(Collectors.groupingBy(Position::getAccount));
Map<String, List<Position>> mapB = listB.stream().sorted(comparator).collect(Collectors.groupingBy(Position::getAccount));
List<String> result = mapA.keySet()
.stream()
.filter(key -> mapA.get(key).equals(mapB.get(key)))
.toList();
System.out.println(result);
}
@AllArgsConstructor
@Getter
@ToString
static class Position {
String account;
String Date;
String Cycle;
String Status;
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final Position position = (Position) o;
return Objects.equals(account, position.account) && Objects.equals(Date, position.Date)
&& Objects.equals(Cycle, position.Cycle) && Objects.equals(Status, position.Status);
}
@Override
public int hashCode() {
return Objects.hash(account, Date, Cycle, Status);
}
}
}
UPDATE
..been told not to override equal and hascode method in PositionEntity... is there any other way to compare the equality of the 2 list of objects without using equals method?
you can do the field by field comparison manually. I have added an example using BiPredicates. May be there are some eleganter ways to do this with some 3rd party libraries. But without changing the first approach too much the below should give you the same result without the need to override equals and hashCode.
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.function.BiPredicate;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
public class Example2 {
public static void main(String[] args) {
List<Position> listA = List.of(new Position("ACC1", "20-Jan-23", "1", "open"),
new Position("ACC1", "20-Jan-23", "2", "closing"),
new Position("ACC2", "20-Jan-23", "1", "open"),
new Position("ACC2", "20-Jan-23", "2", "closing"),
new Position("ACC3", "20-Jan-23", "1", "open"),
new Position("ACC3", "20-Jan-23", "2", "closing"));
List<Position> listB = List.of(new Position("ACC1", "20-Jan-23", "1", "open"),
new Position("ACC1", "20-Jan-23", "2", "closing"),
new Position("ACC2", "20-Jan-23", "1", "open"),
new Position("ACC2", "20-Jan-23", "2", "closed"),
new Position("ACC3", "20-Jan-23", "1", "open"));
Comparator<Position> comparator = Comparator.comparing(Position::getAccount)
.thenComparing(Position::getDate)
.thenComparing(Position::getCycle)
.thenComparing(Position::getStatus);
Map<String, List<Position>> mapA = listA.stream().sorted(comparator).collect(Collectors.groupingBy(Position::getAccount));
Map<String, List<Position>> mapB = listB.stream().sorted(comparator).collect(Collectors.groupingBy(Position::getAccount));
//Since you cannot modify equals and hashCode you can do the field by field comparison manually
//there are maybe some 3rd party libraries which might do this elegantly,
// but something like below should work fine
BiPredicate<Position,Position> positionsEqual = (position1, position2) ->
position1.getAccount().equals(position2.getAccount()) &&
position1.getDate().equals(position2.getDate()) &&
position1.getCycle().equals(position2.getCycle()) &&
position1.getStatus().equals(position2.getStatus());
//Same approach to test the equality of two lists index by index using above predicate
BiPredicate<List<Position>,List<Position>> listsEqual = (list1, list2) -> {
if (list1.size() != list2.size()){
return false;
}
return IntStream.range(0, list1.size()).allMatch(i -> positionsEqual.test(list1.get(i), list2.get(i)));
};
//using the predicate to filter
List<String> result = mapA.keySet()
.stream()
.filter(key -> listsEqual.test(mapA.get(key),(mapB.get(key))))
.toList();
System.out.println(result);
}
@AllArgsConstructor
@Getter
@ToString
static class Position {
String account;
String Date;
String Cycle;
String Status;
//Constructor, getter, toString..
}
}