目录
[一、QLExpress 中的数组与集合语义模型](#一、QLExpress 中的数组与集合语义模型)
[(一) "不造轮子"的设计哲学](#(一) “不造轮子”的设计哲学)
[1. 数组长度与索引访问](#1. 数组长度与索引访问)
[2. 安全访问与边界控制](#2. 安全访问与边界控制)
[1. for 循环是数组操作的基础设施](#1. for 循环是数组操作的基础设施)
[2. 遍历 + 拼接:表达式即结果](#2. 遍历 + 拼接:表达式即结果)
[3. 正向、反向与带索引遍历](#3. 正向、反向与带索引遍历)
[1. 基本统计模型](#1. 基本统计模型)
[2. 最值统计:显式控制优于工具函数](#2. 最值统计:显式控制优于工具函数)
[3. 条件与区间统计](#3. 条件与区间统计)
[1. 线性搜索:最可靠的策略](#1. 线性搜索:最可靠的策略)
[2. 字符串数组搜索](#2. 字符串数组搜索)
[3. 复杂条件过滤](#3. 复杂条件过滤)
[1. List 在 QLExpress 中的语义](#1. List 在 QLExpress 中的语义)
[2. List 遍历与统计](#2. List 遍历与统计)
[3. 集合搜索与条件筛选](#3. 集合搜索与条件筛选)
[(一)适合放在 QLExpress 中的数组逻辑](#(一)适合放在 QLExpress 中的数组逻辑)
干货分享,感谢您的阅读!
在规则引擎、动态计算与业务逻辑配置化系统中,数组与集合并不是"基础语法点",而是承载规则复杂度的核心结构。一条规则是否具备实际工程价值,往往取决于它能否自然、可靠地处理"成批数据""对象列表"以及"多条件筛选"这类真实业务场景。
QLExpress 在数组与集合能力上的设计,并未引入额外的 DSL 抽象,也没有创造新的集合语义,而是坚定地建立在 Java 原生数组与集合模型之上。这种"不过度设计"的策略,使得规则表达式既具备脚本的灵活性,又保留了 Java 语义的可预期性与可维护性。
我们将围绕 QLExpress 中数组与集合的使用方式,从语义模型、操作范式、典型规则场景以及工程实践角度进行系统拆解。通过一组完整、可运行的示例代码,逐步展示数组与 List 在遍历、统计、搜索、过滤等高频规则逻辑中的表达方式,并总结在真实工程中值得遵循的使用原则。

一、QLExpress 中的数组与集合语义模型
(一) "不造轮子"的设计哲学
QLExpress 对数组和集合的核心原则是:
最大限度复用 Java 原生语义,而非引入 DSL 私有集合模型
具体体现在:
-
Java 数组:
int[] / String[] / double[] -
Java 集合:
List / Map / Set -
访问方式:与 Java 语义完全一致
这使得 QLExpress 表达式具备极强的可读性、可迁移性与可维护性。
(二)数组与集合的定位差异
| 类型 | 适合场景 |
|---|---|
| 数组(Array) | 固定结构、顺序敏感、数值计算 |
| 集合(List) | 动态数据、对象集合、业务实体列表 |
后面我们会在示例代码非常典型地体现这一点:
-
int[] scores→ 数值统计 -
String[] products→ 搜索过滤 -
List<String> cities→ 业务对象集合
(三)案例展示
我这里给出的 ArrayOperationsDemo 具体代码演示如下:
java
package org.zyf.javabasic.qlexpress.basic.arrays;
import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;
import java.util.ArrayList;
import java.util.List;
/**
* @program: zyfboot-javabasic
* @description: QLExpress数组操作演示 - 全面展示数组和集合的操作技巧
* @author: zhangyanfeng
* @create: 2025-12-25 23:55
**/
public class ArrayOperationsDemo {
private ExpressRunner runner;
public ArrayOperationsDemo() {
this.runner = new ExpressRunner();
System.out.println("✅ QLExpress数组操作演示引擎初始化完成");
}
/**
* 演示基本数组操作
*/
public void demonstrateBasicArrayOperations() {
System.out.println("\n=== QLExpress基本数组操作演示 ===\n");
try {
DefaultContext<String, Object> context = new DefaultContext<>();
// 准备测试数据
int[] numbers = {10, 25, 3, 47, 15, 8, 92, 33, 61, 7};
String[] names = {"张三", "李四", "王五", "赵六", "钱七"};
double[] scores = {85.5, 92.0, 78.5, 88.0, 95.5};
context.put("numbers", numbers);
context.put("names", names);
context.put("scores", scores);
// 1. 数组基本属性
System.out.println("📊 1. 数组基本属性演示:");
Object numbersLength = runner.execute("numbers.length", context, null, true, false);
Object firstNumber = runner.execute("numbers[0]", context, null, true, false);
Object lastNumber = runner.execute("numbers[numbers.length - 1]", context, null, true, false);
Object middleNumber = runner.execute("numbers[numbers.length / 2]", context, null, true, false);
System.out.printf(" 数组长度: numbers.length = %s%n", numbersLength);
System.out.printf(" 第一个元素: numbers[0] = %s%n", firstNumber);
System.out.printf(" 最后一个元素: numbers[%s] = %s%n", (Integer.parseInt(numbersLength.toString()) - 1), lastNumber);
System.out.printf(" 中间元素: numbers[%s] = %s%n%n", (Integer.parseInt(numbersLength.toString()) / 2), middleNumber);
// 2. 数组元素访问
System.out.println("🔍 2. 数组元素访问演示:");
Object name1 = runner.execute("names[1]", context, null, true, false);
Object score2 = runner.execute("scores[2]", context, null, true, false);
Object dynamicAccess = runner.execute("numbers[3 + 2]", context, null, true, false);
System.out.printf(" names[1] = %s%n", name1);
System.out.printf(" scores[2] = %s%n", score2);
System.out.printf(" numbers[3 + 2] = %s%n%n", dynamicAccess);
// 3. 数组边界检查
System.out.println("⚠️ 3. 数组边界检查演示:");
Object safeAccess1 = runner.execute("numbers.length > 5 ? numbers[5] : -1", context, null, true, false);
Object safeAccess2 = runner.execute("names.length > 10 ? names[10] : '不存在'", context, null, true, false);
System.out.printf(" 安全访问 numbers[5]: %s%n", safeAccess1);
System.out.printf(" 安全访问 names[10]: %s%n%n", safeAccess2);
} catch (Exception e) {
System.err.println("❌ 基本数组操作演示失败: " + e.getMessage());
e.printStackTrace();
}
}
/**
* 演示数组遍历操作
*/
public void demonstrateArrayIteration() {
System.out.println("\n=== QLExpress数组遍历演示 ===\n");
try {
DefaultContext<String, Object> context = new DefaultContext<>();
// 准备测试数据
int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
String[] fruits = {"苹果", "香蕉", "橙子", "葡萄", "西瓜"};
context.put("numbers", numbers);
context.put("fruits", fruits);
// 1. 基本for循环遍历
System.out.println("🔄 1. 基本for循环遍历演示:");
String traverseScript1 =
"result = '';" +
"for (i = 0; i < numbers.length; i++) {" +
" if (i > 0) result = result + ', ';" +
" result = result + numbers[i];" +
"}" +
"return '数组元素: [' + result + ']';";
Object traverse1 = runner.execute(traverseScript1, context, null, true, false);
System.out.printf(" %s%n%n", traverse1);
// 2. 条件遍历
System.out.println("❓ 2. 条件遍历演示:");
String conditionalScript =
"evenNumbers = '';" +
"oddNumbers = '';" +
"evenCount = 0;" +
"oddCount = 0;" +
"for (i = 0; i < numbers.length; i++) {" +
" if (numbers[i] % 2 == 0) {" +
" if (evenCount > 0) evenNumbers = evenNumbers + ', ';" +
" evenNumbers = evenNumbers + numbers[i];" +
" evenCount = evenCount + 1;" +
" } else {" +
" if (oddCount > 0) oddNumbers = oddNumbers + ', ';" +
" oddNumbers = oddNumbers + numbers[i];" +
" oddCount = oddCount + 1;" +
" }" +
"}" +
"return '偶数[' + evenCount + '个]: ' + evenNumbers + '; 奇数[' + oddCount + '个]: ' + oddNumbers;";
Object conditional = runner.execute(conditionalScript, context, null, true, false);
System.out.printf(" %s%n%n", conditional);
// 3. 带索引的遍历
System.out.println("🔢 3. 带索引的遍历演示:");
String indexedScript =
"result = '';" +
"for (i = 0; i < fruits.length; i++) {" +
" if (i > 0) result = result + ', ';" +
" result = result + (i + 1) + '.' + fruits[i];" +
"}" +
"return result;";
Object indexed = runner.execute(indexedScript, context, null, true, false);
System.out.printf(" 带序号的水果列表: %s%n%n", indexed);
// 4. 反向遍历
System.out.println("🔄 4. 反向遍历演示:");
String reverseScript =
"result = '';" +
"for (i = fruits.length - 1; i >= 0; i--) {" +
" if (i < fruits.length - 1) result = result + ' <- ';" +
" result = result + fruits[i];" +
"}" +
"return '反向遍历: ' + result;";
Object reverse = runner.execute(reverseScript, context, null, true, false);
System.out.printf(" %s%n%n", reverse);
} catch (Exception e) {
System.err.println("❌ 数组遍历演示失败: " + e.getMessage());
e.printStackTrace();
}
}
/**
* 演示数组统计操作
*/
public void demonstrateArrayStatistics() {
System.out.println("\n=== QLExpress数组统计演示 ===\n");
try {
DefaultContext<String, Object> context = new DefaultContext<>();
// 准备测试数据
int[] scores = {85, 92, 78, 95, 88, 76, 90, 82, 94, 87};
double[] prices = {19.99, 25.50, 12.30, 45.00, 8.75, 33.20, 15.80, 28.90};
context.put("scores", scores);
context.put("prices", prices);
// 1. 基本统计
System.out.println("📈 1. 基本统计演示:");
String basicStatsScript =
"sum = 0;" +
"count = scores.length;" +
"for (i = 0; i < count; i++) {" +
" sum = sum + scores[i];" +
"}" +
"avg = sum / count;" +
"return '总分: ' + sum + ', 平均分: ' + Math.round(avg * 100) / 100.0;";
Object basicStats = runner.execute(basicStatsScript, context, null, true, false);
System.out.printf(" %s%n%n", basicStats);
// 2. 最值统计
System.out.println("🏆 2. 最值统计演示:");
String minMaxScript =
"minVal = scores[0];" +
"maxVal = scores[0];" +
"for (i = 1; i < scores.length; i++) {" +
" if (scores[i] < minVal) {" +
" minVal = scores[i];" +
" }" +
" if (scores[i] > maxVal) {" +
" maxVal = scores[i];" +
" }" +
"}" +
"return minVal + \",\" + maxVal;";
Object minMax = runner.execute(minMaxScript, context, null, true, false);
String[] parts = minMax.toString().split(",");
System.out.printf(" 最低分: %s, 最高分: %s%n%n", parts[0], parts[1]);
// 3. 条件统计
System.out.println("📊 3. 条件统计演示:");
String conditionalStatsScript =
"excellent = 0;" + // >= 90
"good = 0;" + // 80-89
"fair = 0;" + // 70-79
"poor = 0;" + // < 70
"for (i = 0; i < scores.length; i++) {" +
" if (scores[i] >= 90) {" +
" excellent = excellent + 1;" +
" } else if (scores[i] >= 80) {" +
" good = good + 1;" +
" } else if (scores[i] >= 70) {" +
" fair = fair + 1;" +
" } else {" +
" poor = poor + 1;" +
" }" +
"}" +
"return '优秀:' + excellent + '个, 良好:' + good + '个, 一般:' + fair + '个, 较差:' + poor + '个';";
Object conditionalStats = runner.execute(conditionalStatsScript, context, null, true, false);
System.out.printf(" 成绩分布: %s%n%n", conditionalStats);
// 4. 范围统计
System.out.println("📏 4. 范围统计演示:");
String rangeStatsScript =
"totalPrice = 0;" +
"expensiveCount = 0;" + // > 30
"moderateCount = 0;" + // 15-30
"cheapCount = 0;" + // < 15
"for (i = 0; i < prices.length; i++) {" +
" totalPrice = totalPrice + prices[i];" +
" if (prices[i] > 30) {" +
" expensiveCount = expensiveCount + 1;" +
" } else if (prices[i] >= 15) {" +
" moderateCount = moderateCount + 1;" +
" } else {" +
" cheapCount = cheapCount + 1;" +
" }" +
"}" +
"avgPrice = Math.round(totalPrice / prices.length * 100) / 100.0;" +
"return '总价:¥' + totalPrice + ', 平均:¥' + avgPrice + ', 贵:' + expensiveCount + ', 中:' + moderateCount + ', 便宜:' + cheapCount;";
Object rangeStats = runner.execute(rangeStatsScript, context, null, true, false);
System.out.printf(" 价格分析: %s%n%n", rangeStats);
} catch (Exception e) {
System.err.println("❌ 数组统计演示失败: " + e.getMessage());
e.printStackTrace();
}
}
/**
* 演示数组搜索和过滤操作
*/
public void demonstrateArraySearchAndFilter() {
System.out.println("\n=== QLExpress数组搜索和过滤演示 ===\n");
try {
DefaultContext<String, Object> context = new DefaultContext<>();
// 准备测试数据
int[] numbers = {15, 28, 33, 42, 17, 55, 23, 61, 9, 38};
String[] products = {"手机", "电脑", "平板", "手机壳", "充电器", "电脑包", "鼠标", "键盘"};
context.put("numbers", numbers);
context.put("products", products);
// 1. 线性搜索
System.out.println("🔍 1. 线性搜索演示:");
String searchScript1 =
"target = 42;" +
"found = false;" +
"index = -1;" +
"for (i = 0; i < numbers.length; i++) {" +
" if (numbers[i] == target) {" +
" found = true;" +
" index = i;" +
" break;" +
" }" +
"}" +
"return '搜索' + target + ': ' + (found ? '找到,位置' + index : '未找到');";
Object search1 = runner.execute(searchScript1, context, null, true, false);
System.out.printf(" %s%n%n", search1);
// 2. 字符串搜索
System.out.println("📱 2. 字符串搜索演示:");
String searchScript2 =
"keyword = \"手机\";" +
"results = '';" +
"count = 0;" +
"for (i = 0; i < products.length; i++) {" +
" if (products[i].indexOf(keyword) >= 0) {" +
" if (count > 0) results = results + ', ';" +
" results = results + products[i] + '(位置' + i + ')';" +
" count = count + 1;" +
" }" +
"}" +
"return '搜索 "手机" : ' + (count > 0 ? '找到' + count + '个: ' + results : '未找到');";
Object search2 = runner.execute(searchScript2, context, null, true, false);
System.out.printf(" %s%n%n", search2);
// 3. 数值过滤
System.out.println("🎯 3. 数值过滤演示:");
String filterScript1 =
"threshold = 30;" +
"filtered = '';" +
"count = 0;" +
"for (i = 0; i < numbers.length; i++) {" +
" if (numbers[i] > threshold) {" +
" if (count > 0) filtered = filtered + ', ';" +
" filtered = filtered + numbers[i];" +
" count = count + 1;" +
" }" +
"}" +
"return '大于' + threshold + '的数: [' + filtered + '] (' + count + '个)';";
Object filter1 = runner.execute(filterScript1, context, null, true, false);
System.out.printf(" %s%n%n", filter1);
// 4. 复杂条件过滤
System.out.println("🎪 4. 复杂条件过滤演示:");
String filterScript2 =
"evenLarge = '';" + // 偶数且>20
"oddSmall = '';" + // 奇数且<=20
"evenLargeCount = 0;" +
"oddSmallCount = 0;" +
"for (i = 0; i < numbers.length; i++) {" +
" if (numbers[i] % 2 == 0 && numbers[i] > 20) {" +
" if (evenLargeCount > 0) evenLarge = evenLarge + ', ';" +
" evenLarge = evenLarge + numbers[i];" +
" evenLargeCount = evenLargeCount + 1;" +
" } else if (numbers[i] % 2 == 1 && numbers[i] <= 20) {" +
" if (oddSmallCount > 0) oddSmall = oddSmall + ', ';" +
" oddSmall = oddSmall + numbers[i];" +
" oddSmallCount = oddSmallCount + 1;" +
" }" +
"}" +
"return '偶数且>20: [' + evenLarge + '](' + evenLargeCount + '个); 奇数且<=20: [' + oddSmall + '](' + oddSmallCount + '个)';";
Object filter2 = runner.execute(filterScript2, context, null, true, false);
System.out.printf(" %s%n%n", filter2);
// 5. 多条件搜索
System.out.println("🔎 5. 多条件搜索演示:");
String multiSearchScript =
"keyword1 = \"电\";" +
"keyword2 = \"手机\";" +
"electronics = '';" +
"count = 0;" +
"for (i = 0; i < products.length; i++) {" +
" if (products[i].indexOf(keyword1) >= 0 || products[i].indexOf(keyword2) >= 0) {" +
" if (count > 0) electronics = electronics + ', ';" +
" electronics = electronics + products[i];" +
" count = count + 1;" +
" }" +
"}" +
"return '电子产品搜索结果: [' + electronics + '] (' + count + '个)';";
Object multiSearch = runner.execute(multiSearchScript, context, null, true, false);
System.out.printf(" %s%n%n", multiSearch);
} catch (Exception e) {
System.err.println("❌ 数组搜索和过滤演示失败: " + e.getMessage());
e.printStackTrace();
}
}
/**
* 演示集合操作
*/
public void demonstrateListOperations() {
System.out.println("\n=== QLExpress集合操作演示 ===\n");
try {
DefaultContext<String, Object> context = new DefaultContext<>();
// 准备测试数据
List<String> cities = new ArrayList<>();
cities.add("北京");
cities.add("上海");
cities.add("深圳");
cities.add("广州");
cities.add("杭州");
List<Integer> grades = new ArrayList<>();
grades.add(85);
grades.add(92);
grades.add(78);
grades.add(95);
grades.add(88);
context.put("cities", cities);
context.put("grades", grades);
// 1. 集合基本操作
System.out.println("📋 1. 集合基本操作演示:");
Object size = runner.execute("cities.size()", context, null, true, false);
Object firstCity = runner.execute("cities.get(0)", context, null, true, false);
Object contains = runner.execute("cities.contains('北京')", context, null, true, false);
Object indexOf = runner.execute("cities.indexOf('深圳')", context, null, true, false);
System.out.printf(" cities.size() = %s%n", size);
System.out.printf(" cities.get(0) = %s%n", firstCity);
System.out.printf(" cities.contains('北京') = %s%n", contains);
System.out.printf(" cities.indexOf('深圳') = %s%n%n", indexOf);
// 2. 集合遍历
System.out.println("🔄 2. 集合遍历演示:");
String listTraverseScript =
"result = '';" +
"for (i = 0; i < cities.size(); i++) {" +
" if (i > 0) result = result + ' -> ';" +
" result = result + cities.get(i);" +
"}" +
"return '城市列表: ' + result;";
Object traverse = runner.execute(listTraverseScript, context, null, true, false);
System.out.printf(" %s%n%n", traverse);
// 3. 集合统计
System.out.println("📊 3. 集合统计演示:");
String listStatsScript =
"sum = 0;" +
"highCount = 0;" + // >= 90
"for (i = 0; i < grades.size(); i++) {" +
" grade = grades.get(i);" +
" sum = sum + grade;" +
" if (grade >= 90) {" +
" highCount = highCount + 1;" +
" }" +
"}" +
"avg = Math.round(sum / grades.size() * 100) / 100.0;" +
"return '总分:' + sum + ', 平均分:' + avg + ', 优秀(' + highCount + '/' + grades.size() + ')';";
Object listStats = runner.execute(listStatsScript, context, null, true, false);
System.out.printf(" 成绩统计: %s%n%n", listStats);
// 4. 集合搜索
System.out.println("🔍 4. 集合搜索演示:");
String listSearchScript =
"target = \"上\";" +
"results = '';" +
"count = 0;" +
"for (i = 0; i < cities.size(); i++) {" +
" city = cities.get(i);" +
" if (city.indexOf(target) >= 0) {" +
" if (count > 0) results = results + ', ';" +
" results = results + city;" +
" count = count + 1;" +
" }" +
"}" +
"return '包含\"上\"的城市: ' + (count > 0 ? results + '(' + count + '个)' : '未找到');";
Object listSearch = runner.execute(listSearchScript, context, null, true, false);
System.out.printf(" %s%n%n", listSearch);
} catch (Exception e) {
System.err.println("❌ 集合操作演示失败: " + e.getMessage());
e.printStackTrace();
}
}
/**
* 运行所有演示
*/
public void runAllDemonstrations() {
System.out.println("🚀 开始QLExpress数组操作完整演示...\n");
demonstrateBasicArrayOperations();
demonstrateArrayIteration();
demonstrateArrayStatistics();
demonstrateArraySearchAndFilter();
demonstrateListOperations();
System.out.println("✅ QLExpress数组操作演示完成!");
System.out.println("\n📚 学习要点:");
System.out.println(" 1. 数组访问: array[index], array.length");
System.out.println(" 2. 数组遍历: for循环配合索引访问");
System.out.println(" 3. 数组统计: 求和、平均值、最值、条件统计");
System.out.println(" 4. 数组搜索: 线性搜索、条件过滤");
System.out.println(" 5. 集合操作: List的size(), get(), contains(), indexOf()等");
System.out.println(" 6. 安全访问: 边界检查,防止数组越界");
}
/**
* 主方法 - 运行演示
*/
public static void main(String[] args) {
ArrayOperationsDemo demo = new ArrayOperationsDemo();
demo.runAllDemonstrations();
}
}
基本运行验证如下:
python
✅ QLExpress数组操作演示引擎初始化完成
🚀 开始QLExpress数组操作完整演示...
=== QLExpress基本数组操作演示 ===
📊 1. 数组基本属性演示:
数组长度: numbers.length = 10
第一个元素: numbers[0] = 10
最后一个元素: numbers[9] = 7
中间元素: numbers[5] = 8
🔍 2. 数组元素访问演示:
names[1] = 李四
scores[2] = 78.5
numbers[3 + 2] = 8
⚠️ 3. 数组边界检查演示:
安全访问 numbers[5]: 8
安全访问 names[10]: 不存在
=== QLExpress数组遍历演示 ===
🔄 1. 基本for循环遍历演示:
数组元素: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
❓ 2. 条件遍历演示:
偶数[5个]: 2, 4, 6, 8, 10; 奇数[5个]: 1, 3, 5, 7, 9
🔢 3. 带索引的遍历演示:
带序号的水果列表: 1.苹果, 2.香蕉, 3.橙子, 4.葡萄, 5.西瓜
🔄 4. 反向遍历演示:
反向遍历: 西瓜 <- 葡萄 <- 橙子 <- 香蕉 <- 苹果
=== QLExpress数组统计演示 ===
📈 1. 基本统计演示:
总分: 867, 平均分: 86.0
🏆 2. 最值统计演示:
最低分: 76, 最高分: 95
📊 3. 条件统计演示:
成绩分布: 优秀:4个, 良好:4个, 一般:2个, 较差:0个
📏 4. 范围统计演示:
价格分析: 总价:¥189.44000000000003, 平均:¥23.68, 贵:2, 中:4, 便宜:2
=== QLExpress数组搜索和过滤演示 ===
🔍 1. 线性搜索演示:
搜索42: 找到,位置3
📱 2. 字符串搜索演示:
搜索 "手机" : 找到2个: 手机(位置0), 手机壳(位置3)
🎯 3. 数值过滤演示:
大于30的数: [33, 42, 55, 61, 38] (5个)
🎪 4. 复杂条件过滤演示:
偶数且>20: [28, 42, 38](3个); 奇数且<=20: [15, 17, 9](3个)
🔎 5. 多条件搜索演示:
电子产品搜索结果: [手机, 电脑, 手机壳, 充电器, 电脑包] (5个)
=== QLExpress集合操作演示 ===
📋 1. 集合基本操作演示:
cities.size() = 5
cities.get(0) = 北京
cities.contains('北京') = true
cities.indexOf('深圳') = 2
🔄 2. 集合遍历演示:
城市列表: 北京 -> 上海 -> 深圳 -> 广州 -> 杭州
📊 3. 集合统计演示:
成绩统计: 总分:438, 平均分:87.0, 优秀(2/5)
🔍 4. 集合搜索演示:
包含"上"的城市: 上海(1个)
✅ QLExpress数组操作演示完成!
📚 学习要点:
1. 数组访问: array[index], array.length
2. 数组遍历: for循环配合索引访问
3. 数组统计: 求和、平均值、最值、条件统计
4. 数组搜索: 线性搜索、条件过滤
5. 集合操作: List的size(), get(), contains(), indexOf()等
6. 安全访问: 边界检查,防止数组越界
Process finished with exit code 0
二、各部分详细讲解和指导说明
(一)数组的基础操作能力
1. 数组长度与索引访问
QLExpress 完整支持 Java 数组语义:
java
numbers.length
numbers[0]
numbers[numbers.length - 1]
关键特点:
-
length是属性而非方法 -
下标访问支持任意表达式
-
访问成本与 Java 等价
这使得表达式在逻辑层可以直接参与数组索引计算。
2. 安全访问与边界控制
QLExpress 不会"吞掉"数组越界异常,因此规则层必须显式做边界保护:
java
numbers.length > 5 ? numbers[5] : -1
这是一个非常重要的工程原则:
表达式是逻辑,不是容错层
在代码中,大量使用了三目表达式来保证数组访问安全,这是非常标准、值得推广的写法。
(二)数组遍历:规则引擎的核心能力之一
1. for 循环是数组操作的基础设施
QLExpress 支持标准的 C 风格 for 循环:
java
for (i = 0; i < numbers.length; i++) {
...
}
这意味着:
-
无需额外 API
-
无 DSL 学习成本
-
逻辑与 Java 完全同构
2. 遍历 + 拼接:表达式即结果
在你的示例中,数组遍历往往直接用于构建最终结果字符串:
java
result = result + numbers[i]
这是 QLExpress 的一个典型使用模式:
遍历 + 判断 + 拼接 = 完整业务输出
非常适合:
-
规则命中详情
-
决策解释文本
-
可视化展示数据
3. 正向、反向与带索引遍历
QLExpress 对遍历方向没有任何限制:
-
正向遍历:
i++ -
反向遍历:
i-- -
索引参与输出:
(i + 1)
这使得数组可以自然地支持:
-
排序结果展示
-
倒序分析
-
带序号列表输出
(三)数组统计:规则计算的高频场景
1. 基本统计模型
数组统计的典型范式在 QLExpress 中非常清晰:
java
sum = 0;
for (...) {
sum = sum + scores[i];
}
avg = sum / scores.length;
这种写法的优势在于:
-
无隐藏逻辑
-
易于调试
-
规则人员可读
2. 最值统计:显式控制优于工具函数
在代码中,最小值 / 最大值通过显式循环完成,而非依赖工具方法:
java
minVal = scores[0];
maxVal = scores[0];
这是非常推荐的做法,因为:
-
规则引擎应避免复杂 API 依赖
-
显式逻辑更利于排错
-
行为完全可预测
3. 条件与区间统计
QLExpress 在数组统计方面最强的能力之一是条件分类统计:
-
分数段分布
-
价格区间分析
-
风险等级计数
通过 if / else if,可以非常自然地表达业务规则,这一点在你的"成绩分布""价格区间"示例中体现得非常充分。
(四)数组搜索与过滤:规则引擎的核心价值点
1. 线性搜索:最可靠的策略
QLExpress 并不提供内建搜索函数,但线性搜索在规则层反而是优势:
java
for (...) {
if (numbers[i] == target) {
...
break;
}
}
原因在于:
-
数据规模通常有限
-
规则执行次数可控
-
可附带丰富上下文信息
2. 字符串数组搜索
通过 indexOf,QLExpress 可以轻松实现"包含式搜索":
java
products[i].indexOf(keyword) >= 0
这在以下场景非常常见:
-
商品名称规则
-
标签匹配
-
关键词召回
3. 复杂条件过滤
示例中展示了非常典型的多条件过滤模式:
-
偶数 + 大于某值
-
奇数 + 小于等于某值
-
多关键词 OR 搜索
这是 QLExpress 的规则引擎优势区:复杂条件可以直接展开,而不是隐藏在 Lambda 或 Stream 中。
(五)集合(List)操作:面向业务对象的能力
1. List 在 QLExpress 中的语义
QLExpress 对 List 的支持是直接调用 Java 方法:
java
cities.size()
cities.get(i)
cities.contains('北京')
没有任何语法糖,但也没有任何歧义。
2. List 遍历与统计
List 的遍历模式与数组高度一致,只是访问方式变为 get(i):
java
for (i = 0; i < cities.size(); i++) {
city = cities.get(i);
}
这使得数组与集合在规则层的使用方式几乎可以互换。
3. 集合搜索与条件筛选
通过字符串方法 + 循环,QLExpress 可以完成:
-
城市模糊搜索
-
对象属性筛选
-
规则命中集合构建
这在风控、推荐、配置化业务中非常常见。
三、工程实践建议(关键总结)
(一)适合放在 QLExpress 中的数组逻辑
-
中小规模数组遍历
-
规则型统计计算
-
条件过滤与分类
-
可解释性强的业务逻辑
(二)不建议的使用场景
-
大规模数据处理(上万级)
-
复杂排序、分组
-
高性能计算密集型任务
(三)最佳实践原则
-
规则清晰优于代码简短
-
显式循环优于隐式工具
-
先安全检查,再数组访问
-
数组做计算,List 做业务对象
-
表达式直接产出业务结果
四、结语
从本文的示例与分析可以看到,QLExpress 对数组与集合的支持,本质上并不是"功能堆叠",而是一种克制且高度工程化的能力设计。
它不试图用高级 API 隐藏循环、条件与状态变化,而是选择让规则作者显式表达逻辑过程;
它不引入额外的数据结构语义,而是直接复用 Java 世界中最成熟、最稳定的数组与集合模型;
它不追求表达式的"炫技式简洁",而是优先保证规则的可读性、可调试性与长期可维护性。
正因如此,数组遍历、条件统计、区间分析、多条件过滤等在业务中极其常见的逻辑,才能在 QLExpress 中以一种接近"业务自然语言"的方式展开,而不会演变为难以理解的脚本代码。
如果说规则引擎的价值在于"让业务规则脱离代码而不失工程质量",那么 QLExpress 的数组与集合能力,正是这一目标得以成立的重要基础设施。在教学、规则系统、风控决策、配置化计算等场景中,这套能力已经足够成熟,也非常值得在实际工程中长期使用与推广。
参考资料与延伸阅读
-
QLExpress 官方 GitHub
https://github.com/alibaba/QLExpress -
QLExpress 官方文档(语法与使用说明)
-
Java Array 官方文档(Oracle)
https://docs.oracle.com/javase/specs/jls/se8/html/jls-10.html -
Java List 接口说明(Oracle)
https://docs.oracle.com/javase/8/docs/api/java/util/List.html -
规则引擎设计实践(Martin Fowler)
https://martinfowler.com/bliki/RulesEngine.html