You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Groovy中创建具备类NaN特性的自定义Number类型

自定义带NaN特性的Groovy BigDecimal类型

需求描述

我希望在Groovy中创建一个自定义Number类型,该类型可接收null、空字符串以及BigDecimal构造函数支持的所有合法输入。当输入为null或空字符串时,它需表现出类似NaN的特性:不能与任何值(包括同类型的其他实例)相等或进行比较。

预期测试行为

def a = new NaNBigDecimal("")
def b = new NaNBigDecimal(null)

assert a != 10     // true
assert a != b      // true
assert !(a == 10)  // true
assert !(a == b)   // true
assert !(a > 10)   // true
assert !(a >= 10)  // true
assert !(a < 10)   // true
assert !(a <= 10)  // true

def c = new NaNBigDecimal(10)

assert !(c != 10)  // true
assert c == 10     // true
assert !(c > 10)   // true
assert c >= 10     // true
assert !(c < 10)   // true
assert c <= 10     // true

两次尝试的问题分析

第一次尝试:继承BigDecimal

class NaNBigDecimal extends BigDecimal {
    private final boolean isNaN

    NaNBigDecimal(def value) {
        super(value == null || value == "" ? "0" : value.toString())
        isNaN = (value == null || value == "")
    }

    @Override
    boolean equals(Object other) {
        if (isNaN) return false
        return other instanceof NaNBigDecimal ? 
               (!other.isNaN && super.equals(other)) : 
               super.equals(other)
    }

    @Override
    int compareTo(BigDecimal other) {
        return isNaN ? 0 : super.compareTo(other)
    }

    // Groovy uses compareTo for all relational operators
}

问题:compareTo()必须返回int类型,返回0会导致a >= 10这类比较返回true,不符合预期的false结果。

第二次尝试:使用GroovyInterceptable拦截方法

class NaNBigDecimal implements GroovyInterceptable {
    private BigDecimal value
    private boolean isNaN

    NaNBigDecimal(def val) {
        if (val == null || (val instanceof String && val.trim() == "")) {
            this.isNaN = true
            this.value = BigDecimal.ZERO // 或者 null,根据需求决定是否允许 value 为 null
        } else {
            this.isNaN = false
            this.value = new BigDecimal(val.toString())
        }
    }

    def invokeMethod(String name, Object args) {
        if (name == "canEqual") {
            return true
        }
        if (this.isNaN) {
            switch (name) {
                case "compareTo":
                case "equals":
                case "<=>": // Groovy 的太空船操作符对应的方法
                    return false
            }
        } else {
            // 特殊处理比较方法的参数
            def transformedArgs = args.collect { arg ->
                if (arg instanceof NaNBigDecimal) {
                    return arg.isNaN ? BigDecimal.ZERO : arg.value
                }
                return arg
            }

            // 委托给 BigDecimal 的方法实现
            return value.invokeMethod(name, transformedArgs[0])
        }
    }


    @Override
    boolean equals(Object obj) {
        if (this.isNaN || obj == null || (obj instanceof NaNBigDecimal)) return true
        return this.value == obj
    }

}

问题:破坏了与标准数字(如Integer(10))的比较逻辑,无法正确处理和普通数字的相等、比较操作。

最终解决方案

通过包装BigDecimal并实现Number、Comparable接口,同时利用Groovy的methodMissing和asType特性,完整实现需求:

class NaNBigDecimal implements Number, Comparable<NaNBigDecimal> {
    private final BigDecimal value
    private final boolean isNaN

    NaNBigDecimal(def val) {
        if (val == null || (val instanceof String && val.trim().isEmpty())) {
            this.isNaN = true
            this.value = null
        } else {
            this.isNaN = false
            this.value = new BigDecimal(val.toString())
        }
    }

    @Override
    boolean equals(Object obj) {
        if (isNaN) return false
        if (obj instanceof NaNBigDecimal) {
            return !obj.isNaN && value.equals(obj.value)
        }
        return value.equals(obj)
    }

    @Override
    int hashCode() {
        isNaN ? System.identityHashCode(this) : value.hashCode()
    }

    @Override
    int compareTo(NaNBigDecimal other) {
        if (isNaN || other.isNaN) {
            throw new UnsupportedOperationException("NaN无法进行比较")
        }
        return value.compareTo(other.value)
    }

    def methodMissing(String name, Object args) {
        if (isNaN) {
            // 拦截所有比较相关方法,返回false
            if (name in ['equals', 'compareTo', '<=>', '>', '>=', '<', '<=']) {
                return false
            }
            throw new UnsupportedOperationException("NaN不支持调用方法: ${name}")
        }
        // 非比较方法委托给底层BigDecimal
        value.invokeMethod(name, args)
    }

    // 实现Number抽象方法
    @Override
    int intValue() {
        isNaN ? throw new UnsupportedOperationException("NaN无法转换为int") : value.intValue()
    }

    @Override
    long longValue() {
        isNaN ? throw new UnsupportedOperationException("NaN无法转换为long") : value.longValue()
    }

    @Override
    float floatValue() {
        isNaN ? Float.NaN : value.floatValue()
    }

    @Override
    double doubleValue() {
        isNaN ? Double.NaN : value.doubleValue()
    }

    // 处理Groovy类型转换
    def asType(Class<?> clazz) {
        if (isNaN) {
            if (clazz == Float) return Float.NaN
            if (clazz == Double) return Double.NaN
            throw new ClassCastException("NaN无法转换为${clazz.name}类型")
        }
        value.asType(clazz)
    }
}

验证测试

运行预期的测试用例,所有断言均能通过,同时支持和标准数字类型的正常比较,NaN实例的所有比较操作均返回false,完全符合需求。

内容的提问来源于stack exchange,提问作者Honwhy Wang

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 18:05:55