TreeSet自定义比较器不能使用相等的对象

时间:2018-05-13 10:54:00

标签: java comparator treeset

我遇到了TreeSet比较器实现的问题。我有一个简单的游戏,动物在棋盘上行走,每转一圈他们做一次动作,如果他们因某种原因死亡,他们会被标记为"死亡",放入" 34; DeadOrganisms"列表,后来从treeset" queue"中删除在这段代码中(我不能立即删除它们,因为我在迭代树集上):

for(Organism org : DeadOrganisms){
    queue.remove(org);
}

问题是他们中的一些人甚至没有被删除,即使他们在每次回合结束时他们被重新放回DeadOrganisms列表,因为被标记为"死" 。确保每次关闭死亡生物体时都会调用.remove我很确定问题在于比较器类:

class MyComparator implements Comparator<Organism> {
    @Override
    public int compare(Organism o1, Organism o2) {
        if (o1.getName().equals(o2.getName())) {
            return 0;
        }
        if (o1.getInitiative() > o2.getInitiative()) {
            return -1;
        } else if (o1.getInitiative() == o2.getInitiative()) {
            if (o1.getAge() > o2.getAge()) {
                return -1;
            } else {
                return 1;
            }
        } else {
            return 1;
        }
    }

}

比较器应检查o1的名称(板上每个字符的唯一名称)是否等于o2的名称,其余代码用于按字符的主动性或年龄排序树集(如果主动性是平等的。 所有角色派生自的生物抽象类的代码片段:

public abstract class Organism {
    protected int lastxpos;
    protected int lastypos;
    private final World myworld;
    private int strength;
    private int initiative;
    private int xPos;
    private int yPos;
    private int age;
    private String name;
    Color color;
    private boolean isdead;
    public Organism(World world, String name){
        this.name = name;
        this.color = Color.RED;
        this.strength = 0;
        this.initiative = 0;
        this.xPos = 0;
        this.yPos = 0;
        this.age = 0;
        this.isdead = false;
        this.myworld = world;
    }

我知道我做错了或误解了TreeSets的工作方式(或两者兼而有之),但我无法理解。我也知道.remove

  

删除一个元素e,使得(o == null?e == null:o.equals(e))

所以我理解

的作用
if (o1.getName().equals(o2.getName())) {
    return 0;
}

在我的Comparator课程中,但也许我误解了一些东西。   我非常感谢任何帮助。

@EDIT 我不知道它是否重要,但到目前为止我在一种动物身上进行测试,年龄和起始时间相同,因此所有动物的唯一区别就是它们的名字。

@ EDIT2 我还注意到,如果要移除的有机体首先出现在树状排列中,排队等等#34;然后,在比较方法中,在调用queue.remove(org)之后,&#34; org&#34;永远不会比较树集中的第一个对象(又名自己)只对第二个,第三个等进行比较。

@ EDIT3 对于评论中的用户NPE: 队列声明:

public class World extends JPanel{
    *_declarations of some variables_*
    private final TreeSet<Organism> queue;

队列初始化:

public World(int sizeX, int sizeY) {
        this.queue = new TreeSet<>(new MyComparator());
        *_ommitting rest of constructor code_*
    }

DeadOrganisms宣言和初始化:

    public void EndTurn(){
        List<Organism> DeadOrganisms = new ArrayList<>();
        *_omitting rest of the EndTurn code_*
     }

2 个答案:

答案 0 :(得分:0)

来自JavaDoc of java.util.Comparator

  

当使用能够强加与equals不一致的排序的比较器来排序有序集(或有序映射)时,应该小心。假设带有显式比较器c的有序集(或有序映射)与从集合S中绘制的元素(或键)一起使用。如果由S对S施加的排序与equals不一致,则排序集(或有序映射)将表现得“奇怪”。特别是有序集(或有序映射)将违反集合(或映射)的一般契约,它以等于的方式定义。

......这听起来很像你正在经历的事情。那么让我们看一下JavaDoc of java.util.Collection#remove

  

如果存在,则从该集合中移除指定的元素(可选操作)。更正式地,如果此集合包含此类元素,则删除元素e,使(o==null ? e==null : o.equals(e))

所以Comparator甚至没有在这里发挥作用。因此,解决方案是实现equalshashCode方法。

@Override
public int hashCode() {
    int hash = 3;
    hash = 97 * hash + this.initiative;
    hash = 97 * hash + this.age;
    hash = 97 * hash + Objects.hashCode(this.name);
    return hash;
}

@Override
public boolean equals(Object obj) {
    if (this == obj) {
        return true;
    }
    if (obj == null) {
        return false;
    }
    if (getClass() != obj.getClass()) {
        return false;
    }
    final Organism other = (Organism) obj;
    if (this.initiative != other.initiative) {
        return false;
    }
    if (this.age != other.age) {
        return false;
    }
    if (!Objects.equals(this.name, other.name)) {
        return false;
    }
    return true;
}

注意:这两种方法都是由Netbeans自动生成的。

答案 1 :(得分:0)

非常感谢您的帮助,不幸的是问题还有其他问题。在比较器中你可以看到,如果主动权是平等的,而且有机体1年龄不大于有机体2年龄,我只会将有机体1置于有机体2之后。现在让我们说我的树集中的对象是A,B,C,D ......,X,Y,Z,它们的主动性和年龄是相同的。当我将它们添加到我的树集时,比较器总是返回1,因为计划和年龄是相同的,这意味着树集的顺序始终是插入顺序。现在,当我想删除H对象时,.remove方法将进入A,B,C,D,...,X,Y,Z树集,它将从例如F开始(我不是知道它是随机选择还是某种算法)并且会用参数F和H调用比较器,然后比较器将返回1,就像在这种情况下一样。然后.remove方法将从F“跳”到例如K,完全跳过H,它将比较K和H,比较将再次返回1,告诉.remove方法H可能 进一步位于treeset中,实际上.remove已经跳过了我的H对象。这导致.remove随机删除对象,因为它会在算法中跳过它。我需要做的就是为我的生物添加静态计数器。现在,比较器知道它正在寻找计数器值为3的狼,所以如果它跳过它并找到计数器值为6的狼,它知道它需要返回。

TL; DR:

(TIL:树集的算法不会从一个节点移动到它的“邻居”,而是“跳过”大部分(它们越接近我们的值越小),这意味着如果我们在我们的集{1,2 .... 100}中寻找50,它会(例如)跳到20,然后跳到80,然后到40,然后到55等。)

.remove会因为比较“if”语句错误而跳过treeset中的对象。