原文地址:http://java.sun.com/developer/technicalArticles/scripting/javafxpart1/
JavaFX Script编程语言(以下称为JavaFX)有Sun微系统公司开发的一种declarative, statically typed(声明性的、静态类型)脚本语言。如Open JavaFX(OpenJFX)网站所述,JavaFX技术有着良好的前景,包括可以直接调用Java API的能力。因为JavaFX Script是静态类型,它同样具有结构化代码、重用性和封装性,如包、类、继承和单独编译和发布单元,这些特性使得使用Java技术创建和管理大型程序变为可能。
这一系列的JavaFX入门文章包括三部分。第一部分是JavaFX编程语言的介绍,目标读者是那些熟悉Java技术并且具有脚本语言基础的开发者。第二和第三部分演示如何使用JavaFX连接使用Remote Method Invocation(RMI)和Java API for XML Web Services(JAX-WS)技术的远程服务器
JavaFX Pad应用程序
如果在你的系统上已经安装了JRE,最简单的入门方式就是打开Java Web Start激活演示程序——JavaFX Pad。一旦运行这个该程序,应该可以看到类似图1所示的界面:
图1 运行在Windows Vista,JDK6上的JavaFX Pad
JavaFX Pad启动时加载并执行一个默认的程序。不过,你也可以从本文的例子中复制代码,粘贴到源码去,然后观察变化。另外,你也可以将代码保存到本地,并从本地加载JavaFX源文件。JavaFX Pad可以实时的查看在运行时你做了什么,做了改变,立即可以看到结果。
JavaFX技术:一种Statically Typed语言
JavaFX编程语言是一中有着static typing特性的脚本语言。具体怎么理解呢?来看下面的例子:
其实,如果把上面的两行代码输入到JavaFX Pad的demo中,立即就会在窗口的下方看到错误提示,如图2:
图2 JavaFX中静态类型变量不能其改变类型
JavaFX技术:一个Declarative脚本语言
JavaFX技术也是一种declarative脚本语言。这里的declarative是什么意思呢?为了回答这个问题,来看OpenJFX网站上的Hello World程序:
attribute saying: String;
}
var model = HelloWorldModel {
saying: "Hello World"
};
var win = Frame {
title: bind "{model.saying} Java FX"
width:200
content: TextField{
value: bind model.saying
}
visible: true
}
多数编译性语言,包括Java,被认为是命令式编程语言(imperative programming language)。其中,它们依赖于一个程序的入口,如Java中的main()方法。它们通过这个入口点实例化其他类或字段,或设置基于变量或程序状态的资源。为了稍微扩展一下这个例子,你也可以说命令式编程语言,在运行时“公式化”地决定其运行的途径。尽管这个公式可能导致每次运行都是同样的途径,但是这种语言仍旧在运行决定其运行途径。
注意,上面JavaFX的Hello World程序并没有包含main()方法。脚本引擎在执行之前读入整个程序,然后让解释器执行其所需要的任何步骤以正确运行程序。更准确地说,引擎需要的所有东西都要在执行之前声明,然后引擎通过声明决定做什么以达到预期目标。
在JavaFX Pad中使用System.out.prinltn()
接下来我们将要看到的是,JavaFX调用传统Java类库的能力。然而,在你希望在JavaFX Pad程序中使用System.out.println()之前,需要打开控制台支持,操作如下:
1、Microsoft Windows XP或者Vista,点击控制面板中的Java图标,选择高级选项卡,从Java控制台下的条目中选择“显示控制台”。
2、Solaris操作系统,点击参数(Preferences)选项卡中的Java图标,选择高级选项卡,从Java控制台下的条目中选择“显示控制台”。如果在参数选项卡中没有Java图标,运行bin目录下的控制面板程序(或jcontrol)。
3、Linux操作系统,在bin目录中查找控制面板(或jcontrol)程序,运行,点击参数选项卡中的Java图标,选择高级选项卡,然后从Java控制台下的条目中选择“显示控制台”。
4、Mac OS X操作系统,打开/Application/Utilities/Java/[Java version]/下的Preferences程序。选择高级选项卡,然后从Java控制台下的条目中选择“显示控制台”。注意:如果在Java Preferences更改后Java Web Start在Intel Mac上启动错误,尝试打开home目录下的Library/Caches/Java/deployment.properties文件,然后将所有的osarch的值由i386改为ppc.
在JavaFX Pad中,取消对Run菜单下的Run Automatically的选择,在手动运行JavaFX程序前清空Java控制台。使用JavaFX Pad中的Run菜单下的Run菜单项可以手动运行程序。
图3是JavaFX Pad及控制台运行在Intel-based Macintosh上。图4是JavaFX Pad运行在OpenSolaris上。
图3 运行在Mac OS X上的JavaFX Pad和Java控制台,JDK1.5.0_07
图4 运行在OpenSolaris上的JavaFX Pad及Java控制台,JDK6
最后,如果你正要将变量嵌入到JavaFX的字符串中,这在Java中使用System.out.println()方法时非常普通的操作,注意在JavaFX中的正确语法是:
System.out.println("Text {variable} and more text");
与Java语法不同:
System.out.println("Text" + variable + " and more text");
开始了解JavaFX技术
这一部分讨论JavaFX的基础知识。其中的大部分内容直接来自于官方的JavaFX编程语言参考文档(JavaFX Programming Language Reference),本文作者针对Java编程人员做了一些修改。
原始类型
JavaFX仅支持四种原始类型:String,Boolean,Number和Integer。注意,和Java不一样,JavaFX的原始类型首字母大些。表1列出了JavaFX解释器内的原始类型及与之对应的Java对象。
表1:JavaFX的原始类型及其和Java的对应关系
JavaFX原始类型 | 相应的Java原始类型或类 |
String | java.lang.String |
Boolean | java.lang.Boolean |
Number | java.lang.Number |
Integer |
byte,short,int,long, |
注意:简便期间,Integer表示小数字和大数字,而Java中可能会使用不同的原始类型如short和long。浮点数,如Java中的float和double,全部有Number类型替代。
由于Java对象代表了这些原始类型,因此你可以调用Java中已存在的这些类型的方法。
s.toUpperCase(); //String method that yields "HELLO"
s.substring(1); //String method that yields "ello"
var n:Number = 1.5;
n.intValue(); //Number method that yields integer 1
(1.5).intValue(); //Number method that yields integer 1
var b:Boolean = true;
b instanceof Boolean; //Boolean method that yields true
var s:String = "Hello";
System.out.println(s.toUpperCase()); //String method that yields "HELLO"
System.out.println(s.substring(1)); //String method that yields "ello"
var n:Number = 1.5;
System.out.println(n.intValue()); //Number method that yields integer 1
System.out.println((1.5).intValue()); //Number method that yields integer 1
var b:Boolean = true;
System.out.println(b instanceof Boolean); //Boolean method that yields true
return null; //Final node returned for JavaFX Pad display
var s:String = "A New String";
var s = "A New String";
表2:JavaFX基数操作符
操作符 | 意义 |
? | 可选(可以为空) |
+ | 一个或多个 |
* | 0个或多个 |
下面是一个例子:
typeName、基数操作扶和初始化部分都是可选的,因此下面的声明和前一个例子等价:
字面量(Literals)
在JavaFX中,字面量字符串由单引号或者双引号指定:
var s = "Hello";
var s = "Hello {name}"; // s = 'Hello Joe'
var s = "The answer is {if answer then "Yes" else "No"}";
contains
new lines";
数组和列表(Array and List Comprehensions)
在前面你可能就已经注意到基数操作符可以创建数组。JavaFX中的数组由方括号和逗号标示。和Java一样,JavaFX数组中的所有元素必须为同一类型。
var days = [weekdays, ["Sat","Sun"]];
// returns true
可以使用sizeof操作符获得当前数组的大小:
var arraySize = sizeof oneToAHundred; // size == 100
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]
insert 10 after x[. == 3]; // 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语句以同样的方式工作。注意,如果方括号中的表达式被省略,将会删除整个数组。
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; // clears the array and yields []
// yields [1,4,9,16,25,36,49,64,81,100]
可以如下所示增加一个过滤器:
// yields [4,16,36,64,100]
最后,可以想选择语句中添加多个list:
// yields [200, 400, 400, 800]
使用foreach操作符可以达到同样的效果:
foreach(n in [1..4], m in [100,200] where (n%2 == 0) )
n*m; // also yields [200, 400, 400, 800]
格式化(Formatting)
操作符format as支持几种格式化命令,如表3所示:
表3:JavaFX格式化命令
指令 | 格式化使用的类 |
以%引导的格式化命令 | java.util.Formatter |
表达式是Number | java.text.DecimalFormat |
表达式是java.util.Date | java.text.SimpleDateFormat |
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'
var textArea = new JTextArea();
textArea.<<insert>>("Hello", 0);
声明类(Declaring Classes)
JavaFX声明的一个类型的语法是:class关键字后面是类的名称,可选的extends关键字,使用逗号分隔的基类名称。注意,与Java不同,JavaFX可以继承多个类。再接着是一个开放的花括号({),然后是属性、函数和操作列表,每个都使用分号(;)结尾,最后是一个闭合的花括号(})。下面是一个例子:
attribute name: String;
attribute parent: Person;
attribute children: Person*;
function getFamilyIncome(): Number;
function getNumberOfChildren(): Number;
operation marry(spouse: Person): Boolean;
}
属性、函数和操作(Attribute, Functions, andOperations)
我们进一步看一下三种类成员的声明。属性的声明形式attribute关键字,接着是属性的名称,冒号(:),属性的类型,可选的基数声明,还有一个可选的inverse子句。如果使用了基数,这个属性通常被认为是多值的属性(multivalued attribute)。
将属性的声明语法更加形式化,表示如下:
函数(Functions)代表JavaFX语言的一个纯粹的功能性子集。换句话说,函数体可能只包括一系列的变量声明和一个return语句。这使得它们称为最合适的属性访问方法(getter)和简单的数学运算。下面是函数的一些例子:
var x = a + b;
var y = a - b;
return sq(x) / sq (y);
}
function sq(n) {return n * n; }
JavaFX中的操作(Operations)使用operation关键字声明,操作可能很多语句,例如条件语句、循环语句、try和catch语句等等。这使得它们和Java中类方法很类似。当声明主体,首先给出操作的名称,接着是在圆括号中的输入参数,冒号和返回值类型。下面是一个例子:
try {
return s.substring(n);
} catch (e:StringIndexOutOfBoundsException) {
throw "sorry, index out of bounds";
}
}
初始化声明的属性(Initializing Declared Attributes)
同函数和进程(procedure)的一样,属性的值的初始化在类定义的外部。初始化表达式按照最近创建的对象的上下文中类声明中指定的属性的顺序求值:
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
a: 10
b: -1
};
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
函数和操作定义(Function and Operation Definition)
同Java方法不同,函数和操作的主题在类声明的外面定义,Java程序员刚开始可能会被这种语法感到些许不习惯,但是它是相当容易理解的。JavaFX中,函数或操作的名称由其所属类名称引导。返回列在函数或操作名称的后面,由冒号引导。输入参数包含在圆括号中,由逗号隔开,遵循name: type的语法形式。下面是一个例子:
// Body of operation
}
// Body of operation
}
触发器(Triggers)
和Java不同,JavaFX类没有构造函数,JavaFX属性也没有典型的setter方法。取而代之的是,JavaFX提供了一种类似SQL的触发器来处理数据更改事件。这些触发器使用trigger关键字。
对象创建触发器(Object Creation Triggers)
在最近创建的对象上下文中指定一个创建触发器触发一个动作:
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
Insert触发器(Insert Triggers)
也可以在向多值属性插入一个元素时触发一个动作:
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
Delete 触发器(Delete Triggers)
同样的方式,可以在从多值属性中删除一个元素时触发一个动作:
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
Replace触发器(Replace Trigger)
最后,可以在替换一个属性时触发一个动作。在下面的例子中,oldValue和newValue是任意的变量名,分别表示被替换的元素的以前的值和当前的值。可以随意更改为其他变量名:
class X {
attribute nums: Number*;
attribute num: Number?;
}
trigger on X.nums[oldValue] = newValue {
System.out.println("X.nums: replaced {oldValue} with {newValue} at position {indexof newValue}");
}
trigger on X.num[oldValue] = newValue {
System.out.println("X.num: replaced {oldValue} with {newValue}");
}
var x = X {
nums: [12, 13]
num: 100
};
x.nums[1] = 5;
// prints replaced 13 with 5 at position 1 in X.nums
x.num = 3;
// prints X.num: replaced 100 with 3
x.num = null;
// prints X.num: replaced 3 with null
语句(Statements)
JavaFX包含了几种类型的语句,它们和其在Java中的副本极其类似但是并不相同。这一部分将概述它们的不同。
If语句(If Statement)
JavaFX的if语句很类似Java中的if语句,除了花括号是必需的:
System.out.println("Condition 1");
} else if (condition2) {
System.out.println("Condition2");
} else {
System.out.println("not Condition 1 or Condition 2");
}
While语句(While Statement)
JavaFX的While语句同样需要花括号:
while (i < 10) {
if (i > 5) {
break;
}
System.out.println("i = {i}");
i += 1;
}
Try,Catch和Throw语句(Try, Catch, and Throw Statements)
JavaFX的try和catch语句和Java中的很像,但是使用的是JavaFX变量声明语法。注意,在JavaFX中,任何一个对象都可以被抛出和捕获,并非只是那些继承了java.lang.Throwbale的对象。
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语句(For Statement)
JavaFX的for语句的开头使用和前面讨论的foreach list-comprehension操作符同样的语法。语句的主体在每一个由list comprehension产生的元素上执行。下面是一个简单的例子:
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语句(Return Statement)
JavaFX的return语句与Java中的一样:
return x + y;
}
Break和Continue语句(Break and Continue Statements)
除了不支持标签外,JavaFX的break和continue语句和Java中的一样。同Java一样,break和continue必须出现在while或者for语句的主体内:
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和Do Later语句(Do and Do Later Statement)
JavaFX的do语句允许执行一个JavaFX代码块。然而,do的主体一般在后台线程中执行。正常情况下,JavaFX代码在AWT Event Dispatch Thread(EDT)中执行,只有do语句主体中代码允许在另外一个线程中执行。参见下面的例子:
import java.lang.StringBuffer;
import java.lang.System;
import java.io.InputStreamReader;
import java.io.BufferedReader;
// in the AWT Event Dispatch Thread (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(" ");
}
}
// now back in the EDT
System.out.println("result = {result}");
do语句还有另外一种形式do later,这允许在EDT中异步执行其主体,而不是在后台线程中同步执行,类似于java.awt.EventQueue.invokeLater()提供的功能。下面是一个例子:
var saying1 = "Hello World!";
var saying2 = "Goodbye Cruel World!";
do later {
System.out.println(saying1);
}
System.out.println(saying2);
Hello World!
Incremental Evaluation(增量式求值)
增量式求值是JavaFX中众多令人激动的特色中一个:它允许程序员定义复杂的、动态的GUI声明。在JavaFX中,属性初始化可以使用bind操作符进行增量式求值。一旦绑定,这些属性的行为就不是字面量了,而更像电子表格中包含了公式的单元格。只要被应用的右边的初始化表达式中的任何对象做了改变,左边的属性值就自动更新。
下面是一个简单的例子:
class X {
attribute a: Number;
attribute b: Number;
}
var x1 = X {
a: 1
b: 2
};
var x2 = X {
a: x1.a // nonincremental
b: bind x1.b // incremental
};
System.out.println(x2.a); // prints 1
System.out.println(x2.b); // prints 2
x1.a = 5;
x1.b = 5;
System.out.println(x2.a); // prints 1
System.out.println(x2.b); // prints 5
此例中,x2的属性b绑定到了x1的属性b上。这就意味着,只要x1的属性b被更新了,x2的属性b也同时被更新。
注意,函数(function)体一直是没有使用bind操作符的增量式求值的,而操作(operation)体不是。不像函数,操作内部的局部变量改变时不会触发增量式求值。除非在操作体的内部在表达式前面显式地使用了bind操作符,否则操作体不会执行增量式求值。
也可以修改例子使用lazy incremental evaluation。这里的绑定只有在属性在第一次被访问后才会起作用。
class X {
attribute a: Number;
}
var x1 = X {
a: 1
};
var x2 = X {
a: bind lazy x1.a
// no value is assigned yet
};
System.out.println(x2.a);
// Now the value is accessed, so x2.a is set to 1
lazy incremental evaluation经常在递归数据结构中使用,如树和图。
总结(Conclusion)
本文简单介绍了JavaFX平台。在第二和第三部分将讨论使用基于JavaFX技术的GUI处理客户端/服务器端通信的方式。
更多信息(For More Information)
-
JavaFX Pad -- A JNLP that starts the JavaFX Pad application, which will allow you to iteratively enter JavaFX code and watch the results at the same time.
-
The OpenJFX Web Site -- The Official Site for JavaFX Technology
-
Getting Started With the JavaFX Script Language (for Swing Programmers) -- This tutorial shows you how to use the graphical widgets present in the JavaFX scripting language. Because many of these widgets map directly to the underlying Swing components, those who are familiar with programming Swing will be able to read quickly through this document.
-
JavaFX Script 2D Graphics Tutorial -- Similar to JavaFX Pad, this JNLP will help you learn to use the 2D graphics functionality inside JavaFX technology.
- The JavaFX Script Programming Language Reference -- The official reference on which the latter half of this article is based.