购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

3.2 数据类的局限性

数据类的一个缺点是不提供封装。我们看到了编译器如何为数据类生成equals、hashCode和toString方法,但没有提到它还生成了一个copy方法,该方法为当前的值对象创建一个新的副本,并为新副本中的一个或多个属性设置不同于原来的值。

例如,以下代码创建了一个电子邮件地址的副本,其localPart为postmaster,并且具有和原对象相同的域:

对于很多类型来说,这非常方便。但是,当一个类对其内部表示进行抽象或在其属性之间维护不变性时,copy方法允许调用端代码直接访问值的内部状态,这可能会破坏其不变性。

让我们看一下Travelator应用中的一个抽象数据类型——Money类:

❶ 构造函数是私有的。其他类通过调用静态的Money.of方法获取Money值,该方法确保金额的精度与货币的最小单位一致。大多数货币都可以换算为100个最小单位(两位小数),但有些货币的最小单位数少一些,有些多一些。例如,日元没有最小单位,约旦第纳尔由1000菲尔组成。

of方法遵循现代Java的编码约定,它从源头上区分了具有身份标识的对象(由new运算符构造)和值(从静态方法获得,不可变)。Java的时间API(例如,LocalDate.of(2020,8,17))和集合API中最新添加的方法(例如,List. of(1,2,3)创建一个不可变列表)遵循此约定。

该类为String或int金额提供了一些方便的of方法重载。

❷ Money值基于JavaBean的约定方式公开了其金额和货币属性,尽管它实际上并不是JavaBean。

❸ equals和hashCode方法实现了值语义。

❹ toString方法返回其属性的表示,可以展示给用户,而不仅仅用于调试。

❺ Money提供了货币价值计算的操作。例如,可以将货币值相加。add方法通过直接调用构造函数(而不是使用Money.of)来构造新的Money值,因为BigDecimal. add的结果已经有了正确的精度,所以我们可以避免在Money.of中设置精度的开销。

BigDecimal.setScale方法令人困惑。尽管其方法名类似于JavaBean的属性设置器,但实际上它并不改变BigDecimal对象。与EmailAddress和Money类一样,BigDecimal是一个不可变的值类型,因此setScale会返回具有指定精度的新BigDecimal值。

Sun在Java 1.1的标准库中加入了BigDecimal类。此版本还包括第一版的JavaBeans API。围绕Beans API的大肆宣传使JavaBeans编码约定被广泛采用,即便对于像BigDecimal这种并非JavaBean的类也是如此(参见第1章)。当时并没有针对值类型的Java约定。

如今,我们避免使用set前缀为不改变调用者状态的方法命名,而是当方法返回一个对调用者的转换时,使用方法名来强调其意图。一个常见的约定是对影响单个属性的转换使用with前缀,这将使Money类中的代码变为:

在Kotlin中,可以编写扩展函数来修复此类历史问题。如果我们正在编写大量运用BigDecimal进行计算的代码,那么这样做可以提高代码的清晰度,可能是值得的:

将Money类转换为Kotlin会生成以下代码:

Kotlin类中仍然有一个主构造函数,但该构造函数现在被标记为私有的。其语法有点笨拙:我们重新格式化了翻译器生成的代码,使其更易于扫描。与EmailAddress.parse一样,静态的of工厂函数现在是带有@JvmStatic注解的伴生对象上的方法。总的来说,代码并没有比原来的Java简洁多少。

我们可以通过将它变成数据类来进一步减少代码量吗?

当我们将其Money类改为数据类时,IntelliJ会高亮显示主构造函数的private关键字并发出警告:

这是怎么回事?

Money类的实现中隐藏着一个细节,其属性之间保持着一种不变性,确保金额字段的精度等于货币字段中最小单位货币的默认位数。通过私有构造函数防止Money类之外的代码创建违反不变性的值。Money.of(BigDecimal,Currency)方法确保其不变性对于新的Money值是正确的。add方法可以保持该不变性,因为将两个具有相同精度的BigDecimal值相加会产生一个也具有相同精度的BigDecimal,所以它可以直接调用构造函数。这样,构造函数只需要为字段赋值,在知道它永远不会调用违反其不变性的参数时是安全的。

但是,数据类的copy方法始终是公开的,这样会允许调用端代码创建违反不变性的Money值。与EmailAddress不同,像Money这样的抽象数据类型不能实现为Kotlin的数据类。

如果值类型必须在其属性之间保持不变性,则不要将其定义为数据类。

我们可以使用将在后面章节中遇到的Kotlin功能使类更加简洁和方便。因此,我们暂时放下Money类,第12章将再次讨论它,对它进行全面的改进。 MUEnJ3eEJabhKymDnd7DRkQsv3YwakWOPLTVjvpMcIaxJMrXEalkE0r5eWtyTpXP

点击中间区域
呼出菜单
上一章
目录
下一章
×