使用Comparable比较对象并在TreeMap中对它们进行排序

时间:2012-12-01 10:58:54

标签: java equals treemap comparable

II无法理解在实现Comparable接口时,类的自然排序应该如何“与equals一致”。我在程序中发现了一个缺陷,因此我在界面Comparable的文档中检查了它。我的问题是虽然在equals方法的基础上将两个对象视为不同,但TreeMap结构将它们视为相等,因此不接受第二个插入。示例代码为:

public class Car  implements Comparable<Car> {

 int weight;
 String name;

public Car(int w, String n) {
    weight=w;
    name=n;
}

public boolean equals(Object o){
    if(o instanceof Car){
        Car d = (Car)o;
        return ((d.name.equals(name)) && (d.weight==weight));
    }
    return false;

}

public int hashCode(){
    return weight/2 + 17;
}

public String toString(){
    return "I am " +name+ " !!!";
}


public int compareTo(Car d){
    if(this.weight>d.weight)
        return 1;
    else if(this.weight<d.weight)
        return -1;
    else
        return 0;
}

/*public int compareTo(Car d){
    return this.name.compareTo(d.name);
}*/

}



public static void main(String[] args) {
    Car d1 = new Car(100, "a");
    Car d2 = new Car(110, "b");
    Car d3 = new Car(110, "c");
    Car d4 = new Car(100, "a");

    Map<Car, Integer> m = new HashMap<Car, Integer>();
    m.put(d1, 1);
    m.put(d2, 2);
    m.put(d3, 3);
    m.put(d4, 16);

    for(Map.Entry<Car, Integer> me : m.entrySet())
    System.out.println(me.getKey().toString() + " " +me.getValue());

    TreeMap<Car, Integer> tm = new TreeMap<Car, Integer>(m);
    System.out.println("After Sorting: ");
    for(Map.Entry<Car, Integer> me : tm.entrySet())
        System.out.println(me.getKey().toString() + " " +me.getValue());
}

输出结果为:

I am a !!! 16

I am c !!! 3

I am b !!! 2

After Sorting: 

I am a !!! 16

I am c !!! 2

也就是说,对象c已经(稍微)替换了对象b。 如果我注释原始的equals方法并取消注释第二个equals方法,它根据名称比较对象,则输出是预期的:

I am a !!! 16

I am c !!! 3

I am b !!! 2

After Sorting: 

I am a !!! 16

I am b !!! 2

I am c !!! 3

为什么它以这种方式出现,为了在TreeMap中插入和排序具有相同值的某些属性的不同对象,我应该改变什么?

4 个答案:

答案 0 :(得分:4)

当两个权重相等时,compareTo()需要检查名称:

public int compareTo(Car d){
    if(this.weight>d.weight)
        return 1;
    else if(this.weight<d.weight)
        return -1;
    return this.name.compareTo(d.name);
}

这将使compareTo()equals()一致(后者现在可以根据前者重写)。此外,如果名称不同,地图将允许具有相同权重的多个条目。

答案 1 :(得分:1)

  

也就是说,对象c已经(稍微)替换了对象b。

是的,它会的。它们具有相同的权重,因此TreeMap认为它们是相等的。地图永远不会包含两个“相等”的键(你会如何查找一个值?),因此一个替换另一个。

如果您不希望它们被视为平等,则需要使compareTo方法区分它们(例如,使用name作为辅助排序顺序)。

documentation for TreeMap解释说,如果您的compareTo方法与您的equals方法不一致(不是这样),您将无法获得正常的Map行为:< / p>

  

请注意,如果此有序映射要正确实现Map接口,则树映射维护的顺序(如任何有序映射)以及是否提供显式比较器必须与equals一致。 (有关与equals一致的精确定义,请参阅Comparable或Comparator。)这是因为Map接口是根据equals操作定义的,但是有序映射使用compareTo(或compare)方法执行所有键比较,因此从排序映射的角度来看,通过此方法被视为相等的键是相等的。即使排序与equals不一致,也可以很好地定义有序映射的行为。它只是没有遵守Map接口的一般合同。

答案 2 :(得分:0)

您的compareTo()方法不是consistent with equals()

  

当且仅当c.compare(e1, e2)==0e1.equals(e2)e1 [...]的e2具有相同的布尔值。

请改为尝试:

public int compareTo(Car d){
    if(this.weight>d.weight)
        return 1;
    else if(this.weight<d.weight)
        return -1;
    else
        return this.name.compareTo(d.name);
}

在原始实现中,当比较器具有相同的weight但不同的name时,它们在比较器方面被认为是相等的,而它们在equals()方面则不同。

答案 3 :(得分:0)

Comparable接口doc说“当且仅当e1.compareTo(e2)== 0具有与e1.equals(e2)相同的布尔值时,C类的自然排序被认为与equals一致。 C类的e1和e2“。但是你的compareTo()没有那样做,因为它没有检查name字段的相等性。如果您对compareTo()进行检查,则可以正常工作。