QLExpress 数组与集合解析:语义模型、规则表达与工程实践

目录

[一、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. 复杂条件过滤)

(五)集合(List)操作:面向业务对象的能力

[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 中的数组逻辑

  • 中小规模数组遍历

  • 规则型统计计算

  • 条件过滤与分类

  • 可解释性强的业务逻辑

(二)不建议的使用场景

  • 大规模数据处理(上万级)

  • 复杂排序、分组

  • 高性能计算密集型任务

(三)最佳实践原则

  1. 规则清晰优于代码简短

  2. 显式循环优于隐式工具

  3. 先安全检查,再数组访问

  4. 数组做计算,List 做业务对象

  5. 表达式直接产出业务结果

四、结语

从本文的示例与分析可以看到,QLExpress 对数组与集合的支持,本质上并不是"功能堆叠",而是一种克制且高度工程化的能力设计

它不试图用高级 API 隐藏循环、条件与状态变化,而是选择让规则作者显式表达逻辑过程;

它不引入额外的数据结构语义,而是直接复用 Java 世界中最成熟、最稳定的数组与集合模型;

它不追求表达式的"炫技式简洁",而是优先保证规则的可读性、可调试性与长期可维护性

正因如此,数组遍历、条件统计、区间分析、多条件过滤等在业务中极其常见的逻辑,才能在 QLExpress 中以一种接近"业务自然语言"的方式展开,而不会演变为难以理解的脚本代码。

如果说规则引擎的价值在于"让业务规则脱离代码而不失工程质量",那么 QLExpress 的数组与集合能力,正是这一目标得以成立的重要基础设施。在教学、规则系统、风控决策、配置化计算等场景中,这套能力已经足够成熟,也非常值得在实际工程中长期使用与推广。

参考资料与延伸阅读