第一章 入门
第一节 简介
Xtend是一种静态类型的编程语言,可以转化为可理解的Java源代码。其语法和语义构成基于Java编程语言,但在许多方面得到改进:
u 扩展方法 - 增强封闭类型新功能
u Lambda表达式 - 简洁的匿名函数常量语法
u ActiveAnnotations (积极的注解)- 注解处理的增强因子
u 运算符重载 - 使您的库更具表现力
u 强大的开关表达式 - 隐式的类型转换
u 多重调度 - 又名:多态方法调用
u 模板表达式 - 具有智能空白处理
u 没有语句 - 一切都是表达
u 属性 - 简化了getter和setter的访问
u 类型推断 - 可以少写类型签名
u 完全支持Java泛型 - 包括所有一致性和转换规则
u 翻译为Java而不是字节码 - 了解发生了什么,并可将代码用于Android或GWT等平台
与其他JVM语言不同,Xtend 与Java 具有零互操作性问题:您编写的所有内容都与预期的Java代码完全一致。与此同时,Xtend更加简洁、可读和表现力。Xtend的小型库只是一个薄层,提供有用的实用工具和在顶层提供Java开发工具包(JDK)的扩展。
当然,也可以通过Java来完全透明地调用Xtend方法。此外,Xtend还提供现代风格的、基于Eclipse的IDE,并与Eclipse Java开发工具(JDT)紧密集成,包括调用层次结构、重命名重构、调试等功能。
第二节 Hello,World
就像你看到看到其他任何语言一样,第一件事是Hello World的例子。在Xtend中,它代码为:
class HelloWorld {
def static void main(String[] args) {
println("Hello World")
}
}
Xtend看起来和Java非常相似。乍一看,主要区别似乎是def关键字来声明一个方法。同样在Java中,必须将类和main方法定义为应用程序的入口点。诚然,Hello World程序没有体现出Xtend的优势。可当你让程序真正的做一些事情时,真正的表现力将会释放出来,这将会在下一秒学到。
Xtend类驻留在普通的Eclipse Java项目中。一旦安装了SDK,Eclipse将自动将所有类转换为Java源代码。默认情况下,您将在源代码文件夹xtend-gen中找到它。hello world示例被转换为以下Java代码:
// Generated Java Source Code
import org.eclipse.xtext.xbase.lib.InputOutput;
public class HelloWorld {
public static void main(final String[] args) {
InputOutput.<String>println("Hello World");
}
}
生成的Java代码中唯一令人惊讶的可能是引用库的InputOutput类。它是运行时库(runtime library )的一部分,一个很好的工具,可以非常方便地在表达式中使用。
您可以将Xtend类放入任何Eclipse或Maven的Java项目源文件夹中。如果项目尚未正确配置,Eclipse会告诉你丢失的库。xtend.lib必须在类的路径上。IDE将提供一个快速修复来添加它。
下一件事是,在工作区(workSpace)中,实现一个示例项目。在在Eclipse导航(Navigator)视图任意位置单击鼠标右键,并选择New(新建)→Example(示例)...。
在出现的对话框中,您将找到Xtend的两个示例:
l Xtend介绍示例(Xtend Introductory Examples),包含几个示例代码片段,说明Xtend的某些方面优势。例如,它显示了如何允许编写如下代码构建API:
assertEquals(42.km/h, (40_000.m + 2.km) / 60.min)
l Xtend的Euler解决方案(Xtend Solutions For Euler),您可以在Project Euler中找到一些问题的解决方案。这些例子利用了Xtend的整体表现力。例如Euler问题1可以用这个表达式来解决:
(1..999).filter[ i | i % 3 == 0 || i % 5 == 0 ].reduce[ i1, i2 | i1 + i2 ]
第三节 电影示例
电影示例包含在示例项目Xtend Introductory Examples(src / examples6 / Movies.xtend)中,是读取关于电影的数据文件,并对其进行一些分析。
数据
电影数据库是纯文本文件(data.csv),具体描述电影的数据集。以下是一个示例数据集:
Naked Lunch 1991 6.9 16578 Biography Comedy Drama Fantasy
值由两个空格分隔。列是:
1.标题(title)
2.年份(year)
3.评级(rating)
4.票数(numberOfVotes)
让我们定义一个表示数据集的数据类Movie:
@Data class Movie {
String title
int year
double rating
long numberOfVotes
Set<String> categories
}
电影类是POJO,数据集中的每列都有强类型字段。@Data注解将会把类变成一个不变值类,那么它会得到:
l 每个字段的getter方法,
l 一个hashCode()/ equals()实现,
l Object.toString()实现,
l 构造函数按所有字段的声明顺序接受值。
解析数据
现在让我们在同一个文件中添加另一个类,并一个名为movies的字段,初始化为列表。我们解析文本文件并将数据记录转换为Movie类:
import java.io.FileReader
import java.util.Set
import static extension com.google.common.io.CharStreams.*
class Movies {
val movies = new FileReader('data.csv').readLines.map [ line |
val segments = line.split(' ').iterator
return new Movie(
segments.next,
Integer.parseInt(segments.next),
Double.parseDouble(segments.next),
Long.parseLong(segments.next),
segments.toSet
)
]
}
字段的类型可以从右侧的表达式来推断。这被称为局部类型推断,在Xtend中随处可见。我们希望该字段是final,所以我们使用关键字val,将其声明为一个值变量(只读变量)。
右侧的初始化,首先创建一个新的FileReader。然后在该实例中调用方法readLines()。但是如果你看看FileReader,你不会找到这个方法。事实上,readLines()是来自Google Guava的CharStreams的静态方法,它被导入作为扩展( extension)。扩展允许我们使用这种可读的语法。
import static extension com.google.common.io.CharStreams.*
CharStreams.readLines(Reader)使用另一个扩展方法map返回List<String>类型,其在运行时库中定义的(ListExtensions.map(...)),并自动导入,因此可用于所有列表。该map扩展需要一个函数作为参数。它基本上为列表中的每个值调用该函数,并返回另一个包含函数调用结果的列表。实际上,这个映射是懒惰地执行的,如果你不访问列表的结果值,那么映射函数就永远不会被执行。
而函数对象是使用lambda表达式(方括号中的代码)创建的。在lambda中,我们处理文本文件中的一行,并通过使用两个空格字符作为分隔符作为分割符,将其转换为movie。对于拆分操作的结果,调用iterator()方法。您可能知道String.split(String)返回一个字符串数组(String[]),当我们调用Iterable.iterator()时,Xtend会自动转换为列表。
val segments = line.split(' ').iterator
现在我们使用迭代器,为每个产生的String 创建一个Movie实例。数据类型转换,通过从包装器类型调用静态方法来完成(例如String转为int)。Iterable的其余部分,变成一组类别(categories)。因此,迭代器调用扩展方法IteratorExtensions.toSet(Iterator<T>),以完成其余值的转换。
return new Movie (
segments.next,
Integer.parseInt(segments.next),
Double.parseDouble(segments.next),
Long.parseLong(segments.next),
segments.toSet
)
响应查询
现在我们已经将文本文件解析成了List<Movie>,我们已经准备好对它执行一些查询了。我们使用JUnit来使单个查询可执行并确认其结果。
查询1:动作片数量是多少?
@Test def numberOfActionMovies() {
assertEquals(828,
movies.filter[ categories.contains('Action') ].size)
}
首先,对movies过滤(filter)。lambda表达式检查当前影片的类别是否包含条目'Action'。请注意,不像我们用来将文件中的行转换成movies的lambda,这次我们没有声明一个参数名。所以上面表达式,我们可以这样写:
movies.filter[ movie | movie.categories.contains('Action') ].size
但是由于我们省略了参数名称和垂直条|,所以变量被自动命名it。it是一个隐式变量。它的用法类似于隐式变量this。所以也可以写成这样:
movies.filter[ it.categories.contains('Action') ].size
或甚至更紧凑地写成前面那样:
movies.filter[ categories.contains('Action') ].size
最后我们size也会调用生成的iterable,这也是一个扩展方法。它在实用程序类(utility class )IterableExtensions中定义。
查询2:80年代最好的电影是哪一年上演的?
@Test def void yearOfBestMovieFrom80s() {
assertEquals(1989,
movies.filter[ (1980..1989).contains(year) ].sortBy[ rating ].last.year)
}
在这里,我们过滤1980年至1989年(80年代)范围内的所有电影。操作符“..”又是一个在IntegerExtensions定义的扩展,并返回IntegerRange的实例。操作符重载在第二章中说明。
由此产生的迭代排序,则IterableExtensions.sortBy按movies的rating进行排序。由于它按升序排列,我们从列表中取出最后一个电影并返回其year。
我们也可以按降序排序,还可以列出列表的头:
movies.filter[ (1980..1989).contains(year) ].sortBy[ -rating ].head.year
另一个可能的解决方案是颠倒排序列表的顺序:
movies.filter[ (1980..1989).contains(year) ].sortBy[ rating ].reverseView.head.year
请注意,首先排序,然后取最后或第一个,代价稍大一点。我们可以使用方法reduce更有效地找到最好的电影。你可以自己尝试一下。
前面的例子调用movie.year以及movie.categories,实际上是访问相应的getter方法。
查询3:前两部电影的得票总数是多少?
@Test def void sumOfVotesOfTop2() {
val long sum = movies.sortBy[ -rating ].take(2).map[ numberOfVotes ].reduce[ a, b | a + b ]
assertEquals(47_229L, sum)
}
首先,电影按评级排序,然后我们取最好的两个。接下来,使用map函数将电影列表的numberOfVotes形成新的列表。现在我们有List<Long>,可以通过添加值将其缩小到一个Long。
你也可以用reduce代替map和reduce。你知道怎么做吗?