【问题标题】:TreeSet custom comparator doesn't work with equal objectsTreeSet 自定义比较器不适用于相等的对象
【发布时间】:2018-05-13 10:53:59
【问题描述】:

我遇到了 TreeSet 比较器实现的问题。我有一个简单的游戏,动物在棋盘上行走,每转一圈,它们会做一个动作,如果它们因某种原因死亡,它们会被标记为“死亡”,放入“DeadOrganisms”列表,然后从树集“队列”中删除" 在这段代码中(我无法立即删除它们,因为我正在遍历树集):

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

问题在于,他们中的一些人根本没有被移除,甚至在每回合结束时都没有想到它们因为被标记为“死亡”而被放回了 DeadOrganisms 列表。确保 .remove 每次在死去的有机体上都会被调用,我很确定问题出在 Comparator 类中:

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;
    }

我知道我做错了什么或误解了 TreeSet 的工作原理(或两者兼而有之),但我不明白是什么。我也知道.remove

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

所以在我的理解中,这就是

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

在我的 Comparator 类中,但也许我误解了一些东西。 我将不胜感激。

@编辑 我不知道这是否重要,但到目前为止,我正在对一种动物进行测试,具有相同的年龄和主动性,因此所有动物之间的唯一区别是它们的名字。

@EDIT2 我还注意到,如果要删除的生物首先在树集中“队列”中,那么在比较方法中,在调用 queue.remove(org) 之后,“org”永远不会与树集中的第一个对象(又名。本身)只到第二,第三等。

@EDIT3 对于 cmets 中的用户 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_*
     }

【问题讨论】:

  • 请告诉我们声明和初始化queueDeadOrganisms的代码。

标签: java comparator treeset


【解决方案1】:

来自JavaDoc of java.util.Comparator

在使用能够施加与等于不一致的排序的比较器来对已排序集(或已排序映射)进行排序时,应谨慎行事。假设带有显式比较器 c 的有序集合(或有序映射)与从集合 S 中提取的元素(或键)一起使用。如果 c 对 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 自动生成的。

【讨论】:

    【解决方案2】:

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

    TL;DR:

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

    .remove 会因为比较器“if”语句错误而跳过我在树集中的对象。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-26
      • 1970-01-01
      • 2013-12-25
      • 1970-01-01
      • 1970-01-01
      • 2012-11-11
      相关资源
      最近更新 更多