使用 package 的主要用意有兩個
1. 組織我們的程式碼
2. 避免名稱衝突:比如你自己的程式有個 class A,你使用的 library 也有個 class A,那就麻煩。
Java 建議使用 domain name 的倒裝來命名你的 package。比如,昇陽的 domain name 為 sun.com,所以會以 com.sun.xxx 來當成 package 名稱。
基本上,domain name 倒裝的建議是滿好的,但卻會造成 package name 過長的情況。比如,我們常用的 Apache Log4J,它的 configure 的 package 名稱為 org.apache.log4j.config,夠長。你會希望你自己公司內部的 Java 程式使用這麼長的 package name?因此,通常,我們並不一定會如此命名。
其實,domain name 倒裝主要的因素是為避免名稱衝突,所以一般用在會 release 給不特定用戶的 library 上。若是你的程式只是自己公司內部使用,該程式不準備 release 給別人使用,這時,並不需要如此嚴謹,僅需要按照部門或是其他功能別,做適當的 package 命名即可。
關於 Java 的 package,有一點可能有一些人誤會,那就是 package 與 sub-package 的關係。比如「org.apache.log4j」與「org.apache.log4j.config」,我們有時會說後者是前者的 sub-package。
但,說 sub-package 其實並不合適,因為 Java 對於所謂的 package 與 sub-package 二者是完全無關的,也就是 p1 ,與 p1.p2 兩者可以視為是完全獨立的 package。p1 與 p1.p2 的關係,就如同 pa 與 pb 之間的關係一樣,兩者是完全獨立的,沒有所謂的 p1.p2 繼承 p1 的這件事,請大家要小心。
這個原則不僅在 source code 上有效,在編譯完後的 classfile 也同樣是成立的。
Java 使用 package 這個 keyword 宣告該檔案裡面所有 class 歸屬的 package。
要注意的是, Java 中的 package keyword 需要是該檔案的第一個有效的 statement,否則 compiler 會視為錯誤。Java 檔案只能有一個 package 的 statement。
範例:Java 中使用 package 宣告 package name。
package p1.p2;//宣告本檔案屬於 p1.p2 package class J1 ... class J2 ...
Scala 延續 Java package 的概念,所以我們可以在 Scala 繼續使用 package 的概念,但 Scala 稍微做了一些的修改。
在 Scala,我們可以繼續使用 Java package 的宣告方式,使用這種方法,在該檔案裡面宣告的 class 都隸屬在該 package 裡面,與 Java 的使用方式一模一樣。
範例:Scala 中使用 Java 方式的 package 宣告。
package p1.p2 cass S1 ... class S2 ...
Scala 的 package 命名,也可使用大括號將 package 的範圍刮起來,這種方式稱為 packaging。
範例:Scala 中使用 packaging 的方式宣告 package。
package p1.p2 { // 使用大括號,宣告 p1.p2 的範圍 class S1 class S2 }
Scala 的檔案,可以允許有多個 package,也可以在 package 中宣告 sub-package。
範例:一個檔案中有兩個 package
package p1 { // 宣告 p1 的範圍 class S1 } package p2 { // 宣告 p2 的範圍 class S2 }範例:package 裡面宣告 sub-package
package p1 { // 宣告 p1 的範圍 class S1 // S1 是在 package p1 裡面 package p2 { class S2 // S2 是在 package p1.p2 裡面 } }範例:package 裡面宣告 sub-package,若裡面只有一個 sub-package,可以省略大括號
package p1 // p1 裡面只有一個 p2,所以大括號省略 package p2 { class S2 // S2 是在 package p1.p2 裡面 }範例:package 裡面宣告 sub-package,若省略大括號,可以將 sub-package 往左移動,會比較好看
package p1 // p1 裡面只有一個 p2,所以大括號省略 package p2 { // p2 往左移,看起來猶如連續宣告兩個 package,這種連續宣告 package 的方式,稱為 chained-package clauses class S2 // S2 是在 package p1.p2 裡面 }
在上例這種 package 中,包含 sub-package 的宣告方式,Scala 允許 package 與 sub-package 間有更彈性的使用方式。
但請注意,這只是 Scala 的 syntax sugar,就如同前面提的,編譯後的 package 與 sub-package 彼此完全獨立。所以或許使用 inner-package 的名稱會比 sub-package 的名稱好,以下將使用 inner-package 這樣的名稱。
範例:inner package 可以直接使用 outer-package 的 class
package p1 { class C1 package p2 { //這個 package 實際代表 p1.p2 class C2 { val c1 = new C1 // inner-package 的 class,可以使用 outer package 的 class,而不需使用 full-path class name val c11 = new p1.C1 // 此為完整的 access 方式,上一個 statement 為 syntax sugar } } }範例:outer package 可以使用相對路徑 access inner-package 的 class
package p1 { class C1 { val c2 = new p2.C2 // 使用相對路徑 access inner package 的成員,此仍為 syntax sugar val c22 = new p1.p2.C2 // 此為完整的 access 方式 } package p2 { //這個 package 實際代表 p1.p2 class C2 } }範例:若 inner package 與 outer package 分開寫,syntax sugar 不再啟動,需使用絕對路徑 access 方式
package p1 { class C1 { val c2 = new p2.C2 // 使用相對路徑,這是錯誤的 val c22 = new p1.p2.C2 // 此為完整的 access 方式 } } package p1.p2 { class C2 { val c1 = new C1 //使用相對路徑,這是錯誤的 val c11 = new p1.C1 // 此為完整路徑,這裡才是正確的 } }上例中,package p1 與 p1.p2 回復 Java 原有的規則,彼此之間互相獨立,所以彼此都需要使用絕對路徑來 access。
Scala 定義一個東西,用來表示最外層的 package,此為 _root_,所有 top-level 的 package 都是_root_的 sub-package。
注意:_root_ 只能用來 access 最上層的 package,但不能用來 access 最外層 class。
範例
package p1 { class C1 package p2 { class C2 { val c1 = new C1 //使用相對路徑,使用到 p1.C1 val c1_top = new _root_.C1 // 使用 _root_ access 來 access 最外層的 class C1,錯誤,因為 C1 不是 _root_ 的 element val c1_top2 = new _rrot_.p2.C1 // 使用 _root_ access p2.C1 } } } class C1 package p2 { class C1 }上例中,我們希望在 p1.p2.C2 access 最外層的 p2.C1,此時需要使用 _root_。
但 _root_ 無法用來 access 最外層 package 的 class。
看到這,你是否覺得 Scala 的 package 機制已經過於複雜。若你的程式在這種想細節上玩把戲,筆者認為是不智的。
使用 package 包 inner package,再加上使用相對路徑的方式,只會讓你的頭腦變暈,建議不要這麼複雜,回歸 Java 一個 package 的寫作方式,若有需要運用別 package 的 element,再使用 import statement 就好了。當然一個檔案包含多個 package 的作法到是不需丟棄,可以好好運用。
Scala 雖然對 package 做了修正,但對於 Java 對 package name 使用 domain name 倒裝的建議,仍然維持。
沒有留言:
張貼留言