I have code from two companies asoft and bsoft. I cannot change either. This is a simplified version of my situation which I'm pretty sure has enough information to the find what's causing the problem.
bsoft provides IGang
, which represents a gang that can battle other gangs.
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 provides GangWar
, which allows IGang
s to battle:
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!");
}
}
}
It requires the additional constraint that the Gang
s you supply to it are Comparable
, presumably so it can sort by name or avoid duplicates. Each gang (in an arbitrary order, Set order used here for simplicity) attacks another gang, until only one gang is left (or no gangs, if the last two have a tie). I've written a simple implementation of ComparableGang
to test it out:
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();
}
}
Testing it out...
$ 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!
The problem is, triads do not show up to the fight. In fact, printing gangs.size()
right at the start of doBattle()
returns 3 instead of 4. Why? How to fix it?
The problem is, triads do not show up to the fight. In fact, printing gangs.size() right at the start of doBattle() returns 3 instead of 4. Why?
Both triads
and grove street
have a strength of 9. Therefore they're equal in terms of Gang.compareTo
(implementing Comparable
). Therefore only one is permitted in a TreeSet
.
If you don't want to remove items which are duplicates in terms of their sort order, don't use a TreeSet
...
EDIT: The ComparableGang
interface description indicates what's expected:
/** An `IGang` ordered by identity (name) */
public interface ComparableGang extends IGang, Comparable<IGang> {}
Your compareTo
method does not order "by identity (name)" - it orders by strength. To be honest, it's a pretty stupid interface in the first place, as it would have been perfectly easy for asoft
to create a class of public class GangNameComparator : Comparator<IGang>
, and then supply that as the comparator to the tree set if they wanted to order by name.
However, as they're suggesting that you should implement the comparison, you need to do so as the interface describes:
public int compareTo(IGang g) {
return name.compareTo(g.getName());
}
However... as you note in comments (and as noted in Rob's answer), this then contradicts the convention-bustingly-named IGang
description:
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);
}
It's impossible to implement ComparableGang
to satisfy both its own documentation and the IGang
documentation. This is basically broken by design, on asoft's part.
Any code should be able to use an IGang
implementation, knowing only about IGang
, and relying on the implementation following the IGang
contract. However, asoft broke that assumption by requiring different behaviour in an interface extending IGang
.
It would have been reasonable for them to add more requirements in ComparableGang
, so long as they didn't violate the existing requirements of IGang
.
Note that this is an important difference between C# and Java. In C#, two functions in two different interfaces with the same signature can be combined into one interface that inherits both of them and the two methods remain distinct and accessible. In Java, the two methods, since they are completely abstract and have the same signature, are considered to be the same method and a class implementing the combined interfaces has only one such method. So in Java ComparableGang
is invalid because it cannot have an implementation of compareTo() that satisfies the contract of ComparableGang and the contract of IGang.