是否可以在Java中创建任何编译时功能?

时间:2013-05-28 14:01:15

标签: java gwt

我希望在Java中创建一些将在编译时调用的函数 - 并且完全从运行时(即编译的类文件)中排除。这些函数可以写入变量,这些变量对于软件的不同版本可能具有不同的值。什么是野兽的方式来做到这一点?这些可能是“最终”变量。

顺便说一句,GWT项目和函数应该能够写入客户端和服务器端。

考虑这个简单的例子:

final int x = 10;
final int y = x/100;
final int z = y + x - 500;

这里有三个“最终”变量x,y和z,其中y和z的值依赖于x,这是一个常数!这意味着y和z的值也从未在程序中实际改变。但是因为它们必须被定义为x的函数 - 它们的值将在运行时计算 - 这是在运行时不必要的资源使用。编译时函数会对y和z的值进行硬编码,并稍微加快运行时间!显然,这是一个简单的例子,但我的实际用例有很多复杂耗时的计算要做,这会减慢我的应用程序。在编译时计算这个非常好,并且如果您需要使用不同变量值的应用程序快速需要多个版本,则可以避免因手动对数字进行硬编码而导致的错误。

此外,请考虑以下示例:

final String value = getMyValue("argument");

final String getMyValue(String variable){
    return variable + "Lol!";
}

您是否期望在编译时计算“值”的值?在客户端和服务器端?

2 个答案:

答案 0 :(得分:3)

对于GWT客户端代码,您可以使用deferred binding在编译时生成Java代码。您创建了com.google.gwt.core.ext.Generator的子类。 Google使用延迟绑定进行本地化等。我用它来为GWT实现一个简单的XML映射器,但这是封闭的源代码。

例如,您创建了一个界面com.foo.Constants

public interface Constants {
  int getY();
  int getZ();
}

为常量创建一个Generator:

public class ConstantsGenerator extends Generator {
  private static final String PACKAGE_NAME = "com.foo";
  private static final String SIMPLE_CLASS_NAME = "GeneratedConstants";
  private static final String CLASS_NAME = PACKAGE_NAME + "." + SIMPLE_CLASS_NAME;

  @Override
  public String generate(TreeLogger logger, GeneratorContext context,
    String typeName) throws UnableToCompleteException {
    // Get a SourceWriter to create the class GeneratedConstants.
    // If it returns null, then this class was already created during this compilation
    // step and we only return the name of class.
    final SourceWriter sourceWriter = getSourceWriter(logger, context);
    if(sourceWriter != null) {
      // Otherwise create the new class ...
      generateConstantsClass(sourceWriter, logger);
    }

    return CLASS_NAME;
  }

  private SourceWriter getSourceWriter(TreeLogger logger, GeneratorContext context) {
    final ClassSourceFileComposerFactory composer = 
      new ClassSourceFileComposerFactory(PACKAGE_NAME, SIMPLE_CLASS_NAME);

    // The generated class implements com.foo.Constants
    composer.addImplementedInterface("com.foo.Constants");

    // Add some imports, if needed
    // composer.addImport("java.util.Map");

    final PrintWriter printWriter = context.tryCreate(logger, PACKAGE_NAME, SIMPLE_CLASS_NAME);
    if(printWriter == null)
      return null;
    else {
      final SourceWriter sw = composer.createSourceWriter(context, printWriter);
      return sw;
    }
  }

  private void generateConstantsClass(SourceWriter sourceWriter, TreeLogger logger) {
    final int y = calculateY();  // Do here the heavy calculation for y.
    final int z = calculateZ();  // Do here the heavy calculation for z.

    // Create the source code for the two Methods getY() and getZ().
    sourceWriter.println(String.format("public int getY() { return %d; }", y));
    sourceWriter.println(String.format("public int getZ() { return %d; }", z));

    sourceWriter.commit(logger);
  }
}

然后您必须配置GWT模块XML文件:

<generate-with class="com.foo.ConstantsGenerator">
  <when-type-assignable class="com.foo.Constants" />
</generate-with>

然后在您的GWT客户端代码中,您可以创建生成的类的实例,如下所示:

public Constants getConstants() {
  return (Constants) GWT.create(Constants.class);
}

但这仅适用于客户端代码。只是预先计算一些值可能有点过多的开销。

在构建脚本中包含代码生成步骤会更容易。然后,您可以在服务器和客户端代码中使用生成的类。

但是对于预先计算的值,这可能仍然是过多的开销。如果只使用预先计算的值生成一个简单文件(如属性文件)并在运行时加载它,则可以避免生成代码。

在服务器端,这应该没问题。对于客户端,您可以使用JSP生成GWT主页,并将预先计算的值作为JavaScript dicationary包含在内。在GWT代码中,可以轻松访问这些值。

答案 1 :(得分:2)

您正在寻找编译时元编程。这意味着在编译阶段,您的源或抽象语法树会以某种方式进行预处理或修改。

如果你看一下C ++,就会有模板元编程

至于Java,我并不知道有直接的方法来实现这一点。

所以下一个堂兄会是 Scala :他们称之为,它允许你在编译时修改抽象语法树 。例如,我使用它来完全删除代码,基于布尔条件。

鉴于兼容性,您可以在Scala中编写复杂的计算,并从Java中使用它。