第二章 语言文档参考(一)
第一节 Java互操作性
Xtend像Java一样,是一种静态类型的语言。实际上,它完全支持Java的类型系统,包括基本类型,如int或者boolean、数组以及类路径上所有的Java类、接口、枚举和注解等。
完全支持Java泛型:您可以在方法和类上定义类型参数,并将类型参数传递给通用类型,就像在Java一样使用。类型系统及其一致性和转换规则按照Java语言规范中的定义实现。
支持Java类型系统的各个方面,确保Java和Xtend之间无缝匹配。这意味着Xtend和Java可以100%互操作。你不必在考虑两种语言的特殊情况。您可以从Java调用Xtend代码,反之亦然,无任何意外或麻烦。如果你知道Java的类型系统,并熟悉Java的通用类型,那么你已经知道Xtend中最复杂的部分。
Xtend-to-Java编译器的默认行为,是生成与相应项目中的Java编译器版本兼容性的Java代码。这可以在首选项或Xtend→Compiler页面的项目属性中更改(自2.8起)。根据选择的Java语言版本,Xtend可能会生成不同但等效的代码。例如,如果编译器设置为Java 8,则将lambda表达式转换为Java的lambdas,而较低版本则生成Java的匿名类。
类型推断
Java的一个问题是,你不得不一次又一次写类型签名。这就是为什么这么多人不喜欢静态类型。但实际上这不是静态类型的问题,而是Java的问题。虽然Xtend也是静态类型的,就像Java一样,你却很少需要写类型,因为它们可以从上下文中推断出来。
考虑以下Java变量声明:
final LinkedList<String> list = new LinkedList<String>();
构造函数调用的类型,必须重复编写变量类型声明。在Xtend中,可以从初始化表达式推断出变量类型:
val list = new LinkedList<String>
转换规则
除了Java的自动装箱以外,还可将基本类型转换为相应的包装类型(例如int自动转换为整数)时,Xtend还有其他转换规则。
数组自动转换为List<ComponentType>,反之亦然。就是说,你可以写下列内容:
def toList(String[] array) {
val List<String> asList = array
return asList
}
对数组的后续更改反映在列表中,反之亦然。基本类型的数组将转换为各自的包装类型的列表。
转换也能反过来工作。事实上,所有子类型Iterable的都会自动转换为数组。
另一个非常有用的转换适用于lambda表达式。lambda表达式通常是函数或过程声明的类型之一。但是,如果期望类型是只声明一个抽象方法的接口或类注1,则lambda表达式将自动转换为该类型。这允许lambda表达式使用许多现有的Java库。有关详细信息,请参阅Lambda表达式。
注1:关于:Single Abstract Method(SAM)类型是一种约定。Java语义没有function type或者delegate type。但lambda表达式的需要类型怎么办?于是,就安个接口,而接口里方法叫什么名字没关系,只要保证这个接口只有一个方法是抽象、无实现的就可以了,这样lambda表达式的函数体就可以充当这个抽象方法的实现。SAM类型的约定就这么来了。
第二节 类和成员
Xtend文件看起来像一个Java文件。它以包声明开始,后跟导入和类定义。这些类实际上直接转换为相应的Java类。类可以有构造函数、字段、方法和注解。
这是一个Xtend文件示例:
package com.acme
import java.util.List
class MyClass {
String name
new(String name) {
this.name = name
}
def String first(List<String> elements) {
elements.get(0)
}
}
包声明
包声明可以像Java那样。有两个小的、可选的区别:
· 可以用^字符转义标识符,以防其与关键字冲突。
· 终止位置分号“;”是可选的。
package com.acme
导入
类型名称的普通导入等同于从Java的导入。其次,可以使用一个^避免任何与关键字冲突的名称。与Java不同,终止分号是可选的。为了更好的可用性和明确的依赖关系,非静态通配符类型导入已被弃用。
Xtend还具有静态导入,用于导入静态字段和方法。语义和语法就像Java一样。
与Java一样,来自java.lang包的所有类都被隐式导入。
import java.math.BigDecimal
import static java.util.Collections.sort
import static org.junit.Assert.*
静态方法也可以导入为extensions。有关详细信息,请参阅扩展方法部分。
类声明
类声明重用了很多Java的语法,但在某些方面仍然有些不同:所有Xtend类型默认都是public,因为这是常见的情况。Java默认的“package private”可见性,Xtend中由更明确的关键字package声明。与Java相反,Xtend支持每个文件多个public顶级类声明。每个Xtend类分别被编译为一个顶级Java类。
抽象类使用Java中的abstract修饰符定义。参见抽象方法。
Xtend的继承方法在概念上与Java中的一样。支持类的单一继承以及实现多个接口。Xtend类可以扩展其他Xtend类,甚至Java类也可以从Xtend类继承。如果没有指定超类型,则使用对象。
最简单的类看起来像这样:
class MyClass {
}
Xtend中的一个更高级的泛型类声明:
class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess,
Cloneable, java.io.Serializable {
...
}
构造函数
Xtend类可以定义任意数量的构造函数。与Java不同,您不必一遍又一遍地重复写类的名称,而是使用关键字new声明构造函数。构造函数第一行使用this(args...),可以委托给其他构造函数。
class MyClass extends AnotherClass {
new(String s) {
super(s)
}
new() {
this("default")
}
}
关于继承,使用于Java相同的规则,即如果超类没有定义无参数构造函数,则构造函数体中第一个表达式必须使用super(args...)的来明确地调用。
构造函数的默认可见性public,但你也可以指定一个明确的可见性public、protected、package或private。
字段
一个字段可以有一个初始化器。final字段使用val声明,同时引入var声明非final字段并能够省略。用val或者var声明字段,如果存在初始化表达式,则可以推断字段的类型。关键字final是val同义词。标记为static将被编译为静态Java字段。
class MyClass {
int count = 1
static boolean debug = false
var name = 'Foo' // type String is inferred
val UNIVERSAL_ANSWER = 42 // final field with inferred type int
...
}
字段的默认可见性是private。您也可以显式声明其为public、protected、package或private。
Xtend的专长是提供扩展方法的字段,参见扩展方法。
方法
Xtend方法在类中声明,并被转换为具有完全相同签名的相应的Java方法。唯一的例外是调度(dispatch)方法(稍后解释)。
def String first(List<String> elements) {
elements.get(0)
}
方法声明以关键字def开始。方法的默认可见性是public。你可以明确地声明其为public、protected、package或private。
Xtend支持static方法的修饰符,如果没有明确给出类型,可以推断返回类型:
def static createInstance() {
new MyClass('foo')
}
与Java一样,vararg参数是允许的,可以作为方法体中的数组值使用:
def printAll(String... strings) {
strings.forEach[ s | println(s) ]
}
可以从方法体推断方法的返回类型。递归方法和抽象方法必须显式声明返回类型。
抽象方法
Xtend中的抽象方法不定义一个方法体,必须在abstract类或接口中声明。另外指定返回类型是强制性的,因为它不能被推断。
abstract class MyAbstractClass() {
def String abstractMethod() // 没有方法体
}
方法重写
可以重写(override)超类方法,或使用关键字override实现接口方法。如果方法覆盖了超类的方法,则必需用override关键字替换关键字def。覆盖语义与Java中的相同,例如,不可能覆盖final方法或不可见方法。覆盖方法从超类继承其返回类型声明。
override String second(List<String> elements) {
elements.get(1)
}
声明异常
Xtend不强制您捕获(catch)或声明检查异常。不过,您仍然可以像Java那样,在方法体中使用throws子句,声明抛出异常。
如果您没有在方法中声明检查异常,但仍可能会在您的代码抛出异常,编译器会静默地抛出被检查的异常(使用Lombok引入的sneaky-throw技术)。
/*
* throws an Exception抛出异常
*/
def void throwException() throws Exception {
throw new Exception
}
/*
* throws an Exception without declaring it抛出异常而不声明它
*/
def void sneakyThrowException() {
throw new Exception
}
支持检查异常的可选验证,也可以在相应的Eclipse首选项页面上配置Xtend错误和警告。
推断返回类型
如果方法的返回类型可以从其方法体推断出来,则不必声明它。请看:
def String second(List<String> elements) {
elements.get(1)
}
可以这样声明:
def second(List<String> elements) {
elements.get(1)
}
返回类型对于抽象方法声明以及递归实现是强制性的。
通用方法
您可以在方法上指定类型参数。上一节中方法的参数化变体可按如下所示:
def <T> second(List<T> elements) {
elements.get(1)
}
支持类型参数和边界约束,并且共享Java语言规范中定义的语法和语义。
操作符声明
Xtend支持操作符重载,是基于操作员-名称-映射,见操作符部分所述。要声明一个操作符,可以使用操作符的名称声明一个简单的方法,也可以直接使用操作符,如下所示:
class Money {
def + (Money other) { ... }
def - (Money other) { ... }
def * (BigDecimal times) { ... }
...
}
调度(Dispatch)方法
通常,和Java一样,方法解析和绑定是在编译时静态地进行的。方法调用根据参数的静态类型进行绑定。有时这不是你想要的。特别是在扩展方法的上下文中,您需要具有多态性。
使用关键字dispatch声明调度方法。
def dispatch printType(Number x) {
"it's a number"
}
def dispatch printType(Integer x) {
"it's an int"
}
对于当前类型层次结构中,具有相同名称和相同数量参数的一组可见调度方法,编译器推断出合成调度方法。此调度使用所有参数的常用超类型声明。实际调度的案例,方法名称前加上一个下划线,如果这些方法被定义为public方法,那么这些方法的可见性将被降到protected的范围。客户端代码总是绑定到合成的调度方法。
对于上述示例中的两个调度方法,将生成以下Java代码:
protected String _printType(final Number x) {
return "it\'s a number";
}
protected String _printType(final Integer x) {
return "it\'s an int";
}
public String printType(final Number x) {
if (x instanceof Integer) {
return _printType((Integer)x);
} else if (x != null) {
return _printType(x);
} else {
throw new IllegalArgumentException("Unhandled parameter types: " +
Arrays.<Object>asList(x).toString());
}
}
请注意,instanceof级联被排序,以便首先处理更具体的类型。
调度案例的默认可见性是protected。如果所有调度方法都明确声明相同的可见性,那么这也将是推断调度的可见性。否则是public。参数类型的比较从左到右执行。在下面的例子中,第二种方法声明被认为更具体,因为它的第一个参数类型是最具体的:
def dispatch printTypes(Number x, Integer y) {
"it's some number and an int"
}
def dispatch printTypes(Integer x, Number y) {
"it's an int and a number"
}
生成以下Java代码:
public String printTypes(final Number x, final Number y) {
if (x instanceof Integer
&& y != null) {
return _printTypes((Integer)x, y);
} else if (x != null
&& y instanceof Integer) {
return _printTypes(x, (Integer)y);
} else {
throw new IllegalArgumentException("Unhandled parameter types: " +
Arrays.<Object>asList(x, y).toString());
}
}
代码以不能为null的方式进行编译。如果要处理null值,可以通过使用参数类型Void的调度来处理。
def dispatch printType(Number x) {
"it's some number"
}
def dispatch printType(Integer x) {
"it's an int"
}
def dispatch printType(Void x) {
"it's null"
}
这将编译为以下Java代码:
public String printType(final Number x) {
if (x instanceof Integer) {
return _printType((Integer)x);
} else if (x != null) {
return _printType(x);
} else if (x == null) {
return _printType((Void)null);
} else {
throw new IllegalArgumentException("Unhandled parameter types: " +
Arrays.<Object>asList(x).toString());
}
}
调度方法和继承
所有超类型的所有可见Java方法,都符合编译后调度方法的表示形式,也调试包括的范围。意味着它们具有预期的参数个数,并且具有相同的前缀下划线编译名称。
例如,考虑以下Java类:
public abstract class AbstractLabelProvider {
protected String _label(Object o) {
// some generic implementation一些通用实现
}
}
以下Xtend类扩展自Java类:
class MyLabelProvider extends AbstractLabelProvider {
def dispatch label(Entity it) {
name
}
def dispatch label(Method it) {
name+"("+params.join(",")+"):"+type
}
def dispatch label(Field it) {
name+type
}
}
生成的Java类MyLabelProvider中的dispatch方法,如下所示:
public String label(final Object it) {
if (it instanceof Entity) {
return _label((Entity)it);
} else if (it instanceof Field) {
return _label((Field)it);
} else if (it instanceof Method) {
return _label((Method)it);
} else if (it != null) {
return super._label(it);
} else {
throw new IllegalArgumentException("Unhandled parameter types: " +
Arrays.<Object>asList(it).toString());
}
}
静态调度方法
还支持静态调度方法。禁止静态和非静态调度方法的混合。
Create(创建)方法
在Xtend中Create(创建)方法,允许将通常需要两遍的图形变换,一次进行,这意味着您不需要分两个阶段,单独将一个图形翻译到另一个图形:树结构和树结点互连。您基本上只需要使用Create(创建)方法编写整个转换,内置的身份保护,将负责其余内容。
假如你想创建一个以下的人员列表的副本:
Fred Flintstone {
marriedTo Willma Flintstone
friendWith Barny Rubble
}
Willma Flintstone {
marriedTo Fred Flintstone
}
Barny Rubble {
friendWith Fred Flintstone
}
以下功能可以做到这一点:
def List<Person> copyPersons(List<Person> persons) {
persons.map[copy]
}
def copy(Person p) {
val result = new Person()
result.name = p.name
// 以下是错误的,并导致堆栈溢出
result.friendWith = p.friendWith.map[copy]
result.marriedWith = p.marriedWith.map[copy]
}
该代码的问题是,我们不追踪创建的副本的来源。这是模型转换的主要问题。经典的解决方案是在通过两次复制。首先我们创建所有的实例,然后建立链接。虽然它工作,但导致杂乱和非一致代码。Xtend的create(创建)功能,通过引入身份保护,跟踪每个创建的实例的起源,来处理这个问题。因此,create功能需要两个表达式。一个用于实例化实际对象,另一个用于初始化它。
def create result: new Person copy(Person p) {
result.name = p.name
// now it works
result.friendWith = p.friendWith.map[copy]
result.marriedWith = p.marriedWith.map[copy]
}
如果没有指定结果变量的名称,那么它将被认为是隐式接收变量it,它可以在主体内的特征调用中跳过。此外,您可以定义create函数的返回类型:
def Person create new PersonImpl() copy(Person p) {
/* it.*/name = p.name
friendWith = p.friendWith.map[copy]
marriedWith = p.marriedWith.map[copy]
}
怎么工作的
除了关键字create之外,一个指定两个表达式。第一个表达式是创建一个实例工厂,而第二个将进一步初始化它。在调用工厂表达式之前,执行缓存查找,以查找先前以相同参数创建的实例。如果没有此类实例,则会对工厂表达式进行计算,并将结果存储在缓存中,随后,主表达式(也称为初始化表达式)进行了计算。只有缓存中没有先前创建的实例,才会发生这种情况。如果该表达式反过来调用创建函数,并传递相同的参数集,则返回先前实例化和缓存的对象。请注意,该对象可能正在初始化。也就是说,其内部状态可能尚不可用。缓存的生命周期,附加到Xtend声明类的实例。那就是你可以通过Guice来控制缓存的存储时间。
注解
有关类、字段、方法和参数的注解,它们以@字符为前缀,并接受一些键值对,或者注解属性的默认值value。数组注解值也可以处理单个值。值数组封闭在数组字面值中#['first', 'second']。注解的语义与Java语言规范中的定义完全相同。这是一个例子:
@TypeAnnotation("some value")
class MyClass {
@FieldAnnotation(value = @NestedAnnotation(true))
static val CONSTANT = 'a compile-time constant'
@MethodAnnotation(constant = CONSTANT)
def String myMethod(@ParameterAnnotation String param) {
//...
}
}
此外,积极的注解(Active Annotations)允许用户参与将Xtend代码编译为Java源代码。
扩展方法
扩展方法允许向现有类型添加新方法,而无需修改它们。这个特性(功能)实际上是Xtend名字的由来(Extension )。它们是基于一个简单的句法技巧:代替向调用的扩展方法括号内第一个参数传递参数,该方法可以使用第一个参数作为其接收器来调用 ——该方法可以像是参数类型的成员一样被调用。
"hello".toFirstUpper() // calls StringExtensions.toFirstUpper("hello")
扩展语法中的方法调用,通常会导致更可读的代码,因为它们被链接而不是嵌套。扩展的另一个好处是,可以在特定上下文或应用程序层,添加方法。
例如,您可能不想将UI特定的方法和依赖项放入您的域模型类。因此,此功能通常在静态方法中,或实用程序类、服务层中的方法定义。但是如果你像这样调用方法,代码的可读性就会降低,面向对象更少。在Java中,您经常会看到如下代码:
persistenceManager.save(myObject);
不用把你的实体绑定到persistenceManager,扩展方法可以让你这样写:
myObject.save
还有不同的途径,使方法可用作扩展,这在以下部分中描述。
从库扩展
Xtend库从Java SDK现有类型中,提供了非常有用的扩展方法。
"hello".toFirstUpper // calls StringExtensions.toFirstUpper(String)
listOfStrings.map[ toUpperCase ] // calls ListExtensions.<T, R>map(List<T> list,
Function<? super T, ? extends R> mapFunction)
可以通过察看库的JavaDoc,了解以下可用的功能:
注:源代码在:https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtext.xbase.lib/src/org/eclipse/xtext/xbase/lib
· ObjectExtensions
· IterableExtensions
· MapExtensions
· ListExtensions
· CollectionExtensions
· BooleanExtensions
· IntegerExtensions
· FunctionExtensions
局部扩展方法
当前类的所有可见的非静态方法,及其超类型将自动作为扩展使用。例如
class MyClass {
def doSomething(Object obj) {
// do something with obj
}
def extensionCall(Object obj) {
obj.doSomething() // calls this.doSomething(obj)
}
}
要使用局部静态方法,必须像其他静态方法一样通过导入获得。
扩展导入
在Java中,您通常会使用静态方法编写一个辅助类,以便用额外的行为来装饰现有的类。为了整合这样的静态辅助类,Xtend允许,在静态导入把关键字extension放在关键词static后面,从而使全部导入的静态函数作为扩展方法。
导入声明如下:
import static extension java.util.Collections.singletonList
允许我们像这样使用singletonList方法:
new MyClass().singletonList()
// calls Collections.singletonList(new MyClass())
扩展提供者
通过将extension关键字添加到字段、局部变量或参数声明中,其实例方法变为扩展方法。
想象一下,你想要在一个Person类上,有一些层次特定功能。也就是你在一个类似servlet的类,并希望使用一些持久化机制来保持Person。让我们假设Person实现一个通用的接口Entity。你可以有以下接口:
interface EntityPersistence {
public save(Entity e);
public update(Entity e);
public delete(Entity e);
}
如果您已经获得了这种类型一个实例(通过工厂或依赖注入或者其他):
class MyServlet {
extension EntityPersistence ep = Factory.get(EntityPersistence)
...
}
您可以像这样保存、更新和删除任何实体:
val Person person = ...
person.save // calls ep.save(person)
person.name = 'Horst'
person.update // calls ep.update(person)
person.delete // calls ep.delete(person)
extension对值的修饰符,比静态扩展导入的有着显着优点:您的代码不受实际实现的扩展方法的约束。您可以通过提供不同的实例,简单地将提供引用扩展的组件,与外部的另一个实现进行交换。
接口声明
接口声明与Java类似。一个接口可以声明字段,默认为final static,必须具有一个初始值。当然,方法可以被声明,默认为public。接口可以扩展任意数量的其他接口,并可以声明类型参数。这里有一个例子:
interface MyInterface<T> extends OtherInterface {
val CONSTANT = 42
def T doStuff(String ... varArg) throws SomeException
}
自Java语言版本8以来,接口允许包含非抽象实例方法,称为默认方法,以及静态方法。Xtend(从2.8开始)也支持这一点:如果选择Java 8作为目标语言版本,则允许接口使用如下例所示的方式声明方法。
interface MyInterface {
def doStuff() {
'This is an instance method returning a string.'
}
static def doGlobalStuff() {
'This is a static method returning a string.'
}
}
非抽象实例方法的行为与Java默认方法的行为相当。由于接口可以扩展多个其他接口,因此继承了方法的不同实现时可能会发生多重继承冲突:
interface A {
def execute() {
return 1
}
}
interface B {
def execute() {
return 2
}
}
interface C extends A, B {
}
class D implements A, B {
}
由于方法的多重继承,接口C和类Dexecute()方法,都被标记为错误。解决问题有四种方法。
· 将方法重新声明为抽象(非抽象类不允许):
override execute()
override execute() {
return 3
}
· 请参考一个超类型的实现:
override execute() {
A.super.execute()
}
· 首先避免继承方法的多个实现。这是推荐的方法。
注解类型声明
注解类型也可以被声明。由关键字annotation引入,并使用简明的语法声明其值:
annotation MyAnnotation {
String[] value
boolean isTricky = false
int[] lotteryNumbers = #[ 42, 137 ]
}
枚举类型声明
枚举类型如下所示:
enum MyColor {
GREEN,
BLUE,
RED
}
嵌套类型声明
类、枚举、注解和接口声明可以嵌套。就像Java嵌套枚举一样,注解和接口总是静态的。在Xtend中,嵌套类也总是静态的。嵌套类型默认为public,只能嵌套在类、接口或注解声明中。
class MyClass {
static class NestedClass {}
annotation NestedAnnotation {}
enum NestedEnum {}
interface NestedInterface {}
}
interface MyInterface {
static class NestedClass {}
annotation NestedAnnotation {}
enum NestedEnum {}
interface NestedInterface {}
}
annotation MyAnnotation {
static class NestedClass {}
annotation NestedAnnotation {}
enum NestedEnum {}
interface NestedInterface {}
}