淺談為什么java命令運行class文件出現異常
摘要
相信每一個初學者學習Java時,剛開始練習編程Java都會寫一個HelloWord程序,然后會按照書本上那樣用命令行去編譯,解釋Java程序,或許當時你執行成功了;可是當你進一步學習更多的知識的時候,學到了用包命名空間來解決類名沖突后,你寫的程序會更加完善健壯,而你此時還是利用初學時的方法去編譯解釋這個程序錯誤時,可能會遇到這個錯誤: Exception in thread “main” java.lang.NoClassDefFoundError: 。
今天呢我就淺談一下對于這個異常的發生的原因和解決方案。我相信你看過之后會更加理解包在Java中的存在的意義!
舉例
源程序如下:
package com.study.mengyi.access;
/**
- 用于測試在dos下利用java命令行的形式運行程序
- @ClassName TestDosRunning
- @Description
- @Author Meng Yi
- @Date 2017年7月18日 上午11:01:37
*/
public class TestDosRunning {
public static void main(String[] args){
System.out.println("Code is running!");
}
}</code></pre>
當然你如果用IDE來運行程序,就不會出現開頭出現的異常,因為開發工具已經幫你解決好了,這里先不細說,接下來我們在dos下面cd到源文件的路徑下,用javac 編譯這個源程序。

如上圖,若javac 命令后沒有出現報錯,說明我們的路徑正確,編譯成功,之后我們一般都會直接在當前目錄下進行 java class文件(class文件為編譯后的文件),例如本例 java TestDosRunning ,然后當我們敲下這個命令運行后,發現會報錯!
Exception in thread "main" java.lang.NoClassDefFoundError: TestDosRunning (wrong name: com/study/mengyi/access/TestDosRunning)
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:800)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:449)
at java.net.URLClassLoader.access$100(URLClassLoader.java:71)
at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
at java.lang.ClassLoader.loadClass(ClassLoader.java:425)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:482)</code></pre>
上述異常的說的是:在主線程中存在一個異常:沒有找到要運行的類文件(即class文件)。Wait,我就是在當前目錄下進行運行的啊,文件名也對啊(注意大小寫),怎么會找不到呢? 百度一下吧,網上說的亂七八糟,密密麻麻的說了一堆了,你覺得如果是一個新手,他能認真看嘛,為什么不用自己話來解答這個問題呢?
分析
既然報錯說的是類文件沒有找到,所以我們應該第一時間想到的是ClassPath**類加載路徑**,該變量指示JVM在執行會在這個路徑上面找到我們要運行的class文件,在之前呢,必須手動設置ClassPath,但是對于新手來說,設立這個無異是件麻煩的事情,所以Sun公司將Java2中JDk改造了更加聰明了一點,就是我們在設置好Java運行環境后,就不用再設置ClassPath變量,JDK會智能的找到類加載路徑。而且默認的classpath會從當前目錄中尋找class文件,這樣說來,類加載的路徑是對的。所以排除!
難道我們的訪問方式有問題?是的。首先看這個例子:
/**
- 用于測試在dos下利用java命令行的形式運行程序
- @ClassName TestDosRunning
- @Description
- @Author Meng Yi
- @Date 2017年7月18日 上午11:01:37
*/
public class TestDosRunning {
public static void main(String[] args){
System.out.println("Code is running!");
}
}</code></pre> 
What?這個怎么行? 仔細對比與之前例子發現這個程序沒有寫包名 ,這也是為什么我剛學Java時,dos可以運行,而我用包之后怎么就不行了?
我們先說下什么包? 包是Java中為了解決命名沖突而設置的,包內含有一組類,這些類共同組成類庫,包的命名方式采用域名倒著寫,因為域名在全網是唯一的,所以這樣我們創建的包名也就是唯一的,所以就不會存在類命名沖突問題。 這樣就導致了一個 類的在JVM中必須是全類名,即包含前綴包名,這樣解析器就可以正確的加載類,而不會出現歧義,所以我們在用java 執行命令時必須指定類的全類名。
不寫包名的話,意味著這是默認包,虛擬機會把所有的不寫包名的類規整在一起組成默認包,所以不寫包名情況的下,java TestDosRunning可以被解釋器識別并執行。
好了既然,我們理解了這樣的方式,我們就用最開始的含包的程序檢驗一下是不是這個理,let’s go!

what?我按全類名來運行了,怎么還不行啊?
上面說到了包名采用了域名倒置的寫法,這樣的做法就是要做到有層次,雖然包名能唯一表示一個類,但是類文件始終都是要存儲在操作系統中,而在操作系統是就是靠文件目錄結構來解決命名沖突的,通過目錄層次分劃即可區分開不同的類,所以package名稱被分解為機器上的一個目錄。
Java解析器的運行過程就是:首先,從classpath環境變量中類的各個存放路徑,它們都為class文件的根目錄。從根目錄開始,解釋器根據類的包名將轉換成路徑,比如 com.study.mengyi.access; 會被轉換成根目錄/com/study/mengyi/access,最后從access中讀取到要加載的類文件。
方式一
所以上面我們直接在D:\JavaLearning\ThinkInJava\src\com\study\mengyi\access目錄下進行 java TestDosRunning ,解析器會在access中尋找/com/study/mengyi/access,然而并沒有這個目錄,所以我們來手動生成一個這樣的目錄結構并把class文件復制過來檢驗一下!
最終class文件的路勁為:
D:\JavaLearning\ThinkInJava\src\com\study\mengyi\access\com\study\mengyi\access

運行成功!果然是這樣,是不是一下子就對這個問題有更深的理解了?這樣手動生成目錄太累,所以我們可以在編譯的時候這樣 javac -d . TestDosRunning.java 編譯的同時為我們生成的這樣的目錄結構。
方式二
既然解釋器會找這樣的目錄結構,我們何不利用現成的,即我們可以回退到com父目錄,在這里直接 java com.study.mengyi.access.TestDosRunning ,這時當前目錄為: D:\JavaLearning\ThinkInJava\src

經檢驗確實可以這樣!
總結
在java中類的唯一性靠包名命名空間來得到保證,這樣 在運行某個類必須冠以全類名的形式并對應好相應的目錄結構 ,這樣程序才可以正常運行。java的域名方式表示包名和包名對應操作系統的文件目錄結構真是妙哉!美哉!
來自:http://blog.csdn.net/dawn_after_dark/article/details/75303207