自动生成优化的 Java 类专业化

位置:首页>文章>详情   分类: Java教程 > 编程技术   阅读(236)   2023-10-21 16:34:57

如果你今年访问过 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 ArchetypesThymeleaf 就是很好的例子。一个好的模板引擎将支持更高级的语法,如重复部分、表达条件等。如果你想使用模板引擎生成专业化类,你可以将所有出现的“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

相关阅读

Java HashSet 教程展示了如何使用 Java HashSet 集合。 Java哈希集 HashSet 是一个不包含重复元素的集合。此类为基本操作(添加、删除、包含和大小)提供恒定时间性...
SpringApplicationBuilder 教程展示了如何使用 SpringApplicationBuilder 创建一个简单的 Spring Boot 应用程序。 春天 是用于创建企业应...
通道是继 buffers 之后 java.nio 的第二个主要新增内容,我们在之前的教程中已经详细了解了这一点。通道提供与 I/O 服务的直接连接。 通道是一种在字节缓冲区和通道另一端的实体(通...
课程大纲 Elasticsearch 是一个基于 Lucene 的搜索引擎。它提供了一个分布式的、支持多租户的全文搜索引擎,带有 HTTP Web 界面和无模式的 JSON 文档。 Elasti...
解析器是强大的工具,使用 ANTLR 可以编写可用于多种不同语言的各种解析器。 在这个完整的教程中,我们将: 解释基础:什么是解析器,它可以用来做什么 查看如何设置 ANTLR 以便在 Java...
Java 是用于开发各种桌面应用程序、Web 应用程序和移动应用程序的最流行的编程语言之一。以下文章将帮助您快速熟悉 Java 语言,并迈向 API 和云开发等更复杂的概念。 1. Java语言...
Java中的继承是指子类继承或获取父类的所有非私有属性和行为的能力。继承是面向对象编程的四大支柱之一,用于提高层次结构中类之间的代码可重用性。 在本教程中,我们将了解 Java 支持的继承类型,...
Java Message Service 是一种支持正式通信的 API,称为 网络上计算机之间的消息传递。 JMS 为支持 Java 程序的标准消息协议和消息服务提供了一个通用接口。 JMS 提...
Java 项目中的一项常见任务是将日期格式化或解析为字符串,反之亦然。解析日期意味着你有一个代表日期的字符串,例如“2017-08-3”,你想把它转换成一个代表 Java 中日期的对象,例如Ja...
之前,我介绍了spring 3 + hibernate 集成 示例和struts 2 hello world 示例。在本教程中,我将讨论在将 spring 框架与 struts 与 hibern...