您好,欢迎访问三七文档
当前位置:首页 > 商业/管理/HR > 质量控制/管理 > 动态生成Java代码的方法
摘要:本文介绍了如何在普通Java程序中应用代码动态生成技术,并测试、比较了各种实现方法的性能。提纲:一、概述/二、表达式计算器/三、解释法四、解析法/五、编译法/六、生成法/七、性能和应用正文:一、概述经常有人批评Java的性能,认为Java程序无法与C或C++程序相提并论。为此,Java一直在性能优化上进行着不懈的努力,特别是运行时的性能优化机制,平息了许多责难。但是,不管Java把性能提高到了什么程度,人们对代码性能的渴求是没有止境的。显然,Java在某些操作上的性能确实无法与C/C++相比,这是由Java语言的特点所决定的,例如为了跨平台而采用了中间语言(字节码)机制。另一方面,由于Java有着许多独特的特性,它可以利用许多其他语言很难采用的优化技术,动态代码生成就是其中之一。所谓动态代码生成,就是一种在运行时由程序动态生成代码的过程。动态生成的代码和生成它的程序在同一个JVM中运行,且访问方式也相似。当然,和其他优化技术相似,动态代码生成只适用于某些特定类型的任务。JSP或许就是人们最熟悉的动态代码生成的例子。Servlet引擎能够把客户的请求分发给Servlet处理,但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.javapublicinterfaceCalculator{intevaluate(int[]arguments);}三、解释法首先我们来看一个简单但效率不高的表达式计算器,它利用Stack对象计算表达。每次计算,表达式都要重新分析一次,因此可以称为解释法。不过,表达式的符号分析只在对象创建时执行一次,避免StringTokenizer类带来太大的开销。//SimpleCalculator.javaimportjava.util.ArrayList;importjava.util.Stack;importjava.util.StringTokenizer;publicclassSimpleCalculatorimplementsCalculator{String[]_toks;//符号列表publicSimpleCalculator(Stringexpression){//构造符号列表ArrayListlist=newArrayList();StringTokenizertokenizer=newStringTokenizer(expression);while(tokenizer.hasMoreTokens()){list.add(tokenizer.nextToken());}_toks=(String[])list.toArray(newString[list.size()]);}//将变量值代入表达式中的变量,//然后返回表达式的计算结果publicintevaluate(int[]args){Stackstack=newStack();for(inti=0;i_toks.length;i++){Stringtok=_toks[i];//以‘$’开头的是变量if(tok.startsWith($)){intvarnum=Integer.parseInt(tok.substring(1));stack.push(newInteger(args[varnum]));}else{charopchar=tok.charAt(0);intop=+-*/.indexOf(opchar);if(op==-1){//常量stack.push(Integer.valueOf(tok));}else{//操作符intarg2=((Integer)stack.pop()).intValue();intarg1=((Integer)stack.pop()).intValue();switch(op){//对栈顶的两个值执行指定的操作case0:stack.push(newInteger(arg1+arg2));break;case1:stack.push(newInteger(arg1-arg2));break;case2:stack.push(newInteger(arg1*arg2));break;case3:stack.push(newInteger(arg1/arg2));break;default:thrownewRuntimeException(操作符不合法:+tok);}}}}return((Integer)stack.pop()).intValue();}}从本文后面的性能测试数据可以看出,这种表达式计算方式的效率相当低。对于偶尔需要计算表达式的场合,它也许适用,但我们还有更好的处理方式。四、解析法如果经常要计算表达式的值,一种更好的办法是先解析表达式,应用Composite设计模式,构造一棵表达式树。我们称这种表达式计算方式为解析法。如下面的代码所示,树的内部结构代表了表达式的计算逻辑,因而避免了每次计算表达式时重复分析计算逻辑。//CalculatorParser.javaimportjava.util.Stack;importjava.util.StringTokenizer;publicclassCalculatorParser{publicCalculatorparse(Stringexpression){//分析表达式,构造由表达式各个符号构成的//树形结构。Stackstack=newStack();StringTokenizertoks=newStringTokenizer(expression);while(toks.hasMoreTokens()){Stringtok=toks.nextToken();if(tok.startsWith($)){//以‘$’开头的是变量intvarnum=Integer.parseInt(tok.substring(1));stack.push(newVariableValue(varnum));}else{intop=+-*/.indexOf(tok.charAt(0));if(op==-1){//常量intval=Integer.parseInt(tok);stack.push(newConstantValue(val));}else{//操作符Calculatornode2=(Calculator)stack.pop();Calculatornode1=(Calculator)stack.pop();stack.push(newOperation(tok.charAt(0),node1,node2));}}}return(Calculator)stack.pop();}//常量staticclassConstantValueimplementsCalculator{privateint_value;ConstantValue(intvalue){_value=value;}publicintevaluate(int[]args){return_value;}}//变量staticclassVariableValueimplementsCalculator{privateint_varnum;VariableValue(intvarnum){_varnum=varnum;}publicintevaluate(int[]args){returnargs[_varnum];}}//操作符staticclassOperationimplementsCalculator{char_op;Calculator_arg1;Calculator_arg2;Operation(charop,Calculatorarg1,Calculatorarg2){_op=op;_arg1=arg1;_arg2=arg2;}publicintevaluate(intargs[]){intval1=_arg1.evaluate(args);intval2=_arg2.evaluate(args);if(_op=='+'){returnval1+val2;}elseif(_op=='-'){returnval1-val2;}elseif(_op=='*'){returnval1*val2;}elseif(_op=='/'){returnval1/val2;}else{thrownewRuntimeException(操作符不合法:+_op);}}}}由于表达式的计算逻辑已经事先解析好,CalculatorParser的性能明显高于第一个通过解释方式执行的计算器。尽管如此,我们还可以通过代码动态生成技术进一步优化代码。五、编译法为了进一步优化表达式计算器的性能,我们要直接编译表达式——先根据表达式的逻辑动态生成Java代码,然后执行动态生成的Java代码,这种方法可以称之为编译法。把后缀表达式翻译成Java表达式很简单,例如“$0$1$2*+”可以由Java表达式“args[0]+(args[1]*args[2]”表示。我们要为动态生成的Java类选择一个唯一的名字,然后把代码写入临时文件。动态生成的Java类具有如下形式:publicclass[类的名称]implementsCalculator{publicintevaluate(int[]args){returnargs[0]+(args[1]*args[2]);}}下面是编译法计算器的完整代码。//CalculatorCompiler.javaimportjava.util.Stack;importjava.util.StringTokenizer;importjava.io.*;//定制的类装入器publicclassCalculatorCompilerextendsC
本文标题:动态生成Java代码的方法
链接地址:https://www.777doc.com/doc-2627331 .html