Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8190545

(coll spec) SortedSet/Map should clarify use of comparison methods instead of equals()

XMLWordPrintable

      FULL PRODUCT VERSION :
      java version "1.8.0_131"
      Java(TM) SE Runtime Environment (build 1.8.0_131-b11)
      Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)

      ADDITIONAL OS VERSION INFORMATION :
      Microsoft Windows [Version 10.0.15063]

      A DESCRIPTION OF THE PROBLEM :
      It seems that when custom Comparator is provided for TreeSet then this comparator is used to verify equality of the objects in the TreeSet when calling .contains() method. However the documentation is stating that:

      "TreeSet(Comparator<? super E> comparator)
                      Constructs a new, empty tree set, sorted according to the specified comparator."

      and

      "public boolean contains(Object o)
                      Returns true if this set contains the specified element. More formally, returns true if and only if this set
                      contains an element e such that (o==null ? e==null : o.equals(e))."

      which suggests that Comparator should be used only for the sorting and not for equality checks.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Please see the code sample

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Please see the code sample
      ACTUAL -
      Please see the code sample

      REPRODUCIBILITY :
      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------
      public class Bid {
          private final Integer userId;
          private final Double price;

          Bid(final Integer userId, final Double price){
              this.userId = userId;
              this.price = price;
          }

          Integer getUserId(){
              return userId;
          }

          Double getPrice(){
              return price;
          }

          public boolean equals(Object obj) {
              if (obj == null) {
                  return false;
              }
              if (!Bid.class.isAssignableFrom(obj.getClass())) {
                  return false;
              }
              final Bid other = (Bid) obj;
              return this.userId.equals(other.userId) && this.price.equals(other.price);
          }
      }




      TreeSet<Bid> bidsList = new TreeSet<>(Comparator.comparingDouble(Bid::getPrice));
      bidsList.add(new Bid(1, 1.0));
      bidsList.contains(new Bid(2, 1.0)); // this wrongly returns true and while debugging the code I can see that Bid.equals() is never invoked
      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      The workaround is to include all the values from the class, that is:

      TreeSet<Bid> bidsList = new TreeSet<>(Comparator.comparingDouble(Bid::getPrice).thenComparingInt(Bid::getUserId));
      bidsList.add(new Bid(1, 1.0));
      bidsList.contains(new Bid(2, 1.0)); // this returns false as expected

            smarks Stuart Marks
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            5 Start watching this issue

              Created:
              Updated: