本文解释了如何使用代码反射在 Java 中模拟 C# 语言集成查询 ( LINQ ) 的各个方面。通过使用代码反射,可以
- 在 Java 中模拟 C# 的 LINQ 查询,将 LINQ 查询转换为 SQL 语句,
- 构建符号表示形式的Java代码模型,以模拟LINQ查询的各个方面,包括Lambda表达式和方法调用的转换。
代码反射是 OpenJDK 项目Babylon下正在研究和开发的 Java 平台功能。
Babylon巴比伦计划
Babylon 的主要目标是将 Java 的应用范围扩展到 SQL、可微分编程、机器学习模型和 GPU 等外部编程模型。 Babylon 将通过增强 Java 反射编程(称为代码反射)来实现这一目标。
代码反射由三部分组成:
- 将 Java 程序建模为代码模型,适合访问、分析和转换。
- Java 反射的增强功能,允许在编译时和运行时访问代码模型。
- 用于构建、分析和转换代码模型的 API。
什么是C# LINQ
语言集成查询(LINQ)是基于将查询功能直接集成到 C# 语言中的一组技术的名称。
传统上,针对数据的查询是以简单字符串的形式表达的,没有编译时的类型检查或 IntelliSense 支持。
此外,您还必须为每种类型的数据源学习不同的查询语言:SQL 数据库、XML 文档、各种 Web 服务等等。
有了 LINQ,查询就像类、方法和事件一样,是一种一流的语言构造。
DB db = ...; |
查询语法是便于读写 LINQ 查询的语法糖,但实际上只是方法调用的快捷方式。
DB 类包含模拟 SQL 表的封闭类,如 Customers,其属性 City 和 ContactName 模拟表中的行。
我们可以使用方法语法编写相同的 LINQ 查询。
DB db = ...; |
LINQ 查询是 IQuerable 的一个实例,它有一个 C# 属性 Expression,其值是 LINQ 查询的符号表示。在 C# 中,这种符号表示被称为表达式树。在上面的代码中,查询 custQuery 的表达式树由传递给 Where 和 Select 方法的 lambda 表达式树以及这些方法的调用表达式树组成。
LINQ 查询的表达式属性(表达式树)有一个令人愉悦的特性。也就是说,对于数学爱好者来说,假设 Q 是查询,Q.e 是查询的表达式,E 是评估函数(从表达式到查询),那么 E(Q. e) = Q。这种表达式树旨在进行符号化处理和转换。例如,转换到不同的编程领域,如 SQL,其中查询的表达式树被转换为 SQL 语句。
我们将重点讨论 Java 中的这方面问题,即构建一个查询,其等价表达式属性是一个代码模型,即 Java 代码的符号表示,它是通过使用代码反射功能生成的。
在 Java 中模拟类似 LINQ 的查询表达式
为了便于模拟,我们可以用 Java 来表达相同的查询,如下所示。
// record 建模的表有三列,每一列代表一个组件 |
我们使用客户记录来模拟 SQL 表,其中记录的组件对应于该表中的行。我们可以看到,Java 代码与使用方法语法的 C# LINQ 查询非常相似。
在下面的章节中,我们将解释如何使用代码反射来实现这一点。
在 Babylon 代码库中有一个概念验证实现的测试。该实现远未完成。
让我们将流畅查询扩展为单个语句,以便查看其类型。
Queryable<Customer> allCustomers = qp.query(Customer.class); |
前三个方法调用产生 Queryable 实例,最后一个方法调用产生 QueryResult 实例。每个实例都有一个查询的符号表示,即代码模型。我们稍后会看到。
- 首先,我们创建一个新查询,它返回一个 Queryable<Customer> 的实例。
- 在此基础上,我们通过调用 where 来 "过滤 "位于伦敦市的客户,并接受一个 lambda 表达式,如果客户的城市组件等于字符串 "London",则返回 true。
- 然后,我们通过调用 select 将客户 "映射 "到他们的联系人姓名,接受一个 lambda 表达式,返回客户的 contactName 组件。
- 最后,我们调用元素生成查询结果,并告知我们如何使用查询结果,在本例中,查询结果是客户联系人姓名流。
where 和 select 方法的特征如下。
default Queryable<T> where(QuotablePredicate<T> f) { /* ... */ } |
QuotablePredicate 和 QuotableFunction 是由 Predicate 和 Function 以及代码反射接口 java.lang.reflect.code.Quotable 扩展而来的函数式接口。
下面是 Quotable 的声明。
@FunctionalInterface |
当 lambda 表达式针对一个可引用的功能接口时,源代码编译器将为该 lambda 表达式建立代码模型,并在运行时通过可引用实例进行访问。与我们获取可序列化的 lambda 表达式(使用 Serializable)的方式类似,我们也可以获取其代码模型可以被获取的 quotable lambda 表达式。