Scala - 特征

特征封装了方法和字段定义,然后可以通过将它们混合到类中来重用它们。 与类继承不同,在类继承中,每个类都必须从一个超类继承,而一个类可以混合任意数量的特征。

特征用于通过指定支持的方法的签名来定义对象类型。 Scala 还允许部分实现特征,但特征可能没有构造函数参数。

特征定义看起来就像一个类定义,只是它使用关键字trait。 以下是 trait 的基本示例语法。

语法

trait Equal {
   def isEqual(x: Any): Boolean
   def isNotEqual(x: Any): Boolean = !isEqual(x)
}

该特征由 isEqualisNotEqual 两个方法组成。 在这里,我们没有给出 isEqual 的任何实现,因为另一个方法有它的实现。 扩展特征的子类可以为未实现的方法提供实现。 所以一个 trait 与我们在 Java 中的 抽象类 非常相似。

让我们假设 trait Equal 的示例包含两个方法 isEqual()isNotEqual()。特征 Equal 包含一个实现的方法,即 isEqual() 因此当用户定义的类 Point 扩展特征 Equal ,应提供 Point 类中的 isEqual() 方法的实现。

这里需要了解Scala的两个重要方法,在下面的例子中会用到。

  • obj.isInstanceOf [Point] 检查 obj 的类型和 Point 是否相同。

  • obj.asInstanceOf [Point] 表示通过采用对象 obj 类型进行精确转换,并返回与 Point 类型相同的 obj。

尝试以下示例程序来实现特征。

示例

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 中。 以下命令用于编译和执行该程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

true
false
true

值类别和普遍特征

值类是 Scala 中避免分配运行时对象的新机制。 它包含一个只有一个 val 参数的主构造函数。 它仅包含方法 (def),不允许使用 var、val、嵌套类、特征或对象。 值类不能被另一个类扩展。 这可以通过使用 AnyVal 扩展您的值类来实现。 没有运行时开销的自定义数据类型的类型安全。

让我们以重量、身高、电子邮件、年龄等值类为例。对于所有这些示例,不需要在应用程序中分配内存。

不允许扩展特征的值类。 为了允许值类扩展特征,引入了 通用特征,它扩展了 Any

示例

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 中。 以下命令用于编译和执行该程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

它将为您提供 Wrapper 类的哈希码。

Wrapper@13

何时使用特征?

没有固定的规则,但这里有一些指导方针需要考虑 −

  • 如果该行为不会被重用,则将其设为具体类。 毕竟这不是可重用的行为。

  • 如果它可以在多个不相关的类中重用,请将其设为 trait。 只有特征可以混合到类层次结构的不同部分。

  • 如果您想在 Java 代码中继承它,请使用抽象类。

  • 如果您计划以编译的形式分发它,并且您希望外部组编写继承自它的类,您可能倾向于使用抽象类。

  • 如果效率非常重要,请倾向于使用类。