四种动态生成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.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的性能明显高于第一个通过解释方式执行的计算器。尽管如此,我们还可以通过代码动态生成技术进一步优化代码。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
动态代理是Java中的一种高级特性,它允许在运行时动态生成一个代理类来实现接口或者继承一个类的功能。以下是一个简单的示例代码: ```java import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class DynamicProxy implements InvocationHandler { private Object target; public DynamicProxy(Object target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Before invoking " + method.getName()); Object result = method.invoke(target, args); System.out.println("After invoking " + method.getName()); return result; } public static void main(String[] args) { // 创建被代理对象 Calculator calculator = new CalculatorImpl(); // 创建代理对象 DynamicProxy handler = new DynamicProxy(calculator); Calculator proxy = (Calculator) Proxy.newProxyInstance( calculator.getClass().getClassLoader(), calculator.getClass().getInterfaces(), handler); // 使用代理对象调用方法 proxy.add(1, 2); proxy.subtract(1, 2); proxy.multiply(2, 3); proxy.divide(4, 2); } } interface Calculator { int add(int a, int b); int subtract(int a, int b); int multiply(int a, int b); int divide(int a, int b); } class CalculatorImpl implements Calculator { public int add(int a, int b) { return a + b; } public int subtract(int a, int b) { return a - b; } public int multiply(int a, int b) { return a * b; } public int divide(int a, int b) { return a / b; } } ``` 上面代码中,我们创建了一个代理类`DynamicProxy`,实现了`InvocationHandler`接口,并在`invoke`方法中实现了对目标对象方法的增强。然后创建了一个被代理对象`CalculatorImpl`,并在`main`方法中使用`Proxy.newProxyInstance`创建了一个代理对象,并调用了代理对象的方法来触发`invoke`方法的执行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值