本系列文章將整理到我在GitHub上的《Java面試指南》倉庫,更多精彩內容請到我的倉庫裡檢視
https://github.com/h2pl/Java-Tutorial
喜歡的話麻煩點下Star哈
文章首發於我的個人部落格:
www.how2playlife.com
IDE是把雙刃劍,它可以什麼都幫你做了,你只要敲幾行程式碼,點幾下滑鼠,程式就跑起來了,用起來相當方便。 你不用去關心它後面做了些什麼,執行了哪些命令,基於什麼原理。然而也是這種過分的依賴往往讓人散失了最基本的技能,當到了一個沒有IDE的地方,你便覺得無從下手,給你個程式碼都不知道怎麼去跑。好比給你瓶水,你不知道怎麼開啟去喝,然後活活給渴死。 之前用慣了idea,Java檔案編譯執行的命令基本忘得一乾二淨。
IDE是把雙刃劍,它可以什麼都幫你做了,你只要敲幾行程式碼,點幾下滑鼠,程式就跑起來了,用起來相當方便。
你不用去關心它後面做了些什麼,執行了哪些命令,基於什麼原理。然而也是這種過分的依賴往往讓人散失了最基本的技能,當到了一個沒有IDE的地方,你便覺得無從下手,給你個程式碼都不知道怎麼去跑。好比給你瓶水,你不知道怎麼開啟去喝,然後活活給渴死。
之前用慣了idea,Java檔案編譯執行的命令基本忘得一乾二淨。
那好,不如我們們先來了解一下IDE的實現原理,這樣一來,即使離開IDE,我們還是知道如何執行Java程式了。
像Eclipse等java IDE是怎麼編譯和查詢java原始碼的呢?
這個無需多說,在編譯器寫入程式碼,並儲存到檔案。這個利用流來實現。
java提供了JavaCompiler,我們可以通過它來編譯java原始檔為class檔案。
可以通過Class.forName(fullClassPath)或自定義類載入器來實現。
通過上面一個查詢class,得到Class物件後,可以通過newInstance()或構造器的newInstance()得到物件。然後得到Method,最後呼叫方法,傳入相關引數即可。
示例程式碼:
public class MyIDE { public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { // 定義java程式碼,並儲存到檔案(Test.java) StringBuilder sb = new StringBuilder(); sb.append("package com.tommy.core.test.reflect;\n"); sb.append("public class Test {\n"); sb.append(" private String name;\n"); sb.append(" public Test(String name){\n"); sb.append(" this.name = name;\n"); sb.append(" System.out.println(\"hello,my name is \" + name);\n"); sb.append(" }\n"); sb.append(" public String sayHello(String name) {\n"); sb.append(" return \"hello,\" + name;\n"); sb.append(" }\n"); sb.append("}\n"); System.out.println(sb.toString()); String baseOutputDir = "F:\\output\\classes\\"; String baseDir = baseOutputDir + "com\\tommy\\core\\test\\reflect\\"; String targetJavaOutputPath = baseDir + "Test.java"; // 儲存為java檔案 FileWriter fileWriter = new FileWriter(targetJavaOutputPath); fileWriter.write(sb.toString()); fileWriter.flush(); fileWriter.close(); // 編譯為class檔案 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager manager = compiler.getStandardFileManager(null,null,null); List<File> files = new ArrayList<>(); files.add(new File(targetJavaOutputPath)); Iterable compilationUnits = manager.getJavaFileObjectsFromFiles(files); // 編譯 // 設定編譯選項,配置class檔案輸出路徑 Iterable<String> options = Arrays.asList("-d",baseOutputDir); JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, options, null, compilationUnits); // 執行編譯任務 task.call(); // 通過反射得到物件 // Class clazz = Class.forName("com.tommy.core.test.reflect.Test"); // 使用自定義的類載入器載入class Class clazz = new MyClassLoader(baseOutputDir).loadClass("com.tommy.core.test.reflect.Test"); // 得到構造器 Constructor constructor = clazz.getConstructor(String.class); // 通過構造器new一個物件 Object test = constructor.newInstance("jack.tsing"); // 得到sayHello方法 Method method = clazz.getMethod("sayHello", String.class); // 呼叫sayHello方法 String result = (String) method.invoke(test, "jack.ma"); System.out.println(result); } }
自定義類載入器程式碼:
public class MyClassLoader extends ClassLoader { private String baseDir; public MyClassLoader(String baseDir) { this.baseDir = baseDir; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { String fullClassFilePath = this.baseDir + name.replace("\\.","/") + ".class"; File classFilePath = new File(fullClassFilePath); if (classFilePath.exists()) { FileInputStream fileInputStream = null; ByteArrayOutputStream byteArrayOutputStream = null; try { fileInputStream = new FileInputStream(classFilePath); byte[] data = new byte[1024]; int len = -1; byteArrayOutputStream = new ByteArrayOutputStream(); while ((len = fileInputStream.read(data)) != -1) { byteArrayOutputStream.write(data,0,len); } return defineClass(name,byteArrayOutputStream.toByteArray(),0,byteArrayOutputStream.size()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (null != fileInputStream) { try { fileInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (null != byteArrayOutputStream) { try { byteArrayOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } return super.findClass(name); } }
注:以下紅色標記的引數在下文中有所講解。
本部分參考 https://www.cnblogs.com/xiazdong/p/3216220.html
用法: javac 其中, 可能的選項包括: -g 生成所有除錯資訊 -g:none 不生成任何除錯資訊 -g:{lines,vars,source} 只生成某些除錯資訊 -nowarn 不生成任何警告 -verbose 輸出有關編譯器正在執行的操作的訊息 -deprecation 輸出使用已過時的 API 的源位置 -classpath <路徑> 指定查詢使用者類檔案和註釋處理程式的位置 -cp <路徑> 指定查詢使用者類檔案和註釋處理程式的位置 -sourcepath <路徑> 指定查詢輸入原始檔的位置 -bootclasspath <路徑> 覆蓋引導類檔案的位置 -extdirs <目錄> 覆蓋所安裝擴充套件的位置 -endorseddirs <目錄> 覆蓋簽名的標準路徑的位置 -proc:{none,only} 控制是否執行註釋處理和/或編譯。 -processor [, , …] 要執行的註釋處理程式的名稱; 繞過預設的搜尋程式 -processorpath <路徑> 指定查詢註釋處理程式的位置 -d <目錄> 指定放置生成的類檔案的位置 -s <目錄> 指定放置生成的原始檔的位置 -implicit:{none,class} 指定是否為隱式引用檔案生成類檔案 -encoding <編碼> 指定原始檔使用的字元編碼 -source <發行版> 提供與指定發行版的源相容性 -target <發行版> 生成特定 VM 版本的類檔案 -version 版本資訊 -help 輸出標準選項的提要 -A關鍵字[=值] 傳遞給註釋處理程式的選項 -X 輸出非標準選項的提要 -J<標記> 直接將 <標記> 傳遞給執行時系統 -Werror 出現警告時終止編譯 @<檔名> 從檔案讀取選項和檔名 在詳細介紹javac命令之前,先看看這個classpath是什麼 classpath是什麼 在dos下編譯java程式,就要用到classpath這個概念,尤其是在沒有設定環境變數的時候。classpath就是存放.class等編譯後檔案的路徑。 javac:如果當前你要編譯的java檔案中引用了其它的類(比如說:繼承),但該引用類的.class檔案不在當前目錄下,這種情況下就需要在javac命令後面加上-classpath引數,通過使用以下三種型別的方法 來指導編譯器在編譯的時候去指定的路徑下查詢引用類。 (1).絕對路徑:javac -classpath c:/junit3.8.1/junit.jar Xxx.java (2).相對路徑:javac -classpath ../junit3.8.1/Junit.javr Xxx.java (3).系統變數:javac -classpath %CLASSPATH% Xxx.java (注意:%CLASSPATH%表示使用系統變數CLASSPATH的值進行查詢,這裡假設Junit.jar的路徑就包含在CLASSPATH系統變數中) IDE中的classpath 對於一個普通的Javaweb專案,一般有這樣的配置: 1 WEB-INF/classes,lib才是classpath,WEB-INF/ 是資源目錄, 客戶端不能直接訪問。 2、WEB-INF/classes目錄存放src目錄java檔案編譯之後的class檔案,xml、properties等資源配置檔案,這是一個定位資源的入口。 3、引用classpath路徑下的檔案,只需在檔名前加classpath: classpath:applicationContext-*.xml classpath:context/conf/controller.xml 4、lib和classes同屬classpath,兩者的訪問優先順序為: lib>classes。 5、classpath 和 classpath* 區別: classpath:只會到你的class路徑中查詢找檔案; classpath*:不僅包含class路徑,還包括jar檔案中(class路徑)進行查詢。 總結: (1).何時需要使用-classpath:當你要編譯或執行的類引用了其它的類,但被引用類的.class檔案不在當前目錄下時,就需要通過-classpath來引入類 (2).何時需要指定路徑:當你要編譯的類所在的目錄和你執行javac命令的目錄不是同一個目錄時,就需要指定原始檔的路徑(CLASSPATH是用來指定.class路徑的,不是用來指定.java檔案的路徑的) Java專案和Java web專案的本質區別 (看清IDE及classpath本質) 現在只是說說Java Project和Web Project,那麼二者有區別麼?回答:沒有!都是Java語言的應用,只是應用場合不同罷了,那麼他們的本質到底是什麼? 回答:編譯後路徑!虛擬機器執行的是class檔案而不是java檔案,那麼我們不管是何種專案都是寫的java檔案,怎麼就不一樣了呢?分成java和web兩種了呢? 從.classpath入手來看,這個檔案在每個專案目錄下都是存在的,很少有人開啟看吧,那麼我們就來一起看吧。這是一個XML檔案,使用文字編輯器開啟即可。 這裡展示一個web專案的.classpath Xml程式碼 <?xml version="1.0" encoding="UTF-8"?> <classpath> <classpathentry kind="src" path="src"/> <classpathentry kind="src" path="resources"/> <classpathentry kind="src" path="test"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> <classpathentry kind="lib" path="lib/servlet-api.jar"/> <classpathentry kind="lib" path="webapp/WEB-INF/lib/struts2-core-2.1.8.1.jar"/> …… <classpathentry kind="output" path="webapp/WEB-INF/classes"/> </classpath> XML文件包含一個根元素,就是classpath,類路徑,那麼這裡麵包含了什麼資訊呢?子元素是classpathentry,kind屬性區別了種 類資訊,src原始碼,con你看看後面的path就知道是JRE容器的資訊。lib是專案依賴的第三方類庫,output是src編譯後的位置。 既然是web專案,那麼就是WEB-INF/classes目錄,可能用MyEclipse的同學會說他們那裡是WebRoot或者是WebContext而不是webapp,有區別麼?回答:完全沒有! 既然看到了編譯路徑的本來面目後,還區分什麼java專案和web專案麼?回答:不區分!普通的java 專案你這樣寫就行了: ,看看Eclipse是不是這樣生成的?這個問題解決了吧。 再說說webapp目錄命名的問題,這個無所謂啊,web專案是要釋出到伺服器上的對吧,那麼伺服器讀取的是類檔案和頁面檔案吧,它不管原始檔,它也無法去理解原始檔。那麼webapp目錄的命名有何關係呢?只要讓伺服器找到不就行了。 -g、-g:none、-g:{lines,vars,source} •-g:在生成的class檔案中包含所有除錯資訊(行號、變數、原始檔) •-g:none :在生成的class檔案中不包含任何除錯資訊。 這個引數在javac編譯中是看不到什麼作用的,因為除錯資訊都在class檔案中,而我們看不懂這個class檔案。 為了看出這個引數的作用,我們在eclipse中進行實驗。在eclipse中,我們經常做的事就是“debug”,而在debug的時候,我們會 •加入“斷點”,這個是靠-g:lines起作用,如果不記錄行號,則不能加斷點。 •在“variables”視窗中檢視當前的變數,如下圖所示,這是靠-g:vars起作用,否則不能檢視變數資訊。 •在多個檔案之間來回撥用,比如 A.java的main()方法中呼叫了B.java的fun()函式,而我想看看程式進入fun()後的狀態,這是靠-g:source,如果沒有這個引數,則不能檢視B.java的原始碼。 -bootclasspath、-extdirs -bootclasspath和-extdirs 幾乎不需要用的,因為他是用來改變 “引導類”和“擴充套件類”。 •引導類(組成Java平臺的類):Java\jdk1.7.0_25\jre\lib\rt.jar等,用-bootclasspath設定。 •擴充套件類:Java\jdk1.7.0_25\jre\lib\ext目錄中的檔案,用-extdirs設定。 •使用者自定義類:用-classpath設定。 我們用-verbose編譯後出現的“類檔案的搜尋路徑”,就是由上面三個路徑組成,如下: [類檔案的搜尋路徑: C:\Java\jdk1.7.0_25\jre\lib\resources.jar,C:\Java\jdk1.7.0_25 \jre\lib\rt.jar,C:\Java\jdk1.7.0_25\jre\lib\sunrsasign.jar,C:\Java\jdk1.7.0_25\j re\lib\jsse.jar,C:\Java\jdk1.7.0_25\jre\lib\jce.jar,C:\Java\jdk1.7.0_25\jre\lib\ charsets.jar,C:\Java\jdk1.7.0_25\jre\lib\jfr.jar,C:\Java\jdk1.7.0_25\jre\classes ,C:\Java\jdk1.7.0_25\jre\lib\ext\access-bridge-32.jar,C:\Java\jdk1.7.0_25\jre\li b\ext\dnsns.jar,C:\Java\jdk1.7.0_25\jre\lib\ext\jaccess.jar,C:\Java\jdk1.7.0_25\ jre\lib\ext\localedata.jar,C:\Java\jdk1.7.0_25\jre\lib\ext\sunec.jar,C:\Java\jdk 1.7.0_25\jre\lib\ext\sunjce_provider.jar,C:\Java\jdk1.7.0_25\jre\lib\ext\sunmsca pi.jar,C:\Java\jdk1.7.0_25\jre\lib\ext\sunpkcs11.jar,C:\Java\jdk1.7.0_25\jre\lib \ext\zipfs.jar,..\bin] 如果利用 -bootclasspath 重新定義: javac -bootclasspath src Xxx.java,則會出現下面錯誤: 致命錯誤: 在類路徑或引導類路徑中找不到程式包 java.lang -sourcepath和-classpath(-cp) •-classpath(-cp)指定你依賴的類的class檔案的查詢位置。在Linux中,用“:”分隔classpath,而在windows中,用“;”分隔。 •-sourcepath指定你依賴的類的java檔案的查詢位置。 舉個例子, public class A { public static void main(String[] args) { B b = new B(); b.print(); } } public class B { public void print() { System.out.println("old"); } } 目錄結構如下: sourcepath //此處為當前目錄 |-src |-com |- B.java |- A.java |-bin |- B.class //是 B.java 編譯後的類檔案 如果要編譯 A.java,則必須要讓編譯器找到類B的位置,你可以指定B.class的位置,也可以是B.java的位置,也可以同時都存在。 javac -classpath bin src/A.java //查詢到B.class javac -sourcepath src/com src/A.java //查詢到B.java javac -sourcepath src/com -classpath bin src/A.java //同時查詢到B.class和B.java 如果同時找到了B.class和B.java,則: •如果B.class和B.java內容一致,則遵循B.class。 •如果B.class和B.java內容不一致,則遵循B.java,並編譯B.java。 以上規則可以通過 -verbose選項看出。 -d •d就是 destination,用於指定.class檔案的生成目錄,在eclipse中,原始檔都在src中,編譯的class檔案都是在bin目錄中。 這裡我用來實現一下這個功能,假設專案名稱為project,此目錄為當前目錄,且在src/com目錄中有一個Main.java檔案。‘ package com; public class Main { public static void main(String[] args) { System.out.println("Hello"); } } javac -d bin src/com/Main.java 上面的語句將Main.class生成在bin/com目錄下。 -implicit:{none,class} •如果有檔案為A.java(其中有類A),且在類A中使用了類B,類B在B.java中,則編譯A.java時,預設會自動編譯B.java,且生成B.class。 •implicit:none:不自動生成隱式引用的類檔案。 •implicit:class(預設):自動生成隱式引用的類檔案。 public class A { public static void main(String[] args) { B b = new B(); } } public class B { } 如果使用: javac -implicit:none A.java 則不會生成 B.class。 -source和-target •-source:使用指定版本的JDK編譯,比如:-source 1.4表示用JDK1.4的標準編譯,如果在原始檔中使用了泛型,則用JDK1.4是不能編譯通過的。 •-target:指定生成的class檔案要執行在哪個JVM版本,以後實際執行的JVM版本必須要高於這個指定的版本。 javac -source 1.4 Xxx.java javac -target 1.4 Xxx.java -encoding 預設會使用系統環境的編碼,比如我們一般用的中文windows就是GBK編碼,所以直接javac時會用GBK編碼,而Java檔案一般要使用utf-8,如果用GBK就會出現亂碼。 •指定原始檔的編碼格式,如果原始檔是UTF-8編碼的,而-encoding GBK,則原始檔就變成了亂碼(特別是有中文時)。 javac -encoding UTF-8 Xxx.java -verbose 輸出詳細的編譯資訊,包括:classpath、載入的類檔案資訊。 比如,我寫了一個最簡單的HelloWorld程式,在命令列中輸入: D:\Java>javac -verbose -encoding UTF-8 HelloWorld01.java 輸出: [語法分析開始時間 RegularFileObject[HelloWorld01.java]] [語法分析已完成, 用時 21 毫秒] [原始檔的搜尋路徑: .,D:\大三下\編譯原理\cup\java-cup-11a.jar,E:\java\jflex\lib\J //-sourcepath Flex.jar] [類檔案的搜尋路徑: C:\Java\jdk1.7.0_25\jre\lib\resources.jar,C:\Java\jdk1.7.0_25 //-classpath、-bootclasspath、-extdirs 省略............................................ [正在載入ZipFileIndexFileObject[C:\Java\jdk1.7.0_25\lib\ct.sym(META-INF/sym/rt.j ar/java/lang/Object.class)]] [正在載入ZipFileIndexFileObject[C:\Java\jdk1.7.0_25\lib\ct.sym(META-INF/sym/rt.j ar/java/lang/String.class)]] [正在檢查Demo] 省略............................................ [已寫入RegularFileObject[Demo.class]] [共 447 毫秒] 編寫一個程式時,比如寫了一句:System.out.println(“hello”),實際上還需要載入:Object、PrintStream、String等類檔案,而上面就顯示了載入的全部類檔案。 其他命令 -J <標記> •傳遞一些資訊給 Java Launcher. javac -J-Xms48m Xxx.java //set the startup memory to 48M. -@<檔名> 如果同時需要編譯數量較多的原始檔(比如1000個),一個一個編譯是不現實的(當然你可以直接 javac *.java ),比較好的方法是:將你想要編譯的原始檔名都寫在一個檔案中(比如sourcefiles.txt),其中每行寫一個檔名,如下所示: HelloWorld01.java HelloWorld02.java HelloWorld03.java 則使用下面的命令: javac @sourcefiles.txt 編譯這三個原始檔。 使用javac構建專案 這部分參考: https://blog.csdn.net/mingover/article/details/57083176 一個簡單的javac編譯 新建兩個資料夾,src和 build src/com/yp/test/HelloWorld.java build/ ├─build └─src └─com └─yp └─test HelloWorld.java java檔案非常簡單 package com.yp.test; public class HelloWorld { public static void main(String[] args) { System.out.println("helloWorld"); } } 編譯: javac src/com/yp/test/HelloWorld.java -d build -d 表示編譯到 build資料夾下 檢視build資料夾 ├─build │ └─com │ └─yp │ └─test │ HelloWorld.class │ └─src └─com └─yp └─test HelloWorld.java 執行檔案 E:\codeplace\n_learn\java\javacmd> java com/yp/test/HelloWorld.class 錯誤: 找不到或無法載入主類 build.com.yp.test.HelloWorld.class 執行時要指定main E:\codeplace\n_learn\java\javacmd\build> java com.yp.test.HelloWorld helloWorld 如果引用到多個其他的類,應該怎麼做呢 ? 編譯 E:\codeplace\n_learn\java\javacmd>javac src/com/yp/test/HelloWorld.java -sourcepath src -d build -g 1 -sourcepath 表示 從指定的原始檔目錄中找到需要的.java檔案並進行編譯。 也可以用-cp指定編譯好的class的路徑 執行,注意:執行在build目錄下 E:\codeplace\n_learn\java\javacmd\build>java com.yp.test.HelloWorld 怎麼打成jar包? 生成: E:\codeplace\n_learn\java\javacmd\build>jar cvf h.jar * 執行: E:\codeplace\n_learn\java\javacmd\build>java h.jar 錯誤: 找不到或無法載入主類 h.jar 這個錯誤是沒有指定main類,所以類似這樣來指定: E:\codeplace\n_learn\java\javacmd\build>java -cp h.jar com.yp.test.HelloWorld 生成可以執行的jar包 需要指定jar包的應用程式入口點,用-e選項: E:\codeplace\n_learn\java\javacmd\build> jar cvfe h.jar com.yp.test.HelloWorld * 已新增清單 正在新增: com/(輸入 = 0) (輸出 = 0)(儲存了 0%) 正在新增: com/yp/(輸入 = 0) (輸出 = 0)(儲存了 0%) 正在新增: com/yp/test/(輸入 = 0) (輸出 = 0)(儲存了 0%) 正在新增: com/yp/test/entity/(輸入 = 0) (輸出 = 0)(儲存了 0%) 正在新增: com/yp/test/entity/Cat.class(輸入 = 545) (輸出 = 319)(壓縮了 41%) 正在新增: com/yp/test/HelloWorld.class(輸入 = 844) (輸出 = 487)(壓縮了 42%) 直接執行 java -jar h.jar 額外發現 指定了Main類後,jar包裡面的 META-INF/MANIFEST.MF 是這樣的, 比原來多了一行Main-Class…. Manifest-Version: 1.0 Created-By: 1.8.0 (Oracle Corporation) Main-Class: com.yp.test.HelloWorld 如果類裡有引用jar包呢? 先下一個jar包 這裡直接下 log4j * main函式改成 import com.yp.test.entity.Cat; import org.apache.log4j.Logger; public class HelloWorld { static Logger log = Logger.getLogger(HelloWorld.class); public static void main(String[] args) { Cat c = new Cat("keyboard"); log.info("這是log4j"); System.out.println("hello," + c.getName()); } } 現的檔案是這樣的 ├─build ├─lib │ log4j-1.2.17.jar │ └─src └─com └─yp └─test │ HelloWorld.java │ └─entity Cat.java 這個時候 javac命令要接上 -cp ./lib/*.jar E:\codeplace\n_learn\java\javacmd>javac -encoding "utf8" src/com/yp/test/HelloWorld.java -sourcepath src -d build -g -cp ./lib/*.jar 執行要加上-cp, -cp 選項貌似會把工作目錄給換了, 所以要加上 ;../build E:\codeplace\n_learn\java\javacmd\build>java -cp ../lib/log4j-1.2.17.jar;../build com.yp.test.HelloWorld 結果: log4j:WARN No appenders could be found for logger(com.yp.test.HelloWorld). log4j:WARN Please initialize the log4j system properly. log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info. hello,keyboard 由於沒有 log4j的配置檔案,所以提示上面的問題,往 build 裡面加上 log4j.xml <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'> <appender name="stdout" class="org.apache.log4j.ConsoleAppender"> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%d{ABSOLUTE} %-5p [%c{1}] %m%n" /> </layout> </appender> <root> <level value="info" /> <appender-ref ref="stdout" /> </root> </log4j:configuration> 再執行 E:\codeplace\n_learn\java\javacmd>java -cp lib/log4j-1.2.17.jar;build com.yp.tes t.HelloWorld 15:19:57,359 INFO [HelloWorld] 這是log4j hello,keyboard 說明: 這個log4j配置檔案,習慣的做法是放在src目錄下, 在編譯過程中 copy到build中的,但根據ant的做法,不是用javac的,而是用來處理,我猜測javac是不能copy的,如果想在命令列直接 使用,應該是用cp命令主動去執行 copy操作 ok 一個簡單的java 工程就執行完了 但是 貌似有些繁瑣, 需要手動鍵入 java檔案 以及相應的jar包 很是麻煩, so 可以用 shell 來指令碼來簡化相關操作 shell 檔案整理如下: #!/bin/bash echo "build start" JAR_PATH=libs BIN_PATH=bin SRC_PATH=src # java檔案列表目錄 SRC_FILE_LIST_PATH=src/sources.list #生所有的java檔案列表 放入列表檔案中 rm -f $SRC_PATH/sources find $SRC_PATH/ -name *.java > $SRC_FILE_LIST_PATH #刪除舊的編譯檔案 生成bin目錄 rm -rf $BIN_PATH/ mkdir $BIN_PATH/ #生成依賴jar包 列表 for file in ${JAR_PATH}/*.jar; do jarfile=${jarfile}:${file} done echo "jarfile = "$jarfile #編譯 通過-cp指定所有的引用jar包,將src下的所有java檔案進行編譯 javac -d $BIN_PATH/ -cp $jarfile @$SRC_FILE_LIST_PATH #執行 通過-cp指定所有的引用jar包,指定入口函式執行 java -cp $BIN_PATH$jarfile com.zuiapps.danmaku.server.Main 有一點需要注意的是, javac -d $BIN_PATH/ -cp $jarfile @$SRC_FILE_LIST_PATH 在要編譯的檔案很多時候,一個個敲命令會顯得很長,也不方便修改, 可以把要編譯的原始檔列在檔案中,在檔名前加@,這樣就可以對多個檔案進行編譯, 以上就是吧java檔案放到 $SRC_FILE_LIST_PATH 中去了 編譯 : 1. 需要編譯所有的java檔案 2. 依賴的java 包都需要加入到 classpath 中去 3. 最後設定 編譯後的 class 檔案存放目錄 即 -d bin/ 4. java檔案過多是可以使用 @$SRC_FILE_LIST_PATH 把他們放到一個檔案中去 執行: 1.需要吧 編譯時設定的bin目錄和 所有jar包加入到 classpath 中去 javap javap是jdk自帶的一個工具,可以對程式碼反編譯,也可以檢視java編譯器生成的位元組碼。 情況下,很少有人使用javap對class檔案進行反編譯,因為有很多成熟的反編譯工具可以使用,比如jad。但是,javap還可以檢視java編譯器為我們生成的位元組碼。通過它,可以對照原始碼和位元組碼,從而瞭解很多編譯器內部的工作。 javap命令分解一個class檔案,它根據options來決定到底輸出什麼。如果沒有使用options,那麼javap將會輸出包,類裡的protected和public域以及類裡的所有方法。javap將會把它們輸出在標準輸出上。來看這個例子,先編譯(javac)下面這個類。 import java.awt.*; import java.applet.*; public class DocFooter extends Applet { String date; String email; public void init() { resize(500,100); date = getParameter("LAST_UPDATED"); email = getParameter("EMAIL"); } } 在命令列上鍵入javap DocFooter後,輸出結果如下 Compiled from “DocFooter.java” public class DocFooter extends java.applet.Applet { java.lang.String date; java.lang.String email; public DocFooter(); public void init(); } 如果加入了-c,即javap -c DocFooter,那麼輸出結果如下 Compiled from “DocFooter.java” public class DocFooter extends java.applet.Applet { java.lang.String date; java.lang.String email; public DocFooter(); Code: 0: aload_0 1: invokespecial #1 // Method java/applet/Applet."<init>":()V 4: return public void init(); Code: 0: aload_0 1: sipush 500 4: bipush 100 6: invokevirtual #2 // Method resize:(II)V 9: aload_0 10: aload_0 11: ldc #3 // String LAST_UPDATED 13: invokevirtual #4 // Method getParameter:(Ljava/lang/String;)Ljava/lang/String; 16: putfield #5 // Field date:Ljava/lang/String; 19: aload_0 20: aload_0 21: ldc #6 // String EMAIL 23: invokevirtual #4 // Method getParameter:(Ljava/lang/String;)Ljava/lang/String; 26: putfield #7 // Field email:Ljava/lang/String; 29: return } 上面輸出的內容就是位元組碼。 用法摘要 -help 幫助 -l 輸出行和變數的表 -public 只輸出public方法和域 -protected 只輸出public和protected類和成員 -package 只輸出包,public和protected類和成員,這是預設的 -p -private 輸出所有類和成員 -s 輸出內部型別簽名 -c 輸出分解後的程式碼,例如,類中每一個方法內,包含java位元組碼的指令, -verbose 輸出棧大小,方法引數的個數 -constants 輸出靜態final常量 總結 javap可以用於反編譯和檢視編譯器編譯後的位元組碼。平時一般用javap -c比較多,該命令用於列出每個方法所執行的JVM指令,並顯示每個方法的位元組碼的實際作用。可以通過位元組碼和原始碼的對比,深入分析java的編譯原理,瞭解和解決各種Java原理級別的問題。 參考文章 https://blog.csdn.net/Anbernet/article/details/81449390 https://www.cnblogs.com/luobiao320/p/7975442.html https://www.jianshu.com/p/f7330dbdc051 https://www.jianshu.com/p/6a8997560b05 https://blog.csdn.net/w372426096/article/details/81664431 https://blog.csdn.net/qincidong/article/details/82492140 微信公眾號 Java技術江湖 如果大家想要實時關注我更新的文章以及分享的乾貨的話,可以關注我的公眾號【Java技術江湖】一位阿里 Java 工程師的技術小站,作者黃小斜,專注 Java 相關技術:SSM、SpringBoot、MySQL、分散式、中介軟體、叢集、Linux、網路、多執行緒,偶爾講點Docker、ELK,同時也分享技術乾貨和學習經驗,致力於Java全棧開發! Java工程師必備學習資源: 一些Java工程師常用學習資源,關注公眾號後,後臺回覆關鍵字 “Java” 即可免費無套路獲取。 個人公眾號:黃小斜 作者是 985 碩士,螞蟻金服 JAVA 工程師,專注於 JAVA 後端技術棧:SpringBoot、MySQL、分散式、中介軟體、微服務,同時也懂點投資理財,偶爾講點演算法和計算機理論基礎,堅持學習和寫作,相信終身學習的力量! 程式設計師3T技術學習資源: 一些程式設計師學習技術的資源大禮包,關注公眾號後,後臺回覆關鍵字 “資料” 即可免費無套路獲取。 <source>
其中, 可能的選項包括:
-g 生成所有除錯資訊 -g:none 不生成任何除錯資訊 -g:{lines,vars,source} 只生成某些除錯資訊 -nowarn 不生成任何警告 -verbose 輸出有關編譯器正在執行的操作的訊息 -deprecation 輸出使用已過時的 API 的源位置 -classpath <路徑> 指定查詢使用者類檔案和註釋處理程式的位置 -cp <路徑> 指定查詢使用者類檔案和註釋處理程式的位置 -sourcepath <路徑> 指定查詢輸入原始檔的位置 -bootclasspath <路徑> 覆蓋引導類檔案的位置 -extdirs <目錄> 覆蓋所安裝擴充套件的位置 -endorseddirs <目錄> 覆蓋簽名的標準路徑的位置 -proc:{none,only} 控制是否執行註釋處理和/或編譯。 -processor [, , …] 要執行的註釋處理程式的名稱; 繞過預設的搜尋程式 -processorpath <路徑> 指定查詢註釋處理程式的位置 -d <目錄> 指定放置生成的類檔案的位置 -s <目錄> 指定放置生成的原始檔的位置 -implicit:{none,class} 指定是否為隱式引用檔案生成類檔案 -encoding <編碼> 指定原始檔使用的字元編碼 -source <發行版> 提供與指定發行版的源相容性 -target <發行版> 生成特定 VM 版本的類檔案 -version 版本資訊 -help 輸出標準選項的提要 -A關鍵字[=值] 傳遞給註釋處理程式的選項 -X 輸出非標準選項的提要 -J<標記> 直接將 <標記> 傳遞給執行時系統 -Werror 出現警告時終止編譯 @<檔名> 從檔案讀取選項和檔名
-g 生成所有除錯資訊
-g:none 不生成任何除錯資訊
-g:{lines,vars,source} 只生成某些除錯資訊
-nowarn 不生成任何警告
-verbose 輸出有關編譯器正在執行的操作的訊息
-deprecation 輸出使用已過時的 API 的源位置
-classpath <路徑> 指定查詢使用者類檔案和註釋處理程式的位置
-cp <路徑> 指定查詢使用者類檔案和註釋處理程式的位置
-sourcepath <路徑> 指定查詢輸入原始檔的位置
-bootclasspath <路徑> 覆蓋引導類檔案的位置
-extdirs <目錄> 覆蓋所安裝擴充套件的位置
-endorseddirs <目錄> 覆蓋簽名的標準路徑的位置
-proc:{none,only} 控制是否執行註釋處理和/或編譯。
-processor [, , …] 要執行的註釋處理程式的名稱; 繞過預設的搜尋程式 -processorpath <路徑> 指定查詢註釋處理程式的位置 -d <目錄> 指定放置生成的類檔案的位置 -s <目錄> 指定放置生成的原始檔的位置 -implicit:{none,class} 指定是否為隱式引用檔案生成類檔案 -encoding <編碼> 指定原始檔使用的字元編碼 -source <發行版> 提供與指定發行版的源相容性 -target <發行版> 生成特定 VM 版本的類檔案 -version 版本資訊 -help 輸出標準選項的提要 -A關鍵字[=值] 傳遞給註釋處理程式的選項 -X 輸出非標準選項的提要 -J<標記> 直接將 <標記> 傳遞給執行時系統 -Werror 出現警告時終止編譯 @<檔名> 從檔案讀取選項和檔名
-processorpath <路徑> 指定查詢註釋處理程式的位置
-d <目錄> 指定放置生成的類檔案的位置
-s <目錄> 指定放置生成的原始檔的位置
-implicit:{none,class} 指定是否為隱式引用檔案生成類檔案
-encoding <編碼> 指定原始檔使用的字元編碼
-source <發行版> 提供與指定發行版的源相容性
-target <發行版> 生成特定 VM 版本的類檔案
-version 版本資訊
-help 輸出標準選項的提要
-A關鍵字[=值] 傳遞給註釋處理程式的選項
-X 輸出非標準選項的提要
-J<標記> 直接將 <標記> 傳遞給執行時系統
-Werror 出現警告時終止編譯
@<檔名> 從檔案讀取選項和檔名
在詳細介紹javac命令之前,先看看這個classpath是什麼
在dos下編譯java程式,就要用到classpath這個概念,尤其是在沒有設定環境變數的時候。classpath就是存放.class等編譯後檔案的路徑。
javac:如果當前你要編譯的java檔案中引用了其它的類(比如說:繼承),但該引用類的.class檔案不在當前目錄下,這種情況下就需要在javac命令後面加上-classpath引數,通過使用以下三種型別的方法 來指導編譯器在編譯的時候去指定的路徑下查詢引用類。
(1).絕對路徑:javac -classpath c:/junit3.8.1/junit.jar Xxx.java (2).相對路徑:javac -classpath ../junit3.8.1/Junit.javr Xxx.java (3).系統變數:javac -classpath %CLASSPATH% Xxx.java (注意:%CLASSPATH%表示使用系統變數CLASSPATH的值進行查詢,這裡假設Junit.jar的路徑就包含在CLASSPATH系統變數中)
(1).絕對路徑:javac -classpath c:/junit3.8.1/junit.jar Xxx.java
(2).相對路徑:javac -classpath ../junit3.8.1/Junit.javr Xxx.java
(3).系統變數:javac -classpath %CLASSPATH% Xxx.java (注意:%CLASSPATH%表示使用系統變數CLASSPATH的值進行查詢,這裡假設Junit.jar的路徑就包含在CLASSPATH系統變數中)
對於一個普通的Javaweb專案,一般有這樣的配置:
1 WEB-INF/classes,lib才是classpath,WEB-INF/ 是資源目錄, 客戶端不能直接訪問。 2、WEB-INF/classes目錄存放src目錄java檔案編譯之後的class檔案,xml、properties等資源配置檔案,這是一個定位資源的入口。 3、引用classpath路徑下的檔案,只需在檔名前加classpath: classpath:applicationContext-*.xml classpath:context/conf/controller.xml 4、lib和classes同屬classpath,兩者的訪問優先順序為: lib>classes。 5、classpath 和 classpath* 區別: classpath:只會到你的class路徑中查詢找檔案; classpath*:不僅包含class路徑,還包括jar檔案中(class路徑)進行查詢。
1 WEB-INF/classes,lib才是classpath,WEB-INF/ 是資源目錄, 客戶端不能直接訪問。
2、WEB-INF/classes目錄存放src目錄java檔案編譯之後的class檔案,xml、properties等資源配置檔案,這是一個定位資源的入口。
3、引用classpath路徑下的檔案,只需在檔名前加classpath:
classpath:applicationContext-*.xml
classpath:context/conf/controller.xml
4、lib和classes同屬classpath,兩者的訪問優先順序為: lib>classes。
5、classpath 和 classpath* 區別:
classpath:只會到你的class路徑中查詢找檔案; classpath*:不僅包含class路徑,還包括jar檔案中(class路徑)進行查詢。
總結:
(1).何時需要使用-classpath:當你要編譯或執行的類引用了其它的類,但被引用類的.class檔案不在當前目錄下時,就需要通過-classpath來引入類
(2).何時需要指定路徑:當你要編譯的類所在的目錄和你執行javac命令的目錄不是同一個目錄時,就需要指定原始檔的路徑(CLASSPATH是用來指定.class路徑的,不是用來指定.java檔案的路徑的)
(看清IDE及classpath本質)
現在只是說說Java Project和Web Project,那麼二者有區別麼?回答:沒有!都是Java語言的應用,只是應用場合不同罷了,那麼他們的本質到底是什麼? 回答:編譯後路徑!虛擬機器執行的是class檔案而不是java檔案,那麼我們不管是何種專案都是寫的java檔案,怎麼就不一樣了呢?分成java和web兩種了呢? 從.classpath入手來看,這個檔案在每個專案目錄下都是存在的,很少有人開啟看吧,那麼我們就來一起看吧。這是一個XML檔案,使用文字編輯器開啟即可。 這裡展示一個web專案的.classpath
現在只是說說Java Project和Web Project,那麼二者有區別麼?回答:沒有!都是Java語言的應用,只是應用場合不同罷了,那麼他們的本質到底是什麼?
回答:編譯後路徑!虛擬機器執行的是class檔案而不是java檔案,那麼我們不管是何種專案都是寫的java檔案,怎麼就不一樣了呢?分成java和web兩種了呢?
從.classpath入手來看,這個檔案在每個專案目錄下都是存在的,很少有人開啟看吧,那麼我們就來一起看吧。這是一個XML檔案,使用文字編輯器開啟即可。
這裡展示一個web專案的.classpath
Xml程式碼
<?xml version="1.0" encoding="UTF-8"?> <classpath> <classpathentry kind="src" path="src"/> <classpathentry kind="src" path="resources"/> <classpathentry kind="src" path="test"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> <classpathentry kind="lib" path="lib/servlet-api.jar"/> <classpathentry kind="lib" path="webapp/WEB-INF/lib/struts2-core-2.1.8.1.jar"/> …… <classpathentry kind="output" path="webapp/WEB-INF/classes"/> </classpath>
XML文件包含一個根元素,就是classpath,類路徑,那麼這裡麵包含了什麼資訊呢?子元素是classpathentry,kind屬性區別了種 類資訊,src原始碼,con你看看後面的path就知道是JRE容器的資訊。lib是專案依賴的第三方類庫,output是src編譯後的位置。 既然是web專案,那麼就是WEB-INF/classes目錄,可能用MyEclipse的同學會說他們那裡是WebRoot或者是WebContext而不是webapp,有區別麼?回答:完全沒有! 既然看到了編譯路徑的本來面目後,還區分什麼java專案和web專案麼?回答:不區分!普通的java 專案你這樣寫就行了: ,看看Eclipse是不是這樣生成的?這個問題解決了吧。 再說說webapp目錄命名的問題,這個無所謂啊,web專案是要釋出到伺服器上的對吧,那麼伺服器讀取的是類檔案和頁面檔案吧,它不管原始檔,它也無法去理解原始檔。那麼webapp目錄的命名有何關係呢?只要讓伺服器找到不就行了。
XML文件包含一個根元素,就是classpath,類路徑,那麼這裡麵包含了什麼資訊呢?子元素是classpathentry,kind屬性區別了種 類資訊,src原始碼,con你看看後面的path就知道是JRE容器的資訊。lib是專案依賴的第三方類庫,output是src編譯後的位置。
既然是web專案,那麼就是WEB-INF/classes目錄,可能用MyEclipse的同學會說他們那裡是WebRoot或者是WebContext而不是webapp,有區別麼?回答:完全沒有!
既然看到了編譯路徑的本來面目後,還區分什麼java專案和web專案麼?回答:不區分!普通的java 專案你這樣寫就行了: ,看看Eclipse是不是這樣生成的?這個問題解決了吧。
再說說webapp目錄命名的問題,這個無所謂啊,web專案是要釋出到伺服器上的對吧,那麼伺服器讀取的是類檔案和頁面檔案吧,它不管原始檔,它也無法去理解原始檔。那麼webapp目錄的命名有何關係呢?只要讓伺服器找到不就行了。
•-g:在生成的class檔案中包含所有除錯資訊(行號、變數、原始檔) •-g:none :在生成的class檔案中不包含任何除錯資訊。 這個引數在javac編譯中是看不到什麼作用的,因為除錯資訊都在class檔案中,而我們看不懂這個class檔案。 為了看出這個引數的作用,我們在eclipse中進行實驗。在eclipse中,我們經常做的事就是“debug”,而在debug的時候,我們會 •加入“斷點”,這個是靠-g:lines起作用,如果不記錄行號,則不能加斷點。 •在“variables”視窗中檢視當前的變數,如下圖所示,這是靠-g:vars起作用,否則不能檢視變數資訊。 •在多個檔案之間來回撥用,比如 A.java的main()方法中呼叫了B.java的fun()函式,而我想看看程式進入fun()後的狀態,這是靠-g:source,如果沒有這個引數,則不能檢視B.java的原始碼。
•-g:在生成的class檔案中包含所有除錯資訊(行號、變數、原始檔) •-g:none :在生成的class檔案中不包含任何除錯資訊。
這個引數在javac編譯中是看不到什麼作用的,因為除錯資訊都在class檔案中,而我們看不懂這個class檔案。
為了看出這個引數的作用,我們在eclipse中進行實驗。在eclipse中,我們經常做的事就是“debug”,而在debug的時候,我們會 •加入“斷點”,這個是靠-g:lines起作用,如果不記錄行號,則不能加斷點。 •在“variables”視窗中檢視當前的變數,如下圖所示,這是靠-g:vars起作用,否則不能檢視變數資訊。 •在多個檔案之間來回撥用,比如 A.java的main()方法中呼叫了B.java的fun()函式,而我想看看程式進入fun()後的狀態,這是靠-g:source,如果沒有這個引數,則不能檢視B.java的原始碼。
-bootclasspath和-extdirs 幾乎不需要用的,因為他是用來改變 “引導類”和“擴充套件類”。 •引導類(組成Java平臺的類):Java\jdk1.7.0_25\jre\lib\rt.jar等,用-bootclasspath設定。 •擴充套件類:Java\jdk1.7.0_25\jre\lib\ext目錄中的檔案,用-extdirs設定。 •使用者自定義類:用-classpath設定。 我們用-verbose編譯後出現的“類檔案的搜尋路徑”,就是由上面三個路徑組成,如下:
-bootclasspath和-extdirs 幾乎不需要用的,因為他是用來改變 “引導類”和“擴充套件類”。 •引導類(組成Java平臺的類):Java\jdk1.7.0_25\jre\lib\rt.jar等,用-bootclasspath設定。 •擴充套件類:Java\jdk1.7.0_25\jre\lib\ext目錄中的檔案,用-extdirs設定。 •使用者自定義類:用-classpath設定。
我們用-verbose編譯後出現的“類檔案的搜尋路徑”,就是由上面三個路徑組成,如下:
[類檔案的搜尋路徑: C:\Java\jdk1.7.0_25\jre\lib\resources.jar,C:\Java\jdk1.7.0_25 \jre\lib\rt.jar,C:\Java\jdk1.7.0_25\jre\lib\sunrsasign.jar,C:\Java\jdk1.7.0_25\j re\lib\jsse.jar,C:\Java\jdk1.7.0_25\jre\lib\jce.jar,C:\Java\jdk1.7.0_25\jre\lib\ charsets.jar,C:\Java\jdk1.7.0_25\jre\lib\jfr.jar,C:\Java\jdk1.7.0_25\jre\classes ,C:\Java\jdk1.7.0_25\jre\lib\ext\access-bridge-32.jar,C:\Java\jdk1.7.0_25\jre\li b\ext\dnsns.jar,C:\Java\jdk1.7.0_25\jre\lib\ext\jaccess.jar,C:\Java\jdk1.7.0_25\ jre\lib\ext\localedata.jar,C:\Java\jdk1.7.0_25\jre\lib\ext\sunec.jar,C:\Java\jdk 1.7.0_25\jre\lib\ext\sunjce_provider.jar,C:\Java\jdk1.7.0_25\jre\lib\ext\sunmsca pi.jar,C:\Java\jdk1.7.0_25\jre\lib\ext\sunpkcs11.jar,C:\Java\jdk1.7.0_25\jre\lib \ext\zipfs.jar,..\bin]
如果利用 -bootclasspath 重新定義: javac -bootclasspath src Xxx.java,則會出現下面錯誤:
致命錯誤: 在類路徑或引導類路徑中找不到程式包 java.lang
•-classpath(-cp)指定你依賴的類的class檔案的查詢位置。在Linux中,用“:”分隔classpath,而在windows中,用“;”分隔。 •-sourcepath指定你依賴的類的java檔案的查詢位置。
舉個例子,
public class A { public static void main(String[] args) { B b = new B(); b.print(); } } public class B { public void print() { System.out.println("old"); } }
目錄結構如下:
sourcepath //此處為當前目錄
|-src |-com |- B.java |- A.java |-bin |- B.class //是 B.java
編譯後的類檔案
如果要編譯 A.java,則必須要讓編譯器找到類B的位置,你可以指定B.class的位置,也可以是B.java的位置,也可以同時都存在。
javac -classpath bin src/A.java //查詢到B.class javac -sourcepath src/com src/A.java //查詢到B.java javac -sourcepath src/com -classpath bin src/A.java //同時查詢到B.class和B.java
如果同時找到了B.class和B.java,則: •如果B.class和B.java內容一致,則遵循B.class。 •如果B.class和B.java內容不一致,則遵循B.java,並編譯B.java。
以上規則可以通過 -verbose選項看出。
•d就是 destination,用於指定.class檔案的生成目錄,在eclipse中,原始檔都在src中,編譯的class檔案都是在bin目錄中。
這裡我用來實現一下這個功能,假設專案名稱為project,此目錄為當前目錄,且在src/com目錄中有一個Main.java檔案。‘
package com; public class Main { public static void main(String[] args) { System.out.println("Hello"); } } javac -d bin src/com/Main.java
上面的語句將Main.class生成在bin/com目錄下。
•如果有檔案為A.java(其中有類A),且在類A中使用了類B,類B在B.java中,則編譯A.java時,預設會自動編譯B.java,且生成B.class。 •implicit:none:不自動生成隱式引用的類檔案。 •implicit:class(預設):自動生成隱式引用的類檔案。
public class A { public static void main(String[] args) { B b = new B(); } } public class B { } 如果使用: javac -implicit:none A.java
則不會生成 B.class。
•-source:使用指定版本的JDK編譯,比如:-source 1.4表示用JDK1.4的標準編譯,如果在原始檔中使用了泛型,則用JDK1.4是不能編譯通過的。 •-target:指定生成的class檔案要執行在哪個JVM版本,以後實際執行的JVM版本必須要高於這個指定的版本。
javac -source 1.4 Xxx.java
javac -target 1.4 Xxx.java
預設會使用系統環境的編碼,比如我們一般用的中文windows就是GBK編碼,所以直接javac時會用GBK編碼,而Java檔案一般要使用utf-8,如果用GBK就會出現亂碼。
•指定原始檔的編碼格式,如果原始檔是UTF-8編碼的,而-encoding GBK,則原始檔就變成了亂碼(特別是有中文時)。
javac -encoding UTF-8 Xxx.java
輸出詳細的編譯資訊,包括:classpath、載入的類檔案資訊。
比如,我寫了一個最簡單的HelloWorld程式,在命令列中輸入:
D:\Java>javac -verbose -encoding UTF-8 HelloWorld01.java
輸出:
[語法分析開始時間 RegularFileObject[HelloWorld01.java]] [語法分析已完成, 用時 21 毫秒] [原始檔的搜尋路徑: .,D:\大三下\編譯原理\cup\java-cup-11a.jar,E:\java\jflex\lib\J //-sourcepath Flex.jar] [類檔案的搜尋路徑: C:\Java\jdk1.7.0_25\jre\lib\resources.jar,C:\Java\jdk1.7.0_25 //-classpath、-bootclasspath、-extdirs 省略............................................ [正在載入ZipFileIndexFileObject[C:\Java\jdk1.7.0_25\lib\ct.sym(META-INF/sym/rt.j ar/java/lang/Object.class)]] [正在載入ZipFileIndexFileObject[C:\Java\jdk1.7.0_25\lib\ct.sym(META-INF/sym/rt.j ar/java/lang/String.class)]] [正在檢查Demo] 省略............................................ [已寫入RegularFileObject[Demo.class]] [共 447 毫秒]
編寫一個程式時,比如寫了一句:System.out.println(“hello”),實際上還需要載入:Object、PrintStream、String等類檔案,而上面就顯示了載入的全部類檔案。
-J <標記> •傳遞一些資訊給 Java Launcher.
javac -J-Xms48m Xxx.java //set the startup memory to 48M.
-@<檔名>
如果同時需要編譯數量較多的原始檔(比如1000個),一個一個編譯是不現實的(當然你可以直接 javac *.java ),比較好的方法是:將你想要編譯的原始檔名都寫在一個檔案中(比如sourcefiles.txt),其中每行寫一個檔名,如下所示: HelloWorld01.java HelloWorld02.java HelloWorld03.java
如果同時需要編譯數量較多的原始檔(比如1000個),一個一個編譯是不現實的(當然你可以直接 javac *.java ),比較好的方法是:將你想要編譯的原始檔名都寫在一個檔案中(比如sourcefiles.txt),其中每行寫一個檔名,如下所示:
HelloWorld01.java HelloWorld02.java HelloWorld03.java
則使用下面的命令:
javac @sourcefiles.txt
編譯這三個原始檔。
這部分參考: https://blog.csdn.net/mingover/article/details/57083176
一個簡單的javac編譯
新建兩個資料夾,src和 build src/com/yp/test/HelloWorld.java build/
├─build └─src └─com └─yp └─test HelloWorld.java
java檔案非常簡單
package com.yp.test; public class HelloWorld { public static void main(String[] args) { System.out.println("helloWorld"); } }
編譯: javac src/com/yp/test/HelloWorld.java -d build
-d 表示編譯到 build資料夾下
檢視build資料夾 ├─build │ └─com │ └─yp │ └─test │ HelloWorld.class │ └─src └─com └─yp └─test HelloWorld.java
執行檔案
E:\codeplace\n_learn\java\javacmd> java com/yp/test/HelloWorld.class 錯誤: 找不到或無法載入主類 build.com.yp.test.HelloWorld.class 執行時要指定main E:\codeplace\n_learn\java\javacmd\build> java com.yp.test.HelloWorld helloWorld
E:\codeplace\n_learn\java\javacmd> java com/yp/test/HelloWorld.class 錯誤: 找不到或無法載入主類 build.com.yp.test.HelloWorld.class
執行時要指定main E:\codeplace\n_learn\java\javacmd\build> java com.yp.test.HelloWorld helloWorld
如果引用到多個其他的類,應該怎麼做呢 ?
編譯 E:\codeplace\n_learn\java\javacmd>javac src/com/yp/test/HelloWorld.java -sourcepath src -d build -g 1 -sourcepath 表示 從指定的原始檔目錄中找到需要的.java檔案並進行編譯。 也可以用-cp指定編譯好的class的路徑 執行,注意:執行在build目錄下 E:\codeplace\n_learn\java\javacmd\build>java com.yp.test.HelloWorld
編譯
E:\codeplace\n_learn\java\javacmd>javac src/com/yp/test/HelloWorld.java -sourcepath src -d build -g 1 -sourcepath 表示 從指定的原始檔目錄中找到需要的.java檔案並進行編譯。 也可以用-cp指定編譯好的class的路徑 執行,注意:執行在build目錄下
E:\codeplace\n_learn\java\javacmd\build>java com.yp.test.HelloWorld
怎麼打成jar包?
生成: E:\codeplace\n_learn\java\javacmd\build>jar cvf h.jar * 執行: E:\codeplace\n_learn\java\javacmd\build>java h.jar 錯誤: 找不到或無法載入主類 h.jar 這個錯誤是沒有指定main類,所以類似這樣來指定: E:\codeplace\n_learn\java\javacmd\build>java -cp h.jar com.yp.test.HelloWorld
生成: E:\codeplace\n_learn\java\javacmd\build>jar cvf h.jar * 執行: E:\codeplace\n_learn\java\javacmd\build>java h.jar 錯誤: 找不到或無法載入主類 h.jar
這個錯誤是沒有指定main類,所以類似這樣來指定: E:\codeplace\n_learn\java\javacmd\build>java -cp h.jar com.yp.test.HelloWorld
生成可以執行的jar包
需要指定jar包的應用程式入口點,用-e選項:
E:\codeplace\n_learn\java\javacmd\build> jar cvfe h.jar com.yp.test.HelloWorld * 已新增清單 正在新增: com/(輸入 = 0) (輸出 = 0)(儲存了 0%) 正在新增: com/yp/(輸入 = 0) (輸出 = 0)(儲存了 0%) 正在新增: com/yp/test/(輸入 = 0) (輸出 = 0)(儲存了 0%) 正在新增: com/yp/test/entity/(輸入 = 0) (輸出 = 0)(儲存了 0%) 正在新增: com/yp/test/entity/Cat.class(輸入 = 545) (輸出 = 319)(壓縮了 41%) 正在新增: com/yp/test/HelloWorld.class(輸入 = 844) (輸出 = 487)(壓縮了 42%)
直接執行
java -jar h.jar 額外發現 指定了Main類後,jar包裡面的 META-INF/MANIFEST.MF 是這樣的, 比原來多了一行Main-Class…. Manifest-Version: 1.0 Created-By: 1.8.0 (Oracle Corporation) Main-Class: com.yp.test.HelloWorld
如果類裡有引用jar包呢?
先下一個jar包 這裡直接下 log4j
* main函式改成 import com.yp.test.entity.Cat; import org.apache.log4j.Logger; public class HelloWorld { static Logger log = Logger.getLogger(HelloWorld.class); public static void main(String[] args) { Cat c = new Cat("keyboard"); log.info("這是log4j"); System.out.println("hello," + c.getName()); } }
現的檔案是這樣的
├─build ├─lib │ log4j-1.2.17.jar │ └─src └─com └─yp └─test │ HelloWorld.java │ └─entity Cat.java
這個時候 javac命令要接上 -cp ./lib/*.jar E:\codeplace\n_learn\java\javacmd>javac -encoding "utf8" src/com/yp/test/HelloWorld.java -sourcepath src -d build -g -cp ./lib/*.jar 執行要加上-cp, -cp 選項貌似會把工作目錄給換了, 所以要加上 ;../build E:\codeplace\n_learn\java\javacmd\build>java -cp ../lib/log4j-1.2.17.jar;../build com.yp.test.HelloWorld
結果:
log4j:WARN No appenders could be found for logger(com.yp.test.HelloWorld). log4j:WARN Please initialize the log4j system properly. log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info. hello,keyboard
由於沒有 log4j的配置檔案,所以提示上面的問題,往 build 裡面加上 log4j.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'> <appender name="stdout" class="org.apache.log4j.ConsoleAppender"> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%d{ABSOLUTE} %-5p [%c{1}] %m%n" /> </layout> </appender> <root> <level value="info" /> <appender-ref ref="stdout" /> </root> </log4j:configuration>
再執行
E:\codeplace\n_learn\java\javacmd>java -cp lib/log4j-1.2.17.jar;build com.yp.tes t.HelloWorld 15:19:57,359 INFO [HelloWorld] 這是log4j hello,keyboard
說明: 這個log4j配置檔案,習慣的做法是放在src目錄下, 在編譯過程中 copy到build中的,但根據ant的做法,不是用javac的,而是用來處理,我猜測javac是不能copy的,如果想在命令列直接 使用,應該是用cp命令主動去執行 copy操作
ok 一個簡單的java 工程就執行完了 但是 貌似有些繁瑣, 需要手動鍵入 java檔案 以及相應的jar包 很是麻煩, so 可以用 shell 來指令碼來簡化相關操作 shell 檔案整理如下:
#!/bin/bash echo "build start" JAR_PATH=libs BIN_PATH=bin SRC_PATH=src # java檔案列表目錄 SRC_FILE_LIST_PATH=src/sources.list #生所有的java檔案列表 放入列表檔案中 rm -f $SRC_PATH/sources find $SRC_PATH/ -name *.java > $SRC_FILE_LIST_PATH #刪除舊的編譯檔案 生成bin目錄 rm -rf $BIN_PATH/ mkdir $BIN_PATH/ #生成依賴jar包 列表 for file in ${JAR_PATH}/*.jar; do jarfile=${jarfile}:${file} done echo "jarfile = "$jarfile #編譯 通過-cp指定所有的引用jar包,將src下的所有java檔案進行編譯 javac -d $BIN_PATH/ -cp $jarfile @$SRC_FILE_LIST_PATH #執行 通過-cp指定所有的引用jar包,指定入口函式執行 java -cp $BIN_PATH$jarfile com.zuiapps.danmaku.server.Main
有一點需要注意的是, javac -d $BIN_PATH/ -cp $jarfile @$SRC_FILE_LIST_PATH 在要編譯的檔案很多時候,一個個敲命令會顯得很長,也不方便修改, 可以把要編譯的原始檔列在檔案中,在檔名前加@,這樣就可以對多個檔案進行編譯, 以上就是吧java檔案放到 $SRC_FILE_LIST_PATH 中去了
有一點需要注意的是, javac -d $BIN_PATH/ -cp $jarfile @$SRC_FILE_LIST_PATH 在要編譯的檔案很多時候,一個個敲命令會顯得很長,也不方便修改,
可以把要編譯的原始檔列在檔案中,在檔名前加@,這樣就可以對多個檔案進行編譯,
以上就是吧java檔案放到 $SRC_FILE_LIST_PATH 中去了
編譯 : 1. 需要編譯所有的java檔案 2. 依賴的java 包都需要加入到 classpath 中去 3. 最後設定 編譯後的 class 檔案存放目錄 即 -d bin/ 4. java檔案過多是可以使用 @$SRC_FILE_LIST_PATH 把他們放到一個檔案中去 執行: 1.需要吧 編譯時設定的bin目錄和 所有jar包加入到 classpath 中去
javap是jdk自帶的一個工具,可以對程式碼反編譯,也可以檢視java編譯器生成的位元組碼。 情況下,很少有人使用javap對class檔案進行反編譯,因為有很多成熟的反編譯工具可以使用,比如jad。但是,javap還可以檢視java編譯器為我們生成的位元組碼。通過它,可以對照原始碼和位元組碼,從而瞭解很多編譯器內部的工作。 javap命令分解一個class檔案,它根據options來決定到底輸出什麼。如果沒有使用options,那麼javap將會輸出包,類裡的protected和public域以及類裡的所有方法。javap將會把它們輸出在標準輸出上。來看這個例子,先編譯(javac)下面這個類。
javap是jdk自帶的一個工具,可以對程式碼反編譯,也可以檢視java編譯器生成的位元組碼。
情況下,很少有人使用javap對class檔案進行反編譯,因為有很多成熟的反編譯工具可以使用,比如jad。但是,javap還可以檢視java編譯器為我們生成的位元組碼。通過它,可以對照原始碼和位元組碼,從而瞭解很多編譯器內部的工作。
javap命令分解一個class檔案,它根據options來決定到底輸出什麼。如果沒有使用options,那麼javap將會輸出包,類裡的protected和public域以及類裡的所有方法。javap將會把它們輸出在標準輸出上。來看這個例子,先編譯(javac)下面這個類。
import java.awt.*; import java.applet.*; public class DocFooter extends Applet { String date; String email; public void init() { resize(500,100); date = getParameter("LAST_UPDATED"); email = getParameter("EMAIL"); } }
在命令列上鍵入javap DocFooter後,輸出結果如下
Compiled from “DocFooter.java”
public class DocFooter extends java.applet.Applet { java.lang.String date; java.lang.String email; public DocFooter(); public void init(); }
如果加入了-c,即javap -c DocFooter,那麼輸出結果如下
public class DocFooter extends java.applet.Applet { java.lang.String date; java.lang.String email; public DocFooter(); Code: 0: aload_0 1: invokespecial #1 // Method java/applet/Applet."<init>":()V 4: return public void init(); Code: 0: aload_0 1: sipush 500 4: bipush 100 6: invokevirtual #2 // Method resize:(II)V 9: aload_0 10: aload_0 11: ldc #3 // String LAST_UPDATED 13: invokevirtual #4 // Method getParameter:(Ljava/lang/String;)Ljava/lang/String; 16: putfield #5 // Field date:Ljava/lang/String; 19: aload_0 20: aload_0 21: ldc #6 // String EMAIL 23: invokevirtual #4 // Method getParameter:(Ljava/lang/String;)Ljava/lang/String; 26: putfield #7 // Field email:Ljava/lang/String; 29: return }
上面輸出的內容就是位元組碼。
用法摘要
-help 幫助 -l 輸出行和變數的表 -public 只輸出public方法和域 -protected 只輸出public和protected類和成員 -package 只輸出包,public和protected類和成員,這是預設的 -p -private 輸出所有類和成員 -s 輸出內部型別簽名 -c 輸出分解後的程式碼,例如,類中每一個方法內,包含java位元組碼的指令, -verbose 輸出棧大小,方法引數的個數 -constants 輸出靜態final常量 總結
javap可以用於反編譯和檢視編譯器編譯後的位元組碼。平時一般用javap -c比較多,該命令用於列出每個方法所執行的JVM指令,並顯示每個方法的位元組碼的實際作用。可以通過位元組碼和原始碼的對比,深入分析java的編譯原理,瞭解和解決各種Java原理級別的問題。
https://blog.csdn.net/Anbernet/article/details/81449390
https://www.cnblogs.com/luobiao320/p/7975442.html
https://www.jianshu.com/p/f7330dbdc051
https://www.jianshu.com/p/6a8997560b05
https://blog.csdn.net/w372426096/article/details/81664431
https://blog.csdn.net/qincidong/article/details/82492140
如果大家想要實時關注我更新的文章以及分享的乾貨的話,可以關注我的公眾號【Java技術江湖】一位阿里 Java 工程師的技術小站,作者黃小斜,專注 Java 相關技術:SSM、SpringBoot、MySQL、分散式、中介軟體、叢集、Linux、網路、多執行緒,偶爾講點Docker、ELK,同時也分享技術乾貨和學習經驗,致力於Java全棧開發!
Java工程師必備學習資源: 一些Java工程師常用學習資源,關注公眾號後,後臺回覆關鍵字 “Java” 即可免費無套路獲取。
作者是 985 碩士,螞蟻金服 JAVA 工程師,專注於 JAVA 後端技術棧:SpringBoot、MySQL、分散式、中介軟體、微服務,同時也懂點投資理財,偶爾講點演算法和計算機理論基礎,堅持學習和寫作,相信終身學習的力量!
程式設計師3T技術學習資源: 一些程式設計師學習技術的資源大禮包,關注公眾號後,後臺回覆關鍵字 “資料” 即可免費無套路獲取。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69906029/viewspace-2659076/,如需轉載,請註明出處,否則將追究法律責任。