Groovy比较运算符重载问题

4
我正在使用Groovy构建一个分析应用程序,并需要非常宽容的数学运算符,无论数据格式如何。我通过运算符重载来实现这一点,在许多情况下改善了(在我的情况下)默认的Groovy类型灵活性。例如,我需要123.45f +“05”等于128.45f。默认情况下,Groovy会降级为字符串,结果我得到123.4505。
在大多数情况下,我的重载工作得非常好,但对于比较运算符却不行。我已经跟随了几个讨论,但是没有得到答案,我正在寻找想法。我认识到目标是重载compareTo(与lessThan之类的东西不同),但是Groovy似乎忽略了这一点,而是尝试自己智能比较-例如DefaultTypeTransformation.compareTo(Object left,Object right),但在混合类型上失败。
不幸的是,这对我来说是必须的,因为错误地比较两个值会损害整个解决方案,而我无法控制正在分析的某些数据类型(例如供应商数据结构)。
例如,我需要以下内容正常工作:
Float f = 123.45f;
String s = "0300";

Assert.assertTrue( f < s );

我有许多这样的排列组合,但是我的尝试重载包括(假设如果我能让Groovy调用它,我的JavaTypeUtil会完成我需要的工作):

// overloads on startup, trying to catch all cases
Float.metaClass.compareTo = {
    Object o -> JavaTypeUtil.compareTo(delegate, o)  }

Float.metaClass.compareTo = {
    String s -> JavaTypeUtil.compareTo(delegate, s)  }

Object.metaClass.compareTo = {
    String s -> JavaTypeUtil.compareTo(delegate, s) }

Object.metaClass.compareTo = {
    Object o -> JavaTypeUtil.compareTo(delegate, o) }

当我尝试上述测试时,没有任何一个被调用,反而出现了:
    java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Float
at java.lang.Float.compareTo(Float.java:50)
at org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.compareToWithEqualityCheck(DefaultTypeTransformation.java:585)
at org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.compareTo(DefaultTypeTransformation.java:540)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.compareTo(ScriptBytecodeAdapter.java:690)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.compareLessThan(ScriptBytecodeAdapter.java:710)
at com.modelshop.datacore.generator.GroovyMathTests.testMath(GroovyMathTests.groovy:32)

通过调试,我发现 < 运算符直接进入 ScriptBytecodeAdapter.compareLessThan(),而该函数的实现似乎忽略了此处重载的 compareTo() 函数:

在 DefaultTypeTransformations.java 的第 584 行(2.4.3 版本)中:

        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);          // <--- ***
        }

在绝望的情况下,我也试图重载compareLessThan方法,但是现在我感到困惑,我不知道是否有任何方式可以在Groovy中跳过<映射。

        Float.metaClass.compareLessThan << {
            Object right -> JavaTypeUtil.compareTo(delegate, right) < 0 }
        Float.metaClass.compareLessThan << {
            String right -> JavaTypeUtil.compareTo(delegate, right) < 0 }

有没有关于解决方法的想法?谢谢!

2个回答

3

其中一部分是您需要包含 static,像这样:

Float.metaClass.static.compareTo = { String s -> 0 }

这使得f.compareTo(s)能够工作,但是<运算符仍然无法工作。这是一个已知的限制。可以重载的唯一运算符在文档中提到。也许你可以做一个自定义AST来将所有这些运算符更改为compareTo()
但这还不是全部。尽管<=>委托给compareTo(),但f <=> s也不起作用。我认为这是因为Float没有实现Comparable<Object>Comparable<String>,只有Comparable<Float>。虽然我不确定Groovy在哪里做出决定不使用该方法,但你可以看到它不仅限于Groovy的数学类。这也不起作用。
Foo.metaClass.compareTo = { String s -> 99 }
new Foo() <=> ''

class Foo implements Comparable<Foo> {
    int compareTo(Foo o) {
        0
    }
}

我认为Groovy正在进行一些预解析验证,阻止元类编程的工作。无论它在做什么样的验证,肯定会检查实现的接口,因为这会因为不同的原因而失败。

Foo.metaClass.compareTo = { String s -> 99 }
new Foo() <=> ''

class Foo {
    int compareTo(Foo o) {
        0
    }
}

在这两个例子中,用 compareTo() 替换 <=> 是可行的。
这个问题已经 被问过 几次 之前,但我没有看到一个好的解释。你可以尝试在用户 邮件列表 上询问。我相信 Jochen 或 Cedric 能够解释原因。

谢谢。嗯,我不确定静态的含义。f.compareTo(s)实际上就像我实现的那样工作,只是<...似乎是Groovy中一个简单的修复方法,不使用重载时的本地Comparitor.compareTo。感谢您在用户论坛上的建议,我会尝试的。 - tsquared
通过“工作”,我的意思是这个Float.metaClass.static.compareTo = { String s -> 0 }; Float f = 7.0f; String s ='str'; assert f.compareTo(s)== 0,这更接近,但仍然不是你想要的。 - Keegan
1
Jochen Keegan提到,我可以给你更多细节。调用compareTo基本上是使用Java方法调用逻辑完成的,因此元类版本不可见。此外,“DefaultTypeTransformation.compareToWithEqualityCheck”将检查操作数的类型是否兼容-对于A.compareTo(B),A是B的子类。我们不得不添加这个,因为compareTo可能会抛出异常。与equals不同,compareTo不需要接受不适合的类型。人们不喜欢a<b抛出类型异常。 - blackdrag

0
我想重点是你的compareTo闭包期望一个Object;所以当你用一个String调用compareTo时,你的闭包根本没有被调用。
我只能想到以下几点:在指定闭包输入参数类型时要精确。
Float.metaClass.compareTo = { Integer n -> aStaticHelperMethod(n) }
Float.metaClass.compareTo = { String s -> aStaticHelperMethod(s) }
Float.metaClass.compareTo = { SomeOtherType o -> aStaticHelperMethod(o) }

谢谢 - 我已经尝试了所有的排列组合,包括 Float -> Object、Float -> String、Object -> Object 和 Object -> String。 - tsquared

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接