How to Compare Objects by Multiple Fields

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.

Java 8 - Compare multiple fields in different order using Comparator

Use Comparator.reverseOrder():

.thenComparing(Person::getAge, Comparator.reverseOrder())

If you want to avoid autoboxing, you can do

.thenComparing((p1, p2) -> Integer.compare(p2.getAge(), p1.getAge()))

Or

.thenComparing(Comparator.comparingInt(Person::getAge).reversed())

Comparing objects based on the values of multiple fields using Comparator

1) return 1000000 + getWeight(u1.getUsername()); and return -1000000 + getWeight(u2.getUsername()); are not required. return 1 and return -1 is clearer and produces the same result if you refer to the CompareTo() javadoc :

Compares this object with the specified object for order. Returns a
negative integer, zero, or a positive integer as this object is less
than, equal to, or greater than the specified object

2) You don't chain the field comparisons but you have 3 ways of sorting according to the state of the compared objects. So the fact that the code be a bit verbose to define each case is finally normal.

You could all the same reduce it with an extract method as you duplicate a lot of user.getLastName().isEmpty() invocations.

For example :

public static Comparator<UserConfigurationDto> BY_LASTNAME =  (u1, u2) -> {

// first case
if( u1.isLastAndFirstNameEmpty() && u2.isLastAndFirstNameEmpty()){
return u1.getUsername().compareToIgnoreCase(u2.getUsername());
}
// second case
if(u1.isLastAndFirstNameEmpty()){
return 1;
}
else if(u2.isLastAndFirstNameEmpty()){
return -1;
}
// third case
String s1 = u1.getLastName().isEmpty() ? u1.getFirstName() : u1.getLastName();
String s2 = u2.getLastName().isEmpty() ? u2.getFirstName() : u2.getLastName();
return s1.compareToIgnoreCase(s2);
};

Compare by multiple methods in java compareTo?

You can create a Comparator for your Student class this way:

Comparator<Student> comparator = Comparator
.comparing(Student::getLastName)
.thenComparing(Student::getFirstName)
.thenComparing(Student::getSection);

And then use this comparator (instead of implementing Comparable interface) to sort a list with Student objects, or to create a TreeMap with these objects:

Collections.sort(listOfStudents, comparator);
TreeMap<Student> mapOfStudents = new TreeMap<>(comparator);

Java Comparator for Objects with multiple fields

Your method might be correct, but it is inefficient (unnecessarily calls equals) and difficult to read. It could be rewritten something like this:

public int compare(Collection c1, Collection c2)
{
int n;
n = c1.id.compareTo(c2.id);
if (n != 0) return n;
n = c1.entityType.compareTo(c2.entityType);
if (n != 0) return n;
n = c1.brandId.compareTo(c2.brandId);
if (n != 0) return n;
return c1.productId.compareTo(c2.productId);
}

Even better is to use a library method which abstracts all this logic away so you don't have to think about it. E.g. using apache.commons.lang CompareToBuilder

public int compare(Collection c1, Collection c2)
{
return new CompareToBuilder()
.append(c1.id, c2.id)
.append(c1.entityType, c2.entityType)
.append(c1.brandId, c2.brandId)
.append(c1.productId, c2.productId)
.toComparison();
}

How to use Comparator with a multiple field object?

It seems your question is in regards to simplifying your comparison, but I think you'd rather implement Comparable<Passenger> rather than Comparator, and use the #compareTo method. As for cleanup, that's a bit easy too if you simply abstract the actual boolean logic:

public int compareTo(Passenger other) {
if (this.isDisability() ^ other.isDisability()) { //use an XOR
return this.isDisability() ? 1 : -1; //1 for us, -1 for other
}
//compare #getClase
int clase = -Integer.compare(this.getClase(), other.getClase()); //invert
if (clase == 0) {
//compare arrival times if clase is equal
//normalize to -1, 1 (0 excluded in OP)
return this.getArrivalTime() < other.getArrivalTime() ? 1 : -1;
}
return clase > 0 ? 1 : -1; //normalize to -1, 0, 1
}

This allows you to define a natural ordering to a Passenger, and is encapsulated/internal to your class implementation (doesn't require as much exposure).

This also makes operations like sorting much easier:

List<Passenger> passengers = /* some list */;
Collections.sort(passengers);

If you want to provide a Comparator which can accomplish alternative sorts, you can also do that inside your class:

public class Passenger {

//...

public static class ArrivalComparator implements Comparator<Passenger> {

public int compare(Passenger one, Passenger two) {
return Integer.compare(one.getArrivalTime(), two.getArrivalTime());
}
}

//...

}

Using our previous example, this would let you sort all the passengers based on arrival time:

Collections.sort(passengers, new Passenger.ArrivalComparator());

Additionally, this can just be inlined using Java 8:

//Sort by arrival time
Collections.sort(passengers, (one, two) -> Integer.compare(one.getArrivalTime(), two.getArrivalTime());

But overall, keep in mind a Comparator is mostly for defining a specific sort, while Comparable defines a general/natural ordering.

How does Java decide to sort a list of objects with multiple fields, when the chosen field to compare the object is equal?

Quoting from the Java API for Stream.sorted():

For ordered streams, the sort is stable. For unordered streams, no stability guarantees are made.

A stream over a List is ordered, which means a stable sort algorithm is used. A stable sort guarantees that equal elements will not be swapped. Elements that compare equal are left in the same relative order as in the starting list.

All of the sorting methods in the standard library have similar guarantees:

  • Collections.sort()

  • Arrays.sort()

  • Arrays.parallelSort()

    This sort is guaranteed to be stable: equal elements will not be reordered as a result of the sort.

    ...

    The documentation for the methods contained in this class includes briefs description of the implementations. Such descriptions should be regarded as implementation notes, rather than parts of the specification. Implementors should feel free to substitute other algorithms, so long as the specification itself is adhered to. (For example, the algorithm used by sort(Object[]) does not have to be a MergeSort, but it does have to be stable.)

  • List.sort()

    Implementation Note:

    This implementation is a stable, adaptive, iterative mergesort...

Best way to compare multiple fields of objects in java?

Here is a method to do it with Reflection:

public class Test {
public static void main(String[] args) {
Person person1 = new Person();
person1.name = "Jack";
person1.age = 18;
person1.favoriteSuperhero = "Superman";

Person person2 = new Person();
person2.name = "Jack";
person2.age = 20;
person2.favoriteSuperhero = "Superman";

Map<String, List<Object>> uncommonTraits = getUncommonTraits(person1, person2);
for(Entry<String, List<Object>> entry : uncommonTraits.entrySet()){
System.out.println(entry.getKey() + ":\t" + entry.getValue());
}
}

private static Map<String, List<Object>> getUncommonTraits(Person p1, Person p2) {
Map<String, List<Object>> result = new HashMap<>();
for(Field field : Person.class.getDeclaredFields()){
try {
if(!(field.get(p1).equals(field.get(p2)))){
result.put(field.getName(), new ArrayList<Object>(Arrays.asList(field.get(p1), field.get(p2))));
}
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
}

return result;
}
}

class Person {
public String name;
public int age;
public String favoriteSuperhero;
}

Output:

age: [18, 20]

This is very resistant to changes: if you add a field to your Person class, you won't have to change anything. It is also a bazillion times less redundant than manually checking each field.

I have used public fields for ease of use.

Comparing multiple fields of 2 list of different objects

You can use this:

boolean isMatchs = list1.stream()
.allMatch(el1 -> list2.stream().anyMatch(el2 -> equals(el1, el2)));

with static method equals like this:

static boolean equals(Object1 a, Object2 b) {
return Objects.equals(a.getField1(), b.getField1());// && ...
}


Related Topics



Leave a reply



Submit