使用java语言 要求能对包含java加减乘除运算 取余 倒数 幂运算 括号等的任意表达式进行求解

跟我学Spring3(5.3):Spring 表达式语言之 SpEL 语法 - ImportNew
| 标签: ,
5.3 SpEL语法
5.3.1 基本表达式
一、字面量表达式: SpEL支持的字面量包括:字符串、数字类型(int、long、float、double)、布尔类型、null类型。
String str1 = parser.parseExpression(“‘Hello World!’”).getValue(String.class);
String str2 = parser.parseExpression(“\”Hello World!\”").getValue(String.class);
int int1 = parser.parseExpression(+).getValue(Integer.class);
long long1 = parser.parseExpression(“-1L”).getValue(long.class);
float float1 = parser.parseExpression(&#″).getValue(Float.class);
double double1 = parser.parseExpression(&#E+2″).getValue(double.class);
int hex1 = parser.parseExpression(“0xa”).getValue(Integer.class);
long hex2 = parser.parseExpression(“0xaL”).getValue(long.class);
boolean true1 = parser.parseExpression(“true”).getValue(boolean.class);
boolean false1 = parser.parseExpression(“false”).getValue(boolean.class);
Object null1 = parser.parseExpression(“null”).getValue(Object.class);
二、算数运算表达式: SpEL支持加(+)、减(-)、乘(*)、除(/)、求余(%)、幂(^)运算。
int result1 = parser.parseExpression(&#-3*4/2″).getValue(Integer.class);//-3
int result2 = parser.parseExpression(&#″).getValue(Integer.class);//1
int result3 = parser.parseExpression(&#″).getValue(Integer.class);//8
SpEL还提供求余(MOD)和除(DIV)而外两个运算符,与“%”和“/”等价,不区分大小写。
三、关系表达式:
等于(==)、不等于(!=)、大于(&)、大于等于(&=)、小于(&)、小于等于(&=),区间(between)运算,如“parser.parseExpression(&#″).getValue(boolean.class);”将返回false;而“parser.parseExpression(“1 between {1, 2}”).getValue(boolean.class);”将返回true。
between运算符右边操作数必须是列表类型,且只能包含2个元素。第一个元素为开始,第二个元素为结束,区间运算是包含边界值的,即 xxx&=list.get(0) && xxx&=list.get(1)。
SpEL同样提供了等价的“EQ” 、“NE”、 “GT”、“GE”、 “LT” 、“LE”来表示等于、不等于、大于、大于等于、小于、小于等于,不区分大小写。
四、逻辑表达式:且(and)、或(or)、非(!或NOT)。
java代码:
String expression1 = &2&1 and (!true or !false)&;
boolean result1 = parser.parseExpression(expression1).getValue(boolean.class);
Assert.assertEquals(true, result1);
String expression2 = &2&1 and (NOT true or NOT false)&;
boolean result2 = parser.parseExpression(expression2).getValue(boolean.class);
Assert.assertEquals(true, result2);
注:逻辑运算符不支持 Java中的 && 和 || 。
五、字符串连接及截取表达式:
使用“+”进行字符串连接,使用“’String’[0] [index]”来截取一个字符,目前只支持截取一个,如“’Hello ‘ + ‘World!’”得到“Hello World!”;而“’Hello World!’[0]”将返回“H”。
六、三目运算及Elivis运算表达式:
三目运算符 “表达式1?表达式2:表达式3”用于构造三目运算表达式,如“2&1?true:false”将返回true;
Elivis运算符“表达式1?:表达式2”从Groovy语言引入用于简化三目运算符的,当表达式1为非null时则返回表达式1,当表达式1为null时则返回表达式2,简化了三目运算符方式“表达式1? 表达式1:表达式2”,如“null?:false”将返回false,而“true?:false”将返回true;
七、正则表达式:
使用“str matches regex,如“* matches ‘\\d{3}’”将返回true;
八、括号优先级表达式:
使用“(表达式)”构造,括号里的具有高优先级。
5.3.3 类相关表达式
一、类类型表达式:
使用“T(Type)”来表示java.lang.Class实例,“Type”必须是类全限定名,“java.lang”包除外,即该包下的类可以不指定包名;使用类类型表达式还可以进行访问类静态方法及类静态字段。
具体使用方法如下:
java代码:
public void testClassTypeExpression() {
ExpressionParser parser = new SpelExpressionParser();
//java.lang包类访问
Class&String& result1 = parser.parseExpression(&T(String)&).getValue(Class.class);
Assert.assertEquals(String.class, result1);
//其他包类访问
String expression2 = &T(cn.javass.spring.chapter5.SpELTest)&;
Class&String& result2 = parser.parseExpression(expression2).getValue(Class.class); Assert.assertEquals(SpELTest.class, result2);
//类静态字段访问
int result3=parser.parseExpression(&T(Integer).MAX_VALUE&).getValue(int.class);
Assert.assertEquals(Integer.MAX_VALUE, result3);
//类静态方法调用
int result4 = parser.parseExpression(&T(Integer).parseInt('1')&).getValue(int.class);
Assert.assertEquals(1, result4);
对于java.lang包里的可以直接使用“T(String)”访问;其他包必须是类全限定名;可以进行静态字段访问如“T(Integer).MAX_VALUE”;也可以进行静态方法访问如“T(Integer).parseInt(*)”。
二、类实例化:
类实例化同样使用java关键字“new”,类名必须是全限定名,但java.lang包内的类型除外,如String、Integer。
java代码:
public void testConstructorExpression() {
ExpressionParser parser = new SpelExpressionParser();
String result1 = parser.parseExpression(&new String('haha')&).getValue(String.class);
Assert.assertEquals(&haha&, result1);
Date result2 = parser.parseExpression(&new java.util.Date()&).getValue(Date.class);
Assert.assertNotNull(result2);
实例化完全跟Java内方式一样。
三、instanceof表达式:
SpEL支持instanceof运算符,跟Java内使用同义;如“’haha’ instanceof T(String)”将返回true。
四、变量定义及引用:
变量定义通过EvaluationContext接口的setVariable(variableName, value)方法定义;在表达式中使用“#variableName”引用;除了引用自定义变量,SpE还允许引用根对象及当前上下文对象,使用“#root”引用根对象,使用“#this”引用当前上下文对象;
java代码:
public void testVariableExpression() {
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext();
context.setVariable(&variable&, &haha&);
context.setVariable(&variable&, &haha&);
String result1 = parser.parseExpression(&#variable&).getValue(context, String.class);
Assert.assertEquals(&haha&, result1);
context = new StandardEvaluationContext(&haha&);
String result2 = parser.parseExpression(&#root&).getValue(context, String.class);
Assert.assertEquals(&haha&, result2);
String result3 = parser.parseExpression(&#this&).getValue(context, String.class);
Assert.assertEquals(&haha&, result3);
使用“#variable”来引用在EvaluationContext定义的变量;除了可以引用自定义变量,还可以使用“#root”引用根对象,“#this”引用当前上下文对象,此处“#this”即根对象。
五、自定义函数:
目前只支持类静态方法注册为自定义函数;SpEL使用StandardEvaluationContext的registerFunction方法进行注册自定义函数,其实完全可以使用setVariable代替,两者其实本质是一样的;
java代码:
public void testFunctionExpression() throws SecurityException, NoSuchMethodException {
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
Method parseInt = Integer.class.getDeclaredMethod(&parseInt&, String.class);
context.registerFunction(&parseInt&, parseInt);
context.setVariable(&parseInt2&, parseInt);
String expression1 = &#parseInt('3') == #parseInt2('3')&;
boolean result1 = parser.parseExpression(expression1).getValue(context, boolean.class);
Assert.assertEquals(true, result1);
此处可以看出“registerFunction”和“setVariable”都可以注册自定义函数,但是两个方法的含义不一样,推荐使用“registerFunction”方法注册自定义函数。
六、赋值表达式:
SpEL即允许给自定义变量赋值,也允许给跟对象赋值,直接使用“#variableName=value”即可赋值:
java代码:
public void testAssignExpression() {
ExpressionParser parser = new SpelExpressionParser();
//1.给root对象赋值
EvaluationContext context = new StandardEvaluationContext(&aaaa&);
String result1 = parser.parseExpression(&#root='aaaaa'&).getValue(context, String.class);
Assert.assertEquals(&aaaaa&, result1);
String result2 = parser.parseExpression(&#this='aaaa'&).getValue(context, String.class);
Assert.assertEquals(&aaaa&, result2);
//2.给自定义变量赋值
context.setVariable(&#variable&, &variable&);
String result3 = parser.parseExpression(&#variable=#root&).getValue(context, String.class);
Assert.assertEquals(&aaaa&, result3);
使用“#root=’aaaaa’”给根对象赋值,使用“”#this=’aaaa’”给当前上下文对象赋值,使用“#variable=#root”给自定义变量赋值,很简单。
七、对象属性存取及安全导航表达式:
对象属性获取非常简单,即使用如“a.property.property”这种点缀式获取,SpEL对于属性名首字母是不区分大小写的;SpEL还引入了Groovy语言中的安全导航运算符“(对象|属性)?.属性”,用来避免但“?.”前边的表达式为null时抛出空指针异常,而是返回null;修改对象属性值则可以通过赋值表达式或Expression接口的setValue方法修改。
java代码:
ExpressionParser parser = new SpelExpressionParser();
//1.访问root对象属性
Date date = new Date();
StandardEvaluationContext context = new StandardEvaluationContext(date);
int result1 = parser.parseExpression(&Year&).getValue(context, int.class);
Assert.assertEquals(date.getYear(), result1);
int result2 = parser.parseExpression(&year&).getValue(context, int.class);
Assert.assertEquals(date.getYear(), result2);
对于当前上下文对象属性及方法访问,可以直接使用属性或方法名访问,比如此处根对象date属性“year”,注意此处属性名首字母不区分大小写。
java代码:
//2.安全访问
context.setRootObject(null);
Object result3 = parser.parseExpression(&#root?.year&).getValue(context, Object.class);
Assert.assertEquals(null, result3);
SpEL引入了Groovy的安全导航运算符,比如此处根对象为null,所以如果访问其属性时肯定抛出空指针异常,而采用“?.”安全访问导航运算符将不抛空指针异常,而是简单的返回null。
java代码:
//3.给root对象属性赋值
context.setRootObject(date);
int result4 = parser.parseExpression(&Year = 4&).getValue(context, int.class);
Assert.assertEquals(4, result4);
parser.parseExpression(&Year&).setValue(context, 5);
int result5 = parser.parseExpression(&Year&).getValue(context, int.class);
Assert.assertEquals(5, result5);
给对象属性赋值可以采用赋值表达式或Expression接口的setValue方法赋值,而且也可以采用点缀方式赋值。
八、对象方法调用:
对象方法调用更简单,跟Java语法一样;如“’haha’.substring(2,4)”将返回“ha”;而对于根对象可以直接调用方法;
java代码:
Date date = new Date();
StandardEvaluationContext context = new StandardEvaluationContext(date);
int result2 = parser.parseExpression(&getYear()&).getValue(context, int.class);
Assert.assertEquals(date.getYear(), result2);
比如根对象date方法“getYear”可以直接调用。
九、Bean引用:
SpEL支持使用“@”符号来引用Bean,在引用Bean时需要使用BeanResolver接口实现来查找Bean,Spring提供BeanFactoryResolver实现;
java代码:
public void testBeanExpression() {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext();
ctx.refresh();
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new BeanFactoryResolver(ctx));
Properties result1 = parser.parseExpression(&@systemProperties&).getValue(context, Properties.class);
Assert.assertEquals(System.getProperties(), result1);
在示例中我们首先初始化了一个IoC容器,ClassPathXmlApplicationContext 实现默认会把“System.getProperties()”注册为“systemProperties”Bean,因此我们使用 “@systemProperties”来引用该Bean。
5.3.3 集合相关表达式
一、内联List:
从Spring3.0.4开始支持内联List,使用{表达式,……}定义内联List,如“{1,2,3}”将返回一个整型的ArrayList,而“{}”将返回空的List,对于字面量表达式列表,SpEL会使用java.util.Collections.unmodifiableList方法将列表设置为不可修改。
java代码:
//将返回不可修改的空List
List&Integer& result2 = parser.parseExpression(&{}&).getValue(List.class);
java代码:
//对于字面量列表也将返回不可修改的List
List&Integer& result1 = parser.parseExpression(&{1,2,3}&).getValue(List.class);
Assert.assertEquals(new Integer(1), result1.get(0));
result1.set(0, 2);
//不可能执行到这,对于字面量列表不可修改
Assert.fail();
} catch (Exception e) {
java代码:
//对于列表中只要有一个不是字面量表达式,将只返回原始List,
//不会进行不可修改处理
String expression3 = &{{1+2,2+4},{3,4+4}}&;
List&List&Integer&& result3 = parser.parseExpression(expression3).getValue(List.class);
result3.get(0).set(0, 1);
Assert.assertEquals(2, result3.size());
java代码:
//声明一个大小为2的一维数组并初始化
int[] result2 = parser.parseExpression(&new int[2]{1,2}&).getValue(int[].class);
java代码:
//定义一维数组但不初始化
int[] result1 = parser.parseExpression(&new int[1]&).getValue(int[].class);
二、内联数组:和Java 数组定义类似,只是在定义时进行多维数组初始化。
java代码:
//定义多维数组但不初始化
int[][][] result3 = parser.parseExpression(&new int[1][2][3]&).getValue(int[][][].class);
java代码:
//错误的定义多维数组,多维数组不能初始化
String expression4 = &new int[1][2][3]{{1}{2}{3}}&;
int[][][] result4 = parser.parseExpression(expression4).getValue(int[][][].class);
Assert.fail();
} catch (Exception e) {
三、集合,字典元素访问:
SpEL目前支持所有集合类型和字典类型的元素访问,使用“集合[索引]”访问集合元素,使用“map[key]”访问字典元素;
java代码:
//SpEL内联List访问
int result1 = parser.parseExpression(&{1,2,3}[0]&).getValue(int.class);
//即list.get(0)
Assert.assertEquals(1, result1);
java代码:
//SpEL目前支持所有集合类型的访问
Collection&Integer& collection = new HashSet&Integer&();
collection.add(1);
collection.add(2);
EvaluationContext context2 = new StandardEvaluationContext();
context2.setVariable(&collection&, collection);
int result2 = parser.parseExpression(&#collection[1]&).getValue(context2, int.class);
//对于任何集合类型通过Iterator来定位元素
Assert.assertEquals(2, result2);
java代码:
//SpEL对Map字典元素访问的支持
Map&String, Integer& map = new HashMap&String, Integer&();
map.put(&a&, 1);
EvaluationContext context3 = new StandardEvaluationContext();
context3.setVariable(&map&, map);
int result3 = parser.parseExpression(&#map['a']&).getValue(context3, int.class);
Assert.assertEquals(1, result3);
注:集合元素访问是通过Iterator遍历来定位元素位置的。
四、列表,字典,数组元素修改:
可以使用赋值表达式或Expression接口的setValue方法修改;
java代码:
//1.修改数组元素值
int[] array = new int[] {1, 2};
EvaluationContext context1 = new StandardEvaluationContext();
context1.setVariable(&array&, array);
int result1 = parser.parseExpression(&#array[1] = 3&).getValue(context1, int.class);
Assert.assertEquals(3, result1);
java代码:
//2.修改集合值
Collection&Integer& collection = new ArrayList&Integer&();
collection.add(1);
collection.add(2);
EvaluationContext context2 = new StandardEvaluationContext();
context2.setVariable(&collection&, collection);
int result2 = parser.parseExpression(&#collection[1] = 3&).getValue(context2, int.class);
Assert.assertEquals(3, result2);
parser.parseExpression(&#collection[1]&).setValue(context2, 4);
result2 = parser.parseExpression(&#collection[1]&).getValue(context2, int.class);
Assert.assertEquals(4, result2);
java代码:
//3.修改map元素值
Map&String, Integer& map = new HashMap&String, Integer&();
map.put(&a&, 1);
EvaluationContext context3 = new StandardEvaluationContext();
context3.setVariable(&map&, map);
int result3 = parser.parseExpression(&#map['a'] = 2&).getValue(context3, int.class);
Assert.assertEquals(2, result3);
对数组修改直接对“#array[index]”赋值即可修改元素值,同理适用于集合和字典类型。
五、集合投影:
在SQL中投影指从表中选择出列,而在SpEL指根据集合中的元素中通过选择来构造另一个集合,该集合和原集合具有相同数量的元素;SpEL使用“(list|map).![投影表达式]”来进行投影运算:
java代码:
//1.首先准备测试数据
Collection&Integer& collection = new ArrayList&Integer&();
collection.add(4); collection.add(5);
Map&String, Integer& map = new HashMap&String, Integer&();
map.put(&a&, 1); map.put(&b&, 2);
java代码:
//2.测试集合或数组
EvaluationContext context1 = new StandardEvaluationContext();
context1.setVariable(&collection&, collection);
Collection&Integer& result1 =
parser.parseExpression(&#collection.![#this+1]&).getValue(context1, Collection.class);
Assert.assertEquals(2, result1.size());
Assert.assertEquals(new Integer(5), result1.iterator().next());
对于集合或数组使用如上表达式进行投影运算,其中投影表达式中“#this”代表每个集合或数组元素,可以使用比如“#this.property”来获取集合元素的属性,其中“#this”可以省略。
java代码:
//3.测试字典
EvaluationContext context2 = new StandardEvaluationContext();
context2.setVariable(&map&, map);
List&Integer& result2 =
parser.parseExpression(&#map.![ value+1]&).getValue(context2, List.class);
Assert.assertEquals(2, result2.size());
SpEL投影运算还支持Map投影,但Map投影最终只能得到List结果,如上所示,对于投影表达式中的“#this”将是Map.Entry,所以可以使用“value”来获取值,使用“key”来获取键。
六、集合选择:
在SQL中指使用select进行选择行数据,而在SpEL指根据原集合通过条件表达式选择出满足条件的元素并构造为新的集合,SpEL使用“(list|map).?[选择表达式]”,其中选择表达式结果必须是boolean类型,如果true则选择的元素将添加到新集合中,false将不添加到新集合中。
java代码:
//1.首先准备测试数据
Collection&Integer& collection = new ArrayList&Integer&();
collection.add(4); collection.add(5);
Map&String, Integer& map = new HashMap&String, Integer&();
map.put(&a&, 1); map.put(&b&, 2);
java代码:
//2.集合或数组测试
EvaluationContext context1 = new StandardEvaluationContext();
context1.setVariable(&collection&, collection);
Collection&Integer& result1 =
parser.parseExpression(&#collection.?[#this&4]&).getValue(context1, Collection.class);
Assert.assertEquals(1, result1.size());
Assert.assertEquals(new Integer(5), result1.iterator().next());
对于集合或数组选择,如“#collection.?[#this&4]”将选择出集合元素值大于4的所有元素。选择表达式必须返回布尔类型,使用“#this”表示当前元素。
java代码:
//3.字典测试
EvaluationContext context2 = new StandardEvaluationContext();
context2.setVariable(&map&, map);
Map&String, Integer& result2 =
parser.parseExpression(&#map.?[#this.key != 'a']&).getValue(context2, Map.class);
Assert.assertEquals(1, result2.size());
List&Integer& result3 =
parser.parseExpression(&#map.?[key != 'a'].![value+1]&).getValue(context2, List.class);
Assert.assertEquals(new Integer(3), result3.iterator().next());
对于字典选择,如“#map.?[#this.key != 'a']”将选择键值不等于”a”的,其中选择表达式中“#this”是Map.Entry类型,而最终结果还是Map,这点和投影不同;集合选择和投影可以一起使用,如“#map.?[key != 'a'].![value+1]”将首先选择键值不等于”a”的,然后在选出的Map中再进行“value+1”的投影。
5.3.4 表达式模板
模板表达式就是由字面量与一个或多个表达式块组成。每个表达式块由“前缀+表达式+后缀”形式组成,如“${1+2}”即表达式块。在前边我们已经介绍了使用ParserContext接口实现来定义表达式是否是模板及前缀和后缀定义。在此就不多介绍了,如“Error ${#v0} ${#v1}”表达式表示由字面量“Error ”、模板表达式“#v0”、模板表达式“#v1”组成,其中v0和v1表示自定义变量,需要在上下文定义。
67 官方建议优先使用synchronized而不是Lock
关于ImportNew
ImportNew 专注于 Java 技术分享。于日 11:11正式上线。是的,这是一个很特别的时刻 :)
ImportNew 由两个 Java 关键字 import 和 new 组成,意指:Java 开发者学习新知识的网站。 import 可认为是学习和吸收, new 则可认为是新知识、新技术圈子和新朋友……
新浪微博:
推荐微信号
反馈建议:ImportNew.
广告与商务合作QQ:
– 好的话题、有启发的回复、值得信赖的圈子
– 写了文章?看干货?去头条!
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 活跃 & 专业的翻译小组
– 国内外的精选博客文章
– UI,网页,交互和用户体验
– JavaScript, HTML5, CSS
– 专注Android技术分享
– 专注iOS技术分享
– 专注Java技术分享
– 专注Python技术分享
& 2018 ImportNewBookmarks:
Page Contents
当需要给插值或者指令参数提供值时,可以使用变量或其他复杂的表达式。
例如,我们设x为8,y为5,那么 (x + y)/2
的值就会被处理成数字类型的值6.5。在我们展开细节之前,先来看一些具体的例子:
当给插值提供值时:插值的使用方式为
${expression},
把它放到你想输出文本的位置上,然后给值就可以打印出来了。
即 ${(5 + 8)/2} 会打印出 ''6.5'' 来
(如果输出的语言不是美国英语,也可能打印出''6,5''来)。
当给指令参数提供值时:在入门章节我们已经看到
if 指令的使用了。这个指令的语法是:&#if
expression&...&/#if&。
这里的表达式计算结果必须是布尔类型的。比如
&#if 2 & 3& 中的 2
&3 (2小于3)是结果为 true 的布尔表达式。
这里给已经了解 FreeMarker 的人或有经验的程序员的提个醒:
&Foo& 或者 'Foo' 或者
&It's \&quoted\&& 或者 'It\'s
&quoted&' 或者
r&C:\raw\string&
true, false
[&foo&, &bar&, 123.45]; 值域:
0..9, 0..&10 (或
0..!10), 0..
{&name&:&green mouse&,
&price&:150}
: user.name,
user[&name&]
products[5]
&Hello ${user}!& (或 &Hello
& + user + &!&)
包含结尾:
name[0..4],不包含结尾:
name[0..&5],基于长度(宽容处理):
name[0..*5],去除开头:
users + [&guest&]
:包含结尾:
products[20..29], 不包含结尾:
products[20..&30],基于长度(宽容处理):
products[20..*10],去除开头:
products[20..]
passwords + { &joe&: &secret42& }
(x * 1.5 + 10) / 2 - y %
x == y, x != y,
x & y, x & y,
x &= y, x &= y,
x lt y, x lte y,
x gt y, x gte y,
等等。。。。。。
!registered && (firstVisit
|| fromEurope)
name?upper_case,
path?ensure_starts_with('/')
repeat(&What&, 3)
name!&unknown& 或者
(user.name)!&unknown& 或者
name! 或者
(user.name)!
: name?? 或者
(user.name)??
通常我们喜欢是使用直接确定的值而不是计算的结果。
在文本中确定字符串值的方法是看双引号,比如:
&some text&,或单引号,比如:
'some text'。这两种形式是等同的。
如果文本自身包含用于字符引用的引号
( & 或 ')或反斜杠时,
应该在它们的前面再加一个反斜杠;这就是转义。
转义允许直接在文本中输入任何字符,
也包括。例如:
${&It's \&quoted\& and
this is a backslash: \\&}
${'It\'s &quoted& and
this is a backslash: \\'}
将会输出:
It's &quoted& and
this is a backslash: \
It's &quoted& and
this is a backslash: \
这里当然可以直接在模板中输入文本而不需要
但是我们在这里用它只是为了示例来说明表达式的使用。
下面的表格是FreeMarker支持的所有转义字符。
在字符串使用反斜杠的其他所有情况都是错误的,运行这样的模板都会失败。
引号 (u0022)
单引号(又称为撇号) (u0027)
起始花括号:{
反斜杠 (u005C)
换行符 (u000A)
回车 (u000D)
水平制表符(又称为tab) (u0009)
退格 (u0008)
换页 (u000C)
字符的16进制
在 \x 之后的
是1-4位的16进制码。下面这个示例中都是在字符串中放置版权符号:
&\x0A9 &,
&\x00A9 &。
如果紧跟16进制码后一位的字符也能解释成16进制码时,
就必须把4位补全,否则FreeMarker就会误解你的意图。
请注意,字符序列 ${ (和 #{)
有特殊的含义,它们被用做插入表达式的数值(典型的应用是变量的值:
&Hello ${user}!&)。这将在 中解释。
如果想要打印 ${ 或 #{,
就要使用下面所说的原生字符串,或者进行转义。就像
&foo $\{bar}&中的 {。
原生字符串是一种特殊的字符串。在原生字符串中,
反斜杠和 ${ 没有特殊含义,
它们被视为普通的字符。为了表明字符串是原生字符串,
在开始的引号或单引号之前放置字母r,例如:
${r&${foo}&}
${r&C:\foo\bar&}
将会输出:
C:\foo\bar
输入不带引号的数字就可以直接指定一个数字,
必须使用点作为小数的分隔符而不能是其他的分组分隔符。
可以使用 - 或 +
来表明符号 (+ 是多余的)。
科学记数法暂不支持使用 (1E3 就是错误的),
而且也不能在小数点之前不写0(.5 也是错误的)。
下面的数字都是合法的:0.08,
-5.013,8,
请注意,像 08、
+8、 8.00 和
8 这样的数值是完全等同的,它们都是数字8。
所以, ${08}、${+8}、
${8.00} 和 ${8}
的输出都是一样的。
直接写 true 或者
false 就表示一个布尔值了,不需使用引号。
指定一个文字的序列,使用逗号来分隔其中的每个 ,
然后把整个列表放到方括号中。例如:
&#list [&foo&, &bar&, &baz&] as x&
将会输出:
列表中的项目是表达式,那么也可以这样做:
[2 + 2, [1, 2, 3, 4], &foo&]。
其中第一个子变量是数字4,第二个子变量是一个序列,
第三个子变量是字符串&foo&。
值域也是序列,但它们由指定包含的数字范围所创建,
而不需指定序列中每一项。比如:
0..&m,这里假定 m
变量的值是5,那么这个序列就包含
[0, 1, 2, 3, 4]。值域的主要作用有:使用
&#list...&
来迭代一定范围内的数字, 和
值域表达式的通用形式是(
可以是任意的结果为数字表达式):
start..end:
包含结尾的值域。比如 1..4 就是
[1, 2, 3, 4], 而 4..1
就是 [4, 3, 2, 1]。当心一点,
包含结尾的值域不会是一个空序列,所以 0..length-1
就是 错误的,因为当长度是 0 时,
序列就成了 [0, -1]。
start..&end
start..!end:
不包含结尾的值域。比如 1..&4 就是
[1, 2, 3],4..&1
就是 [4, 3, 2], 而 1..&1
表示 []。请注意最后一个示例;
结果可以是空序列,和
和 ..! 没有区别; 最后这种形式在应用程序中使用了
& 字符而引发问题(如HTML编辑器等)。
start..*length:
限定长度的值域,比如 10..*4 就是
[10, 11, 12, 13],10..*-4
就是 [10, 9, 8, 7],而 10..*0
表示 []。当这些值域被用来切分时,
如果切分后的序列或者字符串结尾在指定值域长度之前,则切分不会有问题;请参考
来获取更多信息。
限定长度的值域是在 FreeMarker 2.3.21版本中引入的。
无右边界值域。这和限制长度的值域很像,只是长度是无限的。
比如 1.. 就是
[1, 2, 3, 4, 5, 6, ... ],直到无穷大。
但是处理(比如列表显示)这种值域时要万分小心,处理所有项时,
会花费很长时间,直到内存溢出应用程序崩溃。
和限定长度的值域一样,当它们被切分时,
遇到切分后的序列或字符串结尾时,切分就结束了。
无右边界值域在 FreeMarker 2.3.21 版本以前只能用于切分,
若用于其它用途,它就像空序列一样了。要使用新的特性,
使用 FreeMarker 2.3.21 版本是不够的,程序员要设置
incompatible_improvements 至少到2.3.21版本。
值域的进一步注意事项:
值域表达式本身并没有方括号,比如这样编写代码
&#assign myRange = 0..&x&,
而不是 &#assign myRange = [0..&x]&。
后者会创建一个包含值域的序列。方括号是切分语法的一部分,就像
seq[myRange]。
可以在 .. 的两侧编写算术表达式而不需要圆括号,
就像 n + 1 ..& m / 2 - 1。
..! 和 ..* 是运算符,
所以它们中间不能有空格。就像 n .. &m
这样是错误的,但是 n ..& m 这样就可以。
无右边界值域的定义大小是 (如果
incompatible_improvements 低于2.3.21版本,那么就是0),
这是由于技术上的限制(32位)。但当列表显示它们的时候,实际的长度是无穷大。
值域并不存储它们包含的数字,那么对于 0..1 和
0.. 来说,创建速度都是一样的,
并且占用的内存也是一样的。
在模板中指定一个哈希表,就可以遍历用逗号分隔开的&键/值&对,
把列表放到花括号内即可。键和值成对出现并以冒号分隔。比如:
{ &name&: &green mouse&, &price&: 150 }。
请注意名和值都是表达式,但是用来检索的名称就必须是字符串类型,
而值可以是任意类型。
访问顶层的变量,可以简单地使用变量名。例如,
用表达式 user 就可以在根上获取以
"user" 为名存储的变量值。然后打印出存储在里面的内容:
如果没有顶层变量,那么 FreeMarker 在处理表达式时就会发生错误,
进而终止模板的执行(除非程序员事先配置了 FreeMarker)。
在这种表达式中,变量名只可以包含字母(也可以是非拉丁文),
数字(也可以是非拉丁数字),下划线 (_),
美元符号 ($),at符号 (@)。
此外,第一个字符不可以是ASCII码数字(0-9)。
从 FreeMarker 2.3.22 版本开始,变量名在任何位置也可以包含负号
(-),点(.)和冒号(:),
但这些必须使用前置的反斜杠(\)来转义,
否则它们将被解释成操作符。比如,读取名为"data-id"的变量,
表达式为 data\-id,因为 data-id
将被解释成 "data minus id"。
(请注意,这些转义仅在标识符中起作用,而不是字符串中。)
如果有一个表达式的结果是哈希表,
那么我们可以使用点和子变量的名字得到它的值,
假设我们有如下的数据模型:
+- title = &Breeding green mouses&
+- name = &Julia Smith&
+- info = &Biologist, , Canada&
+- test = &title&
现在,就可以通过book.title
来读取 title,book表达式将返回一个哈希表
(就像上一章中解释的那样)。按这种逻辑进一步来说,我们可以使用表达式
book.author.name 来读取到auther的name。
如果我们想指定同一个表达式的子变量,那么还有另外一种语法格式:
book[&title&]。在方括号中可以给出任意长度字符串的表达式。
在上面这个数据模型示例中还可以这么来获取title: book[test]。
下面这些示例它们含义都是相等的: book.author.name,
book[&author&].name,
book.author.[&name&],
book[&author&][&name&]。
当使用点式语法时,顶层变量名的命名也有相同的限制
(命名时只能使用字母,数字,_,$,
@,但是不能使用 0-9开头,
同时,从2.3.22版本开始,也可以使用 \-,\.
和 \:)。当使用方括号语法时,则没有这样的限制,
因为名称可以是任意表达式的结果。(请注意,对于FreeMarker的XML支持来说,
如果子变量名称是 * (星号)
或者 **,那么就不要使用方括号语法。)
对于顶层变量来说,如果尝试访问一个不存在的变量也会引起错误导致解析执行模板中断
(除非程序员事先配置过FreeMarker)。
这和从哈希表中检索是相同的,但是只能使用方括号语法形式来进行,
而且方括号内的表达式最终必须是一个数字而不是字符串。比如,要从
中获取第一个动物的名字
(记住第一项数字索引是0而不是1),可以这么来写: animals[0].name
特殊变量是由FreeMarker引擎本身定义的。
使用它们,可以按照如下语法形式来进行:
.variable_name。.
通常情况下是不需使用特殊变量,而对专业用户来说可能用到。
所有特殊变量的说明可以参见 。
如果要在字符串中插入表达式的值,可以在字符串的文字中使用
(已经废弃的 #{...})。
${...} 在字符串中的作用和在
(它遵守相同的 本地化敏感 的数字和日期/时间格式),
示例 (假设user是 ''Big Joe''):
&#assign s = &Hello ${user}!&&
${s} &#-- Just to see what the value of s is --&
将会输出:
Hello Big Joe!
用户所犯的一个常见错误是将插值放在了不需要/不应该使用的地方。
插值 仅 在
中有效。(比如,
&h1&Hello ${name}!&/h1&) 还有在字符串值中
(比如, &#include &/footer/${company}.html&&)。
典型的 错误 使用是
&#if ${big}&...&/#if&,
这会导致语法错误。简单写为
&#if big&...&/#if&即可。
而且, &#if &${big}&&...&/#if&
也是 错误的,
因为它将参数值转换为字符串,但是
if 指令只接受布尔值,
那么这将导致运行时错误。
另外,也可以使用 + 号来达到类似的效果:
&#assign s = &Hello & + user + &!&&
这样的效果和使用 ${...} 是一样的。
因为 + 和使用
的规则相同,附加的字符串受到 locale,
number_format,date_format,
time_format,datetime_format 和
boolean_format 等等设置的影响,
这是对人来说的,而不是通常机器的解析。默认情况下,这会导致数字出问题,
因为很多地区使用分组(千分位分隔符),那么
&someUrl?id=& + id 就可能会是
&someUrl?id=1 234&。
要预防这种事情的发生,请使用 ?c
(对计算机来说)内建函数,那么在 &someUrl?id=& + id?c 或
&someUrl?id=${id?c}&中,
就会得到如 &someUrl?id=1234&
这样的输出, 而不管本地化和格式的设置是什么。
在给定索引值时可以获取字符串中的一个字符,这和 是相似的,
比如 user[0]。这个操作执行的结果是一个长度为1的字符串,
FTL并没有独立的字符类型。和序列中的子变量一样,这个索引也必须是数字,
范围是从0到字符串的长度,否则模板的执行将会发生错误并终止。
由于序列的子变量语法和字符的getter语法冲突,
那么只能在变量不是序列时使用字符的getter语法(因为FTL支持多类型值,所以它是可能的),
这种情况下使用序列方式就比较多。(为了变通,可以使用 ,比如
user?string[0]。不必担心你不理解这是什么意思,
内建函数将会在后续章节中讨论。)
示例(假设 user 是 "Big Joe"):
${user[0]}
${user[4]}
将会输出(请注意第一个字符的索引是0):
(请参看)的相同方式来切分字符串,这就是使用字符来代替序列。不同的是:
降序域不允许进行字符串切分。
(因为不像序列那样,很少情况下会想反转字符串。
如果真要这样做了,那就是疏忽。)
如果变量的值既是字符串又是序列(多类型值),
那么切分将会对序列进行,而不是字符串。当处理XML时,
这样的值就是普通的了。此时,可以使用
someXMLnode?string[range]。
一个遗留的bug:值域 包含 结尾时,
结尾小于开始索引并且是是非负的(就像在 &abc&[1..0] 中),
会返回空字符串而不是错误。(在降序域中这应该是个错误。)
现在这个bug已经向后兼容,但是不应该使用它,否在就会埋下一个错误。
&#assign s = &ABCDEF&&
${s[2..3]}
${s[2..&4]}
${s[2..*3]}
${s[2..*100]}
将会输出:
下面包括了一些使用内建函数来方便字符串切分的典型用例: ,
序列的连接可以按照字符串那样使用 +
号来进行,例如:
&#list [&Joe&, &Fred&] + [&Julia&, &Kate&] as user&
将会输出:
请注意,不要在很多重复连接时使用序列连接操作,
比如在循环中往序列上追加项目,而这样的使用是可以的:
&#list users + admins as person&。
尽管序列连接的速度很快,而且速度是和被连接序列的大小相独立的,
但是最终的结果序列的读取却比原先的两个序列慢那么一点。
通过这种方式进行的许多重复连接最终产生的序列读取的速度会慢。
seq[range],
这里 range 是一个值域
就可以得到序列的一个切分。结果序列会包含原序列
(seq)中的项,
而且索引在值域中。例如:
&#assert seq = [&A&, &B&, &C&, &D&, &E&]&
&#list seq[1..3] as i&${i}&/#list&
将会输出:
此外,切分后序列中的项会和值域的顺序相同。
那么上面的示例中,如果值域是 3..1 将会输出
值域中的数字必须是序列可使用的合法索引,
否则模板的处理将会终止并报错。像上面的示例那样,
seq[-1..0] 就会出错,
而 seq[-1] 就是合法的。
seq[1..5] 也不对,
因为 seq[5] 是非法的。
(请注意,尽管100已经越界,但是
seq[100..&100] 或
seq[100..*0] 是合法的,因为那些值域都是空。)
限制长度的值域
(start..*length)
和无右边界值域 (start..)
适用于切分后序列的长度。它们会切分可用项中尽可能多的部分:
&#assign seq = [&A&, &B&, &C&]&
Slicing with length limited ranges:
- &#list seq[0..*2] as i&${i}&/#list&
- &#list seq[1..*2] as i&${i}&/#list&
- &#list seq[2..*2] as i&${i}&/#list& &#-- Not an error --&
- &#list seq[3..*2] as i&${i}&/#list& &#-- Not an error --&
Slicing with right-unlimited ranges:
- &#list seq[0..] as i&${i}&/#list&
- &#list seq[1..] as i&${i}&/#list&
- &#list seq[2..] as i&${i}&/#list&
- &#list seq[3..] as i&${i}&/#list&
将会输出:
Slicing with length limited ranges:
Slicing with right-unlimited ranges:
请注意,上面的有限长度切分和无右边界切分都允许开始索引超过最后项
一个 (但不能再多了)。
要对序列进行给定大小的切分,就应该使用内建函数 。
像连接字符串那样,也可以使用 +
号的方式来连接哈希表。如果两个哈希表含有键相同的项,那么在
+ 号右侧的哈希表中的项优先。例如:
&#assign ages = {&Joe&:23, &Fred&:25} + {&Joe&:30, &Julia&:18}&
- Joe is ${ages.Joe}
- Fred is ${ages.Fred}
- Julia is ${ages.Julia}
将会输出:
- Joe is 30
- Fred is 25
- Julia is 18
请注意,很多项连接时不要使用哈希表连接,
比如在循环时往哈希表中添加新项。这和
的情况是一致的。
算数运算包含基本的四则运算和求模运算,运算符有:
求模 (求余): %
${100 - x * x}
${12 % 10}
假设 x 是 5,将会输出:
要保证两个操作数都是结果为数字的表达式。
下面的这个例子在运行时,FreeMarker就会发生错误,
因为是字符串 &5& 而不是数字5:
${3 * &5&} &#-- WRONG! --&
但这种情况也有一个例外,就是 + 号,它是用来 的。
如果 + 号的一端是字符串,+
号的另外一端是数字,那么数字就会自动转换为字符串类型(使用当前页面语言的适当格式),
之后使用 + 号作为字符串连接操作符。示例如下:
${3 + &5&}
将会输出:
通常来说,FreeMarker不会自动将字符串转换为数字,反之会自动进行。
有时我们只想获取除法计算(或其它运算)的整数部分,
这可以使用内建函数 int 来解决。(关于内建函数
会来解释):
${(x/2)?int}
${1.1?int}
${1.999?int}
${-1.1?int}
${-1.999?int}
假设 x 是 5,将会输出:
有时我们需要知道两个值是否相等,或者哪个值更大一点。
为了演示具体的例子,我们在这里使用 if 指令。
指令的用法是: &#if
expression&...&/#if&,
其中的表达式的值必须是布尔类型,否则将会出错,模板执行中断。
如果表达式的结果是 true ,
那么在开始和结束标记内的内容将会被执行,否则就会被跳过。
测试两个值相等使用 =
(或者采用Java和C语言中的 == ;二者是完全等同的。)
测试两个值不等使用 !=。比如,
假设 user 是 ''Big Joe'':
&#if user == &Big Joe&&
It is Big Joe
&#if user != &Big Joe&&
It is not Big Joe
&#if ...& 中的表达式
user = &Big Joe& 就是布尔值
true,面的代码将会输出 ''It is Big Joe''。
两边的表达式的结果都必须是标量,而且两个标量都必须是相同类型
(也就是说字符串只能和字符串来比较,数字只能和数字来比较等)否则将会出错,
模板执行中断。例如
&#if 1 = &1&& 就会导致错误。
请注意FreeMarker进行的是精确的比较,所以字符串在比较时要注意大小写和空格:
&x& 和 &x &
和 &X& 是不同的值。
对数字和日期类型的比较,也可以使用 &,
&。不能把它们当作字符串来比较。比如:
&#if x &= 12&
x is less or equivalent with 12
使用 &= 和 &
的时候有一点小问题。FreeMarker解释 &
的时候可以把它当作FTL标签的结束符。为了避免这种问题,可以使用
lt 代替 &,
lte 代替 &=,
gt 代替 & 还有
gte 代替 &=,
例如 &#if x gt y&。另外一个技巧是将表达式放到
尽管这么写并不优雅,例如 &#if (x & y)&。
FreeMarker 也支持一些其它的选择,但是这些已经废弃了:
在可能出问题的关系标记处使用 & 和
& ,就像: &#if x &
y& 或 &#if x &=
y&。 请注意通常FTL不支持标签中的实体引用(如
&...; 这些东西);
做算术比较时就会有异常。
\lt, \lte,
\gt 和 \gte 使用他们时,
不带反斜杠的效果一样。
常用的逻辑操作符:
逻辑 或: ||
逻辑 与: &&
逻辑 非: !
逻辑操作符仅仅在布尔值之间有效,若用在其他类型将会产生错误导致模板执行中止。
&#if x & 12 && color == &green&&
We have less than 12 things, and they are green.
&#if !hot& &#-- here hot must be a boolean --&
It's not hot.
内建函数就像FreeMarker在对象中添加的方法一样。
要防止和实际方法和其它子变量的命名冲突,则不能使用点
(.),这里使用问号
(?)来和父对象分隔开。
比如,想要保证 path 有起始的
/ ,那么可以这么来写:
path?ensure_starts_with('/')。
path 后的Java对象(通常就是 String)
并没有这样的方法,这是FreeMarker添加的。为了简洁,如果方法没有参数,
那么就可以忽略 (),比如想要获取
path 的长度,就可以写作:path?length,
而不是 path?length()。
内建函数关键性的另外一个原因是常见的(尽管它依赖于配置的设置),
FreeMarker不会暴露对象的Java API。那么尽管Java的 String
类有 length() 方法,但在模板中却是不可见的,
就 不得不 使用 path?length 来代替。
这里的优点是模板不依赖下层Java对象的精确类型。(比如某些场景中,
path 也可能是 java.nio.Path 类型,
如果程序员配置了FreeMarker去暴露 Path
对象作为FTL字符串类型,那么模板就不会在意了,使用
?length 也是可以的,
即便 java.nio.Path 没有类似的方法。)
可以找到一些 ,还有
现在,我们只需了解一些重要的内建函数就行了:
${testString?upper_case}
${testString?html}
${testString?upper_case?html}
${testSequence?size}
${testSequence?join(&, &)}
假设 testString 中存储了字符串
''Tom & Jerry'', 而testSequnce中存储了字符串
"foo", "bar" 和 "baz",
将会输出:
TOM & JERRY
Tom & Jerry
TOM & JERRY
foo, bar, baz
请注意:上面的 testString?upper_case?html。因为
test?upper_case 的结果是字符串,那么就可以在它的上面
使用内建函数 html。
很自然可以看到,内建函数的左侧可以是任意的表达式,而不仅仅是变量名:
${testSeqence[1]?cap_first}
${&horse&?cap_first}
${(testString + & & Duck&)?html}
Tom & Jerry & Duck
如果有一个方法,那么可以使用方法调用操作。
方法调用操作是使用逗号来分割在括号内的表达式而形成参数列表,这些值就是参数。
方法调用操作将这些值传递给方法,然后返回一个结果。
这个结果就是整个方法调用表达式的值。
假设程序员定义了一个可供调用的方法 repeat。
第一个参数是字符串类型,第二个参数是数字类型。方法的返回值是字符串类型,
而方法要完成的操作是将第一个参数重复显示,显示的次数是第二个参数设定的值。
${repeat(&Foo&, 3)}
将会输出:
这里的 repeat 就是方法变量(根据如何 ),
(&What&, 3) 就调用了该方法。
这里需要强调方法调用也是普通表达式,和其它都是一样的,所以:
${repeat(repeat(&x&, 2), 3) + repeat(&Foo&, 4)?upper_case}
将会输出:
xxxxxxFOOFOOFOOFOO
这些操作符是从 FreeMarker 2.3.7 版本开始引入的(用来代替内建函数
default, exists 和
if_exists )。
正如我们前面解释的那样,当试图访问一个不存在的变量时,
FreeMarker 将会报错而导致模板执行中断。
通常我们可以使用两个特殊操作符来压制这个错误,控制这种错误情况。
被控制的变量可以是顶层变量,哈希表或序列的子变量。
此外这些操作符还能处理方法调用的返回值不存在的情况
(这点对Java程序员来说:
返回值是 null 或者返回值为 void
类型),通常来说,我们应该使用这些操作符来控制可能不存在的值,
而不仅仅是不存在的变量。
对于知道Java中
null 的人来说,FreeMarker 2.3.x
版本把它们视为不存在的变量。单地说,模板语言中没有 null
这个概念。比如有一个bean,bean中有一个 maidenName 属性,
对于模板而言(假设没有配置FreeMarker来使用一些极端的对象包装),
该属性的值是 null,和不存在这个属性的情况是一致的。
调用方法的返回值如果是
的话 FreeMarker 也会把它当作不存在的变量来处理
(假定只使用了普通的对象包装)。可以在
中了解更多内容。
如果想知道为什么 FreeMarker 对不存在的变量如此挑剔,
使用形式:
unsafe_expr!default_expr
或 unsafe_expr! or
(unsafe_expr)!default_expr
(unsafe_expr)!
这个操作符允许你为可能不存在的变量指定一个默认值。
例如,假设下面展示的代码中没有名为 mouse 的变量:
${mouse!&No mouse.&}
&#assign mouse=&Jerry&&
${mouse!&No mouse.&}
将会输出:
默认值可以是任何类型的表达式,也可以不必是字符串。
也可以这么写:hits!0 或
colors![&red&, &green&, &blue&]。
默认值表达式的复杂程度没有严格限制,还可以这么来写:
cargo.weight!(item.weight * itemCount + 10)。
如果在 !后面有复合表达式,
如 1 + x,通常
使用括号,如 ${x!(1 + y)} 或
${(x!1) + y)},这样就根据你的意图来确定优先级。
由于FreeMarker 2.3.x 版本的源码中的小失误所以必须这么来做。
! (作为默认值操作) 右侧的优先级非常低。
这就意味着 ${x!1 + y} 会被 FreeMarker
误解为 ${x!(1 + y)},而真实的意义是
${(x!1) + y}。 这个源码错误在FreeMarker 2.4中会得到修正。
在编程中注意这个错误,要么就使用FreeMarker 2.4!
如果默认值被省略了,那么结果将会是空串,空序列或空哈希表。
(这是 FreeMarker 允许多类型值的体现)请注意,如果想让默认值为
0 或 false,则不能省略它。例如:
(${mouse!})
&#assign mouse = &Jerry&&
(${mouse!})
将会输出:
因为语法的含糊 &@something a=x! b=y /&
将会解释为 &@something a=x!(b=y) /&,那就是说
b=y 将会被视为是比较运算,然后结果作为
x的默认值,而不是想要的参数
为了避免这种情况,如下编写代码即可: &@something a=(x!) b=y
用于非顶层变量时,默认值操作符可以有两种使用方式:
product.color!&red&
如果是这样的写法,那么在 product 中,
当 color 不存在时(返回 &red& ),
将会被处理,但是如果连 product 都不存在时将不会处理。
也就是说这样写时变量 product 必须存在,否则模板就会报错。
(product.color)!&red&
这时,如果当 product.color 不存在时也会被处理,
那就是说,如果 product 不存在或者
product 存在而 color
不存在,都能显示默认值 &red& 而不会报错。
本例和上例写法的重要区别在于用括号时,
就允许其中表达式的任意部分可以未定义。而没有括号时,
仅允许表达式的最后部分可以不被定义。
当然,默认值操作也可以作用于序列子变量,比如:
&#assign seq = ['a', 'b']&
${seq[0]!'-'}
${seq[1]!'-'}
${seq[2]!'-'}
${seq[3]!'-'}
将会输出:
如果序列索引是负数(比如 seq[-1]!'-')
也会发生错误,不能使用该运算符或者其它运算符去压制它。
使用形式:
unsafe_expr?? 或
(unsafe_expr)??
这个操作符告诉我们一个值是否存在。基于这种情况,
结果是 true 或 false。
示例如下,假设并没有名为 mouse 的变量:
&#if mouse??&
Mouse found
No mouse found
Creating mouse...
&#assign mouse = &Jerry&&
&#if mouse??&
Mouse found
No mouse found
将会输出:
No mouse found
Creating mouse...
Mouse found
访问非顶层变量的使用规则和默认值操作符也是一样的,
也就是说,可以写 product.color??
和 (product.color)??。
这些并不是表达式,只是复制指令语法的一部分,比如 ,
照这样,它们不能任意被使用。
&#assign x += y& 是
&#assign x = x + y& 的简写,&#assign x
*= y& 是 &#assign x = x *
y&的简写等等。。。
&#assign x++& 和
&#assign x += 1& (或 &#assign x
= x + 1&)不同,它只做算术加法运算
(如果变量不是数字的话就会失败),而其它的是进行字符串,序列连接和哈希表连接的重载。
&#assign x--& 是
&#assign x -= 1& 的简写。
括号可以用来给任意表达式分组。示例如下:
&#-- Output will be: --&
${3 * 2 + 2}
&#-- 8 --&
${3 * (2 + 2)}
&#-- 12 --&
${3 * ((2 + 2) * (1 / 2))}
&#-- 6 --&
${&green & + &mouse&?upper_case}
&#-- green MOUSE --&
${(&green & + &mouse&)?upper_case}
&#-- GREEN MOUSE --&
&#if !(color == &red& || color == &green&)&
The color is nor red nor green
使用的括号和给表达式分组的括号含义是完全不同的。
FTL 忽略表达式中的多余的 。下面的表示是相同的:
${x + &:& + book.title?upper_case}
${x+&:&+book.title?upper_case}
upper_case
下面的表格显示了已定义操作符的优先级。
表格中的运算符按照优先程度降序排列:上面的操作符优先级高于它下面的。
高优先级的运算符执行要先于优先级比它低的。表格同一行上的两个操作符优先级相同。
当有相同优先级的二元运算符(运算符有两个''参数'',比如
+和-)挨着出现时,它们按照从左到右的原则运算。
最高优先级运算符
[subvarName]
[subStringRange] . ?
(methodParams)
一元前缀运算符
-expr !expr
乘除法,求模运算符
加减法运算符
关系运算符
& & &= &= (and equivalents:
gt, lt, etc.)
相等,不等运算符
== != (and equivalents:
逻辑 "与" 运算符
逻辑 "或" 运算符
如果你熟悉C语言,Java语言或JavaScript语言,
请注意 FreeMarker 中的优先级规则和它们是相同的,
除了那些只有FTL本身含有的操作符。
因为编程的失误,默认值操作符
不在上面的表格中,按照向后兼容的原则,在 FreeMarker 2.4 版本中将会修正它。
而且它将是最高优先级的运算符,但是在 FreeMarker 2.3.x
版本中它右边的优先级由于失误就非常低。
所以在默认值操作符的右边中使用复杂表达式时可以使用括号,
可以是 x!(y + 1) 或者是 (x!y) + 1。
而不能是 x!y + 1。

我要回帖

更多关于 java中加减乘除 的文章

 

随机推荐