Equality
@djspiewak Ah. To me, universal equality is a failed experiment. Equality is always a contextual construct — the means as how to compare.
— Viktor Klang (@viktorklang) September 12, 2013
For those of us who are implementation minded, that means that boolean equals(Object other)
is a flawed API because there is no one definition of equality that will work in every context. In a previous post on Using Java Collections Effectively by Implementing equals() and hashCode(), I quoted Josh Bloch and said that "The behavior of equals(), hashCode(), and compareTo() must be consistent." Java subtly encourages us to define a single definition of equality and comparison per class.
@runarorama Your mission, should you choose to accept it, is to ensure symmetry by only controlling one side of the relationship :)
— Hugues Chabot (@hugueschabot) March 14, 2014
Java programmers have been "ensuring symmetry by controlling only one side of the equation" for years. There's nothing wrong with defining a *default* context for comparison and equality. Just don't mistake default equality and compareTo() as the only context for comparing objects.
A small potato and a slice of bread might have an equal number of calories (they are equivalent in one context), but the potato takes much more energy to heat up (they are different in another context). So Ideally, we'd like Heating equality and Caloric equality to be different for small potatoes vs. bread.
A context-relative definition of equality might look like this
boolean equals(Object left, Object right)
Hmm... equality and sorting have a good deal of overlap. The Comparator interface already defines something like equality - it returns 0 when things are sorted the same ("equally"), something else otherwise. Better still, any implementation of Comparator provides a context for that comparison.
interface Sugars {
int gramsSugar();
public static final Comparator<Sugars> COMPARATOR = (left, right) ->
left.gramsSugar() - right.gramsSugar();
}
interface Heating {
int cookingEnergy();
public static final Comparator<Heating> COMPARATOR = (left, right) ->
left.cookingEnergy() - right.cookingEnergy();
}
Could we use this for context-relative equality?
The ubiquitous ArrayList, Set, and Map use equals(), hashCode(), and compareTo() are all fixed in their one-sided ways. But the classes implementing SortedSet and SortedMap behave very differently from the other collections when passed a separate Comparator.
Using Context-Relative Equality (in Java)
When you pass a Comparator to TreeSet or TreeMap, you are using context-relative equality. All comparisons for get(), put(), and contains() are made with the Comparator NOT with the methods that the other standard collections use! With a little care you can use this to your advantage:
static class Food implements Heating, Sugars {
private final int gramsSugar;
private final int cookingEnergy;
private Food(int g, int c) { gramsSugar = g; cookingEnergy = c; }
@Override public int gramsSugar() { return gramsSugar; }
@Override public int cookingEnergy() { return cookingEnergy; }
@Override public String toString() {
return "Food(" + gramsSugar + "," + cookingEnergy + ")";
}
}
public static void main(String[] args) {
List<Food> foods = Arrays.asList(new Food(5,3), new Food(4,4));
SortedSet<Food> foodsByHeat = new TreeSet<>(Heating.COMPARATOR);
foodsByHeat.addAll(foods);
System.out.println("Foods by heat:");
for (Food f : foodsByHeat) { System.out.println(f); }
SortedSet<Food> foodsBySugar = new TreeSet<>(Sugars.COMPARATOR);
foodsBySugar.addAll(foods);
System.out.println("Foods by sugar:");
for (Food f : foodsBySugar) { System.out.println(f); }
}
Output
Foods by heat:
Food(5,3)
Food(4,4)
Foods by sugar:
Food(4,4)
Food(5,3)
More
Here is the Source for the above.
Here is my earlier SetInterface test.
No comments:
Post a Comment