#java #findbugs
#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 сообщает об этом как о проблеме. Но почему это так? Что может произойти?
И как мне правильно реализовать решение?
Комментарии:
1. Это не ковариация. В
Impl
вы перегружаете (попробуйте поместить@Override
туда).2. Перегрузка @pingw33n здесь не сработает, поскольку типы параметров разные. И я не могу сравнить c с AbstractBase, поскольку у него его нет.
3. @UweAllner Но то, что вы делаете в
Impl
withcompareTo(Impl other)
, это распространенная ошибка, и она будет вести себя не так, как ожидалось. Это не переопределяет метод Comparable .4. Вопрос в том, что вы хотите здесь реализовать? Рассмотрите экземпляры
a
иb
подклассовA
иB
ofAbstractBase
: хотите ли вы, чтобыa
иb
были сопоставимы, даже если они происходят из разных подклассов (т. Е. Хотите ли вы, чтобы какой-либо экземплярAbstractBase
был сопоставим с любым другим экземпляром?) Или вы хотите указать, что каждый подклассS
AbstractBase
должен иметь возможность сравнивать свои собственные экземпляры друг с другом, но не обязательно свои собственные экземпляры с экземплярами других подклассов?5. @UweAllner Вы всегда можете создать отдельный
Comparator<Impl>
и использовать его во время сортировки.
Ответ №1:
Impl#compareTo(Impl)
не является переопределяющим AbstractBase#compareTo(AbstractBase)
, поскольку они не имеют одинаковой подписи. Другими словами, он не будет вызываться при использовании Collections#sort
, например.
Комментарии:
1. Но он вызывается, поскольку сортировка не выполняет понижение; возможно, он не будет вызван, когда коллекция была объявлена, например, как List<AbstractBase> , чего я не делаю.
2. @UweAllner Ну, вы уверены, что это называется? Вставьте
syso
Impl#compareTo
. Если он будет напечатан, то предоставленный вами код не будет представлять код, который вы выполняете, потому что он не может быть вызван, если явно (напримерnew Impl().compareTo(...)
).sort
используетAbstractBase#compareTo
метод.3. Извините, вы были правы, моя ошибка. Вызывается compareTo абстрактной базы. Теперь я хотел бы понять, почему, как и в sort, элементы преобразуются в сопоставимые (нетипизированные), а сравниваемый объект является Impl …
4. @UweAllner Самостоятельно создайте простую общую коллекцию и добавьте метод сортировки. Тогда вы увидите, что это единственный способ сделать это, если не использовать отражение.
Ответ №2:
РЕДАКТИРОВАТЬ: добавлено решение без приведения
Если вы не хотите выполнять приведение, вы можете попробовать следующее.
Измените свой базовый класс на:
abstract class AbstractBase<T extends AbstractBase<?>> implements Comparable<T> {
//...
public int compareTo(T other) {
//...
}
}
И вы применяете класс к:
class Impl extends AbstractBase<Impl> {
//...
@Override
public int compareTo(Impl other) {
//...
}
}
Решение с приведением:
Возможным решением было бы переопределить метод compareTo(AbstractBase) в классе Impl и явно проверить, передается ли экземпляр 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);
}
}
Комментарии:
1. Я тоже думал об этом, но я считаю это приведение довольно уродливым. Предполагаемое решение (от FindBugs) использовать Object в качестве параметра для compareTo не упоминать…
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;
}
}
Комментарии:
1. Такой ситуации не произойдет (чего FindBugs, конечно, не могут знать). Но это, вероятно, будет причиной сообщения.
2. @UweAllner Я отредактировал ответ, чтобы добавить решение. Может быть, вы можете попробовать что-то подобное?
3. Мой пример был несколько упрощен; на самом деле мне пришлось сравнивать и другие значения, кроме целых чисел; поэтому я бы просто перенес проблему подписи в метод calculate . Но в любом случае спасибо за ваши усилия.
Ответ №4:
Как сказал sp00m, ваша Impl#compareTo(Impl)
подпись отличается AbstractBase#compareTo(AbstractBase)
от, поэтому она не перегружает ее.
Ключевой момент заключается в понимании того, почему он не работает, даже когда вы пытаетесь sort()
сравнить с другим Impl
, где более конкретная подпись совпадает.
Как вы определили Comparable<AbstractBase>
, вам нужно определить, как ваши экземпляры compareTo
AbstractBase
экземпляры. И поэтому вам нужно реализовать compareTo(AbstractBase)
.
Вы можете подумать, что, будучи Impl
подтипом AbstractBase
, более конкретный метод будет использоваться при сравнении между двумя Impl
s. Проблема в том, что Java имеет статическую привязку, и поэтому компилятор определяет во время компиляции , какой метод будет использоваться для решения каждого вызова метода. Если то, что вы делали, было сортировкой AbstractBase
s, тогда компилятор будет использовать compareTo(AbstractBase)
, то есть AbstractBase
интерфейс, определенный при реализации Comparable(AbstractBase)
интерфейса.
Вы можете Impl
реализовать Comparable<Impl>
интерфейс для использования compareTo(Impl)
метода, но это будет работать только в том случае, если вы явно сортируете вещи, которые, как известно, являются Impl
s во время компиляции (т. Е. Impl
Объект или Collection<Impl>
).
Если вы действительно хотите применить другое сравнение всякий раз, когда ваши два объекта равны Impl
s, вам следует прибегнуть к какой-то двойной отправке в вашем Impl#compareTo(AbstractBase)
like:
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
, что, впрочем, не очень красиво, но решает проблему более элегантным способом — использование отражения для этого совсем не элегантно.
Ответ №5:
Принцип подстановки Лискова (http://en.wikipedia.org/wiki/Liskov_substitution_principle ) состояния: если S является подтипом T, то объекты типа T могут быть заменены объектами типа S (т. Е. Объекты типа S могут заменять объекты типа T) без изменения каких-либо желательных свойств этой программы (корректность, выполняемая задача,и т.д.)
В вашем случае вы переопределяете метод compareTo из базового класса таким образом, что нарушает поведение исходного метода. Вероятно, именно поэтому у FindBugs есть проблема с этим.
Если вы хотите, чтобы это было правильно:
abstract class AbstractBase {
}
class Impl1 extends AbstractBase implements Comparable<Impl1> ...
class Impl2 extends AbstractBase implements Comparable<Impl2> ...
или
еще лучше, вообще не используйте сопоставимый интерфейс — вместо этого используйте компаратор во время сортировки.
Однако в реальной жизни бывают ситуации, когда вам нужно обойти это (возможно, у вас нет доступа к источнику AbstractBase, или, может быть, ваш новый класс — это просто POC). В этих особых случаях я бы выбрал «уродливое» приведенное решение, предложенное Джоном.