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

创建BigDecimal代理时遇ClassCastException,求继承式代理方案

问题:JDK动态代理BigDecimal时类型转换失败,重写equals忽略精度

我尝试用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 12:16:26