为什么定义协变compareTo方法被认为是不好的做法?

时间:2014-06-18 07:40:37

标签: java findbugs

以下是我的代码中的一个示例:

基类:

abstract class AbstractBase implements Comparable<AbstractBase> {
    private int a;
    private int b;

    public int compareTo(AbstractBase other) {
        // compare using a and b
    }
}

实现:

class Impl extends AbstractBase {
private int c;

public int compareTo(Impl other) {
    // compare using a, b and c with c having higher impact than b in AbstractBase
}

FindBugs将此报告为一个问题。但那是为什么呢?可能会发生什么?

我将如何正确实施解决方案?

5 个答案:

答案 0 :(得分:13)

Impl#compareTo(Impl)并未覆盖AbstractBase#compareTo(AbstractBase),因为他们没有相同的签名。换句话说,例如,在使用Collections#sort时,它不会被调用。

答案 1 :(得分:4)

编辑:添加了无需投射的解决方案

如果您不想演员,可以尝试以下方式。

将您的基类改为:

abstract class AbstractBase<T extends AbstractBase<?>> implements Comparable<T> {
//...
    public int compareTo(T other) {
      //... 
    }
}

你和Impl上课:

class Impl extends AbstractBase<Impl> {
//...
    @Override
    public int compareTo(Impl other) {
    //...
    }
}

铸造解决方案:

一种可能的解决方案是覆盖Impl类中的compareTo(AbstractBase)方法,并显式检查是否传入了Impl的实例:

   class Impl extends AbstractBase {
   //...
        @Override
        public int compareTo(AbstractBase other) {

            if (other instanceof Impl) {

                int compC = Integer.compare(c, ((Impl) other).c);

                if (compC == 0) {
                    return super.compareTo(other);
                }
                return compC;
            }

            return super.compareTo(other);
        }
    }

答案 2 :(得分:3)

以下是我尝试的内容。不完全确定这就是为什么findbugs会给出错误。

请参阅以下代码,并假设compareTo方法实现。

比较相同的对象会产生不同的输出。

public class Main
{
  public static void main(String[] args)
  {
    Impl implAssignedToImpl = new Impl(1, 2, 3);
    Impl otherImpl = new Impl(3, 2, 1);
    System.out.println(implAssignedToImpl.compareTo(otherImpl)); // prints -2

    AbstractBase implAssignedToAbstract = implAssignedToImpl;
    System.out.println(implAssignedToAbstract.compareTo(otherImpl)); //prints 0
  }
}

class AbstractBase implements Comparable<AbstractBase>
{
  private int a;

  private int b;

  public AbstractBase(int a, int b)
  {
    super();
    this.a = a;
    this.b = b;
  }

  public int compareTo(AbstractBase other)
  {
    return (a + b) - (other.a + other.b);
  }
}

class Impl extends AbstractBase
{
  private int c;

  public Impl(int a, int b, int c)
  {
    super(a, b);
    this.c = c;
  }

  public int compareTo(Impl other)
  {
    return super.compareTo(other) + (c - other.c);
  }
}

在我的假设compareTo的基础上,以下似乎是一个很好的解决方案。您可以尝试使用类似于getSum的方法,该方法为对象实例提供值。

public class Main
{
  public static void main(String[] args)
  {
    Impl implAssignedToImpl = new Impl(1, 2, 3);
    Impl otherImpl = new Impl(3, 2, 1);
    System.out.println(implAssignedToImpl.compareTo(otherImpl)); // prints 0

    AbstractBase implAssignedToAbstract = implAssignedToImpl;
    System.out.println(implAssignedToAbstract.compareTo(otherImpl)); //prints 0
  }
}

class AbstractBase implements Comparable<AbstractBase>
{
  private int a;

  private int b;

  public AbstractBase(int a, int b)
  {
    super();
    this.a = a;
    this.b = b;
  }

  public int compareTo(AbstractBase other)
  {
    return getSum() - other.getSum();
  }

  public int getSum()
  {
    return a + b;
  }
}

class Impl extends AbstractBase
{
  private int c;

  public Impl(int a, int b, int c)
  {
    super(a, b);
    this.c = c;
  }

  @Override
  public int getSum()
  {
    return super.getSum() + c;
  }
}

答案 3 :(得分:2)

正如sp00m所说,Impl#compareTo(Impl)的签名与AbstractBase#compareTo(AbstractBase)的签名不同,因此不会超载它。

关键在于理解为什么它不起作用,即使您尝试sort()与另一个Impl进行比较,其中更具体的签名 匹配。

在定义Comparable<AbstractBase>时,您需要定义自己的方式 实例compareTo AbstractBase个实例。因此,您需要实施compareTo(AbstractBase)

您可以认为,ImplAbstractBase的子类型,当两个Impl之间进行比较时,将使用更具体的方法。问题是 Java具有静态绑定,因此编译器在编译时定义 哪个方法将用于解决每个方法调用。如果你正在做的是排序AbstractBase,那么编译器将使用compareTo(AbstractBase),即AbstractBase接口在实现Comparable(AbstractBase)接口时定义的接口。

您可以Impl实现Comparable<Impl>接口以使用compareTo(Impl)方法,但这只有在您明确排序已知为Impl s的内容时才有效在编译时(即Impl个对象或Collection<Impl>)。

如果确实想要在您的两个对象Impl s时应用不同的比较,那么您应该在Impl#compareTo(AbstractBase)中进行某种双重调度,例如:

Impl >>>
int compareTo(AbstractBase other) {
    return other.compareToImpl(this);
}

int compareToImpl(Impl other) {
    // perform custom comparison between Impl's
}

AbstractBase >>>
int compareTo(AbstractBase other) {
    // generic comparison
}

int compareToImpl(Impl other) {
    // comparison between an AbstractBase and an Impl.
    //Probably could just "return this.compareTo(other);", but check for loops :)
}

这需要您在Impl中添加一些AbstractBase信息,但这不是很好,但是可以更优雅的方式解决问题 - 使用反射不< / strong>优雅。

答案 4 :(得分:2)

Liskov替换原则(http://en.wikipedia.org/wiki/Liskov_substitution_principle)指出:如果S是T的子类型,则类型T的对象可以用类型S的对象替换(即,类型S的对象可以替换类型为T的对象) )不改变该程序的任何理想属性(正确性,执行任务等)

在您的情况下,您将以打破原始方法行为的方式覆盖Base类中的compareTo方法。这可能是FindBugs遇到问题的原因。

如果你想对此保持正确:

abstract class AbstractBase {
}

class Impl1 extends AbstractBase implements Comparable<Impl1> ...  
class Impl2 extends AbstractBase implements Comparable<Impl2> ...

OR

更好的是,根本不要使用Comparable接口 - 而是在排序时使用Comparator。

然而,在现实生活中,有些情况下你需要绕过它(也许你无法访问AbstractBase的源代码,或者你的新类可能只是一个POC)。在这些特殊情况下,我会选择&#34; ugly&#34;约翰提出的演员解决方案。