Rational类的式样书
分数:rational number是一种可以表达为比率 的数字,这里的n和d是数字,其中d不能为零。n被称作是分子:numerator,d被称作是分母:denominator。分数的例子有: , , 和 。与浮点数相比较,分数的优势是小数部分得到了完全表达,没有舍入或估算。
51CTO编辑推荐:Scala编程语言专题
本章我们将要设计的类必须模型化分数的行为,包括允许它们执行加,减,乘还有除运算。要加两个分数,首先要获得公分母,然后才能把两个分子相加。例如,要计算 ,先把左操作数的上下部分都乘上3,右操作数的两部分都乘上2,得到了 。把两个分子相加产生结果, 。要乘两个分数,可以简单的两个分子相乘,然后两个分母相乘。因此, 得到了 ,还可以简化表示成它的“通常”形式 。除法是把右操作数分子分母调换,然后做乘法。例如 与相同,结果是 。
一个或许不怎么重要的发现是,在数学上,分数不具有可变的状态。一个分数加到另外一个分数上,产生的结果是一个新的分数。而原来的数不会被“改变”。我们将在本章设计的不可变的Rational类将秉承这一属性。每个分数将都被表示成一个Rational对象。当两个Rational对象相加时,一个新的带着累加结果的Rational对象将被创建出来。
本章还将捎带提一些Scala让你写出感觉像原生语言支持的库的方法。例如,在本章结尾你将能用Rational类这样做:
创建Rational类
- scala> val oneHalf = new Rational(1, 2)
- oneHalf: Rational = 1/2
- scala> val twoThirds = new Rational(2, 3)
- twoThirds: Rational = 2/3
- scala> (oneHalf / 7) + (1 twoThirds)
- res0: Rational = 17/42
开始设计Rational类的着手点是考虑客户程序员将如何创建一个新的Rational对象。假设我们已决定让Rational对象是不可变的,我们将需要那个客户在创建实例时提供所有需要的数据(本例中,是分子和分母)。因此,我们应该这么开始设计:
这行代码里首先应当注意到的是如果类没有主体,就不需要指定一对空的大括号(当然你如果想的话也可以)。在类名,Rational,之后括号里的n和d,被称为类参数:class parameter。Scala编译器会收集这两个类参数并创造一个带同样的两个参数的主构造器:primary constructor。
- class Rational(n: Int, d: Int)
不可变对象的权衡
不可变对象提供了若干强于可变对象的优点和一个潜在的缺点。首先,不可变对象常常比可变对象更具逻辑性,因为它们没有随着时间而变化的复杂的状态空间。其次,你可以很自由地传递不可变对象,而或许需要在把可变对象传递给其它代码之前,需要先建造个以防万一的副本。第三,没有机会能让两个同时访问不可变对象的线程破坏它合理构造的状态,因为根本没有线程可以改变不可变对象的状态。第四,不可变对象让哈希表键值更安全。比方说,如果可变对象在被放进了HashSet之后被改变,那么你下一次查找这个HashSet就找不到这个对象了。
不可变对象唯一的缺点就是它们有时需要复制很大的对象图而可变对象的更新可以在原地发生。有些情况下这会变得难以快速完成而可能产生性能瓶颈。结果,要求库提供可变替代以使其更容易在大数据结构的中间改变一些元素也并非是一件稀奇的事情。例如,类StringBuilder是不可变的String的可变替代。
注意
这个最初的Rational例子凸显了Java和Scala之间的不同。Java类具有可以带参数的构造器,而Scala类可以直接带参数。Scala的写法更简洁——类参数可以直接在类的主体中使用;没必要定义字段然后写赋值函数把构造器的参数复制到字段里。这可以潜在地节省很多固定写法,尤其是对小类来说。
Scala编译器将把你放在类内部的任何不是字段的部分或者方法定义的代码,编译进主构造器。例如,你可以像这样打印输出一条除错消息:
根据这个代码,Scala编译器将把println调用放在Rational的主构造器。因此,println调用将在每次创建一个新的Rational实例时打印这条除错信息:
- class Rational(n: Int, d: Int) {
- println("Created "+n+"/"+d)
- }
- scala> new Rational(1, 2)
- Created 1/2
- res0: Rational = Rational@a0b0f5
【相关阅读】