朝花夕拾

不要以今天的回避来逃脱明天的责任。

Scala之旅-内部类(INNER CLASSES)和复合类型(COMPOUND TYPES)

内部类(INNER CLASSES)

Scala 中的类可以把其它类作为自己的成员。与内部类是封闭类(enclosing class)成员的 Java 语言相反,Scala 中的内部类被绑定在外部对象上。假设我们希望编译器在编译时能阻止我们,混合哪些节点属于哪个图。路径依赖类型(Path-dependent types)为此提供了一个解决办法。
为了说明这个差异,我们快速书写出图形数据类型的实现:

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 中的所有节点都必须使用来自 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)

为了更加清晰,我们已经明确声明了 node1node2node3 的类型为 graph1.Node,但是要知道编译器可以推断出它们的类型。这是因为当我们调用叫作 new Nodegraph1.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!

这里写图片描述
编译报错:Type mismatch, expected: graph1.Node, actual: graph2.Node(类型不匹配,期望:graph1.Node,实际:graph2.Node

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) {
      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
  }
}

请注意该程序不允许我们将一个节点附加到两个不同的图形中。如果我们也想除去这种限制,我们必须要修改变量节点 nodes 的类型为 Graph#Node

复合类型(COMPOUND TYPES)

有时需要表达一个对象类型是其它几个类型的子类型。在 Scala 里,可以使用复合类型来表达(compound types),复合类型是对象的交集。
假设我们有两个 traitCloneableResetable

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,但不能 reset;如果类型是 Resetable,那我们可以 reset 对象,但是就没有 clone 操作了。为了避免这种情况下的类型转换,我们可以指定 obj 的类型既是 Cloneable 又是 Resetable。这样的复合类型在 Scala 中这样写:Cloneable with Resetable
下面是更新后的函数:

def cloneAndReset(obj: Cloneable with Resetable): Cloneable = {
  //...
}

复合类型可以由多个对象类型组成,它们可以有一个单一的改良(refinement),可用于缩短现有对象成员的签名。一般形式为: A with B with C ... { refinement }
抽象类型(abstract types) 页面可以查看到如何使用 refinement 的例子。

阅读更多
个人分类: scala
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

不良信息举报

Scala之旅-内部类(INNER CLASSES)和复合类型(COMPOUND TYPES)

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭