I want to create a custom Number type in Groovy that accepts null, empty strings, and any input valid for the BigDecimal constructor. When given null or an empty string, it should behave like a special “NaN-like” value: it should not equal or compare to any other value, including another instance of the same type.
Expected behavior in test cases:def a = new NaNBigDecimal("")def b = new NaNBigDecimal(null)
assert a != 10 // trueassert a != b // trueassert !(a == 10) // trueassert !(a == b) // trueassert !(a > 10) // trueassert !(a >= 10) // trueassert !(a < 10) // trueassert !(a <= 10) // true
def c = new NaNBigDecimal(10)
assert !(c != 10) // trueassert c == 10 // trueassert !(c > 10) // trueassert c >= 10 // trueassert !(c < 10) // trueassert c <= 10 // true
Here’s my first attempt:
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}
The problem with this approach is that compareTo()
must return an int
, so a >= 10
ends up returning true
instead of the expected false
.
Here’s my second attempt. This version uses GroovyInterceptable
to intercept method calls. However, it breaks comparison with standard numbers like Integer(10)
:
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 }
}