使用任何語言進行編程都有一個類似的問題,那就是如何組織代碼,具體來說,如何避免命名沖突?如何合理組織各種源文件?如何使用第三方庫?各種代碼和依賴庫如何編譯連接為一個完整的程序?
本節就來討論Java中的解決機制,具體包括包、jar包、程序的編譯與連接,從包開始。
包的概念
使用任何語言進行編程都有一個相同的問題,就是命名沖突,程序一般不全是一個人寫的,會調用系統提供的代碼、第三方庫中的代碼、項目中其他人寫的代碼等,不同的人就不同的目的可能定義同樣的類名/接口名,Java中解決這個問題的方法就是包。
即使代碼都是一個人寫的,將很多個關系不太大的類和接口都放在一起,也不便於理解和維護,Java中組織類和接口的方式也是包。
包是一個比較容易理解的概念,類似於電腦中的文件夾,正如我們在電腦中管理文件,文件放在文件夾中一樣,類和接口放在包中,為便於組織,文件夾一般是一個層次結構,包也類似。
包有包名,這個名稱以逗號分隔表示層次結構。比如說,我們之前常用的String類,就位於包java.lang下,其中java是上層包名, lang是下層包名,帶完整包名的類名稱為其完全限定名,比如String類的完全限定名為java.lang.String。Java API中所有的類和接口都位於包java或javax下,java是標准包,javax是擴展包。
接下來,我們討論包的細節,從聲明類所在的包開始。
聲明類所在的包
語法
我們之前定義類的時候沒有定義其所在的包,默認情況下,類位於默認包下,使用默認包是不建議的,文章中使用默認包只是簡單起見。
定義類的時候,應該先使用關鍵字package,聲明其包名,如下所示:
package shuo.laoma; public class Hello { //類的定義 }
以上聲明類Hello的包名為shuo.laoma,包聲明語句應該位於源代碼的最前面,前面不能有注釋外的其他語句。
包名和文件目錄結構必須匹配,如果源文件的根目錄為 E:\src\,則上面的Hello類對應的文件Hello.java,其全路徑就應該是E:\src\shuo\laoma\Hello.java。如果不匹配,Java會提示編譯錯誤。
命名沖突
為避免命名沖突,Java中命名包名的一個慣例是使用域名作為前綴,因為域名是唯一的,一般按照域名的反序來定義包名,比如,域名是:apache.org,包名就以org.apache開頭。
沒有域名的,也沒關系,使用一個其他代碼不太會用的包名即可,比如本文使用的"shuo.laoma",表示"老馬說編程"中的例子。
如果代碼需要公開給其他人用,最好有一個域名以確保唯一性,如果只是內部使用,則確保內部沒有其他代碼使用該包名即可。
組織代碼
除了避免命名沖突,包也是一種方便組織代碼的機制,一般而言,同一個項目下的所有代碼,都有一個相同的包前綴,這個前綴是唯一的,不會與其他代碼重名,在項目內部,根據不同目的再細分為子包,子包可能又會分為子包,形成層次結構,內部實現一般位於比較底層的包。
包可以方便模塊化開發,不同功能可以位於不同包內,不同開發人員負責不同的包。包也可以方便封裝,供外部使用的類可以放在包的上層,而內部的實現細節則可以放在比較底層的子包內。
通過包使用類
同一個包下的類之間互相引用是不需要包名的,可以直接使用。但如果類不在同一個包內,則必須要知道其所在的包,使用有兩種方式,一種是通過類的完全限定名,另外一種是將用到的類引入到當前類。
只有一個例外,java.lang包下的類可以直接使用,不需要引入,也不需要使用完全限定名,比如String類,System類,其他包內的類則不行。
比如說,使用Arrays類中的sort方法,通過完全限定名,可以這樣使用:
int[] arr = new int[]{1,4,2,3}; java.util.Arrays.sort(arr); System.out.println(java.util.Arrays.toString(arr));
顯然,這樣比較啰嗦,另外一種就是將該類引入到當前類,引入的關鍵字是import,import需要放在package定義之後,類定義之前,如下所示:
package shuo.laoma; import java.util.Arrays; public class Hello { public static void main(String[] args) { int[] arr = new int[]{1,4,2,3}; Arrays.sort(arr); System.out.println(Arrays.toString(arr)); } }
import時,可以一次將某個包下的所有類引入,語法是使用.*,比如,將java.util包下的所有類引入,語法是:import java.util.*,需要注意的是,這個引入不能遞歸,它只會引入java.util包下的直接類,而不會引入java.util下嵌套包內的類,比如,不會引入包java.util.zip下面的類。試圖嵌套引入的形式也是無效的,如import java.util.*.*。
在一個類內,對其他類的引用必須是唯一確定的,不能有重名的類,如果有,則通過import只能引入其中的一個類,其他同名的類則必須要使用完全限定名。
引入類是一個比較繁瑣的工作,不過,大多數Java開發環境都提供工具自動做這件事,比如,在Eclipse中,通過菜單"Source->Organize Imports"或對應的快捷鍵ctrl+shift+O就可以自動管理引入類。
包范圍可見性
前面幾節我們介紹過,對於類、變量和方法,都可以有一個可見性修飾符,public/private/protected,而上節,我們提到可以不寫修飾符。如果什麼修飾符都不寫,它的可見性范圍就是同一個包內,同一個包內的其他類可以訪問,而其他包內的類則不可以訪問。
需要說明的是,同一個包指的是同一個直接包,子包下的類並不能訪問,比如說,類shuo.laoma.Hello和shuo.laoma.inner.Test,其所在的包shuo.laoma和shuo.laoma.inner是兩個完全獨立的包,並沒有邏輯上的聯系,Hello類和Test類不能互相訪問對方的包可見性方法和屬性。
另外,需要說明的是protected修飾符,protected可見性包括包可見性,也就是說,聲明為protected,不僅表明子類可以訪問,還表明同一個包內的其他類可以訪問,即使這些類不是子類也可以。
總結來說,可見性范圍從小到大是:
private < 默認(包) < protected < public
jar包
為方便使用第三方代碼,也為了方便我們寫的代碼給其他人使用,各種程序語言大多有打包的概念,打包的一般不是源代碼,而是編譯後的代碼,打包將多個編譯後的文件打包為一個文件,方便其他程序調用。
在Java中,編譯後的一個或多個包的Java class文件可以打包為一個文件,Java中打包命令為jar,打包後的文件後綴為.jar,一般稱之為jar包。
可以使用如下方式打包,首先到編譯後的java class文件根目錄,然後運行如下命令打包:
jar -cvf <包名>.jar <最上層包名>
比如,對前面介紹的類打包,如果Hello.class位於E:\bin\shuo\laoma\Hello.class,則可以到目錄 E:\bin下,然後運行:
jar -cvf hello.jar shuo
hello.jar就是jar包,jar包其實就是一個壓縮文件,可以使用解壓縮工具打開。
Java類庫、第三方類庫都是以jar包形式提供的。如何使用jar包呢?將其加入到類路徑(classpath)中即可。類路徑是什麼呢?
程序的編譯與連接
從Java源代碼到運行的程序,有編譯和連接兩個步驟。編譯是將源代碼文件變成一種字節碼,後綴是.class的文件,這個工作一般是由javac這個命令完成的。連接是在運行時動態執行的,.class文件不能直接運行,運行的是Java虛擬機,虛擬機聽起來比較抽象,執行的就是java這個命令,這個命令解析.class文件,轉換為機器能識別的二進制代碼,然後運行,所謂連接就是根據引用到的類加載相應的字節碼並執行。
Java編譯和運行時,都需要以參數指定一個classpath,即類路徑。類路徑可以有多個,對於直接的class文件,路徑是class文件的根目錄,對於jar包,路徑是jar包的完整名稱(包括路徑和jar包名),在Windows系統中,多個路徑用分號;分隔,在其他系統中,以冒號:分隔。
在Java源代碼編譯時,Java編譯器會確定引用的每個類的完全限定名,確定的方式是根據import語句和classpath。如果import的是完全限定類名,則可以直接比較並確定。如果是模糊導入(import帶.*),則根據classpath找對應父包,再在父包下尋找是否有對應的類。如果多個模糊導入的包下都有同樣的類名,則Java會提示編譯錯誤,此時應該明確指定import哪個類。
Java運行時,會根據類的完全限定名尋找並加載類,尋找的方式就是在類路徑中尋找,如果是class文件的根目錄,則直接查看是否有對應的子目錄及文件,如果是jar文件,則首先在內存中解壓文件,然後再查看是否有對應的類。
總結來說,import是編譯時概念,用於確定完全限定名,在運行時,只根據完全限定名尋找並加載類,編譯和運行時都依賴類路徑,類路徑中的jar文件會被解壓縮用於尋找和加載類。
小結
本節介紹了Java中代碼組織的機制,包和jar包,以及程序的編譯和連接。將類和接口放在合適的具有層次結構的包內,避免命名沖突,代碼可以更為清晰,便於實現封裝和模塊化開發,通過jar包使用第三方代碼,將自身代碼打包為jar包供其他程序使用,這些都是解決復雜問題所必需的。
我們一直在說,程序主要就是對數據的操作,為表示和操作數據,我們介紹了基本類型,類以及接口,下節,我們介紹Java中表示和操作一種特殊數據的機制 - 枚舉。
----------------
未完待續,查看最新文章,敬請關注微信公眾號“老馬說編程”(掃描下方二維碼),從入門到高級,深入淺出,老馬和你一起探索Java編程及計算機技術的本質。用心寫作,原創文章,保留所有版權。
-----------
更多相關原創文章
計算機程序的思維邏輯 (21) - 內部類的本質
計算機程序的思維邏輯 (20) - 為什麼要有抽象類?
計算機程序的思維邏輯 (19) - 接口的本質
計算機程序的思維邏輯 (18) - 為什麼說繼承是把雙刃劍
計算機程序的思維邏輯 (17) - 繼承實現的基本原理
計算機程序的思維邏輯 (16) - 繼承的細節
計算機程序的思維邏輯 (15) - 初識繼承和多態
計算機程序的思維邏輯 (14) - 類的組合
計算機程序的思維邏輯 (13) - 類