6.2 构建Rational实例
要定义Rational类,首先可以考虑一下使用者如何创建新的Rational对象。由于已经决定Rational对象是不可变的,因此我们将要求使用者在构造Rational实例时就提供所有需要的数据(也就是分子和分母)。我们从下面的设计开始:
关于这段代码,首先要注意的一点是,如果一个类没有定义体,则并不需要给出空的花括号(只要你想,当然也可以)。类名Rational后的圆括号中的标识符n和d被称作类参数(class parameter)。Scala编译器将会采集这两个类参数,并且创建一个主构造方法(primary constructor),接收同样的这两个参数。
不可变对象的设计取舍
与可变对象相比,不可变对象具有若干优势和一个潜在的劣势。首先,不可变对象通常比可变对象更容易推理,因为不可变对象没有随着时间变化而变化的复杂的状态空间。其次,可以相当自由地传递不可变对象,而对于可变对象,在传递给其他代码之前,你可能需要对其进行保护式的复制。再次,假如有两个并发的线程同时访问某个不可变对象,则它们没有机会在对象被正确构造以后破坏其状态,因为没有线程可以改变某个不可变对象的状态。最后,不可变对象可以被安全地用作哈希表里的键。举例来说,如果某个可变对象在被添加到HashSet以后改变了,则当你下次再检索该HashSet的时候,可能就找不到这个对象了。
不可变对象的主要劣势是它有时候需要复制一个大的对象图,而实际上也许一个局部的更新就能满足要求。在某些场景下,不可变对象可能用起来比较别扭,同时会带来性能瓶颈。因此,类库对于不可变的类也提供可变的版本这样的做法并不罕见。例如,StringBuilder类就是对不可变的String类的一个可变的替代。我们将在第16章更详细地介绍Scala中可变对象的设计。
注意
这个Rational示例突出显示了Java和Scala的一个区别。在Java中,类有构造方法,构造方法可以接收参数;而在Scala中,类可以直接接收参数,且Scala的表示法更为精简(类定义体内可以直接使用类参数,不需要定义字段并编写将构造方法参数赋值给字段的代码)。这样可以大幅度节省样板代码,尤其是对小型的类而言。
Scala编译器会将你在类定义体中给出的非字段或方法定义的代码编译进类的主构造方法中。举例来说,可以像这样来打印一条调试消息:
对于这段代码,Scala编译器会将println调用放在Rational类的主构造方法中。这样一来,每当你创建一个新的Rational实例时,都会触发println打印出相应的调试消息:
当你实例化那些接收参数的类(如Rational类)时,可以选择不写new关键字。这样的代码编写方式被称作“通用应用方法”(universal apply method)。例如: