JavaFX编程语言
JavaFX Script™ (下文中成为JavaFX)语言是一种声明式的静态类型编程语言。它具有第一级函数(first-class functions)、声明式的语法、列表推导(list-comprehensions)及基于依赖关系的增量式求值(incremental dependency-based evaluation)等特征。JavaFX 脚本式语言特别适用于Java2D swing GUI组件,它允许简单地创建图形界面。
译者注:第一级函数指函数被当作对象对待,可以在运行时赋值、传递和返回。详见wikipedia上的解释。
译者注:列表推导指一种在函数语言中的表达式,它表示了在一个或者多个列表的成员(被选择的)进行某种操作的结果。它被称为“syntactic sugar”,即为开发者提供了便捷的多种函数的应用组合。详见FOLDC对list comprehension的解释。
本文档给出了JavaFX 脚本式编程语言的非正式描述。
内容:
- 基本类型
- 调用Java对象
- 变量
- 函数、数组、表达式和操作
- 修改数组
- 查询数组
- 表达式
- 字符串和字符串表达式
- 引用标识符
- 范围表达式
- 字符串(String)、数值(Number)和日期的格式化
- 操作
- 类与对象
- 更新触发器
- 增量式求值和懒惰求值
- 反射
- 广度(Extents)和枚举
- 相关资源
- 关于译者
- 译文Feedback
基本类型
JavaFX语言提供四种基本类型:String(字符串)
、Boolean(布尔)
、Number(数值)
和Integer(整数)
。这些类型相当于Java的如下类型:
JavaFX | Java |
String | java.lang.String |
Boolean | java.lang.Boolean |
Number | java.lang.Number |
Integer | byte,short,int,long,BigInteger |
例如:
var s = "Hello"; s.toUpperCase(); // yields "HELLO"; s.substring(1); // yields "ello"; var n = 1.5; n.intValue(); // yields 1 (1.5).intValue(); // yields 1 s.substring(n); // yields "ello" var b = true; b instanceof Boolean; // yields true
在向Java方法传递参数或者从Java方法返回结果的时候,数值类型会自动执行强制类型转换。并且,在转换Number
和Integer
的时候还会进行隐式的强制截断。
调用Java对象
JavaFX可以导入Java类、创建新的Java对象、调用它们的方法,也可以实现Java的接口。下面的代码片断提供了一个示例:
import javax.swing.JFrame; import javax.swing.JButton; import java.awt.event.ActionListener; import java.lang.System; var frame = new JFrame(); var button = new JButton("Press me"); frame.getContentPane().add(button); button.addActionListener(new ActionListener() { operation actionPerformed(event) { System.out.println("You pressed me"); } }); frame.pack(); frame.setVisible(true);
运行上面的程序后,屏幕上显示如下内容:
当然,这并不是JavaFX推荐的创建图形用户界面的方式。下面的JavaFX代码实现了相同的效果:
Frame { content: Button { text: "Press Me" action: operation() { System.out.println("You pressed me"); } } visible: true }
变量
在JavaFX中,var
这个关键词用来声明变量。你可以在声明中指定变量的类型。然而,这在JavaFX中是可选的。如果你不指定类型,JavaFX解释器会根据它的用途推断变量的类型。变量声明使用如下格式:
var variableName : typeName [?,+,*] = initializer;
你可以使
?
、
+
或者
*
操作表示变量的重数(cardinality),如下表:
操作符 | 含义 |
? | Optional (i.e, may be null ) |
+ | One or more |
* | Zero or more |
例如:
var nums:Number* = [1,2,3];
上面的示例声明了一个新的、名为nums的变量,它值由零个或者多个Number类型组成,其初始值为[1,2,3]
。
:typeName
、
[?,+,*]
以及
=initializer
这些声明部分是可选的,所以下面的方式与上面等价:
var nums = [1,2,3];
函数、数组、表达式和操作
JavaFX函数
代表了JavaFX编程语言的纯函数子集。函数体可以仅包含一组变量声明和一个返回语句。JavaFX也提供了过程(procedures)(被调用的操作
,详见下面关于操作的章节),里面可以包括任意数量的声明、条件语句、循环条件、try/catch等等。语句在函数中的给定顺序并不很重要。下面是一个简单的函数程序的例如:
function z(a,b) { var x = a + b; var y = a - b; return sq(x) / sq (y); } function sq(n) {return n * n;} function main() { return z(5, 10); }
尽管JavaFX语言是静态类型的,但这里并没有强制的类型声明(后面会详细叙述)。
最常用的数据结构是数组,它在JavaFX中通过方括弧和逗号来声明:
var week_days = ["Mon","Tue","Wed","Thur","Fri"]; var days = [week_days, ["Sat","Sun"]];
数组代表了一组顺序的对象。JavaFX中的数组本身不是对象,而且不能嵌套。创建嵌套数组的表达式(例如上面“days”的初始化方式)会被自动扁平化,例如:
days == ["Mon","Tue","Wed","Thur","Fri","Sat","Sun"]; // returns true
数组的大小可以通过JavaFX的sizeof
操作符确定:
var n = sizeof days; // n = 7
对于数组成员形成数列(arithmetic series)的数组,JavaFX提供了一种简写符号:“
..
”。下面提供了定义阶乘函数和奇数求和函数的示例,其中“result”的数值是1至100中奇数的和:
function fac(n) {return product([1..n]);} var result = sum([1,3..100]);
数组中的所有元素必须是同一种类型。
数组可以像在Java中那样通过索引访问:
var wednesday = days[2];
JavaFX中的
[]
操作符还可用来表示选择(类似XPath的用法)。在这种情况下,
[]
中包含的表达式应是一个布尔表达式。此表达式可以返回一个新的数组,此数组中只包含满足
[]
中断言(predicate)的成员。
就像在XPath一样,可以在[]
操作符包含的断言中通过.操作符访问上下文对象。例如:
var nums = [1,2,3,4]; var numsGreaterThanTwo = nums[. > 2]; // yields [3, 4]
另一种方法,也可以将变量声明为上下文对象。例如,这种方式与上面的方式等价:
numsGreaterThanTwo = nums[n|n > 2];
JavaFX中的indexof
操作符返回对象在数组中的顺序位置(类似XPath中的position()
函数)。
下面list的car和cdr可以用选择表达式来表示:
function car(list) {return list[indexof . == 0];} function cdr(list) {return list[indexof . > 0];}
当然,car可以用更简单、高效的方式表示:
function car(list) {return list[0];}
例如:
var list = [1..10]; car(list); // yields 1 cdr(list); // yields [2,3,4,5,6,7,8,9,10]
JavaFX中的空数组[]
与null
等价,例如:
[] == null // yields true sizeof null // yields 0
修改数组
除了赋值操作(=
)之外,JavaFX还提供数据修改操作符(insert
和delete
),它类似XQuery-Update规范中的语法和语义:
insert语句
可以用下面方式中的任意一种进行声明:
insert Expression1 [as first | as last] into Expression2 insert Expression1 before Expression2 insert Expression1 after Expression2
insert
语句将表达式1求值后的返回结果插入到下面表达式中所描述的位置:
表达式2必须指向一个属性或者变量。如果表达式2指向一个单值属性,那么插入的效果等同于赋值操作。
如果指定了as first
,那么插入位置就在表达式2所表示的列表的第一个元素的前面。如果指定了as last
,那么插入位置就在表达式2所表示的列表的最后一个元素的后面。如果没有明确地指定as first
或者as last
,则默认为as last
。
例如:
var x = [1,2,3]; insert 12 into x; // yields [1,2,3,12] insert 10 as first into x; // yields [10,1,2,3,12] insert [99,100] as last into x; // yields [10,1,2,3,12,99,100]
表达式2必须是在属性或者变量之上的选择表达式。如果指定了before
,那么插入位置就是在被选择的元素之前。如果指定了after
,插入位置则在被选择的元素之后。
例如:
var x = [1,2,3]; insert 10 after x[. == 10]; // yields [1,2,3,10] insert 12 before x[1]; // yields [1,12,2,3,10] insert 13 after x[. == 2]; // yields [1, 12, 2, 13, 3, 10];
delete语句
delete
语句可以使用下面形式中的一种:
delete variable delete Expression.attribute delete variable[predicate] delete Expression.attribute[predicate]
前两种形式将删除变量或者属性中的所有元素,它们等价于将变量或者属性赋值为[]
或者null
。后两种形式仅删除满足断言的元素。
例如:
var x = [1,2,3]; insert 10 into x; // yields [1,2,3,10] insert 12 before x[1]; // yields [1,12,2,3,10] delete x[. == 12]; // yields [1,2,3,10] delete x[. >= 3]; // yields [1,2] insert 5 after x[. == 1]; // yields [1,5,2]; insert 13 as first into x; // yields [13, 1, 5, 2]; delete x; // yields []
查询数组
像在Miranda、Haskell这些函数式编程语言中一样,JavaFX支持列表推导(list comprehensions),但是为了使用一种Java程序员更加熟悉、易懂的语法形式进行表达,JavaFX采取了select
和foreach
操作符。
这里提供一个例如:
class Album { attribute title: String; attribute artist: String; attribute tracks: String*; } var albums = [Album { title: "A Hard Day's Night" artist: "The Beatles" tracks: ["A Hard Day's Night", "I Should Have Known Better", "If I Fell", "I'm Happy Just To Dance With You", "And I Love Her", "Tell Me Why", "Can't Buy Me Love", "Any Time At All", "I'll Cry Instead", "Things We Said Today", "When I Get Home", "You Can't Do That"] }, Album { title: "Circle Of Love" artist: "Steve Miller Band" tracks: ["Heart Like A Wheel", "Get On Home", "Baby Wanna Dance", "Circle Of Love", "Macho City"] }]; // Get the track numbers of the albums' title tracks // using the select operator: var titleTracks = select indexof track + 1 from album in albums, track in album.tracks where track == album.title; // yields [1,4] // the same expressed using the foreach operator: titleTracks = foreach (album in albums, track in album.tracks where track == album.title) indexof track + 1; // also yields [1,4]
列表推导由一个或多个输入列表,一个可选的过滤器和一个生成器表达式组成。每个源列表与一个变量关联。列表推导的结果是将生成器应用于满足过滤器的源列表成员的笛卡尔乘积的子集后得到的新列表。
译者注:这里的过滤器指的是where子句。
列表推导为创建在列表上进行迭代遍历的通用类提供了一种简明的语法。
列表推导的另外一个简单示例:
select n*n from n in [1..100]
这个列表(顺序地)包含1至100的所有数的平方值。注意上面表达式中的“n”是局部变量。
下面的代码通过定义计算某个数值的所有因子的函数演示了如何使用过滤器:
function factors(n) { return select i from i in [1..n/2] where n % i == 0; }
表达式
JavaFX支持如下操作符:
操作符 | 含义 | Java等价物 |
关系操作符 | ||
== | equality | == |
<> | inequality | != |
< | less than | < |
> | greater than | > |
<= | less than or equal | <= |
>= | greater than or equal | >= |
布尔操作符 | ||
and | logical and | && |
or | logical or | || |
not | logical negation | ! |
算术操作符 | ||
+ | addition | + |
- | subtraction; unary negation | - |
* | multiplication | * |
/ | division | / |
% | remainder | % |
+= | add and assign | += |
-= | subtract and assign | -= |
*= | multiply and assign | *= |
/= | divide and assign | /= |
%= | remainder and assign | %= |
其它操作符 | ||
sizeof | array length | n/a |
indexof | ordinal position | n/a |
if e1 then e2 else e3 | conditional expression | e1 ? e2 : e3 |
select | list comprehension | n/a |
foreach | list comprehension | n/a |
new | allocation | new |
op() | function/operation call | n/a |
x.op() | member function/operation call | x.op() |
instanceof | type check | instanceof |
this | self access | this |
. | attribute access, context access | ., n/a |
bind [lazy] | incremental [lazy] evaluation | n/a |
: | eager initialization | n/a |
[] | array selection | [] |
format as | String formatting | n/a |
<<>> | Identifier quotes | n/a |
{} | String expression | n/a |
(expr) | grouping | (expr) |
reverse | reverses a list | n/a |
[number1,next..number2]
| numeric range | n/a |
一些示例:
import java.lang.System; import java.lang.Math; var x = 2; var y = 4; var a = true; var b = false; System.out.println(x == y); // prints false System.out.println(x <> y); // prints true System.out.println(x < y); // prints true System.out.println(x > y); // prints true System.out.println(x >= y); // prints false System.out.println(x <= y); // prints true System.out.println(x + y); // prints 6 System.out.println(x - y); // prints -2 System.out.println(x * y); // prints 8 System.out.println(x / y); // prints 0.5 System.out.println(x % y); // prints 2 System.out.println(a and b); // prints false System.out.println(a or b); // prints true System.out.println(not a); // prints false System.out.println(sizeof [x,y]); // prints 2 System.out.println([x,y][indexof . == 0]); // prints 2 System.out.println(if a then x else y); // prints 2 System.out.println(select q from q in [x, y] where q > 3); prints 4 System.out.println(foreach(q in [x, y] where q < 3) q); prints 2 System.out.println(Math.max(x, y)); // prints 4 System.out.println("abc".toUpperCase()); // prints ABC System.out.println(x instanceof Number); // prints true x = 10; System.out.println(x); // prints 10
字符串和字符串表达式
在JavaFX中,字符串通过单引号指定,例如:
var s = 'Hello';
或者通过双引号:
var s = "Hello";
在使用双引号的情况下,可以使用 {}
嵌入JavaFX表达式,例如:
var name = 'Joe'; var s = "Hello {name}"; // s = 'Hello Joe'
嵌入的表达式自身也可包含使用双引号引用的字符串(同样,里面还可以包含更深一级的嵌套表达式),例如:
var answer = true; var s = "The answer is {if answer then "Yes" else "No"}"; // s = 'The answer is Yes'
与Java不同,在JavaFX中使用双引号引用的字符串可以包含换行:
var s = "This contains new lines";
引用标识符
在JavaFX中,包含在法语引用符 <<>>
里的任何字符串序列(包括空白)被作为标识符。这样允许你使用像class、variable、function或者属性名这样的JavaFX关键字(或者其它的一般情况下非法的标识符),例如:
var <<while>> = 100;
它也使调用与JavaFX关键字同名的Java方法称为可能,例如:
import javax.swing.JTextArea; var textArea = new JTextArea(); textArea.<<insert>>("Hello", 0);
范围表达式
如前所述,通过下面的语法你可以定义一个形成数列的数值数组。
[number1..number2]此表达式定义了一个从 number1至 number2之间(含两端)的连续整数构成的数组。
例如:
var nums = [0..3]; System.out.println(nums == [0,1,2,3]); // prints true
默认情况下,值与值的间隔是1
,但是也可以指定不同的间隔,这需要在数列的number1后面写出下一个数值,并用逗号隔开。例如,下面的表达式定义了一个包含1至10之间奇数的数组。
[1,3..10]
如果number1比number2要大,会创建降序的数组:
var nums = [3..0]; System.out.println(nums == [3,2,1,0]); // prints true
字符串(String)、数值(Number)和日期的格式化
JavaFX有内建的字符串格式化操作符(format as
),语法如下:
表达式 format as 指令
format as
操作符支持java.text.DecimalFormat
、java.text.SimpleDateFormat
和java.util.Formatter
的格式化指令:如果格式化指令以%开头,那么将会使用java.util.Formatter
;如果表达式是Number
类型,则使用java.text.DecimalFormat
;如果表达式是java.util.Date
类型,则使用java.text.SimpleDateFormat
。指令操作数是一个在语法上的标识符,而不是一个表达式。这就允许了在编译时静态检查指令内容的正确性。
import java.util.Date; 100.896 format as <<%f>>; // yields '100.896000' 31.intValue() format as <<%02X>>; // yields '1F' var d = new Date(); d format as <<yyyy-MM-dd'T'HH:mm:ss.SSSZ>>; // yields '2005-10-31T08:04:31.323-0800' 0.00123 format as <<00.###E0>>; // yields '12.3E-4'
操作
在JavaFX中使用operation
关键字声明过程(procedure)。例如:
import java.lang.StringIndexOutOfBoundsException; operation substring(s:String, n:Number): String { try { return s.substring(n); } catch (e:StringIndexOutOfBoundsException) { throw "sorry, index out of bounds"; } }
在上例中定义了一个称为“substring
”的新过程,它接受两个参数:第一为字符串类型的参数“s”,第二为Number类型的参数“n”,而返回值为字符串类型。
除了从前提到的赋值、delete
、insert
语句之外,下面的语句也可以在过程体中使用:
表达式语句
一个基本的表达式可以被用作一条语句,例如下面示例中的"Hello World!":
System.out.println("Hello World!");
If语句
JavaFX的if
语句用法类似Java,除了大括号必须环绕随后的子句(除非else子句是另一个if
语句)之外。
例如:
if (condition1) { System.out.println("Condition 1"); } else if (condition2) { System.out.println("Condition2"); } else { System.out.println("not Condition 1 or Condition 2"); }
While语句
JavaFX的while
语句用法与Java类似,除了大括号必须环绕语句体之外。
例如:
var i = 0; while (i < 10) { if (i > 5) { break; } System.out.println("i = {i}"); i += 1; }
Try语句
JavaFX的try
语句用法类似Java,但它具有JavaFX变量声明语法。注意:在JavaFX中,任意对象都能够被抛出和捕捉,而并非仅仅是
java.lang.Throwable
的继承类。
例如:
try { throw "Hello"; } catch (s:String) { System.out.println("caught a String: {s}"); } catch (any) { System.out.println("caught something not a String: {any}"); } finally { System.out.println("finally..."); }
For语句
JavaFX的for
语句头与
foreach
列表推导操作符(list comprehension operator)使用相同的语法。但是,在下面示例中for语句体处理的是由列表推导生成的成员。
例如:
for (i in [0..10]) { System.out.println("i = {i}"); } // print only the even numbers using a filter for (i in [0..10] where i % 2 == 0) { System.out.println("i = {i}"); } // print only the odd numbers using a range expression for (i in [1,3..10]) { System.out.println("i = {i}"); } // print the cartesian product for (i in [0..10], j in [0..10]) { System.out.println(i); System.out.println(j); }
Return语句
JavaFX的return
语句与Java相同:
例如:
operation add(x, y) { return x + y; }
Throw语句
JavaFX的throw
语句类似Java。但是在JavaFX中能够抛出任何对象,而不仅仅是
java.lang.Throwable
的继承类。
例如:
import java.lang.Exception; operation foo() { throw new Exception("this is a java exception"); } operation bar() { throw "just a string"; }
Break和Continue语句
JavaFX的break
和
continue
语句用法Java不同的之处:前者不支持标签。像在Java中一样,
break
和
continue
必须出现在
while
或者
for
语句体中。
例如:
operation foo() { for (i in [0..10]) { if (i > 5) { break; } if (i % 2 == 0) { continue; } System.out.println(i); } } operation bar() { var i = 0; while (i < 10) { if (i > 5) { break; } if (i % 2 == 0) { continue; } System.out.println(i); i += 1; } }
Do语句
JavaFX的do
语句允许使用者在后台线程中执行一块JavaFX代码,以便AWT事件调度线程继续处理事件,从而防止UI平台出现挂起现象。目前,在执行后台线程时采用java.awt.EventQueue
实现了对事件的出/入队操作。通常情况下,所有的JavaFX代码都在AWT事件调度线程中执行,只有包含在 do
语句体中的语句被允许在另一个线程中执行。这些代码必须只访问Java对象,如果需要的话,那些Java对象还必须处理其自身的线程同步。
例如:
import java.net.URL; import java.lang.StringBuffer; import java.lang.System; import java.io.InputStreamReader; import java.io.BufferedReader; // in the AWT EDT var result = new StringBuffer(); do { // now in a background thread var url = new URL("http://www.foo.com/abc.xml"); var is = url.openStream(); var reader = new BufferedReader(new InputStreamReader(is)); var line; while (true) { line = reader.readLine(); if (line == null) { break; } result.append(line); result.append("/n"); } } // now back in the EDT System.out.println("result = {result}");
在上面的示例中,在事件调度线程(EDT)中正在执行的那些绿色代码在do
语句(红色代码)执行期间将被阻塞。但如果在等待后台线程完成的期间,一个新的事件调度循环被建立在调用堆栈上,那么在执行do
语句的同时这些GUI事件将继续被处理。不幸的是,由于它能够引发建立在堆栈上的多重事件调度循环,乃至在糟糕的情况下引起堆栈溢出异常,而目前并没有一种对此称得上优秀的解决方案。
do later
do
语句的第二种形式(do later
):它允许在事件调度线程中的语句体内进行异步执行,而不是在后台线程中执行(此功能由java.awt.EventQueue.invokeLater
提供)。顾名思义,do later语句体在事件调度线程执行完成后才被执行。下面是一个例如:
import java.lang.System; var saying1 = "Hello World!"; var saying2 = "Goodbye Cruel World!"; do later { System.out.println(saying1); } System.out.println(saying2);
运行上面的代码将产生如下输出:
Goodbye Cruel World! Hello World!
类与对象
JavaFX中声明类的语法:在class
关键字后面跟着类名,接着是可选的extends
关键字和由逗号分割的基类名列表,一个开放的大括号({),一个属性列表,函数和操作,一个关闭的大括号(}),在大括号中间的每一个语法逻辑行都使用分号结尾。
例如:
class Person { attribute name: String; attribute parent: Person inverse Person.children; attribute children: Person* inverse Person.parent; function getFamilyIncome(): Number; function getNumberOfChildren(): Number; operation marry(spouse: Person); }
属性的声明方式:attribute
关键字后面跟着属性名,一个冒号,属性类型,可选的重数(cardinality)说明(?
代表不确定,*
代表零个或者更多,+
代表一个或者更多),一个可选的、用来说明与类中另一属性之间双向关系的反向子句(inverse
clause),并使用分号结束。
attribute AttributeName : AttributeType Cardinality inverse ClassName.InverseAttributeName;
如果反向子句出现在对象属性定义中,那么当此属性值被修改时JavaFX解释器将自动更新其反向属性(根据更新的类型和属性的重数进行插入、删除或者替换)。
多值属性(例如那些使用*
或者+
重数描述符声明的属性)被表示为数组,并能够通过[]
操作符访问,并使用insert
和delete
操作符更新。
和Java方法不同,所有的JavaFX成员操作体和成员函数体都被定义在类声明外部,例如:
function Person.getNumberOfChildren() { return sizeof this.children; }
在类声明中对操作和函数的声明都需要对参数和返回值类型进行声明,而在操作体和函数体的具体定义中则可被忽略。
属性声明
在JavaFX中可以声明属性的初始值。在新建对象上下文中的初始化程序按照声明顺序被逐一求值(见下例的粗体部分):
import java.lang.System; class X { attribute a: Number; attribute b: Number; } attribute X.a = 10; attribute X.b = -1; var x = new X(); System.out.println(x.a); // prints 10 System.out.println(x.b); // prints -1
还可以通过bind
操作将增量式求值表达式(incrementally evaluated expression)声明为属性值:
import java.lang.System; class X { attribute a: Number; attribute b: Number; attribute c: Number; } attribute X.a = 10; attribute X.b = bind a + 10; attribute X.c = bind lazy b + 10; var x = new X(); System.out.println(x.a); // prints 10 System.out.println(x.b); // prints 20 System.out.println(x.c); // prints 30 x.a = 5; System.out.println(x.a); // prints 5 System.out.println(x.b); // prints 15 System.out.println(x.c); // prints 25
对象声明
JavaFX使用由类名、用大括号包含的属性初始化程序列表构成的说明性语法来完成对象的初始化。每个初始化程序由属性名、冒号、定义属性值的表达式(JavaFX也支持在上下文中进行增量式求值,详见下面的章节)构成:
var chris = Person { name: "Chris" children: [Person { name: "Dee" }, Person { name: "Candice" }] };
JavaFX也支持Java对象的初始化语法。你可以象在Java中一样传递参数给类构造器:
import java.util.Date; import java.lang.System; var date1 = new Date(95, 4, 23); // call a java constructor var date2 = Date { // create the same date as an object literal month: 4 date: 23 year: 95 }; System.out.println(date1 == date2); // prints true
JavaFX允许在对象中声明本地变量。这些变量只在对象本身的范围内可见。另外,一个引用被初始化对象的变量可以通过var
关键字声明为假属性(pseudo-attribute),就如下例中的child1和child2一样:
var chris = Person { var: me name: "Chris" var child1 = Person { name: "Dee" parent: me } var child2 = Person { name: "Candice" } children: [child1, child2] };
更新触发器
JavaFX类没有构造器,其属性也没有“setter”。作为替代物,JavaFX提供了类似SQL的触发器(trigger
)来为使用者提供处理数据修改事件的能力。
触发器使用trigger
关键字声明。
触发器由头部和代码体构成。头部说明了触发器应用的事件类型。代码体则是在特定事件发生时执行的过程。在代码体中你可以使用任何在操作体中有效的语句。与成员函数/操作类似,在触发器中在代码体内的上下文对象可以通过this
关键字访问。
创建触发器
你可以在一个新建对象的上下文中声明一个创建触发器:
import java.lang.System; class X { attribute nums: Number*; } trigger on new X { insert [3,4] into this.nums; } var x = new X(); System.out.println(x.nums == [3,4]); // prints true
上面的示例中定义了一个在X
类的实例被创建时执行的触发器。此触发器完成了对nums
属性的初始赋值。
插入触发器
当一个成员被插入到多值属性时,我们可以定义一个插入触发器:
import java.lang.System; class X { attribute nums: Number*; } trigger on insert num into X.nums { System.out.println("just inserted {num} into X.nums at position {indexof num}"); } var x = new X(); insert 12 into x.nums; // prints just inserted 12 into X.nums at position 0 insert 13 into x.nums; // prints just inserted 13 into X.nums at position 1
以上示例代码中,“num”是引用被插入成员的变量(你可以按照自己喜好命名它)。此变量的上下文索引(由indexof
操作符返回)与插入点一致。
删除触发器
当一个成员从多值属性中被删除时,我们可以定义一个删除触发器:
import java.lang.System; class X { attribute nums: Number*; } trigger on delete num from X.nums { System.out.println("just deleted {num} from X.nums at position {indexof num}"); } var x = X { nums: [12, 13] }; delete x.nums[1]; // prints just deleted 13 from X.nums at position 1 delete x.nums[0]; // prints just deleted 12 from X.nums at position 0
以上示例代码中,“num”是引用被删除成员的变量(你可以按照自己喜好命名它)。此变量的上下文索引(由indexof
操作符返回)与删除点一致。
替换触发器
当一个单值的属性值或者多值属性的成员被替换时,我们可以定义一个替换触发器:
import java.lang.System; class X { attribute nums: Number*; attribute num: Number?; } trigger on X.nums[oldValue] = newValue { System.out.println("just replaced {oldValue} with {newValue} at position {indexof newValue} in X.nums"); } trigger on X.num[oldValue] = newValue { System.out.println("X.num: just replaced {oldValue} with {newValue}"); } var x = X { nums: [12, 13] num: 100 }; x.nums[1] = 5; // prints just replaced 13 with 5 at position 1 in X.nums x.num = 3; // prints X.num: just replaced 100 with 3 x.num = null; // prints X.num: just replaced 3 with null
以上示例代码中,“oldValue”和“newValue”是两个变量,它们分别表示对被替换成员的旧值和替换后的新值的引用(你可以按照自己喜好命名)。变量的上下文索引(由indexof
操作符返回)与被替换成员的顺序位置一致。
增量式求值和懒惰求值
在JavaFX中,属性初始化程序能够使用bind操作符进行懒惰、增量式求值。使用bind
初始化的属性类似于包含公式的电子表格中的单元格。在包含此属性的对象的生命周期中,只要在初始化表达式右侧引用的任何对象发生改变,其左侧的对象属性将被自动更新。示例如下:
import java.lang.System; class X { attribute a: Number; attribute b: Number; attribute c: Number; } trigger on X.b = newValue { System.out.println("X.b is now {newValue}"); } trigger on X.c = newValue { System.out.println("X.c is now {newValue}"); } var x1 = X { a: 1 b: 2 // X.b is now 2 is printed c: 3 // X.c is now 3 is printed }; var x2 = X { a: x1.a // eager, non-incremental b: bind x1.b // eager, incremental (X.b is now 2 is printed) c: bind lazy x1.c // lazy, incremental (nothing is printed yet) }; System.out.println(x2.a); // prints 1 System.out.println(x2.b); // prints 2 System.out.println(x2.c); // prints X.c is now 3, then prints 3 x1.a = 5; x1.b = 5; // prints X.b is now 5, twice x1.c = 5; // prints X.c is now 5, twice System.out.println(x2.a); // prints 1 System.out.println(x2.b); // prints 5 System.out.println(x2.c); // prints 5
上例中,x2的属性b和c被绑定到x1的属性b和c。这意味着当x1的b或c属性被更新时,x2的b或c属性都会相应地被更新。在x2中的b、c属性之间的不同是:前者的属性值在其属性初始化程序中被立即更新,而后者的绑定直到其值被访问时才被求值。
注意:函数体无需bind
操作符便可被增量地求值,但操作体则做不到。在改变本地变量的操作中并不触发增量式求值。增量式求值不能在操作体内执行,除了表达式明确地以bind
作为前缀。
然而,当你在一个增量式求值上下文中调用操作或者Java方法,此调用本身将被增量式求值。这意味着如果此调用变成了对操作或者Java方法的全新调用(由于此调用被增量式求值),那么它所用到的任何参数值将被使用并返回新值。
译者注:此处的新值是与不进行增量式求值相比。
相反,在增量式求值上下文中调用函数,此函数只能被调用一次,而其求值结果也将被合并到调用求值树中。
增量式求值是JavaFX的主要特征,它使定义复杂的动态GUI声明成为了可能。懒惰求值的特性常用在处理像tree或者graph这样的递归数据结构上。
反射
JavaFX类、属性、操作可以通过如下方式反射:
public class Class { public attribute Name: String; public attribute Documentation:String?; public attribute Superclasses: Class* inverse Class.Subclasses; public attribute Subclasses: Class* inverse Class.Superclasses; public attribute Attributes: Attribute* inverse Attribute.Scope; public attribute Operations: Operation* inverse Operation.Target; public function instantiate(); } public class Operation extends Class { public attribute Target: Class? inverse Class.Operations; } public class Attribute { public attribute Name: String; public attribute Documentation: String?; public attribute Scope: Class? inverse Class.Attributes; public attribute Type: Class?; public attribute Inverse: Attribute* inverse Attribute.Inverse; public attribute OneToOne: Boolean; public attribute ManyToOne: Boolean; public attribute OneToMany: Boolean; public attribute ManyToMany: Boolean; public attribute Optional: Boolean; }
JavaFX支持通过class
操作符对类、属性、成员函数和操作的进行反射访问:
import java.lang.System; System.out.println(1.class.Name) // prints "Number" System.out.println("Hello".class.Name); // prints "String" class X { attribute a: Number; } var x = new X(); System.out.println(x.class.Name); // prints "X" System.out.println(sizeof x.class.Attributes); // prints 1 System.out.println(x.class.Attributes[0].Name); // prints "a"
对属性值进行反射访问时,如果访问操作数是属性,则使用[]
操作符:
import java.lang.System; class X { attribute a: Number; } var x = new X(); x.a = 2; System.out.println(x[x.class.Attributes[Name == 'a']]); // prints 2 // the above statement is equivalent to this non-reflective code: System.out.println(x.a);
在JavaFX中,类的成员函数和操作本身被模式化作为在目标类中的类,而形参和返回值被表示为属性。代表目标对象的属性名是“this”。代表返回值的属性名为“return”。代表形参的属性具有和形参相同的属性名。
译者注:这里的目标类是指使用成员函数和操作的类。而目标对象则指使用成员函数和操作的对象。
从上例中可以发现,你也可以从Class
对象中获取相同的、被反射的操作。
被反射的操作能够像函数那样通过将目标对象作为第一个参数、其它参数作为后面的参数的方式被调用:
import java.lang.System; class X { operation foo(n: Number): Number; } var x = new X(); var op = x.class.Operations[Name == 'foo']; System.out.println(op(x, 100)); // the above code is equivalent to the following non-reflective code: System.out.println(x.foo(100));
bean属性和Java类的公共字段被反射为JavaFX属性。但是,Java方法不能被反射为JavaFX操作。如果你想调用某个Java方法,那么你可以通过简单地使用Java API来实现。
注意:与Java不同的:在JavaFX中class
操作符被用于表达式,而不是用于类型名(type name)。因此作为补充,JavaFX支持从类型名中获取反射类对象的语法:
:TypeName
For example:
import java.lang.System; System.out.println(:System.Name); // prints "java.lang.System" System.out.println(:System.class.Name); // prints "Class"
广度(Extents)和枚举
类的“广度”,即此类的所有实例的集合,能够通过以下语法获得:
*:ClassName
例如,下面的代码打印出String
类的所有实例:
import java.lang.System; for (i in *:String) { System.out.println(i); }注意:这是可选特性,默认情况下是失效的。
JavaFX也提供了声明类的命名实例的能力:
objectName:ClassName
例如:
import java.lang.System; myString:String = "This is a string"; System.out.println(myString:String);
这样的命名实例是全局可访问的,但通常必须使用类名进行限制。然而,在属性初始化程序和赋值的上下文中,表达式类型的命名实例被引入到了词法作用域(lexical scope)(可见性弱于变量和属性),并可以通过使用它们的无限定名(unqualified names)引用这些命名实例:
Button { mnemonic: P text: "Press Me" }
在上面实例中,由于Button
的mnemonic
属性是KeyStroke
类型的,因此我能够通过使用它的无限定名访问其命名值P
,而在别处我将不得不使用P:KeyStroke
来引用它。
JavaFX使用与Java1.5同样的语法来访问枚举类型值:
import java.lang.management.MemoryType; var heap = HEAP:MemoryType; var nonHeap = NON_HEAP:MemoryType;
相关资源
- openJavaFX官方网站
- openJavaFX FAQ
- Functional programming
- 什么是第一级函数
- FOLDC对list comprehension的解释
- XPath教材
- 使用JXPath查询Java对象
- XQuery简介
关于译者
cleverpig:BJUG成员,Java社区——Matrix与Java共舞负责人之一,曾参与Buffalo的文档工作、Fielding的《Architectural Styles and the Design of Network-based Software Architectures》中文化研究(还要感谢Tin、Nicholas的大力相助),关注一切新技术,业余时间研究Guru并准备得道升天,但是苦于没有得法,目前还在苦苦追寻……
Tin:中文名“田乐”,BJUG成员,现就职于Sina。曾经在Java Web项目中担任软件架构师和Web设计,注重使用轻量级解决方案和敏捷方法。目前主要做基于Javascript的RIA开发,喜欢研究新技术并进行思考,业余时间继续关注Java和Ruby,并与朋友一起翻译Selenium文档。
译文Feedback
欢迎一切友人的Feedback!!