基本介绍
开发需求是想实现一种既支持算术运算又支持逻辑运算表达式,网上找了好多资料,大多都是以下情况:
-> 基于栈或队列来实现基本的算术运算
-> 基于模板引擎(js、Nashorn、javascript等)
存在问题
-> 底层JS引擎计算时会默认把 true 转化为1 ,false转化为0 (解决: 分支思想)
-> 对于&&、|| 结尾的字符串表达式并不报错,其会直接删除, 判断为合理(解决: 添加校验逻辑)
实现思路
实现过程
1、 使用前提: Java项目中引入依赖 jep 如下所示
<dependency>
<groupId>jep</groupId>
<artifactId>jep</artifactId>
<version>2.24</version>
</dependency>
2、基于JS引擎拆解执行逻辑(思想: 采用归并思想)
import com.monitor.common.exception.ServiceException;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Yaojinge
* @Description : 表达式计算工具类
* @date 2024/8/31
*/
public class ExpressionUtil {
/**
* 逻辑运算符 ||
*/
static final String EXPRESSION_OR="||";
/**
* 逻辑运算符 &&
*/
static final String EXPRESSION_AND="&&";
/**
* 逻辑运算符 &
*/
static final String EXPRESSION_SINGLE_AND="&";
/**
* 逻辑运算符 |
*/
static final String EXPRESSION_SINGLE_OR="|";
/**
* 正则匹配 ||
*/
static final String REGULAR_MATCH_OR="\|\|";
/**
* 正则表达式匹配中文字符串
*/
static final String PATTERN_CHINESE = "[\u4e00-\u9fa5]+";
/**
* 正表达式,匹配单独的 = ,而非 ===、==
*/
static final String PATTERN_EQUAL = "\b=\b";
/**
* 错误符号组合列表
*/
static final String[] COMBINE_LIST = {"*true", "true*", "/true", "true/", "+true", "true+", "-true", "true-",
"/true", "true/", "%true", "%false", "false%", "true%", "*false", "false*", "/false", "false/", "+false", "false+", "-false", "false-",
">true", "<true", ">false", "<false", ">=true", "<=true", ">=false", "<=false"};
/**
* 暂不支持符号列表
*/
final static String[] SYMBOL_LIST = {"++","--", ">>","<<", ">>>", "^", "[", "]", "{", "}", "?:", "instanceof", "+=", "-=", "*=",
"/=", "%=", ">>=", "<<=", ">>>=", "&=", "|=", "^=", "&&=", "||=", "?:"};
/**
* 非法字符列表(待完善)
*/
final static String[] ILLEGAL_LIST = {"@", "#", "$", "~", "null"};
/**
* 布尔运算符集合
*/
final static Set<String> BOOLEAN_SET = new HashSet<>(Arrays.asList("true", "false"));
/**
* 表达式校验和计算
* @param expression 表达式
* @return 计算结果
*/
public static Object calculateExpression(String expression) {
// 非空校验
if (expression == null || expression.length() == 0) {
throw new ServiceException("表达式不能为空!");
}
// 去除空字符
expression = expression.replace(" ", "");
// 排除暂不支持的运算符
checkSymbols(expression);
// 排除非法运算符组合
checkLegality(expression);
// 创建JS引擎
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("js");
// 统计左右括号个数
char[] array = expression.toCharArray();
int leftCount = 0,rightCount=0;
for (char c : array) {
if (c == '(') {
leftCount++;
} else if (c == ')') {
rightCount++;
}
}
// 根据左右括号的个数执行对应的逻辑
return getResult(expression, engine, leftCount, rightCount);
}
/**
* 根据左右括号的个数执行对应的计算逻辑
* @param expression 表达式
* @param engine JS引擎
* @param leftCount 左括号个数
* @param rightCount 右括号个数
* @return 计算结果
*/
private static Object getResult(String expression, ScriptEngine engine, int leftCount, int rightCount) {
Object result = null;
if (leftCount == 0 && rightCount == 0) {
result = attainOutcome(expression, engine);
} else if (leftCount != rightCount) {
throw new ServiceException("表达式左右括号数量不匹配!");
} else {
boolean flag = false;
char[] charArray = expression.toCharArray();
for (int i = 0; i < leftCount; i++) {
int leftIndex = 0,rightIndex = 0;
// 左括号找最右侧的位置
for (int j = 0; j < charArray.length; j++) {
if (charArray[j] == '(') {
leftIndex = j;
}
}
// 根据左括号位置找离其最近的右括号位置
for (int j = leftIndex; j < charArray.length; j++) {
if (charArray[j] == ')') {
rightIndex = j;
break;
}
}
// 取出括号中算式进行计算
if (rightIndex != 0 && (rightIndex > leftIndex)) {
// 拼接字符串
String leftString = expression.substring(0, leftIndex);
String middleString = expression.substring(leftIndex + 1, rightIndex);
String rightString = expression.substring(rightIndex + 1);
// 判断是否还需要再次通过JS引擎计算
boolean condition = (leftString.length() > 0 || rightString.length() > 0);
if ((i == leftCount - 1) || condition) {
// 若是还有其他算式,再执行一次计算逻辑
flag = true;
}
// 将中间的字符串放入到JS中进行计算
result = attainOutcome(middleString, engine);
// 执行完成后,拼接结果,并校验合法性
expression = leftString + result.toString() + rightString;
checkLegality(expression);
// 校验通过将表达式切分成字符数组
charArray= expression.toCharArray();
} else if ((leftIndex > rightIndex)) {
throw new ServiceException("表达式括号位置顺序错误!");
}
}
if (flag) {
// 将最后一次的计算也放入到情况一中执行
result = attainOutcome(expression, engine);
}
}
return result;
}
/**
* 检验表达式中是否存在暂不支持的字符
*
* @param expression 表达式
*/
private static void checkSymbols(String expression) {
for (String s : SYMBOL_LIST) {
if (expression.contains(s)) {
throw new ServiceException("暂不支持该表达式中的<" + s + ">字符运算");
}
}
}
/**
* 过滤非法字符
*
* @param expression 表达式
*/
private static void filterIllegalSymbols(String expression) {
for (String item : ILLEGAL_LIST) {
if (expression.contains(item)) {
throw new ServiceException("表达式中存在非法字符<" + item + ">,请检查");
}
}
}
/**
* 校验表达式合法性
* @param expression 表达式
*/
private static void checkLegality(String expression) {
// 过滤运算符 | & =
if (expression.matches(PATTERN_EQUAL)) {
throw new ServiceException("底层引擎不支持=运算,请修改");
} else if (containsChinese(expression)) {
throw new ServiceException("表达式中存在中文字符,请修改");
}
// 非法字符过滤
filterIllegalSymbols(expression);
// 表达式前后缀过滤(&&、||)
boolean condition = expression.startsWith(EXPRESSION_OR) || expression.endsWith(EXPRESSION_OR);
boolean condition1 = expression.startsWith(EXPRESSION_AND) || expression.endsWith(EXPRESSION_AND);
if (condition1 || condition) {
throw new ServiceException("表达式前后缀不符合运算规则,请检查");
}
// 错误组合过滤
for (String s : COMBINE_LIST) {
if (expression.contains(s)) {
throw new ServiceException("<" + s + ">不符合运算规则,请检查");
}
}
}
/**
* 获取计算结果
* @param expression 表达式
* @param engine JS引擎
* @return 操作结果
*/
private static Object attainOutcome(String expression, ScriptEngine engine) {
Object result = null;
// 剪枝处理(表达式以||或&&开头或结尾)
boolean condition = expression.startsWith(EXPRESSION_OR) || expression.endsWith(EXPRESSION_OR);
boolean condition1 = expression.startsWith(EXPRESSION_AND) || expression.endsWith(EXPRESSION_AND);
if (condition1 || condition) {
throw new ServiceException("表达式中&&或||两侧存在空值,请检查");
}
// 判断字符串中是否含有 || 符
if (expression.contains(EXPRESSION_OR)) {
try {
// 根据||符号进行拆分成字符串数组
String[] strList = expression.split(REGULAR_MATCH_OR);
for (int i = 0; i < strList.length; i++) {
// 遍历每个字符数组
if (strList[i].contains(EXPRESSION_AND)) {
// 存在&&符号
String[] list = strList[i].split(EXPRESSION_AND);
int amount = list.length;
if (amount > 0) {
int count = calculateAndCollect(engine, list);
//条件过滤及拼接
if (count != 0 && count == amount) {
int total = 0;
StringBuilder sb = new StringBuilder();
for (int j = 0; j < list.length; j++, total++) {
if (BOOLEAN_SET.contains(list[j].trim()) && (j != list.length - 1)) {
sb.append(list[j]).append(EXPRESSION_AND);
} else if (BOOLEAN_SET.contains(list[j].trim()) && j == list.length - 1) {
sb.append(list[j]);
} else {
throw new ServiceException(EXPRESSION_AND + "运算符两侧存在非布尔值,表达式错误,请检查");
}
}
if (total != 0 && total == amount) {
try {
strList[i] = engine.eval(sb.toString()).toString();
} catch (ScriptException e) {
throw new ServiceException("表达式有误,请检查");
}
}
}
}
} else {
if(strList[i].contains(EXPRESSION_SINGLE_OR)||strList[i].contains(EXPRESSION_SINGLE_AND)){
throw new ServiceException("表达式不支持|或&运算,请检查");
}
strList[i] = engine.eval(strList[i]).toString();
}
}
StringBuilder sb = new StringBuilder();
// 拼接字符串
for (int i = 0; i < strList.length; i++) {
if (i != strList.length - 1 && (BOOLEAN_SET.contains(strList[i].trim()))) {
sb.append(strList[i]).append(EXPRESSION_OR);
} else if (i == strList.length - 1 && (BOOLEAN_SET.contains(strList[i].trim()))) {
sb.append(strList[i]);
} else {
throw new ServiceException(EXPRESSION_OR + "运算符两侧因数存在非布尔值,表达式错误,请检查");
}
}
// 执行计算逻辑
result = engine.eval(sb.toString());
} catch (ScriptException e) {
throw new ServiceException("表达式有误,请检查");
}
} else if (expression.contains(EXPRESSION_AND)) {
// 存在&&符号
String[] list = expression.split(EXPRESSION_AND);
int amount = list.length;
if (amount > 0) {
int count = calculateAndCollect(engine, list);
// 字符串数组拼接和计算
if (count != 0 && count == amount) {
int total = 0;
StringBuilder sb = new StringBuilder();
for (int j = 0; j < list.length; j++, total++) {
if (BOOLEAN_SET.contains(list[j].trim()) && j != list.length - 1) {
// 开始进行拼接
sb.append(list[j]).append(EXPRESSION_AND);
} else if (BOOLEAN_SET.contains(list[j].trim()) && j == list.length - 1) {
// 最后一个,直接拼接即可
sb.append(list[j]);
} else {
throw new ServiceException(EXPRESSION_AND + "运算符两侧存在非布尔值,表达式错误,请检查");
}
}
if (total != 0 && total == amount) {
try {
result = engine.eval(sb.toString());
} catch (ScriptException e) {
e.printStackTrace();
}
}
}
}
} else {
// 情况三: 表达式中不存在 (、)、||、&& 等运算符,直接通过JS引擎计算即可
if (expression.contains(EXPRESSION_SINGLE_OR)||expression.contains(EXPRESSION_SINGLE_AND)){
throw new ServiceException("表达式不支持|或&运算,请检查");
}
try {
result = engine.eval(expression);
} catch (ScriptException e) {
throw new RuntimeException("表达式有误,请检查!");
}
}
return result;
}
/**
* 计算集合中的每个元素位置表达式
* @param engine JS引擎
* @param list 表达式集合
* @return 计数器结果
*/
private static int calculateAndCollect(ScriptEngine engine, String[] list) {
int count = 0;
// 遍历每个字符数组进行单独计算
for (int j = 0; j < list.length; j++) {
// 空值校验
if (list[j] == null || "".equals(list[j])) {
throw new ServiceException("表达式运算符" + EXPRESSION_AND + "两侧存在空值,请检查");
}
if (BOOLEAN_SET.contains(list[j].trim())) {
count++;
} else {
if(list[j].contains("|")||list[j].contains("&")){
throw new ServiceException("表达式不支持|或&运算,请检查");
}
try {
// 单独计算并赋值
list[j] = engine.eval(list[j]).toString();
count++;
} catch (ScriptException e) {
throw new ServiceException("表达式有误,请检查");
}
}
}
return count;
}
/**
* 判断字符串是否包含中文
* @param str 字符串
* @return 包含中文返回true
*/
public static boolean containsChinese(String str) {
Pattern pattern = Pattern.compile(PATTERN_CHINESE);
Matcher matcher = pattern.matcher(str);
return matcher.find();
}
}
测试代码:
public class LegalExpressionTest {
/**
* 日志对象
*/
private static final Logger logger = LoggerFactory.getLogger(LegalExpressionTest.class);
public static void main(String[] args) {
//算术运算
System.out.println("-------------------------------------------------------------------------算术运算-------------------------------------------------------------------");
logger.info("=> 1+2 = {}", ExpressionUtil.calculateExpression("1+2").toString());
logger.info("=> 1-2 = {}", ExpressionUtil.calculateExpression("1-2").toString());
logger.info("=> 1*2 = {}", ExpressionUtil.calculateExpression("1*2").toString());
logger.info("=> 1/2 = {}", ExpressionUtil.calculateExpression("1/2").toString());
logger.info("=> 1%2 = {}", ExpressionUtil.calculateExpression("1%2").toString());
// 复杂算术运算
System.out.println("-------------------------------------------------------------------------复杂算术运算------------------------------------------------------------------");
logger.info("=> 1+2*3 = {}", ExpressionUtil.calculateExpression("1+2*3").toString());
logger.info("=> 1+2*3/4 = {}", ExpressionUtil.calculateExpression("1+2*3/4").toString());
logger.info("=> 1+2*(3/4) = {}", ExpressionUtil.calculateExpression("1+2*(3/4)").toString());
logger.info("=> 1+2*(3/4)*5 = {}", ExpressionUtil.calculateExpression("1+2*(3/4)*5").toString());
logger.info("=> 1+2*(3/4)*5%6 = {}", ExpressionUtil.calculateExpression("1+2*(3/4)*5%6").toString());
logger.info("=> 1+2*(3/4)*5%6+7-8/9*10%11 = {}", ExpressionUtil.calculateExpression("1+2*(3/4)*5%6+7-8/9*10%11").toString());
logger.info("=> (32+21)%100 ={}", ExpressionUtil.calculateExpression("(32+21)%100").toString());
logger.info("=> 4.2-(32+21)%100 = {}", ExpressionUtil.calculateExpression("4.2-(32+21)%100").toString());
logger.info("=> (32+21)/100 = {}", ExpressionUtil.calculateExpression("(32+21)/100").toString());
logger.info("=> 4.1+(32+21)/100 = {}", ExpressionUtil.calculateExpression("4.1+(32+21)/100").toString());
//关系运算
System.out.println("--------------------------------------------------------------------------关系运算------------------------------------------------------------------");
logger.info("=> 1>2 = {}", ExpressionUtil.calculateExpression("1>2").toString());
logger.info("=> 1<2 = {}", ExpressionUtil.calculateExpression("1<2").toString());
logger.info("=> 1>=2 = {}", ExpressionUtil.calculateExpression("1>=2").toString());
logger.info("=> 1<=2 = {}", ExpressionUtil.calculateExpression("1<=2").toString());
logger.info("=> 1==2 = {}", ExpressionUtil.calculateExpression("1==2").toString());
logger.info("=> 1===2 = {}", ExpressionUtil.calculateExpression("1===2").toString());
logger.info("=> 1!=2 = {}", ExpressionUtil.calculateExpression("1!=2").toString());
logger.info("=> 1!==2 = {}", ExpressionUtil.calculateExpression("1!==2").toString());
// 复杂关系运算
System.out.println("---------------------------------------------------------------------------复杂关系运算------------------------------------------------------------------");
logger.info("=> 1>2&&2>3 = {}", ExpressionUtil.calculateExpression("1>2&&2>3").toString());
logger.info("=> 1>2&&2<3 = {}", ExpressionUtil.calculateExpression("1>2&&2<3").toString());
logger.info("=> 1>2||2<3 = {}", ExpressionUtil.calculateExpression("1>2||2<3").toString());
logger.info("=> (1>2||2<3)&&3>4 = {}", ExpressionUtil.calculateExpression("(1>2||2<3)&&3>4").toString());
logger.info("=> (1>2||2<3)&&3<4 = {}", ExpressionUtil.calculateExpression("(1>2||2<3)&&3<4").toString());
logger.info("=> (1>2||2<3)&&(3>4||4<5) = {}", ExpressionUtil.calculateExpression("(1>2||2<3)&&(3>4||4<5)").toString());
logger.info("=> (1>2||2<3)&&(3<4||4>5) = {}", ExpressionUtil.calculateExpression("(1>2||2<3)&&(3<4||4>5)").toString());
//逻辑运算
System.out.println("---------------------------------------------------------------------------逻辑运算------------------------------------------------------------------");
logger.info("=> true&&false = {}", ExpressionUtil.calculateExpression("true&&false").toString());
logger.info("=> true||false = {}", ExpressionUtil.calculateExpression("true||false").toString());
logger.info("=> !true = {}", ExpressionUtil.calculateExpression("!true").toString());
logger.info("=> !(7>3) = {}", ExpressionUtil.calculateExpression("!(7>3)"));
// 复杂逻辑运算
System.out.println("---------------------------------------------------------------------------复杂逻辑运算------------------------------------------------------------------");
logger.info("=> ((6>1)||(true&&(6<2))&&((4<7)||(6>=3))) = {}", ExpressionUtil.calculateExpression("((6>1)||(true&&(6<2))&&((4<7)||(6>=3)))"));
// 逻辑和算术混合运算
System.out.println("---------------------------------------------------------------------------逻辑和算术混合运算-----------------------------------------------------");
logger.info("=> (1+2>3&&4/2==2) = {}", ExpressionUtil.calculateExpression("(1+2>3&&4/2==2)"));
logger.info("=> false&&(4/2==2) = {}", ExpressionUtil.calculateExpression("false&&(4/2==2)"));
logger.info("=> true||4/2==2 = {}", ExpressionUtil.calculateExpression("true||4/2==2"));
logger.info("=> (1+2>3&&4/2==2)||(false&&(4/2==2)) = {}", ExpressionUtil.calculateExpression("(1+2>3&&4/2==2)||(false&&(4/2==2))"));
}
}
测试结果(合理):
-------------------------------------------------------------------------算术运算-------------------------------------------------------------------
18:29:05.084 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => 1+2 = 3
18:29:05.095 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => 1-2 = -1
18:29:05.102 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => 1*2 = 2
18:29:05.109 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => 1/2 = 0.5
18:29:05.116 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => 1%2 = 1
-------------------------------------------------------------------------复杂算术运算------------------------------------------------------------------
18:29:05.122 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => 1+2*3 = 7
18:29:05.127 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => 1+2*3/4 = 2.5
18:29:05.133 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => 1+2*(3/4) = 2.5
18:29:05.140 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => 1+2*(3/4)*5 = 8.5
18:29:05.147 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => 1+2*(3/4)*5%6 = 2.5
18:29:05.153 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => 1+2*(3/4)*5%6+7-8/9*10%11 = 0.6111111111111107
18:29:05.160 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => (32+21)%100 =53
18:29:05.166 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => 4.2-(32+21)%100 = -48.8
18:29:05.171 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => (32+21)/100 = 0.53
18:29:05.176 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => 4.1+(32+21)/100 = 4.63
--------------------------------------------------------------------------关系运算------------------------------------------------------------------
18:29:05.184 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => 1>2 = false
18:29:05.188 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => 1<2 = true
18:29:05.192 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => 1>=2 = false
18:29:05.195 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => 1<=2 = true
18:29:05.199 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => 1==2 = false
18:29:05.202 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => 1===2 = false
18:29:05.207 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => 1!=2 = true
18:29:05.212 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => 1!==2 = true
---------------------------------------------------------------------------复杂关系运算------------------------------------------------------------------
18:29:05.218 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => 1>2&&2>3 = false
18:29:05.226 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => 1>2&&2<3 = false
18:29:05.233 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => 1>2||2<3 = true
18:29:05.242 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => (1>2||2<3)&&3>4 = false
18:29:05.249 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => (1>2||2<3)&&3<4 = true
18:29:05.258 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => (1>2||2<3)&&(3>4||4<5) = true
18:29:05.267 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => (1>2||2<3)&&(3<4||4>5) = true
---------------------------------------------------------------------------逻辑运算------------------------------------------------------------------
18:29:05.271 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => true&&false = false
18:29:05.277 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => true||false = true
18:29:05.282 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => !true = false
18:29:05.289 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => !(7>3) = false
---------------------------------------------------------------------------复杂逻辑运算------------------------------------------------------------------
18:29:05.311 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => ((6>1)||(true&&(6<2))&&((4<7)||(6>=3))) = true
---------------------------------------------------------------------------逻辑和算术混合运算-----------------------------------------------------
18:29:05.321 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => (1+2>3&&4/2==2) = false
18:29:05.329 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => false&&(4/2==2) = false
18:29:05.340 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => true||4/2==2 = true
18:29:05.350 [main] INFO com.jin.demofunction.test.LegalExpressionTest - => (1+2>3&&4/2==2)||(false&&(4/2==2)) = false
Process finished with exit code 0
· 总结
主要实现了基于JS表达式引擎的逻辑和算术混合表达式的计算,目前的封装类支持的运算符如下:
- 小括号 : ()
- 基本四则运算符: +、-、*、/、%
- 判断: >、<、>= 、<=、!=、==
- 布尔运算符: && 、|| 、!
各位小伙伴测试过程若是遇到表达式不合理的情况,请在评论区给我留言,大家一起进步 !!