Skip to content
dennis zhuang edited this page Jan 30, 2019 · 55 revisions

Table of Contents

Created by gh-md-toc

简介

Aviator是一个高性能、轻量级的 java 语言实现的表达式求值引擎, 主要用于各种表达式的动态求值。现在已经有很多开源可用的 java 表达式求值引擎,为什么还需要 Avaitor 呢?

Aviator的设计目标是轻量级和高性能,相比于Groovy、JRuby的笨重, Aviator非常小, 加上依赖包也才 537K,不算依赖包的话只有 70K; 当然, Aviator的语法是受限的, 它不是一门完整的语言, 而只是语言的一小部分集合。

其次, Aviator的实现思路与其他轻量级的求值器很不相同, 其他求值器一般都是通过解释的方式运行, 而Aviator则是直接将表达式编译成 JVM 字节码, 交给 JVM 去执行。简单来说, Aviator的定位是介于 Groovy 这样的重量级脚本语言和 IKExpression 这样的轻量级表达式引擎之间。

Aviator 的特性:

  1. 支持绝大多数运算操作符,包括算术操作符、关系运算符、逻辑操作符、位运算符、正则匹配操作符(=~)、三元表达式(?:)
  2. 支持操作符优先级和括号强制设定优先级
  3. 逻辑运算符支持短路运算。
  4. 支持丰富类型,例如nil、整数和浮点数、字符串、正则表达式、日期、变量等,支持自动类型转换。
  5. 内置一套强大的常用函数库
  6. 可自定义函数,易于扩展
  7. 可重载操作符
  8. 支持大数运算(BigInteger)和高精度运算(BigDecimal)
  9. 性能优秀

包依赖

Aviator依赖了commons-beanutils, 使用Aviator可以添加下面的maven依赖:

    <dependency>
        <groupId>com.googlecode.aviator</groupId>
        <artifactId>aviator</artifactId>
        <version>{version}</version>
    </dependency>

从 3.2.0 版本开始, Aviator 仅支持 JDK 7 及其以上版本。 JDK 6 请使用 3.1.1 这个稳定版本。

使用手册

执行表达式

Aviator的使用都是集中通过com.googlecode.aviator.AviatorEvaluator这个入口类来处理, 最简单的例子, 执行一个计算1+2+3的表达式:

    import com.googlecode.aviator.AviatorEvaluator;
    public class TestAviator {
        public static void main(String[] args) {
            Long result = (Long) AviatorEvaluator.execute("1+2+3");
            System.out.println(result);
        }
    }

细心的朋友肯定注意到结果是Long,而不是Integer。这是因为Aviator的数值类型仅支持LongDouble, 任何整数都将转换成Long, 任何浮点数都将转换为Double, 包括用户传入的变量数值。这个例子的打印结果将是正确答案6

例外情况是,如果开启了 ALWAYS_PARSE_FLOATING_POINT_NUMBER_INTO_DECIMAL 选项,那么在表达式中出现的浮点数都将解析为 BigDecimal,这是为了方便一些用户要求高精度的计算,又不想额外地给浮点数加上 M 后缀标记为 BigDecimal:

    AviatorEvaluator.setOption(Options.ALWAYS_PARSE_FLOATING_POINT_NUMBER_INTO_DECIMAL, true);

Aviator 的示例代码可以在 example 里找到。

多行表达式

从 4.0.0 开始, aviator支持以分号 ; 隔开的多行表达式,对于多行表达式求值的结果将是最后一个表达式的结果,例如

AviatorEvaluator.execute("print('hello world'); 1+2+3 ; 100-1");

的结果将是最后一个表达式 100-1,也就是 99,但是中间的表达式也将执行,包括打印 hello world。通过在表达式求值过程中加入 println打印,可以方便调试,也可以通过 Options.TRACE_EVAL来跟踪执行过程,参见后续章节。

求值器多实例

AviatorEvaluator是一个全局静态实例,但是很多场景下,你可能想为不同的场景提供一个不同的求值器实例,包括不同的选项配置和自定义函数列表等,那么从 4.0.0开始, Aviator提供了多实例的求值器支持:

AviatorEvaluatorInstance instance = AviatorEvaluator.newInstance();
//接下来使用 instance,几乎跟 AviatorEvaluator 没有不同,只是换成了实例方法

使用变量

想让Aviator对你say hello吗? 很简单, 传入你的名字, 让Aviator负责字符串的相加:

    public class TestAviator {
        public static void main(String[] args) {
            String yourName = "Michael";
            Map<String, Object> env = new HashMap<String, Object>();
            env.put("yourName", yourName);
            String result = (String) AviatorEvaluator.execute(" 'hello ' + yourName ", env);
            System.out.println(result);  // hello Michael
        }
    }

上面的例子演示了怎么向表达式传入变量值, 表达式中的yourName是一个变量, 默认为null, 通过传入Map<String,Object>的变量绑定环境, 将yourName设置为你输入的名称。 env 的key是变量名, value是变量的值。

上面例子中的'hello '是一个AviatorStringAviatorString是任何用单引号或者双引号括起来的字符序列, String可以比较大小(基于unicode顺序), 可以参与正则匹配, 可以与任何对象相加, 任何对象与String相加结果为String。 String中也可以有转义字符,如\n、\\、\' 等。

    AviatorEvaluator.execute(" 'a\"b' ");           // 字符串 a"b
    AviatorEvaluator.execute(" \"a\'b\" ");         // 字符串 a'b
    AviatorEvaluator.execute(" 'hello ' + 3 ");     // 字符串 hello 3
    AviatorEvaluator.execute(" 'hello '+ unknow "); // 字符串 hello null

exec 方法

Aviator 2.2 开始新增加一个exec方法, 可以更方便地传入变量并执行, 而不需要构造env这个map了:

    String name = "dennis";
    AviatorEvaluator.exec(" 'hello ' + yourName ", name); // hello dennis

只要在exec中按照变量在表达式中的出现顺序传入变量值就可以执行, 不需要构建Map了。

调用函数

Aviator 支持函数调用, 函数调用的风格类似 lua, 下面的例子获取字符串的长度:

    AviatorEvaluator.execute("string.length('hello')");  // 5

string.length('hello')是一个函数调用, string.length是一个函数, 'hello'是调用的参数。
再用string.substring来截取字符串:

    AviatorEvaluator.execute("string.contains(\"test\", string.substring('hello', 1, 2))");  // true

通过string.substring('hello', 1, 2)获取字符串'e', 然后通过函数string.contains判断e是否在'test'中。可以看到, 函数可以嵌套调用。
Aviator 的内置函数列表请看后面。

lambda函数定义

从 4.0.0 开始, aviator 支持通过 lambda 关键字定义一个匿名函数,并且支持闭包捕获:

AviatorEvaluator.exec("(lambda (x,y) -> x + y end)(x,y)", 1, 2);

上面的例子我们定义了一个接收两个参数的匿名函数 lambda (x,y) -> x + y end,然后直接使用 x,y两个变量进行调用,求值的时候传入的x,y分别是1和2,因此结果为3.

匿名函数的基本定义形式是

lambda (参数1,参数2...) -> 参数体表达式 end

匿名函数可以作为参数使用,也可以作为结果返回,例如下面这个稍微复杂点的例子,也可以看到闭包捕获的效果:

    AviatorEvaluator
        .exec("(lambda (x) -> lambda(y) -> lambda(z) -> x + y + z end end end)(1)(2)(3)");

我们定义了一个函数,它接收参数 x,然后返回了一个新的函数,这个新函数接收参数 y,新返回的又是另一个新函数,最后这个新函数接收参数 z,并且将 x+y+z 三个参数求和并返回。 假设这个匿名函数”名称“为s,那么上述表达式的调用结果等价于 s(1)(2)(3),最终就是 1+2+3。

匿名函数更大的用户在于后面的 seq 库配合高阶函数使用。

自定义函数

Aviator 除了内置的函数之外,还允许用户自定义函数,只要实现com.googlecode.aviator.runtime.type.AviatorFunction接口, 并注册到AviatorEvaluator即可使用. AviatorFunction接口十分庞大, 通常来说你并不需要实现所有的方法, 只要根据你的方法的参 数个数, 继承AbstractFunction类并override相应方法即可。

可以看一个例子,我们实现一个add函数来做数值的相加:

    public class TestAviator {
        public static void main(String[] args) {
            //注册函数
            AviatorEvaluator.addFunction(new AddFunction());
            System.out.println(AviatorEvaluator.execute("add(1, 2)"));           // 3.0
            System.out.println(AviatorEvaluator.execute("add(add(1, 2), 100)")); // 103.0
        }
    }
    class AddFunction extends AbstractFunction {
        @Override
        public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {
            Number left = FunctionUtils.getNumberValue(arg1, env);
            Number right = FunctionUtils.getNumberValue(arg2, env);
            return new AviatorDouble(left.doubleValue() + right.doubleValue());
        }
        public String getName() {
            return "add";
        }
    }

注册函数通过AviatorEvaluator.addFunction方法, 移除可以通过removeFunction。另外, FunctionUtils 提供了一些方便参数类型转换的方法。

如果你的参数个数不确定,可以继承 AbstractVariadicFunction 类,只要实现其中的 variadicCall 方法即可,比如我们实现一个找到第一个参数不为 null 的函数:

public class GetFirstNonNullFunction extends AbstractVariadicFunction {

    public AviatorObject variadicCall(Map<String, Object> env, AviatorObject... args) {
        if (args != null) {
            for (AviatorObject arg : args) {
                if (arg.getValue(env) != null) {
                    return arg;
                }
            }
        }
        return new AviatorString(null);
    }


    @Override
    public String getName() {
        return "getFirstNonNull";
    }

}

注册后使用就可以传入不定参数了:

getFirstNonNull(1);
getFirstNonNull(1,2,3,4,null,5);
getFirstNonNull(a,b,c,d);

当然,同时你仍然覆写特定的 call 方法来自定义实现。

自定义函数在 4.0.0 之后也可以通过 lambda 来定义:

AviatorEvaluator.defineFunction("add", "lambda (x,y) -> x + y end");
AviatorEvaluator.exec("add(1,2)");

加载自定义函数列表

除了通过代码的方式 AviatorEvaluator.addFunction 来添加自定义函数之外,你可以在 classpath 下放置一个配置文件 aviator_functions.config,内容是一行一行的自定义函数类的完整名称,例如:

# 这是一行注释
com.example.TestFunction
com.example.GetFirstNonNullFunction

那么 Aviator 将在 JVM 启动的时候自动加载这些自定义函数,配置文件中以 # 开头的行将被认为是注释。如果你想自定义文件路径,可以通过传入环境变量

-Dcom.googlecode.aviator.custom_function_config_file=xxxx.config

来设置。

函数加载器

从 4.0.0 开始,Aviator 还支持 FunctionLoader接口,可以用于自定义函数加载器:

/**
 * Function loader to load function when function not found.
 *
 * @author dennis
 *
 */
public interface FunctionLoader {

  /**
   * Invoked when function not found
   *
   * @param name function name
   */
  public AviatorFunction onFunctionNotFound(String name);
}

用户可以自主实现函数加载器,当函数不能从当前求值器中找到的时候,将调用 loader 的 onFunctionNotFound 方法进行查找。自定义的加载器,通过 AviatorEvaluator.addFunctionLoader(loader)注册,可以注册多个加载器,加载顺序将按照添加顺序进行查找,其中任何一个找到,都将中断查找过程。

重载运算符

Aviator 支持的运算符参见操作符一节。部分用户可能有重载这些内置运算符的需求,例如在 Excel 里, & 不是位运算,而是字符串连接符,那么你可以通过 3.3.0 版本支持的运算符重载来实现:

      AviatorEvaluator.addOpFunction(OperatorType.BIT_AND, new AbstractFunction() {

      @Override
      public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {
        return new AviatorString(arg1.getValue(env).toString() + arg2.getValue(env).toString());
      }

      @Override
      public String getName() {
        return "&";
      }
    });

AviatorEvaluator.addOpFunction(opType, func) 就可以重载指定的运算符,重载后运行即可看到:

 assertEquals("43", AviatorEvaluator.exec("a&3", 4));
 assertEquals("hello world", AviatorEvaluator.exec("a&' world'", "hello"));

请注意,运算符重载使用不当,一定程度上会带来混乱,并且有一定的性能损失,请慎重使用。

编译表达式

上面提到的例子都是直接执行表达式, 事实上 Aviator 背后都帮你做了编译并执行的工作。 你可以自己先编译表达式, 返回一个编译的结果, 然后传入不同的env来复用编译结果, 提高性能, 这是更推荐的使用方式:

    public class TestAviator {
        public static void main(String[] args) {
            String expression = "a-(b-c)>100";
            // 编译表达式
            Expression compiledExp = AviatorEvaluator.compile(expression);
            Map<String, Object> env = new HashMap<String, Object>();
            env.put("a", 100.3);
            env.put("b", 45);
            env.put("c", -199.100);
            // 执行表达式
            Boolean result = (Boolean) compiledExp.execute(env);
            System.out.println(result);  // false
        }
    }

通过compile方法可以将表达式编译成Expression的中间对象, 当要执行表达式的时候传入env并调用Expressionexecute方法即可。 表达式中使用了括号来强制优先级, 这个例子还使用了>用于比较数值大小, 比较运算符!=、==、>、>=、<、<=不仅可以用于数值, 也可以用于String、Pattern、Boolean等等, 甚至是任何用户传入的两个都实现了java.lang.Comparable接口的对象之间。

编译后的结果你可以自己缓存, 也可以交给 Aviator 帮你缓存, AviatorEvaluator内部有一个全局的缓存池, 如果你决定缓存编译结果, 可以通过:

    public static Expression compile(String expression, boolean cached)

cached设置为true即可, 那么下次编译同一个表达式的时候将直接返回上一次编译的结果。
使缓存失效通过:

    public static void invalidateCache(String expression)

方法。

访问数组和集合

可以通过中括号去访问数组和java.util.List对象, 可以通过map.key访问java.util.Mapkey对应的value, 一个例子:

    public static void main(String[] args) {
        final List<String> list = new ArrayList<String>();
        list.add("hello");
        list.add(" world");
        final int[] array = new int[3];
        array[0] = 0;
        array[1] = 1;
        array[2] = 3;
        final Map<String, Date> map = new HashMap<String, Date>();
        map.put("date", new Date());
        Map<String, Object> env = new HashMap<String, Object>();
        env.put("list", list);
        env.put("array", array);
        env.put("mmap", map);
        System.out.println(AviatorEvaluator.execute("list[0]+list[1]", env));   // hello world
        System.out.println(AviatorEvaluator.execute("'array[0]+array[1]+array[2]=' + (array[0]+array[1]+array[2])", env));  // array[0]+array[1]+array[2]=4
        System.out.println(AviatorEvaluator.execute("'today is ' + mmap.date ", env));  // today is Wed Feb 24 17:31:45 CST 2016
    }

如果函数调用或者括号表达式结果是一个数组或者List,你同样可以可以通过 [index] 访问:

  assertEquals("a", AviatorEvaluator.exec("string.split(s,',')[0]", "a,b,c,d"));

三元操作符

Aviator 不提供if else语句, 但是提供了三元操作符?:用于条件判断,使用上与 java 没有什么不同:

    AviatorEvaluator.exec("a>0? 'yes':'no'", 1);  // yes

Aviator 的三元表达式对于两个分支的结果类型并不要求一致,可以是任何类型,这一点与 java 不同。

正则表达式匹配

Aviator 支持类 Ruby 和 Perl 风格的表达式匹配运算,通过=~操作符, 如下面这个例子匹配 email 并提取用户名返回:

    public static void main(String[] args) {
        String email = "killme2008@gmail.com";
        Map<String, Object> env = new HashMap<String, Object>();
        env.put("email", email);
        String username = (String) AviatorEvaluator.execute("email=~/([\\w0-8]+)@\\w+[\\.\\w+]+/ ? $1 : 'unknow' ", env);
        System.out.println(username); // killme2008
    }

email与正则表达式/([\\w0-8]+@\\w+[\\.\\w+]+)/通过=~操作符来匹配,结果为一个 Boolean 类 型, 因此可以用于三元表达式判断,匹配成功的时候返回$1,指代正则表达式的分组 1,也就是用户名,否则返回unknown

Aviator 在表达式级别支持正则表达式,通过//括起来的字符序列构成一个正则表达式,正则表达式可以用于匹配(作为=~的右操作数)、比较大小。但是匹配仅能与字符串进行匹配。匹配成功后, Aviator 会自动将匹配成功的捕获分组(capturing groups) 放入 env ${num}的变量中,其中$0 指代整个匹配的字符串,而$1表示第一个分组,$2表示第二个分组以此类推。

请注意,分组捕获放入 env 是默认开启的,因此如果传入的 env 不是线程安全并且被并发使用,可能存在线程安全的隐患。关闭分组匹配,可以通过 AviatorEvaluator.setOption(Options.PUT_CAPTURING_GROUPS_INTO_ENV, false); 来关闭,对性能有稍许好处。

Aviator 的正则表达式规则跟 Java 完全一样,因为内部其实就是使用java.util.regex.Pattern做编译的。

变量的语法糖

Aviator 有个方便用户使用变量的语法糖, 当你要访问变量a中的某个属性b, 那么你可以通过a.b访问到, 更进一步, a.b.c将访问变量ab属性中的c属性值, 推广开来也就是说 Aviator 可以将变量声明为嵌套访问的形式。
TestAviator类符合JavaBean规范, 并且是 public 的,我们就可以使用语法糖:

    public class TestAviator {
        int i;
        float f;
        Date date;
        // 构造方法
        public TestAviator(int i, float f, Date date) {
            this.i = i;
            this.f = f;
            this.date = date;
        }
        // getter and setter

        public static void main(String[] args) {
            TestAviator foo = new TestAviator(100, 3.14f, new Date());
            Map<String, Object> env = new HashMap<String, Object>();
            env.put("foo", foo);
            System.out.println(AviatorEvaluator.execute("'foo.i = '+foo.i", env));   // foo.i = 100
            System.out.println(AviatorEvaluator.execute("'foo.f = '+foo.f", env));   // foo.f = 3.14
            System.out.println(AviatorEvaluator.execute("'foo.date.year = '+(foo.date.year+1990)", env));  // foo.date.year = 2106
        }
    }

对于深度嵌套并且同时有数组的变量访问,例如 foo.bars[1].name,从 3.1.0 版本开始, aviator 通过引用变量来支持(quote variable):

AviatorEvaluator.execute("'hello,' + #foo.bars[1].name", env)

引用变量要求以 # 符号开始,并且变量名中不能包含其他变量,也就是并不支持 #foo.bars[i].name 这样的访问,如果有此类特殊需求,请通过自定义函数实现。

对于一些深度嵌套的 List 或者数组的访问, commons-beanutils还支持类似 #map.array.[0].name这样的访问语法,如果不满足JavaBean规范的,请尝试使用这样的语法做嵌套访问。

nil 对象

nil是 Aviator 内置的常量,类似 java 中的null,表示空的值。nilnull不同的在于,在 java 中null只能使用在==、!=的比较运算符,而nil还可以使用>、>=、<、<=等比较运算符。 Aviator 规定,任何对象都比nil大除了nil本身。用户传入的变量如果为null,将自动以nil替代。

    AviatorEvaluator.execute("nil == nil");   //true
    AviatorEvaluator.execute(" 3> nil");      //true
    AviatorEvaluator.execute(" true!= nil");  //true
    AviatorEvaluator.execute(" ' '>nil ");    //true
    AviatorEvaluator.execute(" a==nil ");     //true, a 是 null

nilString相加的时候,跟 java 一样显示为 null

日期比较

Aviator 并不支持日期类型,如果要比较日期,你需要将日期写字符串的形式,并且要求是形如 “yyyy-MM-dd HH:mm:ss:SS”的字符串,否则都将报错。 字符串跟java.util.Date比较的时候将自动转换为Date对象进行比较:

    public static void main(String[] args) {
        Map<String, Object> env = new HashMap<String, Object>();
        final Date date = new Date();
        String dateStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SS").format(date);
        env.put("date", date);
        env.put("dateStr", dateStr);
        Boolean result = (Boolean) AviatorEvaluator.execute("date==dateStr", env);
        System.out.println(result);  // true
        result = (Boolean) AviatorEvaluator.execute("date > '2010-12-20 00:00:00:00' ", env);
        System.out.println(result);  // true
        result = (Boolean) AviatorEvaluator.execute("date < '2200-12-20 00:00:00:00' ", env);
        System.out.println(result);  // true
        result = (Boolean) AviatorEvaluator.execute("date==date ", env);
        System.out.println(result);  // true
    }

也就是说String除了能跟String比较之外,还能跟niljava.util.Date对象比较。

大数计算和精度

从 2.3.0 版本开始,aviator 开始支持大数字计算和特定精度的计算, 本质上就是支持java.math.BigIntegerjava.math.BigDecimal两种类型, 这两种类型在 aviator 中简称 为big intdecimal类型。 类似99999999999999999999999999999999这样的数字在 Java 语言里是没办法编译通过 的, 因为它超过了Long类型的范围, 只能用BigInteger来封装。但是 aviator 通过包装,可 以直接支持这种大整数的计算,例如:

    public static void main(String[] args) {
        System.out.println(AviatorEvaluator.exec("99999999999999999999999999999999 + 99999999999999999999999999999999"));
    }

结果为类型big int的: 199999999999999999999999999999998

字面量表示

big intdecimal的表示与其他数字不同,两条规则:

  • 以大写字母N为后缀的整数都被认为是big int,如1N,2N,9999999999999999999999N等, 都是big int类型。
  • 超过long范围的整数字面量都将自动转换为big int类型。
  • 以大写字母M为后缀的数字都被认为是decimal, 如1M,2.222M, 100000.9999M等, 都是decimal类型。

用户也可以通过变量传入这两种类型来参与计算。

如果用户觉的给浮点数添加 M 后缀比较繁琐,也可以强制所有浮点数解析为 BigDecimal,通过代码开启下列选项即可:

    AviatorEvaluator.setOption(Options.ALWAYS_PARSE_FLOATING_POINT_NUMBER_INTO_DECIMAL, true);

运算

big intdecimal的运算,跟其他数字类型long,double没有什么区别,操作符仍然是一样的。 aviator重载了基本算术操作符来支持这两种新类型:

    public static void main(String[] args) {
        Object rt = AviatorEvaluator.exec("9223372036854775807100.356M * 2");
        System.out.println(rt + " " + rt.getClass());  // 18446744073709551614200.712 class java.math.BigDecimal
        rt = AviatorEvaluator.exec("92233720368547758074+1000");
        System.out.println(rt + " " + rt.getClass());  // 92233720368547759074 class java.math.BigInteger
        BigInteger a = new BigInteger(String.valueOf(Long.MAX_VALUE) + String.valueOf(Long.MAX_VALUE));
        BigDecimal b = new BigDecimal("3.2");
        BigDecimal c = new BigDecimal("9999.99999");
        rt = AviatorEvaluator.exec("a+10000000000000000000", a);
        System.out.println(rt + " " + rt.getClass());  // 92233720368547758089223372036854775807 class java.math.BigInteger
        rt = AviatorEvaluator.exec("b+c*2", b, c);
        System.out.println(rt + " " + rt.getClass());  // 20003.19998 class java.math.BigDecimal
        rt = AviatorEvaluator.exec("a*b/c", a, b, c);
        System.out.println(rt + " " + rt.getClass());  // 2.951479054745007313280155218459508E+34 class java.math.BigDecimal
    }

类型转换和提升

big int或者decimal和其他类型的数字做运算的时候,按照long < big int < decimal < double的规则做提升, 也就是说运算的数字如果类型不一致, 结果的类型为两者之间更“高”的类型。例如:

  • 1 + 3N, 结果为big int4N
  • 1 + 3.1M,结果为decimal4.1M
  • 1N + 3.1M,结果为decimal的 4.1M
  • 1.0 + 3N,结果为double4.0
  • 1.0 + 3.1M,结果为double4.1

decimal 的计算精度

Java 的java.math.BigDecimal通过java.math.MathContext支持特定精度的计算,任何涉及到金额的计算都应该使用decimal类型。

默认 Aviator 的计算精度为MathContext.DECIMAL128,你可以自定义精度, 通过:

    AviatorEvaluator.setOption(Options.MATH_CONTEXT, MathContext.DECIMAL64);

即可设置,更多关于decimal的精度问题请看java.math.BigDecimal的 javadoc 文档。

强大的 seq 库

aviator 拥有强大的操作集合和数组的 seq 库。整个库风格类似函数式编程中的高阶函数。在 aviator 中, 数组以及java.util.Collection下的子类都称为seq,可以直接利用 seq 库进行遍历、过滤和聚合等操作。

例如,假设我有个 list:

    public static void main(String[] args) {
        Map<String, Object> env = new HashMap<String, Object>();
        ArrayList<Integer> list = new ArrayList<Integer>();
        list.add(3);
        list.add(20);
        list.add(10);
        env.put("list", list);
        Object result = AviatorEvaluator.execute("count(list)", env);
        System.out.println(result);  // 3
        result = AviatorEvaluator.execute("reduce(list,+,0)", env);
        System.out.println(result);  // 33
        result = AviatorEvaluator.execute("filter(list,seq.gt(9))", env);
        System.out.println(result);  // [10, 20]
        result = AviatorEvaluator.execute("include(list,10)", env);
        System.out.println(result);  // true
        result = AviatorEvaluator.execute("sort(list)", env);
        System.out.println(result);  // [3, 10, 20]
        AviatorEvaluator.execute("map(list,println)", env);
    }

我们可以:

  • 求长度: count(list)
  • 求和: reduce(list,+,0)reduce函数接收三个参数,第一个是seq,第二个是聚合的函数,如+等,第三个是聚合的初始值
  • 过滤: filter(list,seq.gt(9)), 过滤出list中所有大于9的元素并返回集合; seq.gt函数用于生成一个谓词,表示大于某个值
  • 判断元素在不在集合里: include(list,10)
  • 排序: sort(list)
  • 遍历整个集合: map(list,println)map接受的第二个函数将作用于集合中的每个元素,这里简单地调用println打印每个元素

其他还有:

  • seq.some(list, pred) 当集合中只要有一个元素满足谓词函数 pred 返回 true,立即返回 true,否则为 false。
  • seq.every(list, pred) 当集合里的每个元素都满足谓词函数 pred 返回 true,则结果为 true,否则返回 false。
  • seq.not_any(list, pred),当集合里的每个元素都满足谓词函数 pred 返回 false,则结果为 true,否则返回 false。

以及 seq.or(p1, p2, ...)seq.and(p1, p2, ...) 用于组合 seq.gtseq.lt 等谓词函数。

两种运行模式

默认 AviatorEvaluator 以执行速度优先:

    AviatorEvaluator.setOption(Options.OPTIMIZE_LEVEL, AviatorEvaluator.EVAL);

你可以修改为编译速度优先,这样不会做编译优化:

   AviatorEvaluator.setOption(Options.OPTIMIZE_LEVEL, AviatorEvaluator.COMPILE); 

调试信息

如果想查看每个表达式生成的字节码,可以通过打开 Trace 选项:

    import com.googlecode.aviator.Options;
    ......
    AviatorEvaluator.setOption(Options.TRACE, true);

默认是打印到标准输出,你可以改变输出指向:

    AviatorEvaluator.setTraceOutputStream(new FileOutputStream(new File("aviator.log")));

语法手册

下面是 Aviator 详细的语法规则定义。

数据类型

  • Number类型: 数字类型,支持四种类型,分别是long,double,java.math.BigInteger(简称 big int)java.math.BigDecimal(简 称 decimal),规则如下:
    • 任何以大写字母 N 结尾的整数都被认为是 big int
    • 任何以大写字母 M 结尾的数字都被认为是 decimal
    • 其他的任何整数都将被转换为 Long
    • 其他任何浮点数都将被转换为 Double
    • 超过 long 范围的整数字面量都将自动转换为 big int 类型

其中 big int 和 decimal 是 2.3.0 版本开始引入的。数字还支持十六进制(以0x或者0X开头的数字), 以及科学计数法,如1e-3等。 不支持其他进制。

  • String类型: 字符串类型,单引号或者双引号括起来的文本串,如'hello world', 变量如果传入的是String或者Character也将转为String类型
  • Bool类型: 常量truefalse,表示真值和假值,与 java 的Boolean.TRUEBoolean.False对应
  • Pattern类型: 正则表达式, 以//括起来的字符串,如/\d+/,内部 实现为java.util.Pattern
  • 变量类型: 与 Java 的变量命名规则相同,变量的值由用户传入
  • nil类型: 常量nil,类似 java 中的null,但是nil比较特殊,nil不仅可以参与==、!=的比较, 也可以参与>、>=、<、<=的比较,Aviator 规定任何类型都大于nil除了nil本身,nil==nil返回true。 用户传入的变量值如果为null,那么也将作为nil处理,nil打印为null

操作符

算术运算符

Aviator 支持常见的算术运算符,包括+ - * / %五个二元运算符,和一元运算符-(负)。其中- * / %和一元的-仅能作用于Number类型。
+不仅能用于Number类型,还可以用于String的相加,或者字符串与其他对象的相加。
Aviator 规定,任何类型与String相加,结果为String

逻辑运算符

Avaitor 的支持的逻辑运算符包括,一元否定运算符!,以及逻辑与的&&,逻辑或的||。逻辑运算符的操作数只能为Boolean
&&||都执行短路规则。

关系运算符

Aviator 支持的关系运算符包括<, <=, >, >=以及==!= 。
关系运算符可以作用于Number之间、String之间、Pattern之间、Boolean之间、变量之间以及其他类型与nil之间的关系比较, 不同类型除了nil之外不能相互比较。

位运算符

Aviator 支持所有的 Java 位运算符,包括&, |, ^, ~, >>, <<, >>>

匹配运算符

匹配运算符=~用于StringPattern的匹配,它的左操作数必须为String,右操作数必须为Pattern。 匹配成功后,Pattern的分组将存于变量$num,num为分组索引。

三元运算符

Aviator 没有提供if else语句,但是提供了三元运算符?:,形式为bool ? exp1: exp2。 其中bool必须为Boolean类型的表达式, 而exp1exp2可以为任何合法的 Aviator 表达式,并且不要求exp1exp2返回的结果类型一致。

内置函数

函数名称 说明
sysdate() 返回当前日期对象 java.util.Date
rand() 返回一个介于 0-1 的随机数,double 类型
rand(n) 返回一个介于 0- n 的随机数,long 类型
print([out],obj) 打印对象,如果指定 out,向 out 打印, 否则输出到控制台
println([out],obj) 与 print 类似,但是在输出后换行
now() 返回 System.currentTimeMillis
long(v) 将值的类型转为 long
double(v) 将值的类型转为 double
boolean(v) 将值的类型转为 boolean,除了 nil 和 false,其他都值都将转为布尔值 true。
str(v) 将值的类型转为 string
identity(v) 返回参数 v 自身,用于跟 seq 库的高阶函数配合使用。
max(v1,v2,v3, ...) 返回参数列表中的最大值,比较规则遵循 aviator 规则。
min(v1,v2,v3, ...) 返回参数列表中的最小值,比较规则遵循 aviator 规则。
date_to_string(date,format) 将 Date 对象转化化特定格式的字符串,2.1.1 新增
string_to_date(source,format) 将特定格式的字符串转化为 Date 对 象,2.1.1 新增
string.contains(s1,s2) 判断 s1 是否包含 s2,返回 Boolean
string.length(s) 求字符串长度,返回 Long
string.startsWith(s1,s2) s1 是否以 s2 开始,返回 Boolean
string.endsWith(s1,s2) s1 是否以 s2 结尾,返回 Boolean
string.substring(s,begin[,end]) 截取字符串 s,从 begin 到 end,如果忽略 end 的话,将从 begin 到结尾,与 java.util.String.substring 一样。
string.indexOf(s1,s2) java 中的 s1.indexOf(s2),求 s2 在 s1 中 的起始索引位置,如果不存在为-1
string.split(target,regex,[limit]) Java 里的 String.split 方法一致,2.1.1 新增函数
string.join(seq,seperator) 将集合 seq 里的元素以 seperator 为间隔 连接起来形成字符串,2.1.1 新增函数
string.replace_first(s,regex,replacement) Java 里的 String.replaceFirst 方法, 2.1.1 新增
string.replace_all(s,regex,replacement) Java 里的 String.replaceAll 方法 , 2.1.1 新增
math.abs(d) 求 d 的绝对值
math.sqrt(d) 求 d 的平方根
math.pow(d1,d2) 求 d1 的 d2 次方
math.log(d) 求 d 的自然对数
math.log10(d) 求 d 以 10 为底的对数
math.sin(d) 正弦函数
math.cos(d) 余弦函数
math.tan(d) 正切函数
map(seq,fun) 将函数 fun 作用到集合 seq 每个元素上, 返回新元素组成的集合
filter(seq,predicate) 将谓词 predicate 作用在集合的每个元素 上,返回谓词为 true 的元素组成的集合
count(seq) 返回集合大小
include(seq,element) 判断 element 是否在集合 seq 中,返回 boolean 值
sort(seq) 排序集合,仅对数组和 List 有效,返回排 序后的新集合
reduce(seq,fun,init) fun 接收两个参数,第一个是集合元素, 第二个是累积的函数,本函数用于将 fun 作用在结果值(初始值为 init 指定)和集合的每个元素上面,返回新的结果值;函数返回最终的结果值
seq.every(seq, fun) fun 接收集合的每个元素作为唯一参数,返回 true 或 false。当集合里的每个元素调用 fun 后都返回 true 的时候,整个调用结果为 true,否则为 false。
seq.not_any(seq, fun) fun 接收集合的每个元素作为唯一参数,返回 true 或 false。当集合里的每个元素调用 fun 后都返回 false 的时候,整个调用结果为 true,否则为 false。
seq.some(seq, fun) fun 接收集合的每个元素作为唯一参数,返回 true 或 false。当集合里的只要有一个元素调用 fun 后返回 true 的时候,整个调用结果立即为 true,否则为 false。
seq.eq(value) 返回一个谓词,用来判断传入的参数是否跟 value 相等,用于 filter 函数,如filter(seq,seq.eq(3)) 过滤返回等于3 的元素组成的集合
seq.neq(value) 与 seq.eq 类似,返回判断不等于的谓词
seq.gt(value) 返回判断大于 value 的谓词
seq.ge(value) 返回判断大于等于 value 的谓词
seq.lt(value) 返回判断小于 value 的谓词
seq.le(value) 返回判断小于等于 value 的谓词
seq.nil() 返回判断是否为 nil 的谓词
seq.exists() 返回判断不为 nil 的谓词
seq.and(p1, p2, p3, ...) 组合多个谓词函数,返回一个新的谓词函数,当今仅当 p1、p2、p3 ...等所有函数都返回 true 的时候,新函数返回 true
seq.or(p1, p2, p3, ...) 组合多个谓词函数,返回一个新的谓词函数,当 p1, p2, p3... 其中一个返回 true 的时候,新函数立即返回 true,否则返回 false。
seq.list(p1, p2, p3, ...) 创建一个 java.util.ArrayList 实例,添加参数到这个集合并返回。
seq.set(p1, p2, p3, ...) 创建一个 java.util.HashSet 实例,添加参数到这个集合并返回。
seq.list(k1, v1, k2, v2, ...) 创建一个 java.util.HashMap 实例,参数要求偶数个,类似 k1,v1 这样成对作为 key-value 存入 map,返回集合。
seq.add(coll, element)
seq.add(coll, key, value)
往集合 coll 添加元素,集合可以是 java.util.Collection,也可以是 java.util.Map(三参数版本)
seq.remove(coll, element)
seq.add(coll, key, value)
从集合或者 hash map 中删除元素或者 key
seq.get(coll, element)
seq.add(coll, key, value)
从 list、数组或者 hash-map 获取对应的元素值,对于 list 和数组, element 为元素的索引位置(从 0 开始),对于 hash map 来说, element 为 key。
seq.min(coll) 返回集合中的最小元素,要求集合元素可比较(实现 Comprable 接口),比较规则遵循 aviator 规则。
seq.max(coll) 返回集合中的最大元素,要求集合元素可比较(实现 Comprable 接口),比较规则遵循 aviator 规则。。

选项列表

AviatorEvaluator.setOption(opt, val) 支持定义求值器的行为,完整的 Options 枚举选项参见完整选项说明

Clone this wiki locally