正如这个问题的标题所示,我希望能够为BigDecimal类型的记录属性设置精度.在我的工作中,我编写了一些代码来反序列化来自一个FTP服务器的文件,并且遇到了这个问题.

record WorldRecordDto(
    // Price can have any precision, even if undesired
    // e.g. (0.000004, 2,50, 12412414,1000100410)
    BigDecimal price
) {}

传统的Java类是我想避免的,因为它们是可变的(DTO应该是不可变的IMO),而且对于它们所做的事情来说太长了.我知道你可以在setter中修复BigDeclare的精度,但是这种方法并不真正适用于记录.当然,你可以重写构造函数,但是有没有更优雅的方法来实现这一点呢?此外,如果您正在编写大型DTO,则此解决方案是痛苦的.

我已经搜索了带有注释的解决方案,但我找不到任何有用的东西.

推荐答案

哎呀.这里有几个要讨论的主题.

杂类

Dto,不是DTO.出于同样的原因,在Java中它是DvdPlayer,而不是DVDPlayer-缩写,即使通常用全大写,在驼峰大小写时也是not全大写.

BD精度

记录是存储引用和值的容器.这不是价值itself的问题.因此,您想要的东西是做不到的,但是,您可以在构建过程中制作新的BD:

record BridgeDto(BigDecimal span) {
  public BridgeDto {
    this.span = span.setScale(2);
  }
}

这会将输入的比例增加或减少到2和returns a new BigDecimal(因此,通常,BridgeDto b = new BridgeDto(x); System.out.println(b.span() == x);将打印false!)

如果提供的值不能缩小到2(例如,因为它是14.955),则会导致您可能希望发生的异常.有其他 Select (例如,.setScale(2, RoundingMode.FLOOR)),但使用BigDecimal作为价格,然后引入静默舍入,总而言之,是愚蠢的.

这不是你定价的方式

只有3种重要的方式来表示金钱:

  1. 坚持原子性
  2. 允许亚原子性
  3. 全力以赴,有一个代表地段的值,including是哪种货币.

"原子"是指这样一种货币单位,即这种货币的"1"是银行系统为该货币所能代表的最小金额.

对于欧元、美元和许多其他货币来说,原子是"美分".对于英镑,它是便士.对于日元,它是..日元对于比特币来说,它是聪.每种货币都有原子.

我想要亚原子公司

我真的很怀疑这一点.

事情是这样的:如果你do not想要亚原子,那么you do not want 100.

想象一下下面的 idea :

你是一家银行.您允许创建共享的联合帐户,其中帐户的所有者始终拥有相同份额的帐户.亚历克斯、布伦特和卡罗琳创建了一个联合账户.

最终,欧元1.04将进入联合账户.

在这一点上,三个所有者共同决定取消联合账户.

您的银行有一个执行此操作的原始操作:"jointAccount.cancel()".它的实现与您预期的一样:帐号是释放的,或者至少标记为非活动的,并且是the funds are transferred to the joint holders.

到目前为止,很明显,对吧?这不应该给人的印象是一个人为的例子.

问题是,how?1.04不能被3整除.即使你引入亚原子也不行!

以下是几个错误答案:

  • 我们使用BigDecimal数学将1.04除以3.这会导致例外,因为您不能这样做(试试看!),因此,银行的文档被更新,列出了取消联名账户是不可能的,除非其中的金额完全可以被联名持有人的数量整除.

  • 我们使用具有设置舍入模式的BigDecimal数学将1.04除以3.这会导致1美分的出现或消失,谁知道呢.一些有进取心的灵魂想出了一种方法,与100名持有者重复建立一个联合账户,向其中转账76美分,注销,然后看着银行给每个持有者转账1整分,每次这样做都能赚到24美分.银行计算出这一点,并将代码更新为始终向下舍入.在这一点上,有人计算着与100个持有者开立一个联合账户,让它变成-99美分,取消它,然后观察每个联合持有者是如何借记0美分的,因为现在它会四舍五入.银行再次注意到了这一点,并解雇了最初决定与亚原子公司打交道的开发团队.

正确答案:

  • 授权"取消联合账户"的代码从根本上理解,"分配现金"不是可以概括为数学模型的东西,而是需要在任何系统中出现"分配一些现金"时进行定制设计的东西.jointAccount.cancel()的代码实际上将尽可能平均地分配资金/债务,然后获取剩余部分,并在帐户持有人之间随机分配(如随机数生成器中的那样).换句话说,如果亚历克斯、布伦特和卡罗琳注销了他们的欧元1.04账户,他们中的两个随机获得35美分,其中一个获得34美分.或者,银行在他们的服务条款中定义这样的情况是对银行有利的:他们都得到34美分(但如果余额是-1.04欧元,他们都会损失35美分!)

那么为什么不是BD呢?

因为这只是误导.BD的主要目的是让你代表亚原子公司.如果你认为‘任何时候任何算法涉足亚原子,那实际上都是错误的’,那么为什么要使用它呢?它只是在嘲弄你(因此,误导了任何阅读你代码的人,毕竟,如果你使用了‘主要是做Y的功能X’,人们就会假设Y是重要的,你需要a lot的 comments 来解释Y是应该避免的!

替代方案-没有亚原子

long来表示你的金钱价值.它们代表atomics,而不是标准货币.换句话说,Alex、Brent和Caroline的联合账户余额是long v = 104;.

但是..这不会正确地格式化!

但BD并没有解决这个问题.任何时候要向用户显示任何货币金额,都需要将其传递给能够执行此操作的格式化程序.String.format("€%d.%02d", v / 100, Math.abs(v % 100))美元就够了.

这感觉太低了

然后将"如何将其存储在DB中"与"如何在Java中表示"分开,并使用例如Joda-money.

外汇呢?

外汇汇率通常是一个非常长的十进制数字.对于这样的操作,获取您的值(以long为单位),将其转换为BD,将其乘以外汇汇率(也是BD),获取结果,根据代码对其进行舍入(通常,根据发生的事情和值的符号,银行倾向于向上或向下舍入,因此,您需要一组if条语句才能正确完成此操作),然后您返回到long,您可以存储、发送到其他方法、进行API调用、存储在数据库中,等等.

坚持使用BD不是解决方案(考虑到您需要舍入最终结果或找出存储/转移亚原子的方法,如果您要经历所有这些麻烦,只需使用long).

不变的DTO

那是...不对.DTO对象表示‘数据库中的行’,行通常是可变的.类应该准确地对它们所表示的事物进行建模.如果它们所代表的东西被锁定到一个您不能更改的原则(以及"数据库中的行",除非您发明了自己的数据库引擎,否则您在语义级别上可以扩展的程度受到一定程度的限制),那么您需要尽可能准确地对其进行建模,这包括如果底层概念是可变的话."首选不变性"指的是这样一种概念,即通常情况下,您可以控制事物的含义,因此构建您的应用程序时要围绕不可变性进行设计.

例如,git(版本控制系统)将所有提交视为不可变的,并相应地进行架构设计.在git架构设计中唯一可变的东西是分支名称.因此,'amend a commit'被翻译成'make an entirely new commit,given that amending is impossible as commits are immutable,then update the main branch ref to this newly created commit'.

DTO通常有一个save()的方法.如果您的DTO是不变的,那么您必须重新设计事物的工作方式.DTO是低级的:如果其中一个的API不完全符合您的喜好,那也没问题--创建第二个"层"API,它使用DTO来完成其工作.That one可以围绕不可变变量构建.

Java相关问答推荐

Spring boot:Bean和动态扩展器

H2弹簧靴试验跌落台

屏蔽字母数字代码的Java正则表达式

缩小画布比例后更改滚动窗格的内部大小

Java中如何根据Font.canDisplay方法对字符串进行分段

如何判断一个矩阵是否为有框矩阵?

我不能再在Android Studio Hedgehog上用Java语言创建新项目了吗?

当Volatile关键字真的是必要的时候?

从ActiveMQ Classic迁移到ActiveMQ Artemis需要进行哪些客户端更改?

如何在不删除Java中已有内容的情况下覆盖文件内容?

如何在Jooq中获取临时表列引用?

AbstractList保证溢出到其他方法

带有可选部分的Java DateTimeForMatter

在一行中检索字符分隔字符串的第n个值

在应用getCellFormula()时,Excel引用中的文件名始终为";[1]";使用Apache POI()

如何使用WebEnvironment.RANDOM_PORT获得第二个随机端口?

我该如何为我的类编写getter和setter方法?

Eureka客户端无法使用用户/通行证注册到Eureka服务器

这是JavaFX SceneBuilder的错误吗?

Maven创建带有特定类的Spring Boot jar和普通jar