黑社会没有出现打架? (Java集遗漏了一个项目)

我有两个公司asoft和bsoft的代码。 我也无法改变。 这是我的情况的一个简化版本,我很确定有足够的信息来找到导致问题的原因。

bsoft提供了IGang ,它代表了一个可以与其他帮派作战的帮派。

package bsoft;

public interface IGang {
    /** @return negative, 0, or positive, respectively
     *          if this gang is weaker than, equal to, or stronger
     *          than the other
     */
    public int compareTo(IGang g);
    public int getStrength();
    public String getName();
    public void attack(IGang g);
    public void weaken(int amount);
}

asoft提供的GangWar可以让IGang战斗:

package asoft;
import java.util.*;
import bsoft.*;
/** An `IGang` ordered by identity (name) */
public interface ComparableGang extends IGang, Comparable<IGang> {}

package asoft;
import java.util.*;

public class GangWar {
    public final Set<ComparableGang> gangs = new TreeSet<ComparableGang>();
    public void add(ComparableGang g) {gangs.add(g);}
    public void doBattle() {
        while (gangs.size() > 1) {
          Iterator<ComparableGang> i = gangs.iterator();
          ComparableGang g1 = i.next();
          ComparableGang g2 = i.next();
          System.out.println(g1.getName() + " attacks " + g2.getName());
          g1.attack(g2);
          if (g2.getStrength() == 0) {
              System.out.println(g1.getName() + " smokes " + g2.getName());
              gangs.remove(g2);
          }
          if (g1.getStrength() == 0) {
              System.out.println(g2.getName() + " repels " + g1.getName());
              gangs.remove(g1);
          }
        }
        for (ComparableGang g : gangs) {
            System.out.println(g.getName() + " now controls the turf!");
        }
    }
}

它需要额外的约束,你提供给它的GangComparable ,大概是这样它可以按名称排序或避免重复。 每个帮派(按照任意顺序,为了简单起见,在这里使用的顺序)攻击另一个帮派,直到只剩下一个帮派(或者如果最后两个帮派没有帮派,那么没有帮派)。 我写了一个ComparableGang的简单实现来测试它:

import asoft.*;
import bsoft.*;
import java.util.*;

class Gang implements ComparableGang {
    final String name;
    int strength;

    public Gang(String name, int strength) {
        this.name = name;
        this.strength = strength;
    }

    public String getName() {return name;}
    public int getStrength() {return strength;}

    public int compareTo(IGang g) {
        return strength - g.getStrength();
    }

    public void weaken(int amount) {
        if (strength < amount) strength = 0;
        else strength -= amount;
    }

    public void attack(IGang g) {
        int tmp = strength;
        weaken(g.getStrength());
        g.weaken(tmp);

    }

    public boolean equals(Object o) {
      if (!(o instanceof IGang)) return false;
      return name.equals(((IGang)o).getName());
    }
}

class Main {
   public static void main(String[] args) {
       GangWar gw = new GangWar();
       gw.add(new Gang("ballas", 2));
       gw.add(new Gang("grove street", 9));
       gw.add(new Gang("los santos", 8));
       gw.add(new Gang("triads", 9));
       gw.doBattle();
   }
}

测试它...

$ java Main
ballas attacks los santos
los santos repels ballas
los santos attacks grove street
grove street repels los santos
grove street now controls the turf!

问题是,黑社会并没有出现在战斗中。 实际上,在gangs.size()的开始处doBattle()返回3而不是4.为什么? 如何解决它?


问题是,黑社会并没有出现在战斗中。 实际上,在doBattle()的开始处打印gangs.size()会返回3而不是4.为什么?

triadsgrove street都有9的实力。因此,他们在Gang.compareTo (实施Comparable )方面是平等的。 因此在TreeSet只允许有一个。

如果您不想删除按排序顺序重复的项目,请不要使用TreeSet ...

编辑: ComparableGang接口描述表明预期:

/** An `IGang` ordered by identity (name) */
public interface ComparableGang extends IGang, Comparable<IGang> {}

您的compareTo方法不按“身份(名称)”排序 - 它按强度排序。 说实话,这是摆在首位一个非常愚蠢的接口,它会一直非常轻松asoft创建一个类的public class GangNameComparator : Comparator<IGang>然后提供,作为比较的树设置,如果他们想按名称订购。

但是,因为他们建议您应该实施比较,所以您需要这样做,因为界面描述如下:

public int compareTo(IGang g) {
    return name.compareTo(g.getName());
}

然而......正如你在评论中注意到的(正如Rob的回答中所指出的那样),这与矛盾的惯例称为IGang描述相矛盾:

public interface IGang {
    /** @return negative, 0, or positive, respectively
     *          if this gang is weaker than, equal to, or stronger
     *          than the other
     */
    public int compareTo(IGang g);
}

实现ComparableGang不可能同时满足其自己的文档和IGang文档。 这在设计上基本上已被打破,在asoft的部分。

任何代码都应该能够使用IGang实现,仅了解IGang ,并依靠IGang合同后的实现。 然而,Asoft通过要求扩展IGang的接口中的不同行为打破了这一假设。

只要他们没有违反IGang的现有要求,他们在ComparableGang添加更多的需求是合理的。

请注意,这是C#和Java之间的一个重要区别。 在C#中,具有相同签名的两个不同接口中的两个函数可以组合成一个继承它们的接口,并且这两个方法保持不同且可访问。 在Java中,这两种方法由于是完全抽象的并且具有相同的签名,因此被认为是相同的方法,实现组合接口的类只有一种这样的方法。 所以在Java中, ComparableGang是无效的,因为它不能执行compareTo(),它满足ComparableGang的合同和IGang的合约。


TL; DR:使用B)以下

从Comparable的javadoc(和Comparator也一样!):

当且仅当e1.compareTo(e2) == 0e1.equals(e2)具有相同的布尔值时,类C的自然顺序被认为与equals相等。 e1.compareTo(e2) == 0类C的每个e1和e2,请注意null不是任何类的实例,即使e.equals(null)返回false,e.compareTo(null)也应抛出NullPointerException。

在你的情况(简化)中,

  • equals被定义为name平等
  • compareTo被定义为strength比较
  • 这不符合上述条件:

  • 当两个strength相等时,但这两个name是不同的
  • 当两个name相等时,但两个strength不同(可能是您的应用程序逻辑避免的一种情况)
  • 回答

    如何纠正?

    A)如果您的要求允许您按name排序(符合asoft代码中的评论):

     // will only return 0 if the strengths are equal AND the names are equal
     public int compareTo(ComparableGang g) {
         return name.compareTo(g.getName());
     }
    

    B)如果您的要求迫使您按照strength (然后name )排序(与bsoft代码中的评论一致)。

     // will return 0 if & only if the strengths are equal AND the names are equal
     public int compareTo(ComparableGang g) {
         int result = strength - g.getStrength();
         if (result == 0) result =  name.compareTo(g.getName());
         return result;
     }
    
     // will return true if & only if the strengths are equal AND the names are equal
     public boolean equals(Object o) {
         if (!(o instanceof ComparableGang)) return false;
         ComparableGang gang2 = (ComparableGang)o;
         return name.equals(gang2.getName()) && strength == gang2.getStrength();
     }
    
     // For this case, if it's illegal to have two gangs of same name but different 
     // strength (it should be illegal!), then app logic must enforce this - the Set 
     // no longer will.
    

    评论1:虽然修改asoft的GangWar类是一个问题,但如果您可以将以上两种B)的方法放在以下内容中,会更好:

     class ComparableGangComparator implements Comparator<ComparableGang> {
     }
    

    然后修改GangWar如何构建Set:

     public final Set<ComparableGang> gangs = new TreeSet<ComparableGang>(
                                                      new ComparableGangComparator());
    

    这样,您可以将A)的两个方法留在类Gang中 - 将类保留为“true”equals&compareTo from object identity POV。

    评论2:对asoft的&bsoft的compareTo方法的评论相反

    从理论上的POV来看: 如果 asoft的评论不是一个错字,那么不仅boft增加了bsoft的接口,而且改变了其中一种方法所需的行为。 这实际上并不矛盾 - 这是一个优先考虑 :asoft的评论“胜利”。

    从一个实际的POV:你需要你的手指越过这个目的和意见是正确的。 如果是来自asoft的拼写错误,那么bsoft的评论会更好,并且bsoft会“胜出”。 您可以向asoft发送查询或查看他们的文档/示例以确认。


    Gang.compareTo方法基于它们的优点,所以既然triadsgrove street具有相同的优势,TreeSet认为它们是平等的,然后将它们移除。

    根据ComparableGang如何对它们进行排序,我会说忽略IGang接口对compareTo行为的请求并将其更改为此。

    public int compareTo(IGang g) {
        return name.compareTo(g.getName());
    }
    
    链接地址: http://www.djcxy.com/p/78905.html

    上一篇: Triads not showing up to fight? (Java Set missing an item)

    下一篇: In C#, do objects which have a local scope use the stack?