Kotlin Android算术表达式解析器Bug排查与替代方案咨询
问题排查与修复方案
我来帮你搞定这个表达式解析的Bug,先说说问题出在哪儿,再给你修复后的代码,同时也推荐几个更省心的替代方案。
核心Bug原因分析
操作符匹配完全错误
你的代码里所有操作符都写成了带双引号的形式(比如"\"-\""),但实际输入的表达式里的减号、加号都是不带引号的!这导致代码根本找不到正确的操作符,后续截取数字时自然会拿到空字符串,转Double就触发异常了。减法操作数顺序搞反了
在basic函数里,减法逻辑是rightNum?.toDouble()!! - leftNum?.toDouble()!!,这完全颠倒了左右操作数的顺序——比如a - b应该是a减b,你写成了b减a,不仅结果错误,还会在处理连续减法时加剧字符串截取的问题。正负号与减法操作符混淆
代码没有区分作为负号的-(比如-8里的-)和作为减法操作符的-(比如10-5里的-)。当表达式出现负数时,开头的-会被当成操作符处理,截取左数字时就会得到空字符串,触发异常。数字截取逻辑有缺陷
原来的parseSimple通过操作符索引的前后位置来截取数字,这种方式在处理负数、连续操作符时非常容易拿到空字符串,根本不可靠。
修复后的完整代码
下面是修复后的代码,解决了上述所有问题:
import kotlin.math.pow fun basic(leftNum: String?, rightNum: String?, op: String?): Double? { return when (op) { "+" -> leftNum?.toDouble()!! + rightNum?.toDouble()!! "-" -> leftNum?.toDouble()!! - rightNum?.toDouble()!! "*" -> leftNum?.toDouble()!! * rightNum?.toDouble()!! "^" -> leftNum?.toDouble()!!.pow(rightNum?.toDouble()!!) "/" -> { val right = rightNum?.toDouble()!! if (right == 0.0) throw ArithmeticException("Division by zero") leftNum?.toDouble()!! / right } else -> null } } fun parseSimple(query: String?): Double? { var calcQuery = query?.trim() ?: return null // 预处理正负号:把负号转换为合法的减法操作,避免被当成操作符误判 calcQuery = calcQuery.replace("^-", "0-") // 开头的负号 → 0-xxx .replace("\\(-", "(0-") // 括号后的负号 → (0-xxx) .replace("\\+-", "-") // 正号加负号 → 负号 .replace("--", "+") // 负号加负号 → 正号 val operations = listOf("^", "/", "*", "-", "+") var hasOperations = operations.any { calcQuery.contains(it) } while (hasOperations) { var processed = false // 按优先级处理:先乘方,再乘除,最后加减 for (op in operations) { val opIndex = calcQuery.indexOf(op) if (opIndex == -1) continue // 跳过作为负号的"-"(已经预处理过,这里只处理真正的减法操作符) if (op == "-" && (opIndex == 0 || calcQuery[opIndex - 1] in listOf('(', '+', '-', '*', '/', '^'))) { continue } // 向左找左边数字的起始位置 var leftIndex = opIndex - 1 while (leftIndex >= 0 && (calcQuery[leftIndex].isDigit() || calcQuery[leftIndex] == '.')) { leftIndex-- } leftIndex++ // 向右找右边数字的结束位置 var rightIndex = opIndex + 1 while (rightIndex < calcQuery.length && (calcQuery[rightIndex].isDigit() || calcQuery[rightIndex] == '.')) { rightIndex++ } rightIndex-- // 校验数字是否有效 if (leftIndex > opIndex - 1 || rightIndex < opIndex + 1) { throw IllegalArgumentException("Invalid expression: $calcQuery") } val leftNum = calcQuery.substring(leftIndex, opIndex) val rightNum = calcQuery.substring(opIndex + 1, rightIndex + 1) val result = basic(leftNum, rightNum, op) ?: throw IllegalArgumentException("Invalid operation: $op") // 替换当前计算部分为结果 calcQuery = calcQuery.substring(0, leftIndex) + result.toString() + calcQuery.substring(rightIndex + 1) processed = true break // 处理完一个操作符后重新扫描整个表达式 } hasOperations = if (processed) operations.any { calcQuery.contains(it) } else false } return try { calcQuery.toDouble() } catch (e: NumberFormatException) { throw IllegalArgumentException("Invalid number format: $calcQuery") } } fun getBrackets(query: String?): List<Int> { val q = query ?: return listOf(-1, -1) var depth = 0 // 先找最内层的括号(没有嵌套的括号对) for (i in q.indices) { if (q[i] == '(') depth++ else if (q[i] == ')') { depth-- if (depth == 0) { // 找到匹配的右括号,反向找对应的左括号 for (j in i-1 downTo 0) { if (q[j] == '(') { val inner = q.substring(j+1, i) if (!inner.contains('(') && !inner.contains(')')) { return listOf(j, i) } break } } } } } // 如果没有最内层,找第一个完整的括号对 val firstOpen = q.indexOf('(') if (firstOpen == -1) return listOf(-1, -1) var depth2 = 1 for (i in firstOpen+1 until q.length) { if (q[i] == '(') depth2++ else if (q[i] == ')') depth2-- if (depth2 == 0) return listOf(firstOpen, i) } return listOf(-1, -1) // 括号不匹配 } fun evaluate(query: String?): Double? { var calcQuery = query?.trim() ?: return null // 校验括号是否匹配 var depth = 0 for (c in calcQuery) { if (c == '(') depth++ else if (c == ')') { depth-- if (depth < 0) throw IllegalArgumentException("Mismatched parentheses: extra closing bracket") } } if (depth != 0) throw IllegalArgumentException("Mismatched parentheses: missing closing bracket") var index = 0 val maxIterations = 100 // 防止极端情况无限循环 while (calcQuery.contains('(') && index < maxIterations) { val bracketPair = getBrackets(calcQuery) val start = bracketPair[0] val end = bracketPair[1] if (start == -1 || end == -1) break val innerExpr = calcQuery.substring(start + 1, end) val innerResult = evaluate(innerExpr) ?: throw IllegalArgumentException("Invalid expression inside brackets: $innerExpr") calcQuery = calcQuery.substring(0, start) + innerResult.toString() + calcQuery.substring(end + 1) index++ } return parseSimple(calcQuery) } fun main() { print("Enter the equation: ") val equation = readLine() try { val result = evaluate(equation) println("Result: $result") } catch (e: Exception) { println("Error: ${e.message}") } }
更可靠的替代方案
如果你不想自己维护表达式解析逻辑,推荐使用成熟的库或者内置工具:
方案1:使用Exp4j(Android友好)
这是一个轻量级的Java表达式解析库,Android可以直接使用,支持所有基本算术操作和括号。
- 在Android项目的
build.gradle(Module级别)添加依赖:
implementation 'net.objecthunter:exp4j:0.4.8'
- 使用示例:
import net.objecthunter.exp4j.Expression import net.objecthunter.exp4j.ExpressionBuilder fun evaluateWithExp4j(query: String?): Double? { return try { val expression = ExpressionBuilder(query).build() expression.evaluate() } catch (e: Exception) { println("Error evaluating expression: ${e.message}") null } } // 在Activity或main函数中调用 fun main() { print("Enter the equation: ") val equation = readLine() println("Result: ${evaluateWithExp4j(equation)}") }
方案2:使用Kotlin脚本引擎(仅限非Android或权限允许的场景)
Kotlin内置了脚本引擎,可以直接解析表达式,但在Android中可能有性能或权限限制:
import javax.script.ScriptEngineManager fun evaluateWithScriptEngine(query: String?): Double? { val engine = ScriptEngineManager().getEngineByName("kotlin") return try { engine.eval(query) as Double } catch (e: Exception) { println("Error evaluating expression: ${e.message}") null } }
内容的提问来源于stack exchange,提问作者Sandaru Fernando
相关产品推荐
相关产品推荐

