在Groovy中,为什么'=='的行为会改变扩展Comparable的接口?

时间:2015-02-05 23:06:16

标签: groovy equality

我正在尝试在Groovy中开发一个项目,并且我发现我的一些测试以一种奇怪的方式失败:我有一个带有两个具体子类的接口Version extends Comparable<Version>。两者都覆盖equals(Object)compareTo(Version) - 但是,如果我尝试使用Version比较具有不同具体类型的两个==实例,则即使显式{等同检查也会失败{1}}和equals检查通过。

如果我删除compareTo的{​​{1}}部分,我会得到预期的行为 - extends Comparable<Version>会得到与Version相同的结果。

我在其他地方读过Groovy将==委托给equals,除非该类实现==,在这种情况下它会委托给equals()。但是,我发现这两种情况都声明两个Comparable实例相等但compareTo检查失败。

我创建了一个演示此行为的SSCCE here

下面还提供了完整的代码:

Version

我要回的是:

==

修改
我想我明白为什么现在会发生这种情况,这要归功于Poundex与下面的JIRA discussion相关联。

从用于处理相等/比较检查的Groovy的DefaultTypeTransformation class开始,我假设在评估表单// 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 方法:< / p>

compareEqual

请注意,如果表达式的LHS是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(); }

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)); } 检查,因为我提供的示例类(以及我的项目中给出问题的代码)是兄弟 ,因此不能彼此分配。

我想我理解为什么检查现在失败了,但我仍然对以下事情感到困惑:

  1. 我如何解决这个问题?
  2. 这背后的理由是什么?这是一个错误或设计功能吗?是否有任何理由说明为什么普通超类的两个子类不能相互比较?

1 个答案:

答案 0 :(得分:2)

为什么Comparable用于==如果存在很容易的答案。这是因为BigDecimal。如果你用&#34; 1.0&#34;制作BigDecimal。和&#34; 1.00&#34; (使用字符串不是双倍!)你得到两个根据等于不相等的BigDecimal,因为它们没有相同的比例。它们在价值方面是平等的,这就是为什么compareTo会将它们看作是平等的。

当然还有GROOVY-4046,它显示了直接调用compareTo会导致ClassCastException的情况。由于此异常是意外的,因此我们决定添加可分配性检查。

要解决此问题,您可以使用<=>代替您已找到的内容。是的,它们仍然通过DefaultTypeTransformation,因此您可以比较例如int和long。如果你不想要那个,那么直接调用compareTo就可以了。如果我误解了你并且你想要实际上有平等,那么你当然应该称之为等于。