Я пытаюсь разработать проект в Groovy, и некоторые из моих тестов оказались нечетными: у меня есть интерфейс Version extends Comparable<Version>
с двумя конкретными подклассами. Оба переопределяют equals(Object)
и compareTo(Version)
. Однако, если я попытаюсь сравнить два экземпляра Version
, которые имеют разные типы бетона, используя ==
, проверка равенства не выполняется, хотя явные equals
и compareTo
проверяет пропуск.В Groovy, почему поведение '==' изменяется для интерфейсов, расширяющих Comparable?
Если удалить extends Comparable<Version>
Version
части, я получаю ожидаемое поведение - ==
дает тот же результат, как equals
бы.
Я читал в другом месте, что Groovy делегаты ==
к equals()
, если класс не реализует Comparable
, в этом случае делегатов compareTo
. Тем не менее, я нахожу случаи, когда оба объявляют два экземпляра Version
равными, и все же ошибки ==
не выполняются.
Я создал SSCCE, который демонстрирует это поведение here.
Полный код также приводится ниже:
// Interface extending Comparable
interface Super extends Comparable<Super> {
int getValue()
}
class SubA implements Super {
int getValue() { 1 }
int compareTo(Super that) { this.value <=> that.value }
boolean equals(Object o) {
if (o == null) return false
if (!(o instanceof Super)) return false
this.value == o.value
}
}
class SubB implements Super {
int getValue() { 1 }
int compareTo(Super that) { this.value <=> that.value }
boolean equals(Object o) {
if (o == null) return false
if (!(o instanceof Super)) return false
this.value == o.value
}
}
// Interface not extending Comparable
interface AnotherSuper {
int getValue()
}
class AnotherSubA implements AnotherSuper {
int getValue() { 1 }
boolean equals(Object o) {
if (o == null) return false
if (!(o instanceof AnotherSuper)) return false
this.value == o.value
}
}
class AnotherSubB implements AnotherSuper {
int getValue() { 1 }
boolean equals(Object o) {
if (o == null) return false
if (!(o instanceof AnotherSuper)) return false
this.value == o.value
}
}
// Check with comparable versions
def a = new SubA()
def b = new SubB()
println "Comparable versions equality check: ${a == b}"
println "Explicit comparable equals check: ${a.equals(b)}"
println "Explicit comparable compareTo check: ${a.compareTo(b)}"
// Check with non-comparable versions
def anotherA = new AnotherSubA()
def anotherB = new AnotherSubB()
println "Non-comparable versions equality check: ${anotherA == anotherB}"
println "Explicit non-comparable equals check: ${anotherA.equals(anotherB)}"
Что я получаю обратно есть:
Comparable versions equality check: false
Explicit comparable equals check: true
Explicit comparable compareTo check: 0
Non-comparable versions equality check: true
Explicit non-comparable equals check: true
EDIT
Я думаю, я понимаю, почему это происходит сейчас, благодаря JIRA discussion, связанный с Poundex ниже.
С Groovy, DefaultTypeTransformation class, который используется для обработки проверки равенства/сравнения, я полагаю, что метод compareEqual
сначала вызывается, когда утверждение вида x == y
проводится оценка:
public static boolean compareEqual(Object left, Object right) {
if (left == right) return true;
if (left == null || right == null) return false;
if (left instanceof Comparable) {
return compareToWithEqualityCheck(left, right, true) == 0;
}
// handle arrays on both sides as special case for efficiency
Class leftClass = left.getClass();
Class rightClass = right.getClass();
if (leftClass.isArray() && rightClass.isArray()) {
return compareArrayEqual(left, right);
}
if (leftClass.isArray() && leftClass.getComponentType().isPrimitive()) {
left = primitiveArrayToList(left);
}
if (rightClass.isArray() && rightClass.getComponentType().isPrimitive()) {
right = primitiveArrayToList(right);
}
if (left instanceof Object[] && right instanceof List) {
return DefaultGroovyMethods.equals((Object[]) left, (List) right);
}
if (left instanceof List && right instanceof Object[]) {
return DefaultGroovyMethods.equals((List) left, (Object[]) right);
}
if (left instanceof List && right instanceof List) {
return DefaultGroovyMethods.equals((List) left, (List) right);
}
if (left instanceof Map.Entry && right instanceof Map.Entry) {
Object k1 = ((Map.Entry)left).getKey();
Object k2 = ((Map.Entry)right).getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = ((Map.Entry)left).getValue();
Object v2 = ((Map.Entry)right).getValue();
if (v1 == v2 || (v1 != null && DefaultTypeTransformation.compareEqual(v1, v2)))
return true;
}
return false;
}
return ((Boolean) InvokerHelper.invokeMethod(left, "equals", right)).booleanValue();
}
Обратите внимание, что если LHS выражения является экземпляром Comparable
, как в примере я обеспечиваю, сравнение делегированы compareToWithEqualityCheck
:
private static int compareToWithEqualityCheck(Object left, Object right, boolean equalityCheckOnly) {
if (left == right) {
return 0;
}
if (left == null) {
return -1;
}
else if (right == null) {
return 1;
}
if (left instanceof Comparable) {
if (left instanceof Number) {
if (right instanceof Character || right instanceof Number) {
return DefaultGroovyMethods.compareTo((Number) left, castToNumber(right));
}
if (isValidCharacterString(right)) {
return DefaultGroovyMethods.compareTo((Number) left, ShortTypeHandling.castToChar(right));
}
}
else if (left instanceof Character) {
if (isValidCharacterString(right)) {
return DefaultGroovyMethods.compareTo((Character)left, ShortTypeHandling.castToChar(right));
}
if (right instanceof Number) {
return DefaultGroovyMethods.compareTo((Character)left,(Number)right);
}
}
else if (right instanceof Number) {
if (isValidCharacterString(left)) {
return DefaultGroovyMethods.compareTo(ShortTypeHandling.castToChar(left),(Number) right);
}
}
else if (left instanceof String && right instanceof Character) {
return ((String) left).compareTo(right.toString());
}
else if (left instanceof String && right instanceof GString) {
return ((String) left).compareTo(right.toString());
}
if (!equalityCheckOnly || left.getClass().isAssignableFrom(right.getClass())
|| (right.getClass() != Object.class && right.getClass().isAssignableFrom(left.getClass())) //GROOVY-4046
|| (left instanceof GString && right instanceof String)) {
Comparable comparable = (Comparable) left;
return comparable.compareTo(right);
}
}
if (equalityCheckOnly) {
return -1; // anything other than 0
}
throw new GroovyRuntimeException(
MessageFormat.format("Cannot compare {0} with value ''{1}'' and {2} with value ''{3}''",
left.getClass().getName(),
left,
right.getClass().getName(),
right));
}
вниз вблизи б ottom, метод имеет блок, который делегирует сравнение с методом compareTo
, , но только при выполнении определенных условий. В приведенном ниже примере ни одно из этих условий не выполняется, в том числе проверка isAssignableFrom
, так как примеры классов, которые я предоставляю (и код в моем проекте, который дает мне проблему), являются siblings и поэтому не могут быть назначены друг другу.
Я предполагаю, что я понимаю, почему чеки не удается сейчас, но я до сих пор ломают голову над следующими вещами:
- Как обойти это?
- В чем причина этого? Это ошибка или функция дизайна? Есть ли причина, почему два подкласса общего суперкласса не должны быть сопоставимы друг с другом?
Похоже, что у вас может быть этот https://jira.codehaus.org/browse/GROOVY-3364 (я пробовал его локально с помощью 2.4.0 и видел те же результаты, что и вы) – Poundex
@Poundex Спасибо за ссылку. Я заметил, что в одном из комментариев упоминается, что «<=>» и «==» проходят через [здесь] (https://github.com/groovy/groovy-core/blob/master/src/main/org/codehaus/groovy/ runtime/typehandling/DefaultTypeTransformation.java) - особый интерес представляют «compareToWithEqualityCheck» и «compareEqual». Тем не менее, я все еще не уверен, что происходит. – Tagc