三合会没有出现在战斗中? (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 {} package asoft; import java.util.*; public class GangWar { public final Set gangs = new TreeSet(); public void add(ComparableGang g) {gangs.add(g);} public void doBattle() { while (gangs.size() > 1) { Iterator 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()开头打印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 {} 

您的compareTo方法不按“标识(名称)”排序 – 按强度排序。 说实话,这首先是一个非常愚蠢的界面,因为asoft创建一类public class GangNameComparator : Comparator非常容易,然后将它作为比较器提供给树集,如果它们想按名字订购。

但是,因为他们建议你应该实现比较,所以你需要这样做,因为界面描述了:

 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文档是IGang 。 在asoft的部分,这基本上被设计破坏了。

任何代码都应该能够使用IGang实现, 知道IGang ,并依赖于IGang合同之后的实现。 然而,asoft通过在扩展IGang的接口中要求不同的行为来打破这种假设。

如果它们没有违反IGang现有要求,那么在ComparableGang添加更多要求是合理的。

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

TL; DR:使用B)下面

来自Comparable的javadoc(同样也适用于Comparator !):

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

在你的情况下(简化),

  • equals定义为name相等
  • compareTo定义为strength比较

这不符合上述条件:

  • 当两个strength s相等时,两个name s不同
  • 当两个name相等时,但两个strength s不同(可能是应用程序逻辑避免的条件)

回答

怎么纠正?

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

然后修改GangWar如何构造Set:

  public final Set gangs = new TreeSet( new ComparableGangComparator()); 

这样,你可以将A)中的两个方法保留在类Gang中 – 让类与它的“true”等于&compareTo来自对象标识POV。

评论2:对asoft&bsoft的compareTo方法的评论不一致

从理论上的POV: 如果 asoft的评论不是拼写错误,那么不仅asoft扩展了bsoft的界面,而且他们改变了其中一种方法所需的行为。 这根本不是一个矛盾 – 它是一个覆盖 :asoft的评论“胜利”。

从一个实际的POV:你需要你的手指交叉asoft这样做是故意和评论是正确的。 如果这是来自asoft的拼写错误,那么bsoft的评论会更好,而bsoft会“胜出”。 您可以向asoft发送查询或检查他们的doc / examples以进行确认。

Gang.compareTo方法基于它们的优势,因此由于Gang.compareTogrove street具有相同的强度,TreeSet认为它们是相等的然后将它们移除。

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

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

从根本上说问题是, ComparableGang 是一个 IGang ,但是IGangs按强度排序,ComparableGangs按名称排序,所以ComparableGang 不是 IGang

TL; DR修复此问题的正确方法是修复接口和库代码。 本答案解释了编写代码以使用这两个库的解决方法。


最好的解决方案是修复两个接口。 bsoft只需要将其代码的一行更改为:

 public interface IGang extends Comparable { 

界面中的任何其他内容或任何其他代码都不需要更改,但Asoft会注意到IGang已经具有可比性。 (编辑:第二个想法,因为IGang.compareTo()equals()不一致 ,这是三合会为什么没有出现在战斗中的根源,bsoft可能做了正确的事情, 没有扩展Comparable 。他们是什么做错了是声明compareTo()而不是比较,例如compareStrengthTo() 。)

不过,asoft不需要等待bsoft改变任何东西。 他们开始创建一个破解设计的界面真的是他们的错。 他们应该选择一种方法来获得按名称排序的Comparator 。 所以,如果我在asoft工作,GangWar GangWar会更像这样:

 public class GangWar { public final Set gangs; public GangWar(Comparator gangNameComparator) { gangs = new TreeSet(gangNameComparator); } public void add(IGang g) {gangs.add(g);} public void doBattle() { while (gangs.size() > 1) { Iterator i = gangs.iterator(); IGang g1 = i.next(); IGang 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 (IGang g : gangs) { System.out.println(g.getName() + " now controls the turf!"); } } } 

因此,他们不是完全打破IGang ,而是在需要时询问他们需要什么(IGang没有)。 通过此更改(以及Comparator实现),程序输出:

 ballas attacks grove street grove street repels ballas grove street attacks los santos los santos repels grove street los santos attacks triads triads repels los santos triads now controls the turf! 

当然,如果您坚持使用库,那么您可以阅读此答案,了解在不触及库的情况下生活破损的一般方法。