扩展BigDecimal?

10

我有很多使用BigDecimal类的代码,但我讨厌它笨拙的接口。

我为了使用整数而减轻BigDecimal的痛苦,创建了一个帮助类,其中包含以下静态方法:

compare(BigDecimal first, int second)
divide(BigDecimal dividend, BigDecimal divisor, int scale)
divide(BigDecimal dividend, int divisor, int scale)
divide(int divident, int divisor, int scale)
multiply(BigDecimal first, BigDecimal second, int scale)
multiply(BigDecimal first, int second, int scale)
multiply(int first, int second, int scale)
zeroPad(BigDecimal value, int totalLength, int scale)
那就是我现在需要的一切,代码比以前更易读了一些。但是,我读到静态方法是一件“坏”事情,它们不遵循OO原则。
然而,如果我扩展BigDecimal,我将定义一个新类型,因此我必须重新定义所有方法来将它们与我的对象包装起来,否则我将无法使用增强方法的结果。 这似乎并不明智。
你会如何解决这个问题?

很有趣,我在我的代码中几乎完全相同的静态方法。对我来说,很疯狂BigDecimal没有内置的.divide(BigDecimal dividend, int divisor)方法。你上一次需要用小数除以什么时候?在我的代码计算中,我从来没有除法以外的除数。 (并不是说这种情况不存在,但是int作为除数似乎是最可能值得包括在标准库中的场景)。 - ryvantage
5个回答

5

我会像你一样做!

所有这类设计决策都必须基于希望使下一个接手代码的程序员更容易维护代码的愿望。这也是我们首先进行面向对象设计和组件化开发的原因。

但是,考虑到Java的语法,很难为基于数学表达式的API制定规范。我对当前BigInteger API有两个问题:

  • 它与通常的数学符号表示形式不够接近
  • 对于像add和multiply这样的对称操作,它缺乏对称性

[运算符重载是C++中我想在Java中拥有的少数功能之一]

哪个更易读?

BigInteger a = ...;
BigInteger b = ...;
BigInteger c = divide(a, b);

或者

BigInteger a = ...;
BigInteger b = ...;
BigInteger c = a.divide(b);

个人而言,我更喜欢第二个版本。

更糟糕的情况是不同基础类型的数字被包含在同一个表达式中。例如:

int a = ...;
BigInteger b = ...;
BigInteger c = divide(a, b);

或者

int a = ...;
BigInteger b = ...;
BigInteger c = BigInteger.valueOf(a).divide(b);

同时请注意,对应数学符号的微小差异在表示法中也有很大的区别:

  • (a-b)*c 被翻译为 a.subtract(b).multiply(c) 或者 multiply(subtract(a,b),c)
  • c*(a-b) 被翻译为 c.multiply(a.subtract(b)) 或者 multiply(c,subtract(a,b))

对我而言,使用静态方法表示法比“纯”面向对象的表示法更容易书写和阅读。


我想,毕竟这是最简单、最安全的方式,因为如果我包装BigDecimal,它就不能再与JDBC一起使用了。 - stivlo

4
不要扩展BigDecimal,使用一个包装类(比如你现在用的)或者像Commons Math这样的框架。Dfp看起来很符合你的需求 - org.apache.commons.math.dfp.Dfp。

我会看一下Dfp。所以一个包装类应该包含一个BigDecimal对象,而且由于它与BigDecimal无关,我必须重写所有委托给BigDecimal的方法以及我的所有方法。这种方法怎么可能更好呢?它似乎需要与扩展BigDecimal相同的努力。只是想理解一下,如果被证明是错误的,我会很高兴的。 - stivlo
1
再仔细想想,这样会更好,因为我不需要提供所有的方法,只需要我需要的一个,这样我就不会混淆MyBigDecimal和BigDecimal实例。 - stivlo
org.apache.commons.math.dfp.Dfp的链接已经失效。 - Lluis Martinez

3
请您解释一下,“我将定义一个新类型,因此必须重新定义所有方法以将它们包装在我的对象中,否则我将无法使用增强的方法结果。”的确切含义是什么?
如果您需要使用BigDecimal API的大部分方法以及自己的其他方法,则建议您从BigDecimal继承自定义类。这样,您就不需要为已经适用于您的BigDecimal方法定义包装器。这种情况是您的自定义类是BigDecimal加上其他内容。
如果您只需要从BigDecimal API中获取几个方法以及自己的其他方法,则建议您不要派生自BigDecimal。相反,您在内部使用BigDecimal,并将您自定义API公开的某些功能委托给它。这种情况是您的自定义类具有BigDecimal,但它不是BigDecimal。
然后为了包装一个返回您自定义类型的BigDecimal方法:
使用继承,您可以像以下示例那样操作:
@Override public MyBD divide(int second)
{
    return new MyBD( this.divide(second) );
}

使用委托(即组合),您可以这样做:

public MyBD divide(int second)
{
    return new MyBD( _innerBigDecimal.divide(second) );
}

使用静态帮助方法,您可以这样做: ```` 使用静态帮助方法,您可以这样做: ```
public static BigDecimal divide(BigDecimal first, int second)
{
    return first.divide(second);
}

1
例如,如果我定义了MyBigDecimal扩展BigDecimal,如果我使用在基类中定义的方法,它将返回一个BigDecimal,因此像这样的代码将无法工作:new MyBigDecimal("12.89").divide(AnotherBigDecimal).methodOfMyBigDecimal() - stivlo
好的,明白了。所以是的,你应该用自己的版本包装你需要的原始BigDecimal方法。只要注意,无论你选择哪种方式,这个“包装工作”都必须完成,无论是继承、委托,还是你正在使用的静态帮助函数! - superjos

0
如果您创建一个扩展另一个类的类(即在您的情况下是BigDecimal),子类仍具有父(超级)类的所有属性。在您的情况下,如果您编写:
public class UsableBigDecimal extends BigDecimal {
...
}

任何可用的BigDecimal对象仍然是BigDecimal的实例,因为它们是其超类。您可以像使用任何BigDecimal对象一样使用它们,包括您的静态实用程序方法。

话虽如此,静态实用程序方法在处理核心Java类时,我认为是一种相当可接受的共享公共代码的方式。


MyBigDecimal将是一个BigDecimal实例,可以像使用BigDecimal一样使用它,但反过来不行。因此,如果我在派生类上调用基类的方法,则会返回一个新的基类实例,我不能再对其使用派生类的方法(除非使用拷贝构造函数,但那会使代码更加笨重)。 - stivlo

-4

我建议你忘记这个问题和原始问题,适应BigDecimal API的使用方式。你现在所做的只是给自己制造麻烦,正如这个问题的存在所证明的那样。


假设我想使用固定格式来显示所有小数,最好的方法是覆盖 toString。最自然的方法是扩展 BigDecimal,但这也需要覆盖其构造函数,因此我更喜欢包装它。 - Lluis Martinez
@LluisMartinez 自然的方法是编写一个新的格式类。toString() 只是一个调试工具。 - user207421

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