groovy - 在 Groovy 中,为什么 '==' 的行为对于扩展 Comparable 的接口(interface)会发生变化?

标签 groovy equality

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

如果我删除 extends Comparable<Version> Version 的一部分,我得到了预期的行为 - ==给出与 equals 相同的结果将。

我在别处读到 Groovy 代表 ==equals()除非该类实现 Comparable , 在这种情况下它委托(delegate)给 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

编辑
我想我明白为什么现在会发生这种情况,感谢 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 的实例,就像在我提供的示例中一样,比较委托(delegate)给 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));
}

在底部附近,该方法有一个将比较委托(delegate)给 compareTo 的 block 。方法,但前提是满足某些条件。在我提供的示例中,这些条件都不满足,包括 isAssignableFrom检查,因为我提供的示例类(以及我的项目中给我带来问题的代码)是 sibling ,因此不能相互分配。

我想我明白为什么现在检查失败了,但我仍然对以下事情感到困惑:
  • 我该如何解决这个问题?
  • 这背后的原因是什么?这是错误还是设计功能?有什么理由为什么一个公共(public)父类(super class)的两个子类不应该相互比较?
  • 最佳答案

    为什么 Comparable 用于 == if existing 的答案很简单。这是因为 BigDecimal。如果你用“1.0”和“1.00”制作一个BigDecimal(使用字符串而不是 double 数!)你会得到两个根据equals不相等的BigDecimal,因为它们的比例不同。但是在值(value)方面它们是相等的,这就是为什么 compareTo 会认为它们是相等的。

    那么当然还有GROOVY-4046 ,这显示了直接调用 compareTo 将导致 ClassCastException 的情况。由于这个异常在这里是意外的,我们决定添加一个可分配性检查。

    要解决这个问题,您可以使用 <=>相反,您已经找到了。是的,他们仍然通过DefaultTypeTransformation因此,您可以比较例如 int 和 long。如果您也不希望那样,那么直接调用 compareTo 是要走的路。如果我误解了你并且你想要真正拥有equals,那么你当然应该调用equals。

    关于groovy - 在 Groovy 中,为什么 '==' 的行为对于扩展 Comparable 的接口(interface)会发生变化?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28355773/

    相关文章:

    gradle - Gradle 编译文件和复制 fileTree 的工作原理是什么

    grails - 覆盖Setter&Getter Domain类

    java - 使用 TITAN DB 手动安装 gremlin 服务器

    groovy - 使用 groovy 脚本在 Soap UI 中创建项目

    java - 比较 LinkedList.contains() 中的对象

    java - 关闭和访客模式之间有显着差异吗?

    java - 如何在 Java 中比较字符串?

    ios - 核心数据。检测相同的对象?

    java - 如何在 Java 中比较字符串?

    立即窗口中的 C# == 运算符的行为与运行时不同