Assume you have some objects which have several fields they can be compared by:
public class Person {
private String firstName;
private String lastN
It is easy to compare two objects with hashcode method in java`
public class Sample{
String a=null;
String b=null;
public Sample(){
a="s";
b="a";
}
public Sample(String a,String b){
this.a=a;
this.b=b;
}
public static void main(String args[]){
Sample f=new Sample("b","12");
Sample s=new Sample("b","12");
//will return true
System.out.println((s.a.hashCode()+s.b.hashCode())==(f.a.hashCode()+f.b.hashCode()));
//will return false
Sample f=new Sample("b","12");
Sample s=new Sample("b","13");
System.out.println((s.a.hashCode()+s.b.hashCode())==(f.a.hashCode()+f.b.hashCode()));
}
You should implement Comparable <Person>
. Assuming all fields will not be null (for simplicity sake), that age is an int, and compare ranking is first, last, age, the compareTo
method is quite simple:
public int compareTo(Person other) {
int i = firstName.compareTo(other.firstName);
if (i != 0) return i;
i = lastName.compareTo(other.lastName);
if (i != 0) return i;
return Integer.compare(age, other.age);
}
I think it'd be more confusing if your comparison algorithm were "clever". I'd go with the numerous comparison methods you suggested.
The only exception for me would be equality. For unit testing, it's been useful to me to override the .Equals (in .net) in order to determine if several fields are equal between two objects (and not that the references are equal).
Writing a Comparator
manually for such an use case is a terrible solution IMO. Such ad hoc approaches have many drawbacks:
So what's the solution?
First some theory.
Let us denote the proposition "type A
supports comparison" by Ord A
. (From program perspective, you can think of Ord A
as an object containing logic for comparing two A
s. Yes, just like Comparator
.)
Now, if Ord A
and Ord B
, then their composite (A, B)
should also support comparison. i.e. Ord (A, B)
. If Ord A
, Ord B
, and Ord C
, then Ord (A, B, C)
.
We can extend this argument to arbitrary arity, and say:
Ord A, Ord B, Ord C, ..., Ord Z
⇒ Ord (A, B, C, .., Z)
Let's call this statement 1.
The comparison of the composites will work just as you described in your question: the first comparison will be tried first, then the next one, then the next, and so on.
That's the first part of our solution. Now the second part.
If you know that Ord A
, and know how to transform B
to A
(call that transformation function f
), then you can also have Ord B
. How? Well, when the two B
instances are to be compared, you first transform them to A
using f
and then apply Ord A
.
Here, we are mapping the transformation B → A
to Ord A → Ord B
. This is known as contravariant mapping (or comap
for short).
Ord A, (B → A)
⇒comap Ord B
Let's call this statement 2.
Now let's apply this to your example.
You have a data type named Person
that comprises three fields of type String
.
We know that Ord String
. By statement 1, Ord (String, String, String)
.
We can easily write a function from Person
to (String, String, String)
. (Just return the three fields.) Since we know Ord (String, String, String)
and Person → (String, String, String)
, by statement 2, we can use comap
to get Ord Person
.
QED.
How do I implement all these concepts?
The good news is you don't have to. There already exists a library which implements all the ideas described in this post. (If you are curious how these are implemented, you can look under the hood.)
This is how the code will look with it:
Ord<Person> personOrd =
p3Ord(stringOrd, stringOrd, stringOrd).comap(
new F<Person, P3<String, String, String>>() {
public P3<String, String, String> f(Person x) {
return p(x.getFirstName(), x.getLastname(), x.getAge());
}
}
);
Explanation:
Ord<String>
. This corresponds to our original "supports comparison" proposition.Ord<A>
, Ord<B>
, Ord<C>
, and returns Ord<P3<A, B, C>>
. This corresponds to statement 1. (P3 stands for product with three elements. Product is an algebraic term for composites.)comap
.A → B
.Hope that helps.