Comparing 2 Objects and Retrieve a List of Fields with Different Values

Compare two lists of different objects by certain two fields

It could make sense to merge id / title into a single String, remap the input lists into List<String> and then use AssertJ hasSameElements to compare the new lists:

assertThat(
aObjectList.stream()
.map(a -> String.join(":", a.aId, a.aTitle))
.collect(Collectors.toList())
).hasSameElementsAs(
bObjectList.stream()
.map(b -> String.join(":", b.bId, b.bTitle))
.collect(Collectors.toList())

);

Compare two objects excluding some fields - Java

The quickest way without writing any code is Lombok

Lombok is one of the most used libraries in java and it takes a lot of Boilerplate code off your projects. If you need to read more on what it can and does, go here.

The way to implement what you need is pretty straightforward:

// Generate the equals and HashCode functions and Include only the fields that I annotate with Include
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Getter // Generate getters for each field
@Setter // Generate setters for each field
public class Class1
{

@EqualsAndHashCode.Include // Include this field
private Long identity;

private String testStr1; // This field is not annotated with Include so it will not be included in the functions.

// ... any other fields
}

Lombok can do a lot more than this. For more information on @EqualsAndHashCode refer to this.

You can always use @EqualsAndHashCode.Exclude for a quicker solution to your use case:

@EqualsAndHashCode
@Getter // Generate getters for each field
@Setter // Generate setters for each field
public final class Class1 {
private String a;
private String b;
private String c;
:
:
:

private String z;

@EqualsAndHashCode.Exclude
private Date createdAt;
@EqualsAndHashCode.Exclude
private Date updatedAt;
}

How to compare a field between two Lists of objects?

One solution :

  1. Construct a Map of the Local supermarkets using the supermarketId as the key by running through the list once
  2. Loop through the cloud list and do you comparison, looking up the local supermarket from your map.

i.e. O(n) instead of O(n2)

Here's a two-line solution:

Map<String, Supermarket> map = getFromLocal().stream()
.collect(Collectors.toMap(s -> s.supermarketId, s -> s));
List<Supermarket> hasDiffLastItem = getFromCloud().stream()
.filter(s -> !map.get(s.supermarketId).lastItemBoughtId.equals(s.lastItemBoughtId))
.collect(Collectors.toList());

Comparing two objects for varying set of properties

anyMatch

You can solve this with the Stream API, using anyMatch, with your rules basically being defined as Predicates.

For example checking if there is any person who is 20 years old:

List<Person> persons = ...

boolean doesAnyMatch = persons.stream()
.anyMatch(p -> p.getAge() == 20);

You can of course also setup the rule in a way that it compares with an existing item, mimicking equals a bit more:

p -> p.getAge() == otherPerson.getAge()


Predicate

You can setup all your rules somewhere else, as Predicates and then use them. For example:

List<Predicate<Person>> rules = List.of(
p -> p.getAge() == 20,
p -> p.getName().equals("John"),
p -> p.getAge() > 18,
p -> p.getName().length() > 10 && p.getAge() < 50
);

And then maybe use them in some sort of loop, whatever you need:

for (Predicate rule : rules) {
boolean doesAnyMatch = persons.stream()
.anyMatch(rule);
...
}


findAny

You can substitute anyMatch by a combination of filter and findAny to receive an actual match, i.e. a Person, instead of just a boolean:

Person matchingPerson = persons.stream()
.filter(rule)
.findAny();

How to compare objects by multiple fields

You can implement a Comparator which compares two Person objects, and you can examine as many of the fields as you like. You can put in a variable in your comparator that tells it which field to compare to, although it would probably be simpler to just write multiple comparators.

comparing multiple fields of n objects for common values

Here is a way to do it using Java 8 streams:

public class Test8 {
public static void main(String[] args) {
MyObj[] testData = {
new MyObj(1, "X", 3.14, "Larry"),
new MyObj(2, "X", 3.14, "Curly"),
new MyObj(3, "X", 3.14, "Moe"),
};
System.out.println("A = " + findCommon(testData, MyObj::getA).orElse(null));
System.out.println("B = " + findCommon(testData, MyObj::getB).orElse(null));
System.out.println("C = " + findCommon(testData, MyObj::getC).orElse(null));
System.out.println("D = " + findCommon(testData, MyObj::getD).orElse(null));
}
private static <T, R> Optional<R> findCommon(T[] data, Function<? super T, ? extends R> getterMethod) {
Set<R> values = Arrays.stream(data)
.map(getterMethod)
.collect(Collectors.toSet());
return (values.size() == 1
? Optional.ofNullable(values.iterator().next())
: Optional.empty());
}
}
class MyObj {
private final int a;
private final String b;
private final double c;
private final String d;
MyObj(int a, String b, double c, String d) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
}
public int getA() {
return this.a;
}
public String getB() {
return this.b;
}
public double getC() {
return this.c;
}
public String getD() {
return this.d;
}
}

Output

A = null
B = X
C = 3.14
D = null

UPDATE

Using Set is an easy solution, but it always has to scan all objects.

If you have many objects, it may be good for performance to stop scanning as soon as you find differing values:

private static <T, R> Optional<R> findCommon(T[] data, Function<? super T, ? extends R> getterMethod) {
R onlyValue = null;
for (T obj : data) {
R value = getterMethod.apply(obj);
if (value == null)
return Optional.empty();
if (onlyValue == null)
onlyValue = value;
else if (! value.equals(onlyValue))
return Optional.empty();
}
return Optional.ofNullable(onlyValue);
}


Related Topics



Leave a reply



Submit