javac和java命令踩坑之路
相信很多人開發的時候(沒錯,說的就是我)都是使用IDE進行開發的,而對於命令編譯就不是那麼熟悉了,今天想練習一下使用命令編譯工程,然後就開始了我的踩坑之路......
在使用命令列編譯Java程式的時候,一般會使用到javac
進行編譯,java
執行編譯後的程式,如:
新建工程D:\Desktop\project
,然後在project下新建src
目錄,按照一般習慣,我們繼續新建一個com
目錄,在新建一個test
目錄,完整的路徑如下:
D:\Desktop\project\src\com\test
在這個目錄下新建Test.java
檔案,編寫如下程式碼:
public class Test{ public static void main(String[] args) { System.out.println("Hello World"); } }
在命令列中輸入如下兩條命令:
D:\Desktop\project\src\com\test>javac Test.java D:\Desktop\project\src\com\test>java Test Hello World
執行完javac
命令後,目錄下除了Test.java
檔案,還會生成Test.class
檔案,而java
命令執行的就是Test.class
檔案。
再做如下測試,回到上兩層目錄使用javac
和java
命令:
D:\Desktop\project\src\com\test>cd ../.. D:\Desktop\project\src>javac com/test/Test.java D:\Desktop\project\src>java com/test/Test 錯誤: 找不到或無法載入主類 com.test.Test D:\Desktop\project\src>java com.test.Test 錯誤: 找不到或無法載入主類 com.test.Test
???為什麼找不到載入主類???黑人問號臉
這時候得理解一下CLASSPATH
環境變數的作用了,在配置JDK環境的時候,通常會加上CLASSPATH
的配置:
.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;
這裡的意思是,.
表示當前路徑尋找執行的class檔案,找不到就去%JAVA_HOME%\lib\dt.jar
和%JAVA_HOME%\lib\tools.jar
包下去找,找到就執行,找不到就報錯誤: 找不到或無法載入主類
。按這麼理解,在D:\Desktop\project\src>
路徑下執行java
命令出錯是因為classpath
路徑不對,因為在這個目錄下執行javac com/test/Test.java
命令,它依然會在com/test/
目錄下生成Test.class
檔案,而當前目錄沒有,所以在當前目錄下執行java
命令就會報錯。
既然有了方向,那麼就可以著手進行解決了,既然是classpath
路徑有問題,那麼執行的時候指定classpath
的路徑就好了,機智如我。修改編譯命令如下,加入-classpath
引數:
D:\Desktop\project\src>java -classpath com/test/ Test Hello World D:\Desktop\project\src>java -classpath com/test com.test.Test 錯誤: 找不到或無法載入主類 com.test.Test D:\Desktop\project\src>java -classpath com/test com/test/Test 錯誤: 找不到或無法載入主類 com.test.Test
???為什麼這種操作下還是會有這個錯誤???難道還是路徑的問題???
我猜,設定classpath
後,會與後面跟的class檔案做一個路徑的拼接,如:
java -classpath com/test com/test/Test
最後展開的路徑是:
java D:\Desktop\project\src\com\test\com\test\Test
D:\Desktop\project\src\com\test\com\test\
這個路徑不存在,所以報找不到或無法載入主類
的錯誤,而如下命令:
java -classpath com/test/ Test
展開後:
java D:\Desktop\project\src\com\test\Test
classpath
的路徑是D:\Desktop\project\src\com\test\
,這個路徑下存在Test.class
,所以執行成功。
這個猜測要如何證明呢?
容我緩緩思考一下,現在先考慮一下另外一個問題:假如我要讓java com.test.Test
或java com/test/Test
執行成功該如何操作?至今這個操作還沒有成功過...
這時候想想,Java中與路徑有關的操作還有哪些?靈機一動,關鍵字import
和package
的作用這時候要顯示出來了,我的理解是這樣的:
- package:把.class的檔案所在的路徑用package表示
- import:匯入這個路徑下的class檔案
好像這麼說還是有點抽象,舉個例子,修改前面的例子,新增package
:
package com.test; public class Test{ public static void main(String[] args) { System.out.println("Hello World"); } }
下面,見證奇蹟的時候到了,輸入以下命令:
D:\Desktop\project\src>javac com/test/Test.java D:\Desktop\project\src>java com/test/Test Hello World D:\Desktop\project\src>java com.test.Test Hello World
哈哈哈哈哈哈,簡直不能更完美,再來一波操作:
D:\Desktop\project\src>java -classpath com/test Test 錯誤: 找不到或無法載入主類 Test
Orz Orz Orz Orz Orz Orz ...蒼天吶,到底是為什麼啊???難道還是路徑問題???再來!!!
D:\Desktop\project\src>java Test 錯誤: 找不到或無法載入主類 Test
.....我去面壁思過兩分鐘
......
兩分鐘過去了...
我回來了,繼續
我們來看一下,加了package
和不加package
編出來的Test.class
有什麼區別,class檔案可以使用javap
命令檢視:
D:\Desktop\project\src\com\test>javap -verbose Test.class
帶package的Test.class輸出如下:
Classfile /D:/Desktop/project/src/com/test/Test.class Last modified 2018-9-21; size 422 bytes MD5 checksum 4464a152af385c705b7b6e4bdf318e66 Compiled from "Test.java" public class com.test.Test minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref#6.#15// java/lang/Object."<init>":()V ...
不帶package的Test.class輸出如下:
Classfile /D:/Desktop/project/src/com/test/Test.class Last modified 2018-9-21; size 413 bytes MD5 checksum 0d07e3139e9025ef77df91b7869675e5 Compiled from "Test.java" public class Test minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref#6.#15// java/lang/Object."<init>":()V ...
哈哈哈哈哈哈,看到這兩個輸出,我終於知道問題所在了,優秀!!!
兩個的區別是:
- 帶package:編譯後類名為public class com.test.Test
- 不帶package:編譯後類名public class Test
這應該就能解釋為什麼java com.test.Test
執行成功,而java Test
執行失敗了
回到前面的猜測,classpath配置和class路徑的問題,預設的classpath是當前路徑,classpath會和class的路徑做拼接,如:
帶package的編譯:
# 展開後為D:\Desktop\project\src\com\test D:\Desktop\project\src>java com.test.Test Hello World # 展開後為D:\Desktop\project\src\com\test\com\test D:\Desktop\project\src>java -classpath com/test Test 錯誤: 找不到或無法載入主類 Test
不帶package的編譯:
# 展開後為D:\Desktop\project\src\com\test D:\Desktop\project\src>java -classpath com/test/ Test Hello World # 展開後為D:\Desktop\project\src\com\test\com\test D:\Desktop\project\src>java -classpath com/test com.test.Test 錯誤: 找不到或無法載入主類 com.test.Test # 在D:\Desktop\project\src\com\test路徑下執行com.test.Test.class # 不存在com.test.Test.class,只有Test.class D:\Desktop\project\src\com\test>java com.test.Test 錯誤: 找不到或無法載入主類 com.test.Test # 在D:\Desktop\project\src\com\test路徑下執行Test.class D:\Desktop\project\src\com\test>java Test Hello World # 在D:\Desktop\project\src路徑下執行com.test.Test.class,不存在 D:\Desktop\project\src>java com.test.Test 錯誤: 找不到或無法載入主類 com.test.Test
講到這裡,前面的問題應該都能解釋了,不管你們信不信,我就這麼理解了
下面講一下應用:
假如我要模仿eclipse編譯project,會建多個目錄和檔案進行編譯,src目錄放置原始碼,編譯後的class檔案編譯到classes目錄,該如何操作?
先建好目錄,目錄結構如下:
D:\Desktop\project>tree /F ├─classes │└─com │└─test ││Test.class ││ │├─bean ││Person.class ││ │└─utils │Logger.class │ └─src └─com └─test │Test.java │ ├─bean │Person.java │ └─utils Logger.java
先看一下Logger.java
的內容:
package com.test.utils; public class Logger { public static final boolean ENABLE = true; public static void e(String message) { if (ENABLE) { System.err.println(message); } } public static void d(String message) { if (ENABLE) { System.out.println(message); } } }
Person.java
的內容:
package com.test.bean; public class Person { public String name; public int age; public Person() {} public Person(String name, int age) { this.name = name; this.age = age; } }
Test.java
修改如下:
package com.test; import com.test.bean.Person; import com.test.utils.Logger; public class Test{ public static void main(String[] args) { Person person = new Person(); person.name = "Person"; person.age = 100; Logger.d("name is " + person.name); Logger.d("age is " + person.age); } }
接下來要用命令進行編譯了,javac
命令走起:
D:\Desktop\project\src>javac -d ../classes D:\Desktop\project\src\com\test\Test.java
-d
引數是指定輸出目錄,目錄得先建好,不然會有如下異常:
D:\Desktop\project\src>javac -d ../classes D:\Desktop\project\src\com\test\Test.java javac: 找不到目錄: ../classes 用法: javac <options> <source files> -help 用於列出可能的選項
javac
執行完了,接著執行java
命令:
D:\Desktop\project\src>java -classpath ../classes com.test.Test
你以為會很順利麼???duang duang duang,想不到吧,執行出現異常了:
D:\Desktop\project\src>java -classpath ../classes com.test.Test Exception in thread "main" java.lang.NoClassDefFoundError: com/test/bean/Person at com.test.Test.main(Test.java:9) Caused by: java.lang.ClassNotFoundException: com.test.bean.Person at java.net.URLClassLoader.findClass(URLClassLoader.java:381) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ... 1 more
這裡就不賣關子了,其實就是Test.java
裡匯入的Person
類和Logger
類沒有參與編譯,classes
目錄下只有com/test/Test.class
檔案,所以執行的時候發生ClassNotFoundException
的異常。
好吧,瞎扯完了,其實Person
和Logger
也是有參與編譯的,不過生成的目錄不是在classes
目錄下,而是在java檔案所在目錄:
D:\Desktop\project\src\com\test\bean\Person.class D:\Desktop\project\src\com\test\utils\Logger.class
而javac
執行的com.test.Test
是到如下目錄去查詢:
D:\Desktop\project\classes\com\test\bean\Person.class D:\Desktop\project\classes\com\test\utils\Logger.class
因為查詢不到對應的類檔案,所以報了ClassNotFoundException
的異常。
解決方法就是編譯的時候把Person.java
和Logger.java
也加上,如下(忽略每一行後面的斜槓):
D:\Desktop\project\src>javac -d ../classes D:\Desktop\project\src\com\test\Test.java \ D:\Desktop\project\src\com\test\bean\Person.java \ D:\Desktop\project\src\com\test\utils\Logger.java
使用相對路徑的寫法也可以:
D:\Desktop\project\src>javac -d ../classes com\test\Test.java \ com\test\bean\Person.java \ com\test\utils\Logger.java
有點長...可以再縮短一點,使用-sourcepath
引數進行編譯,-sourcepath
引數非常適合多檔案的編譯:
D:\Desktop\project\src>javac -d ../classes -sourcepath D:\Desktop\project\src \ D:\Desktop\project\src\com\test\Test.java
使用相對路徑:
D:\Desktop\project\src>javac -d ../classes -sourcepath D:\Desktop\project\src com\test\Test.java
好吧,還是有點長,不過這不是重點,重點是能執行!!!
現在執行程式:
D:\Desktop\project\src>java -classpath ../classes com.test.Test name is Person age is 100
完美!!!
假如我要引用第三方jar包該如何操作???
接下來看我的表演,先製作一個jar包,假設一切很順利,就是那麼自信:
- 新建工程test,路徑結構如下:
D:\Desktop\test>tree /F ├─classes ││hello.jar ││ │└─com │└─tools │Hello.class │ └─src └─com └─tools Hello.java
-
Hello.java
內容:
package com.tools; public class Hello { public static void say() { System.out.println("Hello"); } }
-
編譯
Hello.java
:
D:\Desktop\test\src>javac -d ../classes D:\Desktop\test\src\com\tools\Hello.java
-
生成
Hello.class
進行打包:
D:\Desktop\test\classes>jar cvf hello.jar com/tools/Hello.class
這樣已經生成了hello.jar
檔案
- 拷貝到project工程的libs目錄,目錄結構如下:
D:\Desktop\project>tree /F ├─classes │└─com │└─test ││Test.class ││ │├─bean ││Person.class ││ │└─utils │Logger.class │ ├─libs │hello.jar │ └─src │javac │ └─com └─test │Test.java │ ├─bean │Person.java │ └─utils Logger.java
-
修改
Test.java
,呼叫Hello
類的say
方法:
package com.test; import com.test.bean.Person; import com.test.utils.Logger; import com.tools.Hello; public class Test{ public static void main(String[] args) { Person person = new Person(); person.name = "Person"; person.age = 100; Logger.d("name is " + person.name); Logger.d("age is " + person.age); Hello.say(); } }
-
重新編譯
Test.java
,增加-classpath
引數,把hello.jar
一同編譯:
javac -d classes -classpath libs/hello.jar -sourcepath src/ src/com/test/Test.java
-
執行
com.test.Test
,-classpath
需要加上hello.jar
路徑,以;
分隔,結果如下:
D:\Desktop\project>java -classpath classes/;libs/hello.jar com.test.Test name is Person age is 100 Hello
優秀!!!
踩坑之路到此結束,收場,感謝您的觀看,我們下回再見~~
附錄:
關於javac
的用法:
在命令列視窗輸入javac
命令:
D:\Desktop\project\src>javac
輸出如下:
用法: javac <options> <source files> 其中, 可能的選項包括: -g生成所有除錯資訊 -g:none不生成任何除錯資訊 -g:{lines,vars,source}只生成某些除錯資訊 -nowarn不生成任何警告 -verbose輸出有關編譯器正在執行的操作的訊息 -deprecation輸出使用已過時的 API 的源位置 -classpath <路徑>指定查詢使用者類檔案和註釋處理程式的位置 -cp <路徑>指定查詢使用者類檔案和註釋處理程式的位置 -sourcepath <路徑>指定查詢輸入原始檔的位置 -bootclasspath <路徑>覆蓋引導類檔案的位置 -extdirs <目錄>覆蓋所安裝擴充套件的位置 -endorseddirs <目錄>覆蓋簽名的標準路徑的位置 -proc:{none,only}控制是否執行註釋處理和/或編譯。 -processor <class1>[,<class2>,<class3>...] 要執行的註釋處理程式的名稱; 繞過預設的搜尋程序 -processorpath <路徑>指定查詢註釋處理程式的位置 -parameters生成元資料以用於方法引數的反射 -d <目錄>指定放置生成的類檔案的位置 -s <目錄>指定放置生成的原始檔的位置 -h <目錄>指定放置生成的本機標標頭檔案的位置 -implicit:{none,class}指定是否為隱式引用檔案生成類檔案 -encoding <編碼>指定原始檔使用的字元編碼 -source <發行版>提供與指定發行版的源相容性 -target <發行版>生成特定 VM 版本的類檔案 -profile <配置檔案>請確保使用的 API 在指定的配置檔案中可用 -version版本資訊 -help輸出標準選項的提要 -A關鍵字[=值]傳遞給註釋處理程式的選項 -X輸出非標準選項的提要 -J<標記>直接將 <標記> 傳遞給執行時系統 -Werror出現警告時終止編譯 @<檔名>從檔案讀取選項和檔名
關於java
命令的用法:
在命令列視窗輸入java
命令:
D:\Desktop\project\src>java
輸出如下:
用法: java [-options] class [args...] (執行類) 或java [-options] -jar jarfile [args...] (執行 jar 檔案) 其中選項包括: -d32使用 32 位資料模型 (如果可用) -d64使用 64 位資料模型 (如果可用) -server選擇 "server" VM 預設 VM 是 server. -cp <目錄和 zip/jar 檔案的類搜尋路徑> -classpath <目錄和 zip/jar 檔案的類搜尋路徑> 用 ; 分隔的目錄, JAR 檔案 和 ZIP 檔案列表, 用於搜尋類檔案。 -D<名稱>=<值> 設定系統屬性 -verbose:[class|gc|jni] 啟用詳細輸出 -version輸出產品版本並退出 -version:<值> 警告: 此功能已過時, 將在 未來發行版中刪除。 需要指定的版本才能執行 -showversion輸出產品版本並繼續 -jre-restrict-search | -no-jre-restrict-search 警告: 此功能已過時, 將在 未來發行版中刪除。 在版本搜尋中包括/排除使用者專用 JRE -? -help輸出此幫助訊息 -X輸出非標準選項的幫助 -ea[:<packagename>...|:<classname>] -enableassertions[:<packagename>...|:<classname>] 按指定的粒度啟用斷言 -da[:<packagename>...|:<classname>] -disableassertions[:<packagename>...|:<classname>] 禁用具有指定粒度的斷言 -esa | -enablesystemassertions 啟用系統斷言 -dsa | -disablesystemassertions 禁用系統斷言 -agentlib:<libname>[=<選項>] 載入本機代理庫 <libname>, 例如 -agentlib:hprof 另請參閱 -agentlib:jdwp=help 和 -agentlib:hprof=help -agentpath:<pathname>[=<選項>] 按完整路徑名載入本機代理庫 -javaagent:<jarpath>[=<選項>] 載入 Java 程式語言代理, 請參閱 java.lang.instrument -splash:<imagepath> 使用指定的影象顯示啟動螢幕 有關詳細資訊, 請參閱 http://www.oracle.com/technetwork/java/javase/documentation/index.html。