最近遇到一個需求,需要對邏輯表達式進行計算,支持數據類型包括數字,日期以及字符串,運算符包括<,<=,>,>=,(,),==,!=,&&,||。
代碼結構:
OperatorEnum 運算符枚舉類 枚舉了支持的運算符,信息包括運算符及他們的優先級
OperandTypeEnum 數據類型枚舉類
LogicUtil 邏輯運算工具類,實現邏輯運算
DateUtil 日期工具類,提供驗證是否為日期以及比較大小方法,是否為日期類型見https://www.cnblogs.com/lin0/p/13323925.html
(后續還在實現邏輯表達式中添加了判斷表達式是否合法代碼,括號不匹配,運算符兩邊數據類型不一致,邏輯表達式不完整等,未粘貼上來)
OperatorEnum:
import java.util.HashMap; import java.util.Map; public enum OperatorEnum { //!,&,|運算時暫時未考慮在內,主要用於判斷字符是否為操作 NOT("!",0), LT("<",1), ELT("<=",1), GT(">",1), EGT(">=",1), EQ("==",2), NEQ("!=",2), BAND("&", 3), BOR("|", 4), AND("&&",5), OR("||",6), E("=", 7); private String name; private Integer priority; OperatorEnum(String name, Integer priority){ this.name = name; this.priority = priority; } private static Map<String, OperatorEnum> enums = new HashMap<>(); public static void enumToMap(){ enums.put("!", OperatorEnum.NOT); enums.put("<", OperatorEnum.LT); enums.put("<=", OperatorEnum.ELT); enums.put(">", OperatorEnum.GT); enums.put(">=", OperatorEnum.EGT); enums.put("==", OperatorEnum.EQ); enums.put("!=", OperatorEnum.NEQ); enums.put("&", OperatorEnum.BAND); enums.put("|", OperatorEnum.BOR); enums.put("&&", OperatorEnum.AND); enums.put("||", OperatorEnum.OR); enums.put("=", OperatorEnum.E); } public static OperatorEnum getEnumByName(String name){ if(enums.size() < 1){ enumToMap(); } return enums.get(name); } public static boolean isOperator(String name){ if(enums.size() < 1){ enumToMap(); } return enums.containsKey(name); } public Integer getPriority() { return priority; } public String getName() { return name; } }
OperandTypeEnum:
public enum OperandTypeEnum { NUM("數字"), DATE("日期"), STR("字符串"); private String typeName; OperandTypeEnum(String typeName){ this.typeName = typeName; } }
DateUtil:
/** * @author Carol * 比較日期大小 * @return */ public static int compareDate(String date1, String date2){ String d1 = date1.trim().replaceAll("-|:|/| ",""); String d2 = date2.trim().replaceAll("-|:|/| ", ""); StringBuilder sb1 = new StringBuilder(d1); StringBuilder sb2 = new StringBuilder(d2); while(sb1.length() < 14){ sb1.append("0"); } while(sb2.length() < 14){ sb2.append("0"); } long num1 = Long.parseLong(sb1.toString()); long num2 = Long.parseLong(sb2.toString()); return num1 == num2 ? 0 : num1 > num2 ? 1 : -1; }
LogicUtil:
/** * @author Carol * 計算表達式 * 利用一個操作數棧和一個操作棧來進行計算 * 出棧情況:當前字符串為操作並且優先級小於棧頂操作元素或者遇到“)”,將操作棧中棧頂元素出棧,操作數棧中出棧倆元素 * 入棧情況:“(”直接入棧,或者當前操作優先級大於棧頂元素則直接入棧,操作數直接入棧 * @param expression * @return */ public static boolean parse2(String expression) { Deque<String> operands = new LinkedList<>();//操作數棧 Deque<String> operate = new LinkedList<>();//操作棧 StringBuilder sb = new StringBuilder(); try { byte[] bytes = expression.getBytes("GBK"); for (int i = 0 ; i < bytes.length ; i ++) { //漢字 if (bytes[i] < 0) { if (i == bytes.length - 1) {//字符串異常截斷 //不要這個字 } else { sb.append(new String(new byte[]{bytes[i], bytes[i + 1]}, "GBK")); i ++; } continue; } String thisOp = new String(new byte[]{bytes[i]}); //直接入棧 if ("(".equals(thisOp)) { pushOperandIntoStack(operands, sb); operate.addFirst(thisOp); continue; } //到“(”之前的全部出棧 if (")".equals(thisOp)) { pushOperandIntoStack(operands, sb); String topOp = operate.pollFirst(); while(!"(".equals(topOp)){ calculate(operands, topOp); if (operate.size() > 0) { topOp = operate.pollFirst(); } else { //括號沒匹配上,邏輯表達式出錯 return false; } } continue; } if (OperatorEnum.isOperator(thisOp)) {//當前是否為操作符 //是,1.查看之前是否有字符未入棧,先入棧字符 pushOperandIntoStack(operands, sb); //2.查看下一個是否為操作,並且非括號,是合並當前一起入操作棧 String nextOp = new String(new byte[]{bytes[i + 1]}); //下個與當前一起組成一個操作 if (!"(".equals(nextOp) && !")".equals(nextOp) && OperatorEnum.isOperator(nextOp)) { thisOp += nextOp; i ++; } //判斷當前操作與棧頂操作優先級 if(operate.size() > 0){ String topOp = operate.getFirst(); while(!"(".equals(topOp) && OperatorEnum.getEnumByName(topOp).getPriority() < OperatorEnum.getEnumByName(thisOp).getPriority()){ //優先級高,出棧進行計算 topOp = operate.pollFirst(); calculate(operands, topOp); if (operate.size() > 0) { topOp = operate.getFirst(); } else { break; } } } operate.addFirst(thisOp); } else { sb.append(thisOp); } } } catch (Exception e){ e.printStackTrace(); } if(sb.length() > 0){ operands.addFirst(sb.toString()); } while(operate.size() > 0){ String topOp = operate.pollFirst(); calculate(operands, topOp); } if (operands.size() > 0){ String str = operands.pollFirst(); return StringUtils.isNotBlank(str) ? Boolean.parseBoolean(str) : false; } return false; } private static void pushOperandIntoStack(Deque operands, StringBuilder sb){ if(sb.length() > 0){ operands.addFirst(sb.toString()); sb.setLength(0); } } private static void calculate(Deque<String> operands, String topOp){ String operand2 = operands.pollFirst().trim(); String operand1 = operands.pollFirst().trim(); //判斷兩個操作數類型,不一致不可比較直接返回false OperandTypeEnum type1 = judgeType(operand1); OperandTypeEnum type2 = judgeType(operand2); if (type1 == type2) { switch (type1){ case NUM: operands.addFirst(numCalculate(Long.parseLong(operand1), Long.parseLong(operand2), OperatorEnum.getEnumByName(topOp)) + ""); break; case DATE: operands.addFirst(dateCalculate(operand1, operand2, OperatorEnum.getEnumByName(topOp)) + ""); break; case STR: operands.addFirst(strCalculate(operand1, operand2, OperatorEnum.getEnumByName(topOp)) + ""); break; } } else { operands.addFirst("false"); } } private static OperandTypeEnum judgeType (String operands) { operands = operands.trim(); if (Pattern.matches("^[-\\+]?[\\d]*$", operands)) { return OperandTypeEnum.NUM; } if (DateUtil.verifyDateLegal(operands)) { return OperandTypeEnum.DATE; } return OperandTypeEnum.STR; } private static boolean numCalculate(long operand1, long operand2, OperatorEnum operate){ switch (operate){ case LT: return operand1 < operand2; case ELT: return operand1 <= operand2; case GT: return operand1 > operand2; case EGT: return operand1 >= operand2; case EQ: return operand1 == operand2; case NEQ: return operand1 != operand2; default: return true; } } private static boolean strCalculate(String operand1, String operand2, OperatorEnum operate){ switch (operate){ case EQ: return operand1.equals(operand2); case NEQ: return !operand1.equals(operand2); case AND: return "true".equals(operand1) && "true".equals(operand2); case OR: return "true".equals(operand1) || "true".equals(operand2); default: return true; } } private static boolean dateCalculate(String operand1, String operand2, OperatorEnum operate){ switch (operate){ case LT: return DateUtil.compareDate(operand1, operand2) == -1 ? true : false; case ELT: return DateUtil.compareDate(operand1, operand2) <= 0 ? true : false; case GT: return DateUtil.compareDate(operand1, operand2) == 1 ? true : false; case EGT: return DateUtil.compareDate(operand1, operand2) >= 0 ? true : false; case EQ: return DateUtil.compareDate(operand1, operand2) == 0 ? true : false; case NEQ: return DateUtil.compareDate(operand1, operand2) != 0 ? true : false; default: return true; } }
測試代碼:
System.out.println( LogicUtil.parse2("2==3 && 2==2 || 3==3")); System.out.println("true == " + parse2("3>2")); System.out.println("false == " + parse2("true == false")); System.out.println("false == " + parse2("(3<2||2==2)&&(4==5)")); System.out.println("false == " + parse2("(abc==fdg||民事==民事&&嘻嘻==嘻ha)&&(4>2||5<10)")); System.out.println("true == " + parse2("()(((abc!=fdg||民事==民事&&嘻嘻==嘻ha)&&(4>2||5<10)))")); System.out.println("true == " + parse2("5 < 19")); System.out.println("false == " + parse2("5 > 19"));
測試結果: