在 Scala 中,可以将类作为成员让其他类使用。与 Java 类似的语言中,此类内部类是封闭类的成员,而在 Scala 中,此类内部类绑定到外部对象。假设我们希望编译器在编译时阻止我们混淆哪些节点属于哪个图。路径相关类型提供了解决方案。
为了说明差异,我们快速概述一下图数据类型的实现
class Graph {
class Node {
var connectedNodes: List[Node] = Nil
def connectTo(node: Node): Unit = {
if (!connectedNodes.exists(node.equals)) {
connectedNodes = node :: connectedNodes
}
}
}
var nodes: List[Node] = Nil
def newNode: Node = {
val res = new Node
nodes = res :: nodes
res
}
}
class Graph:
class Node:
var connectedNodes: List[Node] = Nil
def connectTo(node: Node): Unit =
if !connectedNodes.exists(node.equals) then
connectedNodes = node :: connectedNodes
var nodes: List[Node] = Nil
def newNode: Node =
val res = Node()
nodes = res :: nodes
res
此程序将图表示为节点列表 (List[Node]
)。每个节点都有一个连接到的其他节点列表 (connectedNodes
)。class Node
是一个路径相关类型,因为它嵌套在 class Graph
中。因此,connectedNodes
中的所有节点都必须使用来自 Graph
的同一实例的 newNode
创建。
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
以提高清晰度,但编译器本可以推断出这一点。这是因为当我们调用调用 new Node
的 graph1.newNode
时,该方法使用特定于实例 graph1
的 Node
实例。
如果我们现在有两个图,Scala 的类型系统不允许我们将一个图中定义的节点与另一个图的节点混合,因为另一个图的节点具有不同的类型。这是一个非法的程序
val graph1: Graph = new Graph
val node1: graph1.Node = graph1.newNode
val node2: graph1.Node = graph1.newNode
node1.connectTo(node2) // legal
val graph2: Graph = new Graph
val node3: graph2.Node = graph2.newNode
node1.connectTo(node3) // illegal!
类型 graph1.Node
与类型 graph2.Node
不同。在 Java 中,上一个示例程序中的最后一行将是正确的。对于两个图的节点,Java 会分配相同的类型 Graph.Node
;即 Node
前缀为类 Graph
。在 Scala 中,这样的类型也可以表示,它写为 Graph#Node
。如果我们想要连接不同图的节点,我们必须按以下方式更改初始图实现的定义
class Graph {
class Node {
var connectedNodes: List[Graph#Node] = Nil
def connectTo(node: Graph#Node): Unit = {
if (!connectedNodes.exists(node.equals)) {
connectedNodes = node :: connectedNodes
}
}
}
var nodes: List[Node] = Nil
def newNode: Node = {
val res = new Node
nodes = res :: nodes
res
}
}
class Graph:
class Node:
var connectedNodes: List[Graph#Node] = Nil
def connectTo(node: Graph#Node): Unit =
if !connectedNodes.exists(node.equals) then
connectedNodes = node :: connectedNodes
var nodes: List[Node] = Nil
def newNode: Node =
val res = Node()
nodes = res :: nodes
res