2

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

4 回答 4

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()(后者现在可以根据前者重写)保持一致。此外,只要名称不同,该地图将允许多个具有相同权重的条目。

于 2012-12-01T11:05:26.957 回答
1

也就是说,对象 c 已经(某种程度上)替代了对象 b。

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

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

文档TreeMap解释说,如果您的compareTo方法与您的方法不一致equals(事实并非如此),您将不会获得正常Map行为:

请注意,树形映射维护的顺序与任何排序映射一样,以及是否提供显式比较器,如果此排序映射要正确实现 Map 接口,则必须与 equals 保持一致。(参见 Comparable 或 Comparator 以了解与 equals 一致的精确定义。)这是因为 Map 接口是根据 equals 操作定义的,但排序后的映射使用其 compareTo(或比较)方法执行所有键比较,所以两个从排序映射的角度来看,此方法认为相等的键是相等的。已排序映射的行为是明确定义的,即使它的排序与 equals 不一致;它只是不遵守 Map 接口的一般合同。

于 2012-12-01T11:04:00.333 回答
1

您的compareTo()方法与以下内容不一致equals()

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

试试这个:

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().

于 2012-12-01T11:05:15.040 回答
0

Comparable 接口文档说“当且仅当 e1.compareTo(e2) == 0 对于每个 e1 和 e2 具有与 e1.equals(e2) 相同的布尔值时,类 C 的自然排序被称为与 equals 一致C级。”。但是您的 compareTo() 并没有这样做,因为它不检查名称字段的相等性。如果你在 compareTo() 中检查它,它就可以工作。

于 2012-12-01T11:15:51.277 回答