套件(Package)


到目前為止所撰寫的Scala範例程式,都可以寫在一個.scala的檔案中,使用scala指令以直譯的方式來執行,.scala中可以包括數個類別定義,也可以包括陳述句,這方便你快速撰寫一些簡單類別或程式。

一個應用程式中會有多個類別彼此合作,也有可能由多個團隊共同分工,完成應用程式的某些功能塊,再組合在一起。一旦你程式開始要好好地管理類別,你就要為類別分門別類,而不再是一個.scala檔案從頭寫到尾,你要有個分門別類管理類別的方式,無論是實體檔案上的分類管理,或是程式邏輯上的分類管理!

在Scala中使用套件(Package)機制來管理類別,舉個例子來說,你要將Point類別放在cc.openhome套件中:
  • Point.scala
package cc.openhome
class Point(val x: Int, val y: Int)

若要以直譯的方式來使用這個類別,則以上的內容必須存成一個Point.scala,並放在cc資料夾的openhome資料夾中,例如:
|workspace|
          Main.scala
          |cc|
             |openhome|
                      |Point.scala

接著你可以如下寫個.scala來直譯執行,例如Main.scala:
  • Main.scala
val x = new cc.openhome.Point(1, 1)

注意!套件名稱與類別名稱會結合為完全吻合名稱(Fully-qualified name),以上例來說,類別的完全吻合名稱就是cc.openhome.Point。直譯時,使用scala Main.scala,scala會去尋找對應套件設定的資料夾下,是否有對應的.scala檔案。

你可以使用scalac編譯出.class, 例如scalac Point.scala,這會為你自動建立cc資料夾與openhome子資料夾,然後將Point.class置於openhome資料夾中,接著你在 執行scala指令時,就可以使用-classpath或-cp指定類別路徑來使用編譯好的.class。

總而言之,Scala套件管理不僅是語法層面的,也是實體檔案管理的一種方式,套件名稱對應至實體資料夾名稱,類別必須放在對應的資料夾之中。

事實上,之前的Point.scala的撰寫方式,其實是以下撰寫方式的語法蜜糖:
package cc {
package openhome {
class Point(val x: Int, val y: Int)
  }
}

由於cc與openhome套件之間,並沒有別的類別定義,所以也可以撰寫為以下的方式:
package cc.openhome {
class Point(val x: Int, val y: Int)
}

Scala也允許你在同一個.scala中巢狀套件,例如:
  • Demo.scala
package cc {
package openhome {
class Point(val x: Int, val y: Int)
}
package drawing {
class Cricle(val x: Int, val y: Int, val r: Int) {
// 不需要寫 cc.openhome.Point
val center = new openhome.Point(x, y)
}
}
}

如果你在同一個.scala中巢狀套件,那麼使用當中類別的方式是使用scalac將之編譯為.class,scalac會根據當中的套件資訊建立對應的資料夾,並將.class置入對應的資料夾中,上面的例子中,scalac的結果如下:
|workspace|
          Demo.scala
          |cc|
             |openhome|
                      |Point.class
             |drawing|
                      |Circle.class

注意到在上例中,在cc.drawing中的類別在使用到cc.openhome.Point類別時,並不用真的寫cc.openhome.Point,而只要寫openhome.Point,這是因為cc.drawing與cc.openhome的上一層套件都是cc,因此可以省略(你要完整寫出cc.openhome.Point也是可以的)。

但下面這個範例,使用的是哪個Point類別定義呢?
package cc {
package openhome {
class Point(val x: Int, val y: Int)
}
package drawing {
class Cricle(val x: Int, val y: Int, val r: Int) {
// 這個使用的是 cc.openhome.Point
val center = new openhome.Point(x, y)
}
}
}

package openhome {
class Point(val x: Int, val y: Int)
}

如果你想要使用的,真的就是從最上層套件開始的openhome.Point,而不是cc.openhome.Point,則可以使用_root_來指定,例如:
package cc {
package openhome {
class Point(val x: Int, val y: Int)
}
package drawing {
class Cricle(val x: Int, val y: Int, val r: Int) {
// 這個使用的是 openhome.Point
val center = new _root_.openhome.Point(x, y)
}
}
}

package openhome {
class Point(val x: Int, val y: Int)
}