0

I have a simple class that contains a string (name) and an integer (age). The objects, that shall be stored in the collection, must not have double name values and shall be sorted according to descending age. The first code example removes all double names, but doesn't contain a second ordering criterion:

public int compare(Person p1, Person p2) {  
    int reVal = 1;

       if(p1.getName().compareTo(p2.getName()) != 0){
       reVal = 1;       
       }
       else {
       reVal = 0;       
       }                               
         return reVal;                  
    } 

The next example comparator shall order the rest set of the objects, that doesn't contain any double names:

public int compare(Person p1, Person p2) {  
    boolean ageGt = (p1.getAge() > p2.getAge());
    int reVal = 1;

       if(p1.getName().compareTo(p2.getName()) != 0){
       if(scoreGt)
            reVal = -1;
       else 
            reVal = 1;      
       }
       else {
       reVal = 0;       
       }                               
         return reVal;                  
    } 

The second comparator orders the objects according their age values correctly, but it allows double names, which I don't understand, because the outer if-statement already checked if the names of both objects are equal. Why does that happen?

user1812379
  • 827
  • 3
  • 11
  • 22
  • What do you mean with double name ? – Konstantin Yovkov Jun 06 '13 at 09:58
  • @kocko I think he means he can't have two objects with the same name, so if they do have the same name they are considered equal. – devrobf Jun 06 '13 at 09:59
  • 4
    A `Comparator` is _not_ meant to test for unicity. If you want to test for unicity of names, you should use a `Map` with names as keys and persons as values. – fge Jun 06 '13 at 09:59
  • `compare` is used for, well, comparing objects, according to some sorting. There is no filtering involved, so a sorted (ordered) collection has the same amount of objects as the unsorted one. If you need uniqueness, in your case for the name, try putting the objects into a set with a dedicated .hashCode() method. – Dominik Sandjaja Jun 06 '13 at 10:03
  • I have used a set and the first piece of code works as expected and doesn't allow to add a second element that has the name of an element that is already contained. However, the 2nd piece of code doesn't work. I think I forgot to implement the hashCode() method. – user1812379 Jun 06 '13 at 10:20
  • That is normal. See my answer. Implementing `.hashCode()` will not help either. You'll need to create a custom "repository" class, which you can make implement `Iterable` for instance. – fge Jun 06 '13 at 10:25

1 Answers1

3

You have a fundamental problem here: you want at the same time to test for unicity and to order entries. There is no builtin collection which will check at the same time that entries are equal and that their comparison is 0.

For instance, two Set implementations are HashSet and TreeSet:

  • HashSet uses Object's .equals()/.hashCode() to test for equality;
  • TreeSet uses a Comparator (or the objects' Comparable capability if they implement it) to test for equality.

This is not quite the same thing. In fact, with one particular JDK class, that is, BigDecimal, this can get quite surprising:

final BigDecimal one = new BigDecimal("1");
final BigDecimal oneDotZero = new BigDecimal("1.0");

final Set<BigDecimal> hashSet = new HashSet<>();
// BigDecimal implements Comparable of itself, so we can use that
final Set<BigDecimal> treeSet = new TreeSet<>();

hashSet.add(one);
hashSet.add(oneDotZero);
// hashSet's size is 2: one.equals(oneDotZero) == false

treeSet.add(one);
treeSet.add(oneDotZero);
// treeSet's size is... 1! one.compareTo(oneDotZero) == 0

You cannot both have your cake and eat it. Here, you want to test unicity according to the name and comparison according to the age, you must use a Map.

As to obtain a sorted list of persons, you will have to do a copy of this map's .values() as a list and use Collections.sort(). If you use Guava, this latter part is as simple as Ordering.natural().sortedCopy(theMap.values()), provided your values implement Comparable.

fge
  • 119,121
  • 33
  • 254
  • 329
  • Thanks. Could it also be a good choice to overwrite the `equals()` method of the object's class? In this case I would only have to call `mySet.contains(o1)` before I add the object o1 to the collection. Afterwards, uniqueness is guaranteed according to the equals-contract. – user1812379 Jun 06 '13 at 12:43
  • If you override `.equals()` you must also override `.hashCode()`! And yes, if you use these values in a `Set` which depends on equality, you must do that anyway. – fge Jun 06 '13 at 12:46
  • But `hashCode()` is only necessary for Hash-based collections, isn't it? Others just ignore it. In this case, could `name.hashCode()` be a simple but viable hash code? Thank you. – user1812379 Jun 06 '13 at 14:10
  • `hashCode()` is necessary when you override `equals()`; this is the basic contract for `Object`. Don't break it! – fge Jun 06 '13 at 14:19