如果你今年访问过 JavaOne,你可能已经看过我关于“如何从你的数据库生成定制的 Java 8 代码”的演讲。在那次演讲中,我展示了如何使用 Speedment 开源工具包 生成各种使用数据库作为域模型的 Java 代码。不过,我们没有时间深入研究的一件事是,Speedment 不仅使代码生成更容易,而且它本身也由生成的代码组成。在本文中,我将向您展示我们如何设置 Speedment 来生成许多类的专用版本,以减少系统性能关键部分的内存占用。
您可能知道,Java 有许多内置值类型。这些是字节、短整数、整数、长整数、浮点数、双精度数、布尔值和字符。原始值类型与普通对象的区别主要在于它们可以直接在内存栈上分配,减轻了垃圾收集器的负担。不从 Object 继承的一个问题是它们不能放入集合或作为参数传递给采用对象参数而不被包装的方法。因此,典型的包装类是“Integer”、“Double”、“Boolean”等。
包装值类型并不总是坏事。 JIT(即时)编译器非常擅长优化包装器类型,如果它们可以安全地替换为原始值,但这并不总是可能的。如果这发生在代码的性能关键部分(如内部循环),这会严重影响整个应用程序的性能。
这就是我们在开发 Speedment 时遇到的情况。我们有特殊的谓词和函数,其中包含有关其用途的元数据。需要在内部循环中非常快速地分析元数据,但由于大部分元数据都包装在泛型类型中以便可以动态实例化,因此我们的速度变慢了。
这个问题的一个常见解决方案是创建一些包含包装器类型的类的“特化”。这些特化与原始类相同,只是它们使用一种原始值类型而不是通用(仅对象)类型。专门化的一个很好的例子是 Java 8 中存在的各种 Stream 接口。除了“Stream”之外,我们还有“IntStream”、“DoubleStream”和“LongStream”。这些特化对于它们的特定值类型更有效,因为它们不必依赖对象中的包装类型。
专业化课程的问题在于它们向系统添加了大量样板。假设需要优化的部分由 20 个组件组成。如果你想支持 Java 拥有的所有 8 个原始变体,你突然拥有 160 个组件。需要维护的代码很多。更好的解决方案是生成所有额外的类。
高级语言中最常见的代码生成形式是基于模板的。这意味着您编写一个模板文件,然后根据您生成的内容进行关键字替换来修改文本。 Maven Archetypes 或 Thymeleaf 就是很好的例子。一个好的模板引擎将支持更高级的语法,如重复部分、表达条件等。如果你想使用模板引擎生成专业化类,你可以将所有出现的“int”、“Integer”、“IntStream”替换为特定的关键字,如“${primitive}”、“${wrapper}”、“${stream}”,然后指定要与每个新值类型关联的单词字典。
基于模板的代码生成的优点是易于设置和维护。我想大多数读过这篇文章的程序员可能会很容易地弄清楚如何编写模板引擎。缺点是模板难以重用。假设您有一个专门的基本模板,但您希望浮动类型也有一个附加方法。您可以使用条件语句解决此问题,但如果您希望该额外方法也存在于其他地方,则需要复制代码。经常需要复制的典型代码示例是 hashCode() 方法或 toString()。这是基于模型的代码生成更强大的地方。
在基于模型的代码生成中,您在要生成的代码上构建一个抽象语法树,然后使用合适的渲染器渲染该树。语法树可以根据使用它的上下文发生变化,例如通过添加或删除方法来实现某个接口。这样做的主要优点是灵活性更高。您可以动态获取现有模型并操纵要包含的方法和字段。缺点是基于模型的代码生成通常需要更长的时间来设置。
在 Speedment,我们开发了一个名为 CodeGen 的代码生成器,它使用基于模型的方法自动为所有原始值类型生成字段专业化。每个构建总共生成大约 300 个类。
Speedment CodeGen 使用围绕面向对象设计的基本概念构建的抽象语法树。您拥有用于构建域模型的类、接口、字段、方法、构造函数等。在方法级别以下,您仍然需要编写模板代码。要定义一个新的主类,您可以这样写:
import com.speedment.common.codegen.model.Class; // Not java.lang.Class ... Class createMainClass() { return Class.of("Main") .public_().final_() .set(Javadoc.of("The main entry point of the application") .add(AUTHOR.setValue("Emil Forslund")) .add(SINCE.setValue("1.0.0")) ) .add(Method.of("main", void.class) .public_().static_() .add(Field.of("args", String[].class)) .add( "if (args.length == 0) " + block( "System.out.println(\"Hello, World!\");" ) + " else " + block( "System.out.format(\"Hi, %s!%n\", args[0]);" ) ) ); }
这将生成以下代码:
/** * The main entry point of the application. * * @author Emil Forslund * @since 1.0.0 */ public final class Main { public static void main(String[] args) { if (args.length == 0) { System.out.println("Hello, World!"); } else { System.out.format("Hi, %s!%n", args[0]); } } }
不必立即生成整个模型。例如,如果我们想要自动生成一个 toString() 方法,我们可以将其定义为一个单独的方法。
public void generateToString(File file) { file.add(Import.of(StringBuilder.class)); file.getClasses().stream() .filter(HasFields.class::isInstance) .filter(HasMethods.class::isInstance) .map(c -> (HasFields & HasMethods) c) .forEach(clazz -> clazz.add(Method.of("toString", void.class) .add(OVERRIDE) .public_() .add("return new StringBuilder()") .add(clazz.getFields().stream() .map(f -> ".append(\"" + f.getName() + "\")") .map(Formatting::indent) .toArray(String[]::new) ) .add(indent(".toString();")) ) ); }
在这里,您可以看到如何使用 Trait Pattern 从逻辑中抽象出底层实现。该代码将适用于 Enum 和 Class,因为它们都实现了“HasFields”和“HasMethods”特征。
在本文中,我解释了什么是专业化类,以及为什么有时需要它们来提高应用程序关键部分的性能。我还向您展示了 Speedment 如何使用基于模型的代码生成来自动生成专业化类。如果您有兴趣自己使用这些工具生成代码,请继续查看 GitHub 上最新版本的生成器!
标签2: Java教程地址:https://www.cundage.com/article/jcg-auto-generate-optimized-java-class-specializations.html