這是最近在項目中的一個需求,已知a=3,求字元串"a<=2"的值,也就是應該傳回false。這個問題可大可小,就我們的應用場景也就是用來讓使用者自定義變量區間,比如類似下面這樣的規則:
a<=2 傳回積分系數1.0
2<a<=5 傳回積分系數1.1
a>5 傳回積分系數1.2
如果用switch寫死在代碼中,以後要修改規則實在是很麻煩的事情,使用者也希望能自己維護這樣些區間值。于是我想就讓使用者自己輸入這樣的表達式和變量的值儲存在資料庫中,然後計算的時候由系統來解析表達式并求值。問題就歸結到求值字元串型邏輯表達式。這個問題恰好是規則引擎的應用領域,可我們的系統已經上線蠻久了,從維護角度也不希望再引入新的開源工具,況且也就這麼一個地方用到。如果是算術表達式(比如2+3之類)可以直接扔進資料庫執行即可,邏輯表達式倒是可以通過調用腳本語言來eval,但是同樣是考慮後期維護問題,也不想引入beanshell、groovy的腳本語言。是以,我就自己寫了個parser用于求值。
基本原理就是維護兩個棧:操作數棧和操作符号棧,解析和求值的過程就是入棧和出棧操作。首先使用arraylist實作一個棧,很容易的事情:
class stack {
protected java.util.arraylist pool = new java.util.arraylist();
public stack() {
}
public stack(int n) {
pool.ensurecapacity(n);
public void clear() {
pool.clear();
public boolean isempty() {
return pool.isempty();
public int size() {
return pool.size();
public object topel() {
if (isempty())
throw new java.util.emptystackexception();
return pool.get(pool.size() - 1);
public object pop() {
return pool.remove(pool.size() - 1);
public void push(object el) {
pool.add(el);
public string tostring() {
return pool.tostring();
}
然後看看expressionparser.java,原理已經列上,注釋也有,使用了單例模式,就請自己看了:
package net.rubyeye.codelib.util;
/**
* <p>類說明:用于表達式與實際值的比較</p>
* <p>注意事項:</p>
* <pre></pre>
* <p>建立日期:aug 6, 2007 10:18:58 am</p>
* <p>檔案名:expressionparser.java</p>
* @author:莊曉丹
* @version $id:$
*/
public class expressionparser {
private static final boolean debug = true;
private static expressionparser parser = new expressionparser();
private expressionparser() {
public static expressionparser getinstance() {
return parser;
public boolean firerule(string expression, double fact) {
tracecalculate("\nexpression:" + expression);
expression = expression.replace("\n|\r", "").trim();
char[] chars = expression.tochararray();
return parseexpression(fact, chars);
/**
* @param fact
* @param operatorsstack
* @param operandsstack
* @param chars
* @param operand
* @param operator
* @return
*/
private boolean parseexpression(double fact, char[] chars) {
boolean result = true;
string operand = "";
string operator = "";
stack operatorsstack = new stack();
stack operandsstack = new stack();
for (int i = 0; i < chars.length; i++) {
char token = chars[i];
tracecalculate("token:" + token);
if (character.isdigit(token) || token == '.') {
if (!operator.equals("")) {
tracecalculate("push operator:" + operator);
// 将操作符放入操作符号棧
operatorsstack.push(operator);
operator = "";
}
operand += token;
result = checktail(fact, operatorsstack, operandsstack,
chars.length, operand, result, i);
continue;
} else if (character.isletter(token)) {
operand = string.valueof(token);
//将操作數放入操作數棧
operandsstack.push(operand);
tracecalculate("push operand:" + token);
operand = "";
} else {
if (!operatorsstack.isempty() && !operandsstack.isempty()) {
//目前操作數是字母(變量),已存入棧,是以需要取出
if (operand.equals("")) {
operand = (string) operandsstack.pop();
result = result
&& calculateperfomance(operatorsstack,
operandsstack, operand, fact);
//目前操作數是數字
} else {
}
if (!operand.equals("")) {
result = checktail(fact, operatorsstack, operandsstack,
chars.length, operand, result, i);
//将操作數放入操作數棧
operandsstack.push(operand);
tracecalculate("push2 operand:" + operand);
operand = "";
operator += token;
}
}
return result;
* 判斷是否已經到表達式尾端,如果是,計算
* @param result
* @param i
private boolean checktail(double fact, stack operatorsstack,
stack operandsstack, int chars_length, string operand,
boolean result, int i) {
if (i == chars_length - 1) {
result = result
&& calculateperfomance(operatorsstack, operandsstack,
operand, fact);
private void displaystack(string name,stack stack) {
if (debug) {
for (int i = 0; i < stack.pool.size(); i++)
system.out.println(name+stack.pool.get(i));
private boolean calculateperfomance(stack operatorsstack,
stack operandsstack, string currentoperand, double fact) {
tracecalculate("開始計算");
displaystack("operators stack:",operatorsstack);
displaystack("operands stack:",operandsstack);
tracecalculate("currentoperand=" + currentoperand);
string operator = (string) operatorsstack.pop();
double lastoperand = coveroperandtodouble((string) operandsstack.pop(),
fact);
double nextoperand = coveroperandtodouble(currentoperand, fact);
if (operator.equals("=="))
return lastoperand == nextoperand;
if (operator.indexof("=") >= 0)
hasequal = true;
char[] operators = operator.tochararray();
for (int i = 0; i < operators.length; i++) {
switch (operators[i]) {
case '<':
result = result && (lastoperand < nextoperand);
break;
case '=':
//result為false,也就是小于,大于符号不滿足的時候,判斷等号是否成立
if (!result)
result = (lastoperand == nextoperand);
case '>':
result = result && (lastoperand > nextoperand);
if ((!result) && hasequal)
result = lastoperand == nextoperand;
* 用于debug
private void tracecalculate(string info) {
if (debug)
system.out.println(info);
private double coveroperandtodouble(string operand, double fact) {
//如果是字母,也就是變量,傳回fact變量
if (character.isletter(operand.tochararray()[0]))
return fact;
else
return double.parsedouble(operand);
通過debug變量來決定是否輸出計算過程,你可以設定為true來看看某個表達式的計算過程。附上單元測試來看看怎麼用:
* 測試表達式計算
import junit.framework.testcase;
public class expressioncalculatetest extends testcase {
string exp1,exp2,exp3, exp4;
double v1, v2, v3, v4, v5;
expressionparser parser = null;
protected void setup() throws exception {
exp1 = "a<80";
exp2 = "80<=a<81";
exp3 = "81<=a<=82";
exp4 = "a>=90";
v1 = 70.0;
v2 = 81.2;
v3 = 80;
v4 = 90;
v5 = 92;
parser = expressionparser.getinstance();
public void testfirerule() throws exception {
assertfalse(parser.firerule(exp1, v4));
asserttrue(parser.firerule(exp1, v1));
assertfalse(parser.firerule(exp1, v3));
assertfalse(parser.firerule(exp2, v2));
asserttrue(parser.firerule(exp2, v3));
assertfalse(parser.firerule(exp2, 82));
asserttrue(parser.firerule(exp3, v2));
asserttrue(parser.firerule(exp4, v4));
assertfalse(parser.firerule(exp4, v1));
asserttrue(parser.firerule(exp4, v5));
asserttrue(parser.firerule("b==100.00", 100.0));
asserttrue(parser.firerule("c==0.00", 0));
asserttrue(parser.firerule("60<=c<=80", 79.9));
assertfalse(parser.firerule("60<=50<=80", 0.0));
asserttrue(parser.firerule("60<=79<=80", 0.0));
assertfalse(parser.firerule("60<=99<=80", 0.0));
asserttrue(parser.firerule("60<=80<=90<100", 0.0));
assertfalse(parser.firerule("60<=99<=80<100", 0.0));
asserttrue(parser.firerule("10=<a=<30", 25));
這個小程式對處理一般的類似區間的規則計算應該還有點用,希望對别人幫助吧。表達式中的邏輯運算符>=和<=可以用=>和=<替代。
文章轉自莊周夢蝶 ,原文釋出時間 2007-08-06