1、内部类和抽象类型成员作为对象成员
- 内部类
在Scala中,可以让类将其他类作为成员。这些内部类是封闭类的成员。在Scala中,这样的内部类绑定到外部对象。假设希望编译器在编译时阻止我们混合哪些节点属于哪个图。路径相关类型提供了解决方案。
为了说明差异,绘制了图数据类型的实现:
class Graph { class Node { var connectedNodes: List[Node] = Nil def connectTo(node: Node) { if (connectedNodes.find(node.equals).isEmpty) { connectedNodes = node :: connectedNodes } } } var nodes: List[Node] = Nil def newNode: Node = { val res = new Node nodes = res :: nodes res } }
上例将图表表示为节点列表(List[Node]
)。每个节点都有一个与其连接的其他节点的列表(connectedNodes
)。这class Node
是一个路径依赖类型,因为它嵌套在class Graph
。因此,connectedNodes
必须使用newNode
来自同一实例的所有节点创建Graph
。
val graph1: Graph = new Graph val node1: graph1.Node = graph1.newNode val node2: graph1.Node = graph1.newNode val node3: graph1.Node = graph1.newNode node1.connectTo(node2) node3.connectTo(node1)
明确宣布的类型node1
,node2
以及node3
作为graph1.Node
为清楚起见,但编译器可能推断出它。这是因为当我们调用graph1.newNode
哪些调用时new Node
,该方法正在使用Node
特定于实例的实例graph1
。
如果现在有两个图形,Scala的类型系统不允许我们将一个图形中定义的节点与另一个图形的节点混合,因为另一个图形的节点具有不同的类型。这是一个非法的程序:
val graph1: Graph = new Graph val node1: graph1.Node = graph1.newNode val node2: graph1.Node = graph1.newNode node1.connectTo(node2) // 合法的 val graph2: Graph = new Graph val node3: graph2.Node = graph2.newNode node1.connectTo(node3) // 非法的
graph1.Node
与graph2.Node不同图表
。在Scala中,这样的类型可以表示,它是写的Graph#Node
。如果希望能够连接不同图形的节点,必须通过以下方式更改初始图形实现的定义:
class Graph { class Node { var connectedNodes: List[Graph#Node] = Nil def connectTo(node: Graph#Node) { //Graph#Node与上面匹配 if (connectedNodes.find(node.equals).isEmpty) { connectedNodes = node :: connectedNodes } } } var nodes: List[Node] = Nil def newNode: Node = { val res = new Node nodes = res :: nodes res } }
- 抽象类型
抽象类型(如traits和抽象类)又可以具有抽象类型成员。这意味着具体实现定义了实际类型。这是一个例子:
trait Buffer { type T val element: T }
这里定义了一个摘要type T
。它用于描述类型element,并使之
更具体。
abstract class SeqBuffer extends Buffer { type U type T <: Seq[U] def length = element.length }
注意如何U
在上类型绑定的规范中使用另一个抽象类型T
。这class SeqBuffer
允许我们仅通过声明类型T
必须Seq[U]
是新抽象类型的子类型来仅在缓冲区中存储序列U
。
具有抽象类型成员的特征或类通常与匿名类实例一起使用。为了说明这一点,我们现在看一个程序,它处理一个引用整数列表的序列缓冲区:
abstract class IntSeqBuffer extends SeqBuffer { type U = Int } def newIntSeqBuf(elem1: Int, elem2: Int): IntSeqBuffer = new IntSeqBuffer { type T = List[U] val element = List(elem1, elem2) } val buf = newIntSeqBuf(7, 8) println("length = " + buf.length) println("content = " + buf.element)
这里工厂newIntSeqBuf
使用IntSeqBuf
(ie new IntSeqBuffer
)的匿名类实现将抽象类型T
设置为具体类型List[Int]
。
也可以将抽象类型成员转换为类的类型参数,反之亦然。这是上面代码的一个版本,它只使用类型参数:
abstract class Buffer[+T] { val element: T } abstract class SeqBuffer[U, +T <: Seq[U]] extends Buffer[T] { def length = element.length } def newIntSeqBuf(e1: Int, e2: Int): SeqBuffer[Int, Seq[Int]] = new SeqBuffer[Int, List[Int]] { val element = List(e1, e2) } val buf = newIntSeqBuf(7, 8) println("length = " + buf.length) println("content = " + buf.element)
2、复合类型
有时需要表达对象的类型是其他几种类型的子类型。在Scala中,这可以在复合类型的帮助下表达,复合类型是对象类型的交叉点。
假设我们有两个特点Cloneable
和Resetable
:
trait Cloneable extends java.lang.Cloneable { override def clone(): Cloneable = { super.clone().asInstanceOf[Cloneable] } } trait Resetable { def reset: Unit }
现在假设我们想编写一个cloneAndReset
接受对象的函数,克隆它并重置原始对象:
def cloneAndReset(obj: ?): Cloneable = { val cloned = obj.clone() obj.reset cloned }
问题出现了参数的类型obj
。如果是,Cloneable
则对象可以是clone
d,但不是reset
; 如果它是Resetable
我们可以reset
,但没有clone
操作。为了避免这种情况的类型转换,我们可以指定类型obj
既Cloneable
和Resetable
。这种复合类型在Scala中是这样写的:Cloneable with Resetable
。
更新的功能如下:
def cloneAndReset(obj: Cloneable with Resetable): Cloneable = { //... }
复合类型可以由多个对象类型组成,它们可以具有单个细化,可以用于缩小现有对象成员的签名。一般形式是:A with B with C ... { refinement }
3、自我类型
自我类型是一种声明特征必须混合到另一个特征中的方法,即使它没有直接扩展它。这使得依赖的成员可以在没有导入的情况下使用。
自我类型是一种缩小this
别名的类型或另一个标识符的方法this
。语法看起来像普通函数语法,但意味着完全不同的东西。
要在特征中使用自我类型,请写入标识符,要混合的另一个特征的类型,以及=>
(例如someIdentifier: SomeOtherTrait =>
)。
trait User { def username: String } trait Tweeter { this: User => def tweet(tweetText: String) = println(s"$username: $tweetText") } class VerifiedTweeter(val username_ : String) extends Tweeter with User { def username = s"real $username_" } val realBeyoncé = new VerifiedTweeter("Beyoncé") realBeyoncé.tweet("Just spilled my glass of lemonade")
因为this: User =>
的trait Tweeter
,现在的变量username
是在范围上的tweet
方法。这也意味着,自VerifiedTweeter
扩展以来Tweeter
,它还必须混合User
(使用with User
)。