20.3 类和trait
现在来看看类与接口在Java和Scala中的不同。这两种结构在我们设计应用时都很常用。你会看到相对于Java的类和接口,Scala的类和接口提供了更多的灵活性。
20.3.1 更加简洁的Scala类
由于Scala也是一门完全的面向对象语言,因此你可以创建类,并将其实例化生成对象。最基础的形态上,声明和实例化类的语法与Java非常类似。比如,下面是一个声明Hello类的例子:
class Hello {def sayThankYou(){println("Thanks for reading our book")}}val h = new Hello()h.sayThankYou()
getter方法和setter方法
一旦你定义的类具有了字段,这件事情就变得有意思了。你碰到过单纯只定义字段列表的Java类吗?很明显,你还需要声明一长串的getter方法、setter方法,以及恰当的构造器。多麻烦啊!除此之外,你还需要为每一个方法编写测试。在企业Java应用中,大量的代码都消耗在了这样的类中。比如下面这个简单的Student类:
public class Student {private String name;private int id;public Student(String name) {this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getId() {return id;}public void setId(int id) {this.id = id;}}
你需要手工定义构造器对所有的字段进行初始化,还要实现两个getter方法和两个setter方法。一个非常简单的类现在需要超过20行的代码才能实现!有的集成开发环境或者工具能帮你自动生成这些代码,不过你的代码库中还是需要增加大量额外的代码,而这些代码与你实际的业务逻辑并没有太大的关系。
Scala语言中构造器、getter方法以及setter方法都能隐式地生成,从而大大降低你代码中的冗余:
class Student(var name: String, var id: Int)val s = new Student("Raoul", 1) ←---- 初始化Student对象println(s.name) ←---- 取得名称,打印输出Raouls.id = 1337 ←---- 设置idprintln(s.id) ←---- 打印输出1337
在Java中,可以通过定义公共字段来获得类似的行为,但仍然需要显式地定义构造函数。Scala类为你保存模板代码。
20.3.2 Scala的trait与Java 8的接口对比
Scala还提供了另一个非常有助于抽象对象的特性,名称叫trait。它是Scala为实现Java中的接口而设计的替代品。trait中既可以定义抽象方法,也可以定义带有默认实现的方法。trait同时还支持Java中接口那样的多继承,所以你可以将它们看成与Java中接口类似的特性,它们都支持默认方法。trait中还可以包含像抽象类这样的字段,而Java的接口不支持这样的特性。那么,trait就类似于抽象类吗?显然不是,因为trait支持多继承,而抽象类不支持多继承。Java支持类型的多继承,因为一个类可以实现多个接口。现在,Java 8通过默认方法又引入了对行为的多继承,不过它依旧不支持对状态的多继承,而这恰恰是trait支持的。
为了展示Scala中的trait到底是什么样,来看一个例子。我们定义了一个名为Sized的trait,它包含一个名为size的可变字段,以及一个带有默认实现的isEmpty方法:
trait Sized{var size : Int = 0 ←---- 名为size的字段def isEmpty() = size == 0 ←---- 带默认实现的isEmpty方法}
你现在可以使用一个类在声明时构造它,下面这个例子中Empty类的size恒定为0:
class Empty extends Sized ←---- 一个继承自trait Sized的类println(new Empty().isEmpty()) ←---- 打印输出true
有一件事非常有趣,trait和Java的接口类似,也是在对象实例化时被创建(不过这依旧是一个编译时的操作)。比如,你可以创建一个Box类,动态地决定到底选择哪一个实例支持由trait Sized定义的操作:
class Boxval b1 = new Box() with Sized ←---- 在对象实例化时构建traitprintln(b1.isEmpty()) ←---- 打印输出trueval b2 = new Box()b2.isEmpty() ←---- 编译错误:因为Box类的声明并未继承Sized
如果一个类继承了多个trait,各trait中声明的方法又使用了相同的签名或者相同的字段,这时会发生什么情况?为了解决这些问题,Scala中定义了一系列限制,这些限制和之前在第13章介绍默认方法时的限制极其类似。
