夯實(shí)Java基礎(chǔ)系列20:從IDE的實(shí)現(xiàn)原理聊起方面,談?wù)勀切┠晡覀冇眠^(guò)的Java命令

本系列文章將整理到我在GitHub上的《Java面試指南》倉(cāng)庫(kù)对室,更多精彩內(nèi)容請(qǐng)到我的倉(cāng)庫(kù)里查看

https://github.com/h2pl/Java-Tutorial

喜歡的話麻煩點(diǎn)下Star哈

文章首發(fā)于我的個(gè)人博客:

www.how2playlife.com

聊聊IDE的實(shí)現(xiàn)原理

IDE是把雙刃劍蜡秽,它可以什么都幫你做了蚤氏,你只要敲幾行代碼代赁,點(diǎn)幾下鼠標(biāo)扰她,程序就跑起來(lái)了兽掰,用起來(lái)相當(dāng)方便。

你不用去關(guān)心它后面做了些什么徒役,執(zhí)行了哪些命令孽尽,基于什么原理。然而也是這種過(guò)分的依賴往往讓人散失了最基本的技能忧勿,當(dāng)?shù)搅艘粋€(gè)沒(méi)有IDE的地方泻云,你便覺(jué)得無(wú)從下手,給你個(gè)代碼都不知道怎么去跑狐蜕。好比給你瓶水宠纯,你不知道怎么打開(kāi)去喝,然后活活給渴死层释。

之前用慣了idea婆瓜,Java文件編譯運(yùn)行的命令基本忘得一干二凈。

那好贡羔,不如咱們先來(lái)了解一下IDE的實(shí)現(xiàn)原理廉白,這樣一來(lái),即使離開(kāi)IDE乖寒,我們還是知道如何運(yùn)行Java程序了猴蹂。

像Eclipse等java IDE是怎么編譯和查找java源代碼的呢?

源代碼保存

這個(gè)無(wú)需多說(shuō)楣嘁,在編譯器寫(xiě)入代碼磅轻,并保存到文件。這個(gè)利用流來(lái)實(shí)現(xiàn)逐虚。

編譯為class文件

java提供了JavaCompiler聋溜,我們可以通過(guò)它來(lái)編譯java源文件為class文件。

查找class

可以通過(guò)Class.forName(fullClassPath)或自定義類加載器來(lái)實(shí)現(xiàn)叭爱。

生成對(duì)象撮躁,并調(diào)用對(duì)象方法

通過(guò)上面一個(gè)查找class,得到Class對(duì)象后买雾,可以通過(guò)newInstance()或構(gòu)造器的newInstance()得到對(duì)象把曼。然后得到Method,最后調(diào)用方法漓穿,傳入相關(guān)參數(shù)即可嗤军。

示例代碼:

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);

        // 編譯
        // 設(shè)置編譯選項(xiàng)器净,配置class文件輸出路徑
        Iterable<String> options = Arrays.asList("-d",baseOutputDir);
        JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, options, null, compilationUnits);
        // 執(zhí)行編譯任務(wù)
        task.call();


        // 通過(guò)反射得到對(duì)象
//        Class clazz = Class.forName("com.tommy.core.test.reflect.Test");
        // 使用自定義的類加載器加載class
        Class clazz = new MyClassLoader(baseOutputDir).loadClass("com.tommy.core.test.reflect.Test");
        // 得到構(gòu)造器
        Constructor constructor = clazz.getConstructor(String.class);
        // 通過(guò)構(gòu)造器new一個(gè)對(duì)象
        Object test = constructor.newInstance("jack.tsing");
        // 得到sayHello方法
        Method method = clazz.getMethod("sayHello", String.class);
        // 調(diào)用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);
    }
}    

javac命令初窺

注:以下紅色標(biāo)記的參數(shù)在下文中有所講解欣舵。

本部分參考https://www.cnblogs.com/xiazdong/p/3216220.html

用法: javac <options> <source files>

其中, 可能的選項(xiàng)包括:

-g 生成所有調(diào)試信息

-g:none 不生成任何調(diào)試信息

-g:{lines,vars,source} 只生成某些調(diào)試信息

-nowarn 不生成任何警告

-verbose 輸出有關(guān)編譯器正在執(zhí)行的操作的消息

-deprecation 輸出使用已過(guò)時(shí)的 API 的源位置

-classpath <路徑> 指定查找用戶類文件和注釋處理程序的位置

-cp <路徑> 指定查找用戶類文件和注釋處理程序的位置

-sourcepath <路徑> 指定查找輸入源文件的位置

-bootclasspath <路徑> 覆蓋引導(dǎo)類文件的位置

-extdirs <目錄> 覆蓋所安裝擴(kuò)展的位置

-endorseddirs <目錄> 覆蓋簽名的標(biāo)準(zhǔn)路徑的位置

-proc:{none,only} 控制是否執(zhí)行注釋處理和/或編譯晨炕。

-processor <class1>[,<class2>,<class3>...] 要運(yùn)行的注釋處理程序的名稱; 繞過(guò)默認(rèn)的搜索進(jìn)程

-processorpath <路徑> 指定查找注釋處理程序的位置

-d <目錄> 指定放置生成的類文件的位置

-s <目錄> 指定放置生成的源文件的位置

-implicit:{none,class} 指定是否為隱式引用文件生成類文件

-encoding <編碼> 指定源文件使用的字符編碼

-source <發(fā)行版> 提供與指定發(fā)行版的源兼容性

-target <發(fā)行版> 生成特定 VM 版本的類文件

-version 版本信息

-help 輸出標(biāo)準(zhǔn)選項(xiàng)的提要

-A關(guān)鍵字[=值] 傳遞給注釋處理程序的選項(xiàng)

-X 輸出非標(biāo)準(zhǔn)選項(xiàng)的提要

-J<標(biāo)記> 直接將 <標(biāo)記> 傳遞給運(yùn)行時(shí)系統(tǒng)

-Werror 出現(xiàn)警告時(shí)終止編譯

@<文件名> 從文件讀取選項(xiàng)和文件名

在詳細(xì)介紹javac命令之前,先看看這個(gè)classpath是什么

classpath是什么

在dos下編譯java程序,就要用到classpath這個(gè)概念陨亡,尤其是在沒(méi)有設(shè)置環(huán)境變量的時(shí)候。classpath就是存放.class等編譯后文件的路徑趁桃。

javac:如果當(dāng)前你要編譯的java文件中引用了其它的類(比如說(shuō):繼承)踊沸,但該引用類的.class文件不在當(dāng)前目錄下,這種情況下就需要在javac命令后面加上-classpath參數(shù)权纤,通過(guò)使用以下三種類型的方法 來(lái)指導(dǎo)編譯器在編譯的時(shí)候去指定的路徑下查找引用類钓简。

(1).絕對(duì)路徑:javac -classpath c:/junit3.8.1/junit.jar Xxx.java

(2).相對(duì)路徑:javac -classpath ../junit3.8.1/Junit.javr Xxx.java

(3).系統(tǒng)變量:javac -classpath %CLASSPATH% Xxx.java (注意:%CLASSPATH%表示使用系統(tǒng)變量CLASSPATH的值進(jìn)行查找,這里假設(shè)Junit.jar的路徑就包含在CLASSPATH系統(tǒng)變量中)

IDE中的classpath

對(duì)于一個(gè)普通的Javaweb項(xiàng)目汹想,一般有這樣的配置:

1 WEB-INF/classes,lib才是classpath外邓,WEB-INF/ 是資源目錄, 客戶端不能直接訪問(wèn)。

2古掏、WEB-INF/classes目錄存放src目錄java文件編譯之后的class文件损话,xml、properties等資源配置文件槽唾,這是一個(gè)定位資源的入口丧枪。

3、引用classpath路徑下的文件庞萍,只需在文件名前加classpath:

<param-value>classpath:applicationContext-*.xml</param-value>

<param-value>classpath:context/conf/controller.xml</param-value>

4拧烦、lib和classes同屬classpath,兩者的訪問(wèn)優(yōu)先級(jí)為: lib>classes钝计。

5恋博、classpath 和 classpath* 區(qū)別:

classpath:只會(huì)到你的class路徑中查找找文件;
classpath*:不僅包含class路徑,還包括jar文件中(class路徑)進(jìn)行查找私恬。

總結(jié):

(1).何時(shí)需要使用-classpath:當(dāng)你要編譯或執(zhí)行的類引用了其它的類交播,但被引用類的.class文件不在當(dāng)前目錄下時(shí),就需要通過(guò)-classpath來(lái)引入類

(2).何時(shí)需要指定路徑:當(dāng)你要編譯的類所在的目錄和你執(zhí)行javac命令的目錄不是同一個(gè)目錄時(shí)践付,就需要指定源文件的路徑(CLASSPATH是用來(lái)指定.class路徑的秦士,不是用來(lái)指定.java文件的路徑的)

Java項(xiàng)目和Java web項(xiàng)目的本質(zhì)區(qū)別

(看清IDE及classpath本質(zhì))

現(xiàn)在只是說(shuō)說(shuō)Java Project和Web Project,那么二者有區(qū)別么永高?回答:沒(méi)有隧土!都是Java語(yǔ)言的應(yīng)用,只是應(yīng)用場(chǎng)合不同罷了命爬,那么他們的本質(zhì)到底是什么曹傀?

回答:編譯后路徑!虛擬機(jī)執(zhí)行的是class文件而不是java文件饲宛,那么我們不管是何種項(xiàng)目都是寫(xiě)的java文件皆愉,怎么就不一樣了呢?分成java和web兩種了呢?

從.classpath文件入手來(lái)看幕庐,這個(gè)文件在每個(gè)項(xiàng)目目錄下都是存在的久锥,很少有人打開(kāi)看吧,那么我們就來(lái)一起看吧异剥。這是一個(gè)XML文件瑟由,使用文本編輯器打開(kāi)即可。

這里展示一個(gè)web項(xiàng)目的.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文檔包含一個(gè)根元素冤寿,就是classpath歹苦,類路徑,那么這里面包含了什么信息呢督怜?子元素是classpathentry殴瘦,kind屬性區(qū)別了種 類信息,src源碼号杠,con你看看后面的path就知道是JRE容器的信息痴施。lib是項(xiàng)目依賴的第三方類庫(kù),output是src編譯后的位置究流。

既然是web項(xiàng)目辣吃,那么就是WEB-INF/classes目錄,可能用MyEclipse的同學(xué)會(huì)說(shuō)他們那里是WebRoot或者是WebContext而不是webapp芬探,有區(qū)別么神得?回答:完全沒(méi)有!

既然看到了編譯路徑的本來(lái)面目后偷仿,還區(qū)分什么java項(xiàng)目和web項(xiàng)目么哩簿?回答:不區(qū)分!普通的java 項(xiàng)目你這樣寫(xiě)就行了:<classpathentry kind="output" path="bin"/>酝静,看看Eclipse是不是這樣生成的节榜?這個(gè)問(wèn)題解決了吧。

再說(shuō)說(shuō)webapp目錄命名的問(wèn)題别智,這個(gè)無(wú)所謂啊宗苍,web項(xiàng)目是要發(fā)布到服務(wù)器上的對(duì)吧,那么服務(wù)器讀取的是類文件和頁(yè)面文件吧薄榛,它不管源文件讳窟,它也無(wú)法去理解源文件。那么webapp目錄的命名有何關(guān)系呢敞恋?只要讓服務(wù)器找到不就行了丽啡。

-g、-g:none硬猫、-g:{lines,vars,source}

?-g:在生成的class文件中包含所有調(diào)試信息(行號(hào)补箍、變量改执、源文件)
?-g:none :在生成的class文件中不包含任何調(diào)試信息。

這個(gè)參數(shù)在javac編譯中是看不到什么作用的坑雅,因?yàn)檎{(diào)試信息都在class文件中辈挂,而我們看不懂這個(gè)class文件。

為了看出這個(gè)參數(shù)的作用霞丧,我們?cè)趀clipse中進(jìn)行實(shí)驗(yàn)。在eclipse中冕香,我們經(jīng)常做的事就是“debug”蛹尝,而在debug的時(shí)候,我們會(huì)
?加入“斷點(diǎn)”悉尾,這個(gè)是靠-g:lines起作用突那,如果不記錄行號(hào),則不能加斷點(diǎn)构眯。
?在“variables”窗口中查看當(dāng)前的變量愕难,如下圖所示,這是靠-g:vars起作用惫霸,否則不能查看變量信息猫缭。
?在多個(gè)文件之間來(lái)回調(diào)用,比如 A.java的main()方法中調(diào)用了B.java的fun()函數(shù)壹店,而我想看看程序進(jìn)入fun()后的狀態(tài)猜丹,這是靠-g:source,如果沒(méi)有這個(gè)參數(shù)硅卢,則不能查看B.java的源代碼射窒。

-bootclasspath、-extdirs

-bootclasspath和-extdirs 幾乎不需要用的将塑,因?yàn)樗怯脕?lái)改變 “引導(dǎo)類”和“擴(kuò)展類”脉顿。
?引導(dǎo)類(組成Java平臺(tái)的類):Java\jdk1.7.0_25\jre\lib\rt.jar等,用-bootclasspath設(shè)置点寥。
?擴(kuò)展類:Java\jdk1.7.0_25\jre\lib\ext目錄中的文件艾疟,用-extdirs設(shè)置。
?用戶自定義類:用-classpath設(shè)置敢辩。

我們用-verbose編譯后出現(xiàn)的“類文件的搜索路徑”汉柒,就是由上面三個(gè)路徑組成,如下:

[類文件的搜索路徑: 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责鳍,則會(huì)出現(xiàn)下面錯(cuò)誤:

致命錯(cuò)誤: 在類路徑或引導(dǎo)類路徑中找不到程序包 java.lang

-sourcepath和-classpath(-cp)

?-classpath(-cp)指定你依賴的類的class文件的查找位置碾褂。在Linux中,用“:”分隔classpath历葛,而在windows中正塌,用“;”分隔嘀略。
?-sourcepath指定你依賴的類的java文件的查找位置。

舉個(gè)例子乓诽,

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");
    }
}

目錄結(jié)構(gòu)如下:

sourcepath //此處為當(dāng)前目錄

|-src
    |-com
      |- B.java
    |- A.java
  |-bin
    |- B.class               //是 B.java

編譯后的類文件

如果要編譯 A.java帜羊,則必須要讓編譯器找到類B的位置,你可以指定B.class的位置鸠天,也可以是B.java的位置讼育,也可以同時(shí)都存在。

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    //同時(shí)查找到B.class和B.java

如果同時(shí)找到了B.class和B.java稠集,則:
?如果B.class和B.java內(nèi)容一致奶段,則遵循B.class。
?如果B.class和B.java內(nèi)容不一致剥纷,則遵循B.java痹籍,并編譯B.java。

以上規(guī)則可以通過(guò) -verbose選項(xiàng)看出晦鞋。

-d

?d就是 destination蹲缠,用于指定.class文件的生成目錄,在eclipse中悠垛,源文件都在src中线定,編譯的class文件都是在bin目錄中。

這里我用來(lái)實(shí)現(xiàn)一下這個(gè)功能确买,假設(shè)項(xiàng)目名稱為project渔肩,此目錄為當(dāng)前目錄,且在src/com目錄中有一個(gè)Main.java文件拇惋≈苜耍‘

package com;
public class Main
{
    public static void main(String[] args) {
        System.out.println("Hello");
    }
}



javac -d bin src/com/Main.java

上面的語(yǔ)句將Main.class生成在bin/com目錄下。

-implicit:{none,class}

?如果有文件為A.java(其中有類A)撑帖,且在類A中使用了類B蓉坎,類B在B.java中,則編譯A.java時(shí)胡嘿,默認(rèn)會(huì)自動(dòng)編譯B.java蛉艾,且生成B.class。
?implicit:none:不自動(dòng)生成隱式引用的類文件衷敌。
?implicit:class(默認(rèn)):自動(dòng)生成隱式引用的類文件勿侯。

public class A
{
    public static void main(String[] args) {
        B b = new B();
    }
}

public class B
{
}

如果使用:


 javac -implicit:none A.java

則不會(huì)生成 B.class。

-source和-target

?-source:使用指定版本的JDK編譯缴罗,比如:-source 1.4表示用JDK1.4的標(biāo)準(zhǔn)編譯助琐,如果在源文件中使用了泛型,則用JDK1.4是不能編譯通過(guò)的面氓。
?-target:指定生成的class文件要運(yùn)行在哪個(gè)JVM版本兵钮,以后實(shí)際運(yùn)行的JVM版本必須要高于這個(gè)指定的版本蛆橡。

javac -source 1.4 Xxx.java

javac -target 1.4 Xxx.java

-encoding

默認(rèn)會(huì)使用系統(tǒng)環(huán)境的編碼,比如我們一般用的中文windows就是GBK編碼掘譬,所以直接javac時(shí)會(huì)用GBK編碼泰演,而Java文件一般要使用utf-8,如果用GBK就會(huì)出現(xiàn)亂碼葱轩。

?指定源文件的編碼格式睦焕,如果源文件是UTF-8編碼的,而-encoding GBK靴拱,則源文件就變成了亂碼(特別是有中文時(shí))垃喊。

javac -encoding UTF-8 Xxx.java

-verbose

輸出詳細(xì)的編譯信息,包括:classpath缭嫡、加載的類文件信息缔御。

比如抬闷,我寫(xiě)了一個(gè)最簡(jiǎn)單的HelloWorld程序妇蛀,在命令行中輸入:

D:\Java>javac -verbose -encoding UTF-8 HelloWorld01.java

輸出:

[語(yǔ)法分析開(kāi)始時(shí)間 RegularFileObject[HelloWorld01.java]]
[語(yǔ)法分析已完成, 用時(shí) 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]
省略............................................
[已寫(xiě)入RegularFileObject[Demo.class]]
[共 447 毫秒]

編寫(xiě)一個(gè)程序時(shí)评架,比如寫(xiě)了一句:System.out.println("hello"),實(shí)際上還需要加載:Object炕泳、PrintStream纵诞、String等類文件,而上面就顯示了加載的全部類文件培遵。

其他命令

-J <標(biāo)記>
?傳遞一些信息給 Java Launcher.

javac -J-Xms48m   Xxx.java          //set the startup memory to 48M.

-@<文件名>

如果同時(shí)需要編譯數(shù)量較多的源文件(比如1000個(gè))浙芙,一個(gè)一個(gè)編譯是不現(xiàn)實(shí)的(當(dāng)然你可以直接 javac *.java ),比較好的方法是:將你想要編譯的源文件名都寫(xiě)在一個(gè)文件中(比如sourcefiles.txt)籽腕,其中每行寫(xiě)一個(gè)文件名嗡呼,如下所示:

HelloWorld01.java
HelloWorld02.java
HelloWorld03.java

則使用下面的命令:

javac @sourcefiles.txt

編譯這三個(gè)源文件。

使用javac構(gòu)建項(xiàng)目

這部分參考:
https://blog.csdn.net/mingover/article/details/57083176

一個(gè)簡(jiǎn)單的javac編譯

新建兩個(gè)文件夾,src和 build
src/com/yp/test/HelloWorld.java
build/

├─build
└─src
    └─com
        └─yp
            └─test
                    HelloWorld.java

java文件非常簡(jiǎn)單

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

運(yùn)行文件

E:\codeplace\n_learn\java\javacmd> java com/yp/test/HelloWorld.class
錯(cuò)誤: 找不到或無(wú)法加載主類 build.com.yp.test.HelloWorld.class

運(yùn)行時(shí)要指定main
E:\codeplace\n_learn\java\javacmd\build> java com.yp.test.HelloWorld
helloWorld

如果引用到多個(gè)其他的類皇耗,應(yīng)該怎么做呢 南窗?

編譯

E:\codeplace\n_learn\java\javacmd>javac src/com/yp/test/HelloWorld.java -sourcepath src -d build -g
1
-sourcepath 表示 從指定的源文件目錄中找到需要的.java文件并進(jìn)行編譯。
也可以用-cp指定編譯好的class的路徑
運(yùn)行,注意:運(yùn)行在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 *
運(yùn)行:
E:\codeplace\n_learn\java\javacmd\build>java h.jar
錯(cuò)誤: 找不到或無(wú)法加載主類 h.jar

這個(gè)錯(cuò)誤是沒(méi)有指定main類郎楼,所以類似這樣來(lái)指定:
E:\codeplace\n_learn\java\javacmd\build>java -cp h.jar com.yp.test.HelloWorld

生成可以運(yùn)行的jar包

需要指定jar包的應(yīng)用程序入口點(diǎn)万伤,用-e選項(xiàng):

E:\codeplace\n_learn\java\javacmd\build> jar cvfe h.jar com.yp.test.HelloWorld *
已添加清單
正在添加: com/(輸入 = 0) (輸出 = 0)(存儲(chǔ)了 0%)
正在添加: com/yp/(輸入 = 0) (輸出 = 0)(存儲(chǔ)了 0%)
正在添加: com/yp/test/(輸入 = 0) (輸出 = 0)(存儲(chǔ)了 0%)
正在添加: com/yp/test/entity/(輸入 = 0) (輸出 = 0)(存儲(chǔ)了 0%)
正在添加: com/yp/test/entity/Cat.class(輸入 = 545) (輸出 = 319)(壓縮了 41%)
正在添加: com/yp/test/HelloWorld.class(輸入 = 844) (輸出 = 487)(壓縮了 42%)

直接運(yùn)行

java -jar h.jar

額外發(fā)現(xiàn) 
指定了Main類后,jar包里面的 META-INF/MANIFEST.MF 是這樣的呜袁, 比原來(lái)多了一行Main-Class….
Manifest-Version: 1.0
Created-By: 1.8.0 (Oracle Corporation)
Main-Class: com.yp.test.HelloWorld

如果類里有引用jar包呢?

先下一個(gè)jar包 這里直接下 log4j

* main函數(shù)改成

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());
    }

}

現(xiàn)的文件是這樣的

├─build
├─lib
│      log4j-1.2.17.jar
│
└─src
    └─com
        └─yp
            └─test
                │  HelloWorld.java
                │
                └─entity
                        Cat.java
這個(gè)時(shí)候 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


運(yùn)行要加上-cp, -cp 選項(xiàng)貌似會(huì)把工作目錄給換了敌买, 所以要加上 ;../build
E:\codeplace\n_learn\java\javacmd\build>java -cp ../lib/log4j-1.2.17.jar;../build com.yp.test.HelloWorld

結(jié)果:

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

由于沒(méi)有 log4j的配置文件,所以提示上面的問(wèn)題,往 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>

再運(yùn)行

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

說(shuō)明:
這個(gè)log4j配置文件阶界,習(xí)慣的做法是放在src目錄下, 在編譯過(guò)程中 copy到build中的,但根據(jù)ant的做法放妈,不是用javac的北救,而是用來(lái)處理,我猜測(cè)javac是不能copy的,如果想在命令行直接 使用芜抒,應(yīng)該是用cp命令主動(dòng)去執(zhí)行 copy操作

ok 一個(gè)簡(jiǎn)單的java 工程就運(yùn)行完了
但是 貌似有些繁瑣, 需要手動(dòng)鍵入 java文件 以及相應(yīng)的jar包 很是麻煩,
so 可以用 shell 來(lái)腳本來(lái)簡(jiǎn)化相關(guān)操作
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  
  
#編譯 通過(guò)-cp指定所有的引用jar包珍策,將src下的所有java文件進(jìn)行編譯
javac -d $BIN_PATH/ -cp $jarfile @$SRC_FILE_LIST_PATH  
  
#運(yùn)行 通過(guò)-cp指定所有的引用jar包,指定入口函數(shù)運(yùn)行
java -cp $BIN_PATH$jarfile com.zuiapps.danmaku.server.Main  

有一點(diǎn)需要注意的是, javac -d BIN_PATH/ -cpjarfile @$SRC_FILE_LIST_PATH
在要編譯的文件很多時(shí)候宅倒,一個(gè)個(gè)敲命令會(huì)顯得很長(zhǎng)攘宙,也不方便修改,

可以把要編譯的源文件列在文件中拐迁,在文件名前加@蹭劈,這樣就可以對(duì)多個(gè)文件進(jìn)行編譯,

以上就是吧java文件放到 $SRC_FILE_LIST_PATH 中去了

編譯 :
     1. 需要編譯所有的java文件
     2. 依賴的java 包都需要加入到 classpath 中去
     3. 最后設(shè)置 編譯后的 class 文件存放目錄  即 -d bin/
     4. java文件過(guò)多是可以使用  @$SRC_FILE_LIST_PATH 把他們放到一個(gè)文件中去
運(yùn)行:
   1.需要吧 編譯時(shí)設(shè)置的bin目錄和 所有jar包加入到 classpath 中去

javap

javap是jdk自帶的一個(gè)工具线召,可以對(duì)代碼反編譯铺韧,也可以查看java編譯器生成的字節(jié)碼。

情況下缓淹,很少有人使用javap對(duì)class文件進(jìn)行反編譯哈打,因?yàn)橛泻芏喑墒斓姆淳幾g工具可以使用,比如jad讯壶。但是料仗,javap還可以查看java編譯器為我們生成的字節(jié)碼。通過(guò)它伏蚊,可以對(duì)照源代碼和字節(jié)碼立轧,從而了解很多編譯器內(nèi)部的工作。

javap命令分解一個(gè)class文件躏吊,它根據(jù)options來(lái)決定到底輸出什么氛改。如果沒(méi)有使用options,那么javap將會(huì)輸出包,類里的protected和public域以及類里的所有方法比伏。javap將會(huì)把它們輸出在標(biāo)準(zhǔn)輸出上胜卤。來(lái)看這個(gè)例子,先編譯(javac)下面這個(gè)類凳怨。

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后瑰艘,輸出結(jié)果如下

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肤舞,那么輸出結(jié)果如下

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       

}

上面輸出的內(nèi)容就是字節(jié)碼紫新。

用法摘要

-help 幫助
-l 輸出行和變量的表
-public 只輸出public方法和域
-protected 只輸出public和protected類和成員
-package 只輸出包,public和protected類和成員李剖,這是默認(rèn)的
-p -private 輸出所有類和成員
-s 輸出內(nèi)部類型簽名
-c 輸出分解后的代碼芒率,例如,類中每一個(gè)方法內(nèi)篙顺,包含java字節(jié)碼的指令偶芍,
-verbose 輸出棧大小充择,方法參數(shù)的個(gè)數(shù)
-constants 輸出靜態(tài)final常量
總結(jié)

javap可以用于反編譯和查看編譯器編譯后的字節(jié)碼。平時(shí)一般用javap -c比較多匪蟀,該命令用于列出每個(gè)方法所執(zhí)行的JVM指令椎麦,并顯示每個(gè)方法的字節(jié)碼的實(shí)際作用〔谋耄可以通過(guò)字節(jié)碼和源代碼的對(duì)比观挎,深入分析java的編譯原理,了解和解決各種Java原理級(jí)別的問(wèn)題段化。

參考文章

https://blog.csdn.net/Anbernet/article/details/81449390

https://www.cnblogs.com/luobiao320/p/7975442.html

http://www.reibang.com/p/f7330dbdc051

http://www.reibang.com/p/6a8997560b05

https://blog.csdn.net/w372426096/article/details/81664431

https://blog.csdn.net/qincidong/article/details/82492140

微信公眾號(hào)

Java技術(shù)江湖

如果大家想要實(shí)時(shí)關(guān)注我更新的文章以及分享的干貨的話嘁捷,可以關(guān)注我的公眾號(hào)【Java技術(shù)江湖】一位阿里 Java 工程師的技術(shù)小站,作者黃小斜显熏,專注 Java 相關(guān)技術(shù):SSM雄嚣、SpringBoot、MySQL喘蟆、分布式缓升、中間件、集群履肃、Linux仔沿、網(wǎng)絡(luò)坐桩、多線程尺棋,偶爾講點(diǎn)Docker、ELK绵跷,同時(shí)也分享技術(shù)干貨和學(xué)習(xí)經(jīng)驗(yàn)膘螟,致力于Java全棧開(kāi)發(fā)!

Java工程師必備學(xué)習(xí)資源: 一些Java工程師常用學(xué)習(xí)資源碾局,關(guān)注公眾號(hào)后荆残,后臺(tái)回復(fù)關(guān)鍵字 “Java” 即可免費(fèi)無(wú)套路獲取。

我的公眾號(hào)

個(gè)人公眾號(hào):黃小斜

作者是 985 碩士净当,螞蟻金服 JAVA 工程師内斯,專注于 JAVA 后端技術(shù)棧:SpringBoot、MySQL像啼、分布式俘闯、中間件、微服務(wù)忽冻,同時(shí)也懂點(diǎn)投資理財(cái)真朗,偶爾講點(diǎn)算法和計(jì)算機(jī)理論基礎(chǔ),堅(jiān)持學(xué)習(xí)和寫(xiě)作僧诚,相信終身學(xué)習(xí)的力量遮婶!

程序員3T技術(shù)學(xué)習(xí)資源: 一些程序員學(xué)習(xí)技術(shù)的資源大禮包蝗碎,關(guān)注公眾號(hào)后,后臺(tái)回復(fù)關(guān)鍵字 “資料” 即可免費(fèi)無(wú)套路獲取旗扑。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蹦骑,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子臀防,更是在濱河造成了極大的恐慌脊串,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件清钥,死亡現(xiàn)場(chǎng)離奇詭異琼锋,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)祟昭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門缕坎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人篡悟,你說(shuō)我怎么就攤上這事谜叹。” “怎么了搬葬?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵荷腊,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我急凰,道長(zhǎng)女仰,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任抡锈,我火速辦了婚禮疾忍,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘床三。我一直安慰自己一罩,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布撇簿。 她就那樣靜靜地躺著聂渊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪四瘫。 梳的紋絲不亂的頭發(fā)上汉嗽,一...
    開(kāi)封第一講書(shū)人閱讀 49,772評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音莲组,去河邊找鬼诊胞。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的撵孤。 我是一名探鬼主播迈着,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼邪码!你這毒婦竟也來(lái)了裕菠?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤闭专,失蹤者是張志新(化名)和其女友劉穎奴潘,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體影钉,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡画髓,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了平委。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奈虾。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖廉赔,靈堂內(nèi)的尸體忽然破棺而出肉微,到底是詐尸還是另有隱情,我是刑警寧澤蜡塌,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布碉纳,位于F島的核電站,受9級(jí)特大地震影響馏艾,放射性物質(zhì)發(fā)生泄漏劳曹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一攒至、第九天 我趴在偏房一處隱蔽的房頂上張望厚者。 院中可真熱鬧躁劣,春花似錦迫吐、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至鳖擒,卻和暖如春溉浙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蒋荚。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工戳稽, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓惊奇,卻偏偏與公主長(zhǎng)得像互躬,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子颂郎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容