我是C++ 中静态元编程的粉丝。我知道Java现在有泛型。这是否意味着静态元编程(即编译时程序执行)在 Java 中是可能的?如果是这样,任何人都可以推荐任何可以了解更多信息的好资源吗?
11 回答
不,这是不可能的。泛型不如模板强大。例如,模板参数可以是用户定义的类型、原始类型或值;但是通用模板参数只能是Object
或其子类型。
编辑:这是一个旧答案;自 2011 年以来,我们有了 Java 7,它具有可用于此类诡计的注释。
看看Clojure。它是一个带有宏(元编程)的 LISP,在 JVM 上运行,并且与 Java 具有很强的互操作性。
简短的回答
这个问题已经有将近 10 年的历史了,但我仍然缺少一个答案。这是:是的,但不是因为泛型和注意与 C++ 完全相同。
从 Java 6 开始,我们有了可插入的注解处理 api。静态元编程是(正如您在问题中已经说过的)
编译时程序执行
如果您了解元编程,那么您也知道这不是真的,但为了简单起见,我们将使用它。如果您想了解有关元编程的更多信息,请查看此处。
可插入注解处理 api 由编译器在读取 .java 文件之后但在编译器将字节码写入 .class 文件之前调用。(我有一个来源,但我再也找不到了。也许有人可以在这里帮助我?)。
它允许您在编译时使用纯 java 代码执行逻辑。然而,你正在编码的世界是完全不同的。不是特别糟糕或什么,只是不同。您正在分析的类尚不存在,您正在处理这些类的元数据。但是编译器是在JVM中运行的,这意味着你也可以正常创建类和程序。但此外,您可以分析泛型,因为我们的注解处理器是在类型擦除之前调用的。
关于 java 中静态元编程的主要要点是,您提供元数据(以注释的形式),处理器将能够找到所有带注释的类来处理它们。(更简单的)示例可以在Baeldung上找到,其中形成了一个简单的示例。在我看来,这是一个很好的入门资源。如果您了解这一点,请尝试自己google。那里有很多好的资源,这里要列出很多。还可以查看使用注释处理器的Google AutoService来消除您创建和维护服务文件的麻烦。如果您想创建类,我建议您查看JavaPoet。
遗憾的是,这个 API 不允许我们操作源代码。但如果你真的想,你应该看看Project Lombok。他们这样做,但不支持。
为什么这很重要(对你们中感兴趣的人进一步阅读)
TL;DR:这让我很困惑,为什么我们不像动态那样使用静态元编程,因为它有很多优点。
大多数开发人员看到“动态和静态”并立即得出动态更好的结论。没有错,静态对开发人员有很多负面含义。但在这种情况下(特别是对于 java),情况恰恰相反。
动态元编程需要反射,这有一些 主要 缺点。他们有很多。简而言之:性能、安全性和设计。
静态元编程(即注释处理)允许我们与编译器相交,编译器已经完成了我们尝试通过反射完成的大部分事情。我们还可以在这个过程中创建类,这些类再次传递给注释处理器。然后,您可以(例如)生成类,这些类通常必须使用反射来完成。此外,我们可以实现“快速失败”系统,因为我们可以将错误、警告等通知编译器。
尽可能总结和比较:让我们想象一下春天。Spring 尝试在运行时查找所有 Component 注释类(我们可以通过在编译时使用服务文件来简化),然后生成某些代理类(我们已经可以在编译时完成)并解析 bean 依赖项(再次,我们已经可以在编译时完成)。Jake Whartons 谈到了 Dagger2,他在其中解释了为什么他们转向静态元编程。我仍然不明白为什么像 Spring 这样的大玩家不使用它。
This post is to short to fully explain those differences and why static would be more powerful. If you want, i am currently working on a presentation for this. If you are interested and speak German (sorry about that), you can have a look at my website. There you find a presentation, which tries to explain the differences in 45 minutes. Only the slides though.
“静态元编程”到底是什么意思?是的,C++ 模板元编程在 Java 中是不可能的,但它提供了其他方法,比 C++ 中的方法强大得多:
- 反射
- 面向方面的编程(@AspectJ)
- 字节码操作(Javassist、ObjectWeb ASM、Java 代理)
- 代码生成(注释处理工具,模板引擎,如 Velocity)
- 抽象语法树操作(流行 IDE 提供的 API)
- 即使在运行时也可以运行 Java 编译器并使用已编译的代码
没有最好的方法:每种方法都有其优点和缺点。由于 JVM 的灵活性,Java 中的所有这些方法都可以在编译时和运行时使用。
不,更重要的是,泛型类型会被编译器擦除到其上限,因此您无法在运行时创建泛型类型 T 的新实例。
在 Java 中进行元编程的最佳方法是绕过类型擦除并Class<T>
提交类型 T 的对象。不过,这只是一个 hack。
如果您需要 Java 的强大编译时逻辑,一种方法是使用某种代码生成。因为,正如其他海报所指出的,Java 语言不提供任何适合执行编译时逻辑的功能,这可能是您的最佳选择(如果您确实需要编译时逻辑)。一旦你用尽了其他可能性,并且确定要进行代码生成,你可能会对我的开源项目 Rjava 感兴趣,可在以下网址获得:
http://www.github.com/blak3mill3r
它是一个用 Ruby 编写的 Java 代码生成库,我编写它是为了自动为 Ruby on Rails 应用程序生成 Google Web Toolkit 接口。事实证明,它非常方便。
作为警告,调试 Rjava 代码可能非常困难,Rjava 不会做太多检查,它只是假设您知道自己在做什么。无论如何,这几乎就是静态元编程的状态。我会说它比使用 C++ TMP 完成的任何重要操作都容易得多,并且可以将它用于相同类型的事情。
无论如何,如果您正在考虑编写一个输出 Java 源代码的程序,请立即停止并查看 Rjava。它可能还不能满足您的需求,但它已获得 MIT 许可,因此请随时对其进行改进、油炸或卖给您的祖母。我很高兴有其他具有通用编程经验的开发人员对设计发表评论。
Lombok提供了一种弱形式的编译时元编程。但是,他们使用的技术是完全通用的。
有关相关讨论,请参阅编译时的 Java 代码转换
Manifold 项目为 Java提供类型安全的静态元编程。当 Javac 解析类型名称时,您可以使用它在编译时动态构建类型。JSON Schema 演示根据动态、编译时类型投影(无代码生成构建步骤)演示了静态元编程。
You can use a metaprogramming library for Java such as Spoon: https://github.com/INRIA/spoon/
不,Java 中的泛型纯粹是一种避免对象转换的方法。