4.6類路徑
4.6.1什麼是類路徑
前面我們討論過包,知道位元組碼檔案最終都會被放到和包名相匹配的樹狀結構子目錄中。例如上一節的例子:
其實類還有一種存放方式,就是可以歸檔到一個jar檔案中,jar檔案其實就是把位元組碼檔案連同子目錄一同歸檔到一個壓縮檔案中。jar檔案是使用zip格式壓縮的,我們可以使用zip程式來檢視和解壓jar檔案。其實Java自帶的類庫就是jar檔案。例如JRE安裝目錄jre/lib和jre/lib/ext下就有很多jar。我們看一下jre/lib/rt.jar的結構:
我們看到,無論是單獨存放還是歸檔jar,都有一個基目錄(黑色部分),上面2個圖的基目錄分別為:
D:\Java大失叔\workspace\BaseJava\bin
C:\Program Files\Java\jre1.8.0_261\lib
我們採用基目錄+包樹狀結構,就可以定位到某個類,例如:
D:\Java大失叔\workspace\BaseJava\bin\com\javadss\javase\ch04\PackageTest.class
C:\Program Files\Java\jre1.8.0_261\lib\java\lang\System.class
這裡的基目錄,就是類路徑,英文叫classpath。類路徑就是java編譯器或JVM用來定位類的基目錄,類路徑可以有多個,是一組路徑的集合。無論是編譯還是執行,都需要設定類路徑,類路徑的形式和作業系統相關。
在Windows環境下,採用分號(;)分隔,如果路徑中含有空格,需要用引號(“”)括起來,形式如下:
D:\Java大失叔\workspace\BaseJava\bin; “C:\Program Files\Java\jre1.8.0_261\lib”;
在Linux環境下,採用冒號(:)分隔,形式如下:
usr/local/bin:usr/dss/java/bin
4.6.2編譯
我們假設有3個類:A、B、C。main方法在C中,C中訪問了A和B,同時還訪問了java.lang.System類。它們的結構如下:
其中A和B是空類,C類的程式碼如下:
package com.javadss.javase.ch04.ccc; import com.javadss.javase.ch04.aaa.A; import com.javadss.javase.ch04.bbb.B; public class C { public static void main(String[] args) { A a = new A(); B b = new B(); System.out.println("classpath"); } }
現在我們用命令列來編譯A、B、C,還記得編譯命令嗎?編譯命令如下:
javac -d 編譯後class的路徑 原始檔
則編譯命令如下:
javac -d D:\Java大失叔\workspace\BaseJava\bin D:\Java大失叔\workspace\BaseJava\src\com\javadss\javase\ch04\aaa\A.java
javac -d D:\Java大失叔\workspace\BaseJava\bin D:\Java大失叔\workspace\BaseJava\src\com\javadss\javase\ch04\bbb\B.java
javac -d D:\Java大失叔\workspace\BaseJava\bin D:\Java大失叔\workspace\BaseJava\src\com\javadss\javase\ch04\ccc\C.java
編譯A、B的時候沒有問題,但是編譯C的時候,遇到了問題,報錯:
這是因為C類中引用了A和B,但是編譯命令中沒有指定A和B的絕對路徑,因此會報錯“程式包不存在”、“找不到符號”這些錯誤。我們可以在命令列中增加-classpath或-cp選項,設定A和B的類路徑,設定後的命令如下:
javac -cp D:\Java大失叔\workspace\BaseJava\bin -d D:\Java大失叔\workspace\BaseJava\bin D:\Java大失叔\workspace\BaseJava\src\com\javadss\javase\ch04\ccc\C.java
再次執行,編譯成功。有的同學可能要問了,C中也引用了java.lang.System類,為什麼不用設定System類的類路徑呢?這是因為System屬於JDK的類庫,javac編譯時,會預設搜尋JDK的類路徑。
當我們的程式引用了很多類,這些類分散在不同的地方,就需要把所有的類路徑都寫到命令列中,比如類路徑為:
D:\Java大失叔\workspace\BaseJava\bin;.;“C:\Program Files\Java\jre1.8.0_261\lib”;
注意,中間有一個“.”,這個表示當前目錄。當我們這樣寫的時候會導致命令列非常長,我們可以用設定環境變數classpath的方式來減少命令列的長度,設定環境變數的具體形式和作業系統有關,Windows命令格式如下:
set classpath=類路徑集合
例如:
set classpath= D:\Java大失叔\workspace\BaseJava\bin; “C:\Program Files\Java\jre1.8.0_261\lib”;
我們在命令列視窗中執行上述命令後,在視窗關閉之前,所有的編譯命令都不需要用-cp選項來設定類路徑了。網上有很多網文或教程中,都喜歡在系統環境變數中設定classpath,這是筆者不推薦的。推薦的幾種做法是
- 在命令列中用-classpath或-cp選項
- 在命令列中設定classpath環境變數
- 編寫shell指令碼,將設定classpath環境變數和編譯命令一起寫入指令碼
事實上,編譯器會按照下面方式搜尋類:
- 從JDK的類庫中搜尋
- 從當前目錄下搜尋
- 從classpath環境變數中搜尋
- 從classpath選項中搜尋
如果搜尋類的時候發現了一個以上的同一個類,就會產生編譯錯誤。
另外,編譯器還會做很多其他工作,例如編譯器在搜尋類的時候,還會檢視原始檔,如果發現被引用的類的原始檔比類檔案新,還會自動的重新編譯原始檔。
4.6.3執行
用命令列執行程式和編譯類似,我們也需要用-classpath或-cp選項指定類路徑,常用的命令格式為:
java -classpath 類路徑 包含main方法的類的完整類名
我們來執行上面的例子C類,則命令列如下:
javac -cp D:\Java大失叔\workspace\BaseJava\bin com.javadss.javase.ch04.ccc.C
同樣,對於JDK的核心類庫,我們不需要顯示的加到類路徑中。當然,我們也可以用設定classpath環境變數的方法預先設定,然後執行執行命令的時候可以不用加上-classpath選項了。
這裡需要注意一點,對於編譯器來說,總是會搜尋當前目錄(換句話說,會預設把當前目錄加入到類路徑),但是虛擬機器JVM僅僅在不設定classpath環境變數,也不加-classpath或-cp選項的時候,才會把當前目錄加入到類路徑中。