1. 程式人生 > >Scala之旅-內部類(INNER CLASSES)和複合型別(COMPOUND TYPES)

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)

為了更加清晰,我們已經明確聲明瞭 node1

node2node3 的型別為 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 的例子。