我遇到了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_*
}
答案 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
甚至没有在这里发挥作用。因此,解决方案是实现equals
和hashCode
方法。
@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中的对象。