Java程序中应用代码动态生成技术
本文介绍了如何在普通Java程序中应用代码动态生成技术,并测试、比较了各种实现方法
的性能。?
提纲:?
一、概述?/?二、表达式计算器?/?三、解释法?/?四、解析法?/?五、编译法?/?六、生成法
?/?七、性能和应用
正文:?
一、概述?
经常有人批评Java的性能,认为Java程序无法与C或C++程序相提并论。为此,Java一直在
性能优化上进行着不懈的努力,特别是运行时的性能优化机制,平息了许多责难。但是,
不管Java把性能提高到了什么程度,人们对代码性能的渴求是没有止境的。?
显然,Java在某些操作上的性能确实无法与C/C++相比,这是由Java语言的特点所决定的,
例如为了跨平台而采用了中间语言(字节码)机制。另一方面,由于Java有着许多独特的
特性,它可以利用许多其他语言很难采用的优化技术,动态代码生成就是其中之一。?
所谓动态代码生成,就是一种在运行时由程序动态生成代码的过程。动态生成的代码和生
成它的程序在同一个JVM中运行,且访问方式也相似。当然,和其他优化技术相似,动态代
码生成只适用于某些特定类型的任务。?
JSP或许就是人们最熟悉的动态代码生成的例子。Servlet引擎能够把客户的请求分发给Se
rvlet处理,但Servlet天生是一种静态的结构。在启动服务器之前,Servlet一般必须先编
译和配置好。虽然Servlet有着许多优点,但在灵活性方面,Servlet略逊一筹。JSP技术突
破了Servlet的限制,允许在运行时以JSP文件为基础动态创建Servlet。?
当客户程序发出了对JSP文件的请求,Servlet引擎向JSP引擎发出请求,JSP引擎处理JSP文
件并返回结果。JSP文件是一系列动作的文本描述,这一系列动作的执行结果就是返回给用
户的页面。显然,如果每一个用户的请求到达时都通过解释的方式执行JSP页面,开销肯定
比较大。所以,JSP引擎编译JSP页面动态创建Servlet。一旦JSP页面被改变,JSP引擎就会
动态地创建新的Servlet。?
在这里,动态代码生成技术的优势非常明显——既满足了灵活性的要求,又不致于对性能
产生太大的影响。在编译Servlet甚至启动服务器时,系统的行为方式不必完全固定;同时
,由于不必在应答每一个请求时解释执行JSP文件,所以也就减少了响应时间。?
二、表达式计算器?
下面我们来看看如何在普通Java程序中使用动态代码生成技术。本文的例子是一个简单的
四则运算表达式计算器,它能够计算形如“4?$0?+?$1?*”的后缀表达式,其中$0和$1分别
表示变量0、变量1。可能出现在表达式中的符号有三种:变量,常量,操作符。?
后缀表达式是一种基于堆栈的计算表达式,处理过程从左到右依次进行,仍以前面的表达
式为例:先把4和变量0压入堆栈,下一个字符是操作符“+”,所以把当时栈顶的两个值(
4和变量0)相加,然后用加法结果取代栈顶的两个值。接着,再把1压入堆栈,由于接下来
的是操作符“*”,所以对这时栈顶的两个值执行乘法操作。如果把这个表达式转换成通常
的代数表达式(即中缀表达式),它就是“(4?+?$0)?*?$1”。如果两个变量分别是“[3,
6]”,则表达式的计算结果是(4+3)*6=42。
为了比较代码动态生成和常规编程方式的性能差异,我们将以各种不同的方式实现表达式
计算器,然后测试各个计算器的性能。?
本文的所有表达式计算器都实现(或隐含地实现)calculator接口。calculator接口只有
一个evaluate方法,它的输入参数是一个整数数组,返回值是一个表示计算结果的整数。
?
//Calculator.java
public?interface?Calculator?{
????int?evaluate(int[]?arguments);
}
三、解释法?
首先我们来看一个简单但效率不高的表达式计算器,它利用Stack对象计算表达。每次计算
,表达式都要重新分析一次,因此可以称为解释法。不过,表达式的符号分析只在对象创
建时执行一次,避免StringTokenizer类带来太大的开销。?
//SimpleCalculator.java
import?java.util.ArrayList;
import?java.util.Stack;
import?java.util.StringTokenizer;
public?class?SimpleCalculator?implements?Calculator?{
????String[]?_toks;?//?符号列表
????public?SimpleCalculator(String?expression)?{
????????//?构造符号列表
????????ArrayList?list?=?new?ArrayList();
????????StringTokenizer?tokenizer?
??????????????=?new?StringTokenizer(expression);
????????while?(tokenizer.hasMoreTokens())?{
????????????list.add(tokenizer.nextToken());
????????}
????????_toks?=?(String[])?
??????????list.toArray(new?String[list.size()]);
????}
????//?将变量值代入表达式中的变量,
????//?然后返回表达式的计算结果
????public?int?evaluate(int[]?args)?{
????????Stack?stack?=?new?Stack();
????????for?(int?i?=?0;?i?<?_toks.length;?i++)?{
?????????String?tok?=?_toks[i];
?????????//?以‘$’开头的是变量
?????????if?(tok.startsWith("$"))?{
?????????????int?varnum?=?Integer.parseInt(tok.substring(1));
?????????????stack.push(new?Integer(args[varnum]));
?????????}?else?{
?????????????char?opchar?=?tok.charAt(0);
?????????????int?op?=?"+-*/".indexOf(opchar);
?????????????if?(op?==?-1)?{
?????????????????//?常量
?????????????????stack.push(Integer.valueOf(tok));
?????????????}?else?{
?????????????????//?操作符
?????????????????int?arg2?=?((Integer)?stack.pop()).intValue();
?????????????????int?arg1?=?((Integer)?stack.pop()).intValue();
?????????????????switch?(op)?{
?????????????????????//?对栈顶的两个值执行指定的操作
?????????????????????case?0:
?????????????????????????stack.push(new?Integer(arg1?+?arg2));
?????????????????????????break;
?????????????????????case?1:
?????????????????????????stack.push(new?Integer(arg1?-?arg2));
?????????????????????????break;
?????????????????????case?2:
?????????????????????????stack.push(new?Integer(arg1?*?arg2));
?????????????????????????break;
?????????????????????case?3:
?????????????????????????stack.push(new?Integer(arg1?/?arg2));
?????????????????????????break;
?????????????????????default:
?????????????????????????throw?new?RuntimeException
?????????????????????????????("操作符不合法:?"?+?tok);
?????????????????}
?????????????}
?????????}
???????}
?????return?((Integer)?stack.pop()).intValue();
????}
}
?
从本文后面的性能测试数据可以看出,这种表达式计算方式的效率相当低。对于偶尔需要
计算表达式的场合,它也许适用,但我们还有更好的处理方式。?
四、解析法?
如果经常要计算表达式的值,一种更好的办法是先解析表达式,应用Composite设计模式,
构造一棵表达式树。我们称这种表达式计算方式为解析法。如下面的代码所示,树的内部
结构代表了表达式的计算逻辑,因而避免了每次计算表达式时重复分析计算逻辑。?
//CalculatorParser.java
import?java.util.Stack;
import?java.util.StringTokenizer;
public?class?CalculatorParser?{
????public?Calculator?parse(String?expression)?{
????????//?分析表达式,构造由表达式各个符号构成的
????????//?树形结构。
????????Stack?stack?=?new?Stack();
????????StringTokenizer?toks?
???????????=?new?StringTokenizer(expression);
????????while?(toks.hasMoreTokens())?{
????????????String?tok?=?toks.nextToken();
????????????if?(tok.startsWith("$"))?{
????????????????//?以‘$’开头的是变量
????????????????int?varnum?
??????????????????=?Integer.parseInt(tok.substring(1));
????????????????stack.push(new?VariableValue(varnum));
????????????}?else?{
????????????????int?op?=?"+-*/".indexOf(tok.charAt(0));
????????????????if?(op?==?-1)?{
????????????????????//常量
????????????????????int?val?=?Integer.parseInt(tok);
????????????????????stack.push(new?ConstantValue(val));
????????????????}?else?{
????????????????????//操作符
????????????????????Calculator?node2?=?(Calculator)?stack.pop();
????????????????????Calculator?node1?=?(Calculator)?stack.pop();
????????????????????stack.push(?
????????????????????????new?Operation(tok.charAt(0),?node1,?node2));
????????????????}
????????????}
????????}
????????return?(Calculator)?stack.pop();
????}
????//?常量
????static?class?ConstantValue?implements?Calculator?{
????????private?int?_value;
????????ConstantValue(int?value)?{
????????????_value?=?value;??}
????????public?int?evaluate(int[]?args)?{
????????????return?_value;??}
????}
????//?变量
????static?class?VariableValue?implements?Calculator?{
????????private?int?_varnum;
????????VariableValue(int?varnum)?{
????????????_varnum?=?varnum;??}
????????public?int?evaluate(int[]?args)?{
????????????return?args[_varnum];??}
????}
????//?操作符
????static?class?Operation?implements?Calculator?{
????????char?_op;
????????Calculator?_arg1;
????????Calculator?_arg2;
????????Operation(char?op,?Calculator?arg1,?Calculator?arg2)?{
????????????_op?=?op;
????????????_arg1?=?arg1;
????????????_arg2?=?arg2;
????????}
????????public?int?evaluate(int?args[])?{
????????????int?val1?=?_arg1.evaluate(args);
????????????int?val2?=?_arg2.evaluate(args);
????????????if?(_op?==?'+')?{
????????????????return?val1?+?val2;
????????????}?else?if?(_op?==?'-')?{
????????????????return?val1?-?val2;
????????????}?else?if?(_op?==?'*')?{
????????????????return?val1?*?val2;
????????????}?else?if?(_op?==?'/')?{
????????????????return?val1?/?val2;
????????????}?else?{
????????????????throw?new?RuntimeException("操作符不合法:?"?+?_op);
????????????}
????????}
????}
}
?
由于表达式的计算逻辑已经事先解析好,CalculatorParser的性能明显高于第一个通过
解释方式执行的计算器。尽管如此,我们还可以通过代码动态生成技术进一步优化代码。
五、编译法?
为了进一步优化表达式计算器的性能,我们要直接编译表达式——先根据表达式的逻辑动
态生成Java代码,然后执行动态生成的Java代码,这种方法可以称之为编译法。?
把后缀表达式翻译成Java表达式很简单,例如“$0?$1?$2?*?+”可以由Java表达式“args
[0]?+?(args[1]?*?args[2]”表示。我们要为动态生成的Java类选择一个唯一的名字,然
后把代码写入临时文件。动态生成的Java类具有如下形式:?
public?class?[类的名称]?implements?Calculator{
????public?int?evaluate(int[]?args)?{
????????return?args[0]?+?(args[1]?*?args[2]);
????}
}
?
下面是编译法计算器的完整代码。?
//CalculatorCompiler.java
import?java.util.Stack;
import?java.util.StringTokenizer;
import?java.io.*;
//定制的类装入器
public?class?CalculatorCompiler?extends?ClassLoader?{
????String?_compiler;
????String?_classpath;
????public?CalculatorCompiler()?{
????????super(ClassLoader.getSystemClassLoader());
????????//编译器类型
????????_compiler?=?System.getProperty("calc.compiler");
????????//默认编译器
????????if?(_compiler?==?null)?_compiler?=?"javac";
????????_classpath?=?".";
????????String?extraclasspath?
??????????????=?System.getProperty("calc.classpath");
????????if?(extraclasspath?!=?null)?{
????????????_classpath?=?_classpath?+
??????????????System.getProperty("path.separator")
?????????????????????????????+?extraclasspath;
????????}
????}
????public?Calculator?compile(String?expression)?{
????????//?A3
????????String?jtext?=?javaExpression(expression);
????????String?filename?=?"";
????????String?classname?=?"";
????????try?{
????????????//创建临时文件
????????????File?javafile?=?File.createTempFile(
???????????????????"compiled_",?".java",?new?File("."));
????????????filename?=?javafile.getName();
????????????classname?=?filename.substring(
???????????????????0,?filename.lastIndexOf("."));
????????????generateJavaFile(javafile,?classname,?expression);
????????????//编译文件
????????????invokeCompiler(javafile);
????????????//创建java类
????????????byte[]?buf?=?readBytes(classname?+?".class");
????????????Class?c?=?defineClass(buf,?0,?buf.length);
????????????try?{
????????????????//?创建并返回类的实例
????????????????return?(Calculator)?c.newInstance();
????????????}?catch?(IllegalAccessException?e)?{
????????????????throw?new?RuntimeException(e.getMessage());
????????????}?catch?(InstantiationException?e)?{
????????????????throw?new?RuntimeException(e.getMessage());
????????????}
????????}?catch?(IOException?e)?{
????????????throw?new?RuntimeException(e.getMessage());
????????}
????}
????//生成java文件
????void?generateJavaFile(
????????File?javafile,?String?classname,?String?expression)
????????????throws?IOException?{
????????FileOutputStream?out?=?new?FileOutputStream(javafile);
????????String?text?=?"public?class?"?+?classname?+
????????????????"?implements?Calculator?{"?+
????????????????"?public?int?evaluate(int[]?args)?{"?+
????????????????"?"?+?javaExpression(expression)?+
????????????????"?}"?+?"}";
????????out.write(text.getBytes());
????????out.close();
????}
????//编译java文件
????void?invokeCompiler(File?javafile)?throws?IOException?{
????????String[]?cmd?=?{_compiler,?"-classpath",
???????????????????_classpath,?javafile.getName()};
????????//执行编译命令
????????//A1:
????????Process?process?=?Runtime.getRuntime().exec(cmd);
????????try?{?//等待编译器结束
????????????process.waitFor();
????????}?catch?(InterruptedException?e)?{
????????}
????????int?val?=?process.exitValue();
????????if?(val?!=?0)?{
????????????throw?new?RuntimeException(
??????????????"编译错误:"?+?"错误代码"?+?val);
????????}
????}
????//以byte数组形式读入类文件
????byte[]?readBytes(String?filename)?throws?IOException?{
????????//?A2
????????File?classfile?=?new?File(filename);
????????byte[]?buf?=?new?byte[(int)?classfile.length()];
????????FileInputStream?in?=?new?FileInputStream(classfile);
????????in.read(buf);
????????in.close();
????????return?buf;
????}
????String?javaExpression(String?expression)?{
????????Stack?stack?=?new?Stack();
????????StringTokenizer?toks?
?????????????=?new?StringTokenizer(expression);
????????while?(toks.hasMoreTokens())?{
????????????String?tok?=?toks.nextToken();
????????????if?(tok.startsWith("$"))?{
????????????????stack.push("args[
??????????"?+?Integer.parseInt(tok.substring(1))?+?"]");
????????????}?else?{
????????????????int?op?=?"+-*/".indexOf(tok.charAt(0));
????????????????if?(op?==?-1)?{
????????????????????stack.push(tok);
????????????????}?else?{
????????????????????String?arg2?=?(String)?stack.pop();
????????????????????String?arg1?=?(String)?stack.pop();
????????????????????stack.push("(
??????????????"?+?arg1?+?"?"?+?tok.charAt(0)?+?"?"?+?arg2?+?")");
????????????????}
????????????}
????????}
????????return?"return?"?+?(String)?stack.pop()?+?";";
????}
}
?
有了动态生成的代码之后,还要编译这些代码。我们假定系统使用的是javac编译器,且系
统的PATH环境变量包含了javac编译器的路径。如果javac不在PATH环境变量中,或者要使
用其他的编译器,则可以通过compiler属性指定,例如“-Dcalc.compiler=jikes”。如果
编译器不是javac,一般还要把Java运行时JAR文件(jre/lib目录下的rt.jar)放入编译器
的CLASSPATH。我们通过classpath属性为编译器指示额外的CLASSPATH成员。例如“-Dcal
c.classpath=c:\java\jre\lib\rt.jar”。?
编译器可以通过Runtime.exec(String[]?cmd)作为一个外部进程调用,Runtime.exec的执
行结果是一个Process对象(参见注释为“A1”的代码,下文以相似的方式引用代码的特定
部分)。cmd数组包含了要执行的系统命令,其中第一个元素必须是待执行程序的名称,其
余元素是传递给执行程序的各个参数。启动编译进程后,我们要等待编译进程运行结束,
然后获取编译器的返回值。编译进程返回0表示编译成功。?
最后一个与编译器有关的问题是,由于编译器作为外部进程运行,所以最好能够读取编译
器的输出和错误报告。如果编译器遇到了大量的错误,编译过程可能处于阻塞状态(等待
读取)。本文的例子只是为了测试性能,为简单计,不处理该问题。但是,在正式的Java
工程中,这个问题是必须处理的。?
编译成功之后,当前目录下就会有一个class文件,我们要用ClassLoader装入它(注释“
A2”)。ClassLoader读取的是byte数组,所以我们先把class文件的内容读入byte数组,
然后创建一个类。这里的类装入器属于最简单的定制类装入器,不过它已经足以完成我们
这里的任务。成功装入类之后,创建该类的实例,然后返回这个实例(注释“A3”)。?
从测试结果可以看出,编译法计算器的性能有了显著的提高。同样是1000000次计算,现在
只需要100-200ms,而不是原来的1-2秒。不过,编译操作也带来了很大的时间开销,调用
javac编译器编译代码大约需要1-2秒,抵消了计算器本身性能的提升。不过,javac并不是
一个高性能的编译器,如果我们改用jikes之类的高速编译器,编译时间大大改善,降低到
了100-200ms。?
六、生成法?
最理想的方案当然是既有编译法的运行时性能优势,又避免调用外部编译器的开销。下面
我们要通过在内存中直接生成Java字节码避免调用外部编译器的开销,称之为生成法。?
Java?class文件的格式比较复杂,所以我们要用一个第三方的字节码代码库来生成文件。
本例使用的是BCEL,即Bytecode?Engineering?Library。BCEL是一个源代码开放的免费代
码库(http://sourceforge.net/projects/bcel/),可以帮助我们分析、创建、处理二进
制的Java字节码。先来看看用BCEL直接生成字节码的计算器代码清单。?
//CalculatorGenerator.java
import?java.io.*;
import?java.util.Stack;
import?java.util.StringTokenizer;
//从sourceforge.net/projects/bcel/下载BCEL代码库
import?de.fub.bytecode.classfile.*;
import?de.fub.bytecode.generic.*;
import?de.fub.bytecode.Constants;
public?class?CalculatorGenerator
????????extends?ClassLoader?{
????public?Calculator?generate(String?expression)?{
????????String?classname?=?
???????????????"Calc_"?+?System.currentTimeMillis();
????????//?声明类
????????//?B1
????????ClassGen?classgen?
????????????=?new?ClassGen(classname,?"java.lang.Object",?"",
????????????????Constants.ACC_PUBLIC?|?Constants.ACC_SUPER,
????????????????new?String[]{"Calculator"});
????????//?构造函数
????????//?B2
????????classgen.addEmptyConstructor(Constants.ACC_PUBLIC);
????????//?加入计算表达式的方法
????????//?B3
????????addEvalMethod(classgen,?expression);
????????byte[]?data?=?classgen.getJavaClass().getBytes();
????????Class?c?=?defineClass(data,?0,?data.length);
????????try?{
????????????return?(Calculator)?c.newInstance();
????????}?catch?(IllegalAccessException?e)?{
????????????throw?new?RuntimeException(e.getMessage());
????????}?catch?(InstantiationException?e)?{
????????????throw?new?RuntimeException(e.getMessage());
????????}
????}
????private?void?addEvalMethod(
????????????ClassGen?classgen,?String?expression)?{
????????//?B4
????????ConstantPoolGen?cp?=?classgen.getConstantPool();
????????InstructionList?il?
??????????????????=?new?InstructionList();
????????StringTokenizer?toks
??????????????????=?new?StringTokenizer(expression);
????????int?stacksize?=?0;
????????int?maxstack?=?0;
????????while?(toks.hasMoreTokens())?{
????????????String?tok?=?toks.nextToken();
????????????if?(tok.startsWith("$"))?{
????????????????int?varnum?=?Integer.parseInt(tok.substring(1));
????????????????//?数组引用
????????????????il.append(InstructionConstants.ALOAD_1);
????????????????//?数组序号
????????????????il.append(new?PUSH(cp,?varnum));
????????????????il.append(InstructionConstants.IALOAD);
????????????}?else?{
????????????????int?op?=?"+-*/".indexOf(tok.charAt(0));
????????????????//?根据操作符生成操作指令
????????????????switch?(op)?{
????????????????????case?-1:
????????????????????????int?val?=?Integer.parseInt(tok);
????????????????????????il.append(new?PUSH(cp,?val));
????????????????????????break;
????????????????????case?0:
????????????????????????il.append(InstructionConstants.IADD);
????????????????????????break;
????????????????????case?1:
????????????????????????il.append(InstructionConstants.ISUB);
????????????????????????break;
????????????????????case?2:
????????????????????????il.append(InstructionConstants.IMUL);
????????????????????????break;
????????????????????case?3:
????????????????????????il.append(InstructionConstants.IDIV);
????????????????????????break;
????????????????????default:
????????????????????????throw?new?RuntimeException("操作符非法");
????????????????}
????????????}
????????}
????????il.append(InstructionConstants.IRETURN);
????????//?创建方法
????????//?B5
????????MethodGen?method?
?????????????=?new?MethodGen(Constants.ACC_PUBLIC,?Type.INT,
????????????????new?Type[]
??????????????????????{
?????????????????????Type.getType("[I")},?new?String[]{"args"},
????????????????"evaluate",?classgen.getClassName(),?il,?cp);
????????//?B6
????????method.setMaxStack();
????????method.setMaxLocals();
????????//?将方法加入到类
????????classgen.addMethod(method.getMethod());
????}
}
?
使用BCEL时,首先要创建一个代表Java类的ClassGen对象(注释“B1”)。就象前面的编
译法一样,我们要定义一个唯一的类名字。与普通Java代码不同的是,现在我们要明确声
明超类java.lang.Object。ACC_PUBLIC声明该类是public类型。所有Java?1.0.2或更高版
本的Java类都必须声明ACC_SUPER访问标记。最后,我们指定了该类实现Calculator接口。
?
其次,我们要保证类有一个默认的构造函数(注释“B2”)。对于一般的Java编译器,如
果Java类没有定义构造函数,则Java编译器会自动插入一个默认的构造函数。现在我们用
BCEL直接生成字节码,必须显式声明构造函数。用BCEL生成默认构造函数的办法很简单,
只须调用ClassGen.addEmptyConstructor即可。?
最后,我们要生成计算表达式的evaluate(int[]?arguments)方法(注释“B3”和“B4”)
。JVM本身就是以堆栈为基础,所以把表达式转换成字节码的过程很简单,基于堆栈的计算
器几乎可以直接转换成字节码。指令按照执行次序收集到一个InstructionList。另外,我
们还要一个指向常量池的引用ConstantPoolGen。?
准备好InstructionList之后,接着我们就可以创建MethodGen对象(注释“B5”)。我们
要创建的是一个public类型的方法,它的返回值是int,输入参数是一个整数数组(注意,
这里我们用到了整数数组的内部表示法“[I”)。另外,我们还提供了参数的名字,不过
这不是必需的。在这里,参数的名字是args,方法的名字是evaluate,最后几个参数包括
一个类的名字,一个InstructionList和一个常量池。?
在BCEL中定义Java方法的限制比较严格(注释“B6”)。例如,Java方法必须声明它需要
多少大的操作符栈空间和为局部变量分配的空间。如果这些值错误,JVM将拒绝执行方法。
对于本例来说,手工计算这些值也不是很麻烦,但BCEL提供了几个能够分析字节码的方法
,我们只需简单地调用setMaxStack()和setMaxLocals()方法即可。?
至此为止,整个类已经构造完毕。剩下的任务就是将类装入JVM,只要内存中有了byte数组
形式的类,我们就可以象在编译法中那样调用类装入器。?
直接生成的代码和编译法生成的代码执行起来一样快,但初始的对象创建时间却大大减少
了。如果调用外部编译器,最好的情况下也需要100ms以上,利用BCEL创建类平均只需4ms
。?
七、性能和应用?
表一显示了四种方法的平均对象创建时间,其中编译法分两种编译器分别测试。表二是5个
测试用的表达式,表三是计算这些表达式1000000次所需时间。?
http://bbs1.nju.edu.cn/file/C/crudeII/performance.jpg?
显然,本文的例子完全是出于测试性能的目的,在实际应用中,要计算一个表达式100000
0次的情形是非常罕见的。然而,需要在运行时解析数据(XML、脚本语言、查询语句,等
等)却是经常会遇到的情形。动态代码生成不一定适用于每一类任务,但在下面这类场合
应该比较有用:?
·处理过程主要由运行时才有效的定义信息决定。?
·处理过程需要多次重复执行。?
·如果每次执行处理过程时都重新解析定义信息,需要付出较大的开销。?
如果某个问题适合于使用代码动态生成技术,接下来还有一个问题:应该使用编译法,还
是使用生成法?一般而言,首先生成Java代码然后调用外部编译器的方式比较简单。与JV
M指令相比,大多数人更熟悉Java代码;调试有源代码的程序也比直接调试字节码来得方便
。另外,好的编译器会在编译过程中优化代码,而这类优化操作在手工编码时一般是难以
顾及的。另一方面,调用外部编译器是一个开销很大的过程,配置编译器和CLASSPATH也增
加了维护应用的复杂程度。?
生成法的性能优势非常明显。但是,它要求开发者深入了解class文件的格式和JVM字节码
指令。编译器在生成代码的过程中实际上完成了许多表面上看不到的工作,手工编写的字
节码不一定能够达到编译器自动编译的效果。如果要生成的代码比较复杂,在选择使用生
成法之前,务必仔细斟酌。