在 Groovy 中,为什么扩展 Comparable 的接口的“==”行为会发生变化?

2024-03-30

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

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

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

我创建了一个 SSCCE 来演示这种行为here https://groovyconsole.appspot.com/script/5705097937944576.

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

// 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讨论 https://jira.codehaus.org/browse/GROOVY-3364Poundex 链接到下面。

来自格罗维默认类型转换类 https://github.com/groovy/groovy-core/blob/master/src/main/org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.java#L557,用于处理相等/比较检查,我假设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));
}

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

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

  1. 我该如何解决这个问题?
  2. 这背后的理由是什么?这是一个错误还是一个设计功能?是否有任何理由为什么一个公共超类的两个子类不应该相互比较?

如果现有的话,为什么 Comparable 用于 == 的答案很简单。这是因为BigDecimal。如果你用“1.0”和“1.00”创建一个 BigDecimal(使用字符串而不是双精度数!),你会得到两个根据 equals 不相等的 BigDecimal,因为它们没有相同的标度。但就值而言,它们是相等的,这就是为什么compareTo 会将它们视为相等的原因。

那么当然还有Groovy-4046 http://jira.codehaus.org/browse/GROOVY-4046,它显示了直接调用compareTo将导致ClassCastException的情况。由于此异常在这里是意外的,我们决定添加可分配性检查。

为了解决这个问题,你可以使用<=>而不是你已经找到的。是的,他们仍然经历DefaultTypeTransformation例如,您可以比较 int 和 long。如果您也不希望这样,那么直接调用compareTo就是正确的方法。如果我误解了你,而你实际上想要 equals,那么你当然应该调用 equals。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 Groovy 中,为什么扩展 Comparable 的接口的“==”行为会发生变化? 的相关文章

随机推荐

  • 如何使用另一个数据库(非默认数据库)中的 auth_user?

    我有两个数据库 默认数据库和示例数据库 我想用auth user来自示例数据库而不是来自默认数据库的表 默认情况下 它是从默认数据库获取的 我想在我的模型文件中指定它 以便我可以在我的视图中访问 这怎么可能 DATABASES defaul
  • 在Python中创建饼图

    我已经创建了饼图 但现在我正在使用一系列单元格 如下所示 chart3 add series name Pie data categories Pivots A 3 A 10 values Pivots F 3 F 10 这为我提供了一个饼
  • 按列对多维关联数组进行排序并保留第一级键

    我有一个看起来像这样的数组 this gt wordswithdata team1 gt points gt 10 players gt team2 gt points gt 23 players gt 我想按照每支球队的得分从高到低对球队
  • 新的 ms botbuilder 直线语音是否适合呼叫中心场景?

    MS 最近推出了直接语音通道以及一些供 Web 前端使用它的示例 但我想知道它是否适合在使用某些 SIP 或 twilio 电话等服务的呼叫中心场景中使用 如果是这样 我想看看一些文档如何使用直线语音 api 并将其连接到某些电话 我已经创
  • Linux 上的 cp + git 基准测试与 Linux 上的基准测试Windows:为什么会有这样的差异?

    我用这个创建了大量文件Python脚本 https gist github com nowox fd62b89b69ea730f3dbd0969e7693fbe我主要用它来对 Git 进行基准测试 结果非常令人惊讶 尤其是 Windows
  • 如何更改 ASP.NET Core API 中的默认控制器和操作?

    我正在创建一个 ASP NET Core API 应用程序 目前 当创建一个新项目时 会有一个名为 Values 的控制器 默认情况下 API 会在您运行时打开它 因此 我删除了该控制器并添加了一个名为 Intro 的新控制器 并在其中添加
  • pyodbc rowcount 只返回 -1

    行计数如何工作 我正在使用 pyodbc 它总是返回 1 return query conn query db param query q params print return query rowcount def query db pa
  • MSBuild 构建前步骤

    我昨天问了关于获得AfterBuild工作并能够通过将其放置在最底部来使其工作Project部分 MSBuild AfterBuild 步骤 https stackoverflow com q 26760052 2642059 我在不同的项
  • 无法在 websphere 8.5 上启动应用程序,但在版本 7 上运行

    我遇到了一个特殊的问题 我有一个包含 ejb jar 的 Ear 应用程序在 websphere 7 上独立运行 我下载了 8 5 试用版 创建了一个垂直集群并在其上安装了应用程序 但该应用程序在 8 5 版本上尚未启动 每次我尝试启动它时
  • 如何删除头部?

    我错误地推送了一些文件 它在主存储库中显示了不同的头 我怎样才能删除那个头 您可以通过编辑您的文件来启用 mq 扩展 hgrc文件 确保存在以下行 extensions mq 之后 您可以 剥离 特定修订版 将其删除 这样您就只有一个头 h
  • 测试依赖于 NUnit 的常用功能

    我有一些初始化代码来使用我的 API 初始化可能会失败 我想在 NUnit 测试中测试它 初始化之后就可以使用API 了 我也在测试 API 但我所有的测试方法都将使用相同的 通用的初始化代码 我理想的情况是这种行为 运行初始化测试 如果
  • Java 8 流 - 超时?

    我想循环一个巨大的数组并执行一组需要很长时间的复杂指令 但是 如果超过 30 秒 我希望它放弃 ex final long start System currentTimeMillis myDataStructure stream whil
  • Swift 数字和 CGFloat(CGPoint、CGRect 等)

    我发现 Swift 数字特别笨拙 就像现实生活中经常发生的那样 我必须与 Cocoa Touch 就 CGRect 和 CGPoint 进行交流 例如 因为我们正在谈论某事frame or bounds CGFloat 与 Double 考
  • 尝试在 Maven 中构建具有嵌入式依赖项的 OSGi 包。似乎无法从 BND 类路径中排除传递依赖项

    基本上 我的 Web 服务必须可部署为单个 OSGi jar 包 所以 该包必须包含所有编译和运行时 Maven 依赖项 它还必须包含依赖于这些依赖项的所有非可选依赖项 即传递依赖项 我正在尝试使用 maven bundle plugin
  • 使用 java.util.TimeZone 查找 DST 转换时间戳

    是否可以使用 Java Calendar Date TimeZone API 获取上一个和下一个 DST 转换时间戳 With Joda Time我可以写 DateMidnight today new DateMidnight 2009 2
  • 无法在浏览器中显示希腊字母

    我正在使用 html 和 css 开发一个网站 但我看不到希腊字母 相反 我只看到符号 我的 html 文件中有以下行 我也尝试过
  • Backbone.js 控制器中的默认路由?

    我想为我的backbone js 控制器设置默认路由 目前我是这样做的 class DealSearchController extends Backbone Controller routes list showListView phot
  • 将 HSB/HSV 颜色转换为 HSL

    如何将 HSB 颜色转换为 HSL Photoshop 在其颜色选择器中显示 HSB 颜色 HSB 颜色不能在 CSS 中使用 但 HSL 可以 我尝试了这个JS function hsb2hsl h s b return h h s s
  • 如何创建 WPF 圆角容器?

    我们正在创建一个 XBAP 应用程序 我们需要在单个页面的各个位置具有圆角 并且我们希望有一个 WPF 圆角容器来在其中放置一堆其他元素 有人对我们如何最好地实现这一目标有一些建议或示例代码吗 是使用样式还是创建自定义控件 您不需要自定义控
  • 在 Groovy 中,为什么扩展 Comparable 的接口的“==”行为会发生变化?

    我正在尝试在 Groovy 中开发一个项目 我发现我的一些测试以一种奇怪的方式失败 我有一个界面Version extends Comparable