【问题标题】:Java TreeSet: remove and contains() not workingJava TreeSet:删除和包含()不起作用
【发布时间】:2013-06-02 22:34:39
【问题描述】:

我已经向 TreeSet 添加了一些简单的对象,但是当我调用 TreeSet 的 remove() 和 contains() 方法时,它们不起作用。但是,当我遍历集合时,会打印对象。员工对象应添加到集合中,而对象的唯一性基于对象名称属性。 Id 属性是应该排序的值,但它不是唯一的。

public class Employee {
    private String name;
    private int id;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

 // Two objects are considered equal if their names are equal
    @Override
    public boolean equals(Object o) {
    if (o == null)
        return false;
    if (this == o)
        return true; 
    if (o.getClass() == this.getClass()) {
        Employee p = ( Employee) o;
        if (p.getName() != null && this.getName() != null)
        return this.getName().equals(p.getName());
        else
        return false;
    } else {
        return false;
    }
    }
} 

//*******************************************************

public class EmployeeComp implements Comparator<Employee> {

    // Sort Ids, but allow duplicates, hence this function is never returning 0
    @Override
    public int compare(Employee p1, Employee p2) {
    int re = 0;

    boolean scoreLt = (p1.getId() > p2.getId());
    boolean scoreGt = (p1.getId() < p2.getId());

    if(scoreLt)
        re = -1;
    if(scoreGt)
        re = 1;
    else 
        re = -1;                       
         return re;                 
    }    
}
//*******************************************************
// Collection shall store unique names with ordered values like:
// Alex, 923
// Toni, 728
// Eddi, 232
// Peter, 232
// Eddi, 156  *** not allowed
import java.util.TreeSet;


public class Main {
    private static EmployeeComp comp = new EmployeeComp(); 
    private static TreeSet<Employee> employees = new TreeSet<Employee>(comp); 

    public static void main(String[] args) {

    Employee p1 = new Employee();
    p1.setName("Eddi");
    p1.setId(232);

    Employee p2 = new Employee();
    p2.setName("Toni");
    p2.setId(728);

    Employee p3 = new Employee();
    p3.setName("Peter");
    p3.setId(232);

    Employee p4 = new Employee();
    p4.setName("Alex");
    p4.setId(923);

    employees.add(p1);
    employees.add(p2);
    employees.add(p3);
    employees.add(p4);

    // Here, contains() and remove() should check the object address
    // and not perform their actions based on compareTo

       } 
}

【问题讨论】:

  • 不,我先做了,后来注释掉了,所以这不是原因。
  • @fge 当然,你说得对,我忽略了这一点。
  • 好的,看看解决方案,还有一件事你必须记住:你不能单独使用 .equals()/.hashCode()Comparator 做你想做的事——你不能同时拥有你的蛋糕吃吧。我的BigDecimal 例子应该已经给你提示了!
  • 注意我的解决方案:还有一个使用 Guava 的Equivalence,我个人更喜欢它;并且由于会使用番石榴,这意味着您将获得Ordering 作为奖励

标签: java collections set


【解决方案1】:

一个TreeSet插入/删除根据Comparable的结果,而不是.equals()/.hashCode()

顺便说一句,这意味着您的Set 的对象确实实现了Comparable(如果它们没有实现,那么每次您尝试插入一个成员时,您都会收到@987654329 @)。

更准确地说,TreeSetSortedSet 的实现。

如果您想要一个.equals()/.hashCode() 兼容集,例如,使用HashSet

为了说明,下面是 BigDecimal 发生的情况(几小时前发布的 here):

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

引用Comparable的javadoc,表示BigDecimal.compareTo()“与.equals()不一致”。

** 编辑 ** 至于 OP 想要什么:

  • Collection 不接受重名;
  • Collection 的排序视图,将根据用户 ID 进行排序。

如上所述,你不能有一个集合同时兼顾。解决办法:

  • 第一个,HashSet
  • 第二个,将该集合的副本放入ArrayList,然后使用Collections.sort()

这意味着.equals().hashCode() 必须只作用于名称,而自定义Comparator 将作用于id。 Comparator 没有其他选择,只能自定义,因为它是一个比较器,无论如何都与 .equals() 不一致。

关于提议的代码,有问题。

首先:Employee 覆盖 .equals() 但不是 .hashCode()。因此,Employee 违反了.equals() 合约(其中一部分是如果两个对象相等,则它们必须具有相同的哈希码)。更重要的是,.hashCode() 对于HashSet 的工作至关重要。修复:

@Override
public int hashCode()
{
    return name == null ? 0 : name.hashCode();
}

@Override
public boolean equals(final Object obj)
{
    if (obj == null)
        return false;
    if (this == obj)
        return false;
    if (!(obj instanceof Employee))
        return false;
    final Employee other = (Employee) obj;
    return name == null ? other.name == null
        : name.equals(other.name);
}

第二:比较器与Employee 一样被破坏,因为它破坏了Comparator 合约(对于任何o1o2o1.compareTo(o2) == - o2.compareTo(o1))。修复:

public final class EmployeeComp
    implements Comparator<Employee>
{
    @Override
    public int compare(final Employee o1, final Employee o2)
    {
        final int id1 = o1.getId(), id2 = o2.getId();
        if (id1 == id2)
            return 0;
        return id1 > id2 ? 1 : -1;
    }
}

那么,如何获取集合的排序副本:

// "set" contains the unique employees
final List<Employee> sorted = new ArrayList<Employee>(set);
Collections.sort(list, new EmployeeComp());

完成。

【讨论】:

  • +1,很好的发现。默认答案仅适用于 95% 的问题;)
  • @fge 然后它不起作用,因为我没有在 compareTo() 中实现相等,因为我想允许重复。对于像这样的对象顺序
  • @user1812379 .compareTo() 应该实现全排序,而不是相等
  • @fge 我没有实现compareTo() == 0 的情况来允许数字重复,因为我想对包含唯一名称的名称-值-对-对象进行排序,而非唯一值只能排序为 (user4, 1), (user3, 2), (user7, 2), (user2, 5) 等。在这种情况下,如果用户名相等,则两个对象相等,但整个集合应根据到非唯一值。看来 Set 确实是错误的选择。也许我应该选择一个TreeMap?感谢您对这个话题有所了解。
  • @CaioFaustino 当且仅当如答案所述,Comparable.equals() 一致; BigDecimal 并非如此。
【解决方案2】:

您的问题是概念性的。

如果你想要一个唯一对象的排序集合:TreeSet
如果你想要一个排序的集合,不同的对象可以有相同的比较值用于排序:PriorityQueue

顺便说一下,PriorityList 中的方法比 TreeSet 中的方法更适合第二种情况的通常需要。我曾经认为它是 TreeSet 的缺点。例如,从集合中取出第一项。

希望有所帮助:-)

【讨论】:

    猜你喜欢
    • 2015-06-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-03
    • 1970-01-01
    • 2013-10-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多