2

我有来自两家公司 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,它允许IGangs 进行战斗:

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!");
        }
    }
}

Gang它需要您提供给它的 s 的附加约束Comparable,大概是这样它可以按名称排序或避免重复。每个帮派(以任意顺序,为了简单起见,此处使用设置顺序)攻击另一个帮派,直到只剩下一个帮派(或者没有帮派,如果最后两个有平局)。我写了一个简单的实现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。为什么?如何解决?

4

4 回答 4

5

问题是,三合会不会出现在战斗中。事实上,在 doBattle() 的开头打印 gangs.size() 返回 3 而不是 4。为什么?

两者triadsgrove street具有 9 的强度。因此它们在Gang.compareTo(实现Comparable)方面是相等的。因此在 a 中只允许一个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

他们在 中添加更多要求是合理的ComparableGang,只要它们不违反IGang.

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

于 2013-04-03T18:27:17.233 回答
3

TL;DR:在下面使用 B)

来自Comparable的 javadoc (对于Comparator也是如此!):

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

在您的情况下(简化),

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

这不符合上述条件:

  • 当两个strengths 相等,但两个names 不同时
  • 当两个names 相等,但两个strengths 不同时(可能是您的应用程序逻辑避免的情况)

回答

如何纠正?

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 类中 - 让类具有“真实”的 equals & compareTo 对象标识 POV。

评论 2:对 asoft 和 bsoft 的 compareTo 方法的评论相互矛盾

从理论上的观点来看:如果asoft 的注释不是错字,那么 asoft 不仅扩展了 bsoft 的接口,而且还改变了其中一种方法所需的行为。这实际上根本不是矛盾 - 它是一种覆盖:asoft 的评论“获胜”。

从一个实际的 POV 来看:您需要双手交叉,这是故意这样做的,并且评论是正确的。 如果这是来自 asoft 的错字,那么 bsoft 的评论会更好,并且 bsoft “获胜”。您可以向 asoft 发送查询或查看他们的文档/示例以确认。

于 2013-05-09T02:24:26.800 回答
1

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

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

public int compareTo(IGang g) {
    return name.compareTo(g.getName());
}
于 2013-04-03T18:31:27.173 回答
0

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

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


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

public interface IGang extends Comparable<IGang> {

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

不过,asoft 不需要等待 bsoft 更改任何内容。他们一开始就创建了一个被设计破坏的界面,这确实是他们的错。他们应该刚刚选择了一种Comparator<IGang>按名称排序的方法。所以如果我在 asoft 工作,GangWar看起来会更像这样:

public class GangWar {
    public final Set<IGang> gangs;
    public GangWar(Comparator<IGang> gangNameComparator) {
        gangs = new TreeSet<IGang>(gangNameComparator);
    }
    public void add(IGang g) {gangs.add(g);}
    public void doBattle() {
        while (gangs.size() > 1) {
          Iterator<IGang> 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!

当然,如果您坚持使用图书馆,您可以阅读此答案,了解在不接触图书馆的情况下与破损一起生活的一般方法。

于 2013-05-18T20:34:19.600 回答