四种动态⽣成Java代码的⽅法
四种动态⽣成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.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.Token());
}
_toks = (String[])
}
// 将变量值代⼊表达式中的变量,
// 然后返回表达式的计算结果
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 = Token();
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;
jsp创建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);
}
}
}
}
四种动态⽣成Java代码的⽅法(三)
--------------------------------------------------------------------------------
五、编译法
为了进⼀步优化表达式计算器的性能,我们要直接编译表达式——先根据表达式的逻辑动态⽣成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.*;
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论