性状(Trait
)封装了方法和字段定义,然后可以通过将它们混合到类中来重用它们。不同于类继承,每个类都必须从一个超类继承,一个类可以混合任意数量的性状。
性状(Trait
)用于通过指定支持的方法的签名来定义对象类型。 Scala还允许性状部分实现,但性状(Trait
)可能没有构造函数参数。
性状(Trait
)定义看起来就像一个类定义,除了它使用关键字trait
。 以下是性状(Trait
)的基本示例语法。
语法
trait Equal {
def isEqual(x: Any): Boolean
def isNotEqual(x: Any): Boolean = !isEqual(x)
}
这个性状(Trait
)由两个方法组成:isEqual
和isNotEqual
。 在这里,还没有给出isEqual
的任何实现方式,因为另一种方法已经实现了。扩展性状的子类可以实现未实现的方法。所以性状与Java中有抽象类非常相似。
让我们假设一个性状(Trait
)的例子 - Equal
包含两个方法isEqual()
和isNotEqual()
。性状(Trait
) - Equal
包含一个实现的isEqual()
方法,所以当用户定义的类Point
扩展特征Equal
时,应该提供Point
类中的Equal
,实现isEqual()
方法。
这里需要知道Scala的两个重要方法,它们在下面的例子中使用 -
obj.isInstanceOf [Point]
- 用来检查obj
和Point
的类型是否相同。obj.asInstanceOf [Point]
- 通过取对象obj
类型来表示精确转换,并返回与Point
类型相同的obj
。
尝试以下示例程序来实现traits。
示例
trait Equal {
def isEqual(x: Any): Boolean
def isNotEqual(x: Any): Boolean = !isEqual(x)
}
class Point(xc: Int, yc: Int) extends Equal {
var x: Int = xc
var y: Int = yc
def isEqual(obj: Any) = obj.isInstanceOf[Point] && obj.asInstanceOf[Point].x == y
}
object Demo {
def main(args: Array[String]) {
val p1 = new Point(2, 3)
val p2 = new Point(2, 4)
val p3 = new Point(3, 3)
println(p1.isNotEqual(p2))
println(p1.isNotEqual(p3))
println(p1.isNotEqual(2))
}
}
将上述程序保存在源文件:Demo.scala中,使用以下命令编译和执行此程序。
D:\>scalac Demo.scala
D:\>scala Demo
true
false
true
值类和通用性状
值类是Scala中避免分配运行时对象的新机制。它包含一个具有一个val
参数的主构造函数。 它只包含方法(def
)不允许var
,val
,嵌套类,traits
或对象。值类不能被另一个类扩展。 这可以通过使用AnyVal
扩展值类来实现。没有运行时开销的自定义数据类型的类型安全。
让我们来看一些值类的例子:体重,身高,电子邮件,年龄等。对于所有这些例子,不需要在应用程序中分配内存。
值类不允许扩展traits
。 为了允许值类扩展traits
,引入了扩展Any
的通用traits
。
示例
trait Printable extends Any {
def print(): Unit = println(this)
}
class Wrapper(val underlying: Int) extends AnyVal with Printable
object Demo {
def main(args: Array[String]) {
val w = new Wrapper(3)
w.print() // actually requires instantiating a Wrapper instance
}
}
将上述程序保存在源文件:Demo.scala中,使用以下命令编译和执行此程序。
D:\>scalac Demo.scala
D:\>scala Demo
Wrapper@13
什么时候使用性状?
没有固定的规则,但这里是一些参考指导 -
- 如果行为不会重复使用,那么将其定义作为具体的类。毕竟这不是可重用的行为。
- 如果它可以在多个不相关的类中重复使用,那么可考虑定义成为一个性状。只有性状可以混合到类层次结构的不同部分。
- 如果想从Java代码继承它,使用一个抽象类。
- 如果您打算以编译形式分发,并且希望外部组可以编写继承自己的类,则可能倾向于使用抽象类。
- 如果效率非常重要,建议倾向于使用类。