创建BigDecimal代理时遇ClassCastException,求继承式代理方案
我尝试用JDK动态代理创建BigDecimal的代理类,重写equals()方法使其忽略精度(scale),但将代理对象强制转换为BigDecimal时抛出ClassCastException。原本以为代理类继承了原类,向上转型应该没问题,实际却报错。
具体代码
public class BigDecimalUtils { /** * Compares two instances of <code>BigDecimal</code> ignoring scale * @return <code>true</code> if two decimals are equal, <code>false</code> otherwise */ public static boolean areEqual(BigDecimal firstDecimal, BigDecimal secondDecimal) { return firstDecimal.compareTo(secondDecimal) == 0; } /** * Returns a <code>BigDecimal</code> proxy with an overriden <code>equals()</code> method that doesn't take scale into account * @param scaleAwareBigDecimal <code>BigDecimal</code> that needs to be proxied * @return new <code>BigDecimal</code> proxy instance */ public static BigDecimal getScaleObliviousProxy(BigDecimal scaleAwareBigDecimal) { return (BigDecimal) Proxy.newProxyInstance( scaleAwareBigDecimal.getClass().getClassLoader(), scaleAwareBigDecimal.getClass().getInterfaces(), (proxy, method, args) -> { if (isEquals(method)) { BigDecimal bigDecimalEqualsWasCalledOn = (BigDecimal) proxy; BigDecimal bigDecimalPassedToEquals = (BigDecimal) args[0]; return areEqual(bigDecimalEqualsWasCalledOn, bigDecimalPassedToEquals); } else { return invokeAsUsual(proxy, method, args); } }); } private static boolean isEquals(Method method) { return method.getName().equals("equals"); } private static Object invokeAsUsual(Object proxy, Method method, Object[] args) throws IllegalAccessException, InvocationTargetException { return method.invoke(proxy, args); } }
抛出的异常信息
java.lang.ClassCastException: class jdk.proxy1.$Proxy8 cannot be cast to class java.math.BigDecimal (jdk.proxy1.$Proxy8 is in module jdk.proxy1 of loader 'bootstrap'; java.math.BigDecimal is in module java.base of loader 'bootstrap')
尝试的修改
我试过修改代理创建时的接口参数,传入空接口数组,期望触发继承式代理,但仍出现相同异常:
public static BigDecimal getScaleObliviousProxy(BigDecimal scaleAwareBigDecimal) { return (BigDecimal) Proxy.newProxyInstance( scaleAwareBigDecimal.getClass().getClassLoader(), new Class<?>[0], // "You know what, it doesn't implement anything! Go extend" (proxy, method, args) -> {
需求
有没有办法强制使用基于继承的代理而非基于接口的代理?另外我需要实现运行时后置处理,不想使用如下匿名内部类的方式(注:这段代码实际无法编译,因为BigDecimal是final类,不能被继承):
public static BigDecimal getScaleObliviousProxy(BigDecimal scaleAwareBigDecimal) { return new BigDecimal(scaleAwareBigDecimal.toString()) { @Override public boolean equals(Object otherObject) { return otherObject instanceof BigDecimal && areEqual(this, (BigDecimal) otherObject); } }; }
核心原因
JDK动态代理只能基于接口生成代理类,无法继承普通类。生成的代理对象本质是目标类所实现接口的实例,而非目标类的子类。BigDecimal是具体类,即便它实现了Serializable、Comparable等接口,代理对象也只是这些接口的实现类,和BigDecimal没有继承关系,因此强转必然失败。
另外,BigDecimal是final类,这意味着无论是JDK代理还是CGLIB(基于继承的字节码生成库)都无法通过继承的方式修改它的方法——CGLIB也不能代理final类。
可行方案
既然无法直接修改BigDecimal的equals方法,推荐以下两种方式:
1. 直接使用自定义比较方法
在所有需要忽略精度比较的地方,直接调用BigDecimalUtils.areEqual()方法,替代原生的equals():
// 替代a.equals(b) if (BigDecimalUtils.areEqual(a, b)) { // 逻辑处理 }
2. 封装自定义包装类
创建一个包装类,内部持有BigDecimal实例,重写equals和hashCode方法以实现忽略精度的比较,同时按需委托BigDecimal的其他方法:
import java.math.BigDecimal; public class ScaleObliviousBigDecimal { private final BigDecimal delegate; public ScaleObliviousBigDecimal(BigDecimal delegate) { this.delegate = delegate; } // 按需实现需要的BigDecimal方法,示例: public BigDecimal add(BigDecimal augend) { return delegate.add(augend); } public BigDecimal subtract(BigDecimal subtrahend) { return delegate.subtract(subtrahend); } public BigDecimal multiply(BigDecimal multiplicand) { return delegate.multiply(multiplicand); } // 重写equals,忽略精度 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ScaleObliviousBigDecimal that = (ScaleObliviousBigDecimal) o; return BigDecimalUtils.areEqual(this.delegate, that.delegate); } // 重写hashCode,保证与equals逻辑一致 @Override public int hashCode() { // 去除末尾零后计算哈希值,确保相同数值不同精度的实例哈希一致 return delegate.stripTrailingZeros().hashCode(); } // 提供转换回原生BigDecimal的方法 public BigDecimal toBigDecimal() { return delegate; } }
使用时直接用这个包装类替代BigDecimal即可实现需求。
内容的提问来源于stack exchange,提问作者Sergey Zolotarev

