夯实Java基础系列20:从IDE的实现原理聊起,谈谈那些年我们用过的Java命令 程序员文章站 2022-04-14 20:45:40 本系列文章将整理到我在GitHub上的《Java面试指南》仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java Tutorial 喜欢的话麻烦点下Star哈 文章首发于我的个人博客: www.how2playlife.com 本系列文章将整理到我在github上的《java面试指南》仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/java-tutorial 喜欢的话麻烦点下star哈 文章首发于我的个人博客: www.how2playlife.com 聊聊ide的实现原理 ide是把双刃剑,它可以什么都帮你做了,你只要敲几行代码,点几下鼠标,程序就跑起来了,用起来相当方便。 你不用去关心它后面做了些什么,执行了哪些命令,基于什么原理。然而也是这种过分的依赖往往让人散失了最基本的技能,当到了一个没有ide的地方,你便觉得无从下手,给你个代码都不知道怎么去跑。好比给你瓶水,你不知道怎么打开去喝,然后活活给渴死。 之前用惯了idea,java文件编译运行的命令基本忘得一干二净。 那好,不如咱们先来了解一下ide的实现原理,这样一来,即使离开ide,我们还是知道如何运行java程序了。 像eclipse等java ide是怎么编译和查找java源代码的呢? 源代码保存 这个无需多说,在编译器写入代码,并保存到文件。这个利用流来实现。 编译为class文件 java提供了javacompiler,我们可以通过它来编译java源文件为class文件。 查找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); } } javac命令初窥 注:以下红色标记的参数在下文中有所讲解。 本部分参考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-*.xmlclasspath: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技术学习资源: 一些程序员学习技术的资源大礼包,关注公众号后,后台回复关键字 “资料” 即可免费无套路获取。 文件名>标记> 上一篇: Linux:目录的查找 下一篇: RMQ求LCA 推荐阅读 夯实Java基础系列20:从IDE的实现原理聊起,谈谈那些年我们用过的Java命令 夯实Java基础系列20:从IDE的实现原理聊起,谈谈那些年我们用过的Java命令