如果您今年访问过JavaOne,您可能已经参加了我的演讲“如何从数据库生成定制的Java 8代码”。 在那次演讲中,我展示了如何使用Speedment Open Source工具包使用数据库作为域模型来生成各种Java代码。 我们没有时间要考虑的一件事是,Speedment不仅使代码生成变得更容易,它本身也由生成的代码组成。 在本文中,我将向您展示我们已经设置了Speedment来生成许多类的专用版本,以减少系统中性能关键部分的内存占用。
背景
您可能知道,Java有许多内置值类型。 这些是字节,短裤,整数,长号,浮点数,双打,布尔值和字符。 基本值类型与普通对象的不同之处主要在于,它们可以直接在内存堆栈上分配,从而减轻了垃圾回收器的负担。 不从Object继承的一个问题是它们不能被放入集合中或作为参数传递给不带包装而接受对象参数的方法。 因此,典型的包装器类为“整数”,“双精度”,“布尔”等。
包装值类型并不总是一件坏事。 如果可以安全地用原始值替换J包装(Just-In-Time),则该编译器非常擅长优化包装类型,但这并不总是可行的。 如果这种情况发生在代码的性能关键部分(如内部循环)中,则会严重影响整个应用程序的性能。
这就是我们在Speedment上发生的事情。 我们有特殊的谓词和函数,其中包含有关其目的的元数据。 需要在一个内部循环内非常快速地分析该元数据,但是由于大多数元数据都包装在通用类型中,以便可以动态实例化这些事实,我们感到很慢。
解决此问题的常见方法是为包含包装器类型的类创建多个“专业化”类。 除了使用原始值类型之一而不是通用(仅对象)类型之外,这些专业化与原始类相同。 专门化的一个很好的例子是Java 8中存在的各种Stream接口。除了“ Stream”之外,我们还具有“ IntStream”,“ DoubleStream”和“ LongStream”。 这些专业化对于其特定的值类型更为有效,因为它们不必依赖于对象中的包装类型。
专业化类的问题在于它们为系统增加了很多样板。 假设需要优化的零件包含20个组件。 如果要支持Java拥有的所有8种原始变体,您突然会拥有160个组件。 那要维护很多代码。 更好的解决方案是生成所有额外的类。
基于模板的代码生成
高级语言中最常见的代码生成形式是基于模板的。 这意味着您编写一个模板文件,然后根据生成的内容进行关键字替换来修改文本。 Maven原型或Thymeleaf是很好的例子。 好的模板引擎将支持更高级的语法,例如重复节,表达条件等。如果要使用模板引擎生成专业化类,则可以将所有出现的“ int”,“ Integer”,“ IntStream”替换为特定的像“ $ {primitive}”,“ $ {wrapper}”,“ $ {stream}”之类的关键字,然后指定单词字典以与每种新值类型相关联。
基于模板的代码生成的优点是易于设置和维护。 我认为大多数阅读本文的程序员都可能会想出如何轻松编写模板引擎的方法。 缺点是模板难以重用。 假设您有一个专门的基本模板,但是您希望浮动类型也有其他方法。 您可以使用条件语句解决此问题,但是如果您希望该其他方法也可以在其他地方存在,则需要复制代码。 通常需要重复的典型代码示例是hashCode()-methods或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();"))
)
);
}
在这里,您可以看到特质模式如何用于从逻辑中抽象出底层实现。 该代码将对Enum和Class均适用,因为两者都实现了特征“ HasFields”和“ HasMethods”。
摘要
在本文中,我解释了什么是专业化类,以及为什么有时必须使用它们来提高应用程序关键部分的性能。 我还向您展示了Speedment如何使用基于模型的代码生成来自动生成专业化类。 如果您有兴趣自己使用这些工具生成代码,请继续并在GitHub上查看生成器的最新版本 !
翻译自: https://www.javacodegeeks.com/2016/11/auto-generate-optimized-java-class-specializations.html
所有评论(0)