我可以使用Java注解来定义编译时检查吗?

16
例如,我想创建一个注释 @Out 来定位参数。然后,我会以某种方式使用编译器来检查函数返回之前是否设置了参数值。这可行吗?
另外,我考虑了一个 @Immutable 注释,它不允许调用任何未注释为 @Const 的方法或访问任何公共字段(编译时和可能的运行时)。
到目前为止,我有这个:
//I'm assuming Class retention is a subset of Runtime retention
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Out
{
    //no idea what to go in here.
}

这是另一个注释。同样,我没有完整的定义:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Immutable
{

}

我认为我可以开始设计一种使用反射在运行时实现的策略,但我希望指示编译器或预处理器来检查那些内容,这样我的注释就不会有额外开销。

这是其中一件事情,你会想“如果这个能够被做到,它肯定已经存在了,如果它确实存在,那我该去哪里找它。”

编辑:经过进一步思考关于@Const@Immutable,以及记住java通过值传递对象指针后,我扩展了@Const的定义,去掉了@Immutable,并修改了@Out的定义,如下所示:

/**
* When Applied to a method, ensures the method doesn't change in any
* way the state of the object used to invoke it, i.e., all the fields
* of the object must remain the same, and no field may be returned,
* unless the field itself is marked as {@code @Const}. A method 
* annotated with {@code @Const} can only invoke other {@code @Const}
* methods of its class, can only use the class's fields to invoke
* {@code @Const} methods of the fields classes and can only pass fields
* as parameters to methods that annotate that formal parameter as
* {@code @Const}.
*
* When applied to a formal parameter, ensures the method will not
* modify the value referenced by the formal parameter. A formal   
* parameter annotated as {@code @Const} will not be aliased inside the
* body of the method. The method is not allowed to invoke another 
* method and pass the annotated parameter, save if the other method 
* also annotates the formal parameter as {@code @Const}. The method is 
* not allowed to use the parameter to invoke any of its type's methods,
* unless the method being invoked is also annotated as {@code @Const}
* 
* When applied to a field, ensures the field cannot be aliased and that
* no code can alter the state of that field, either from inside the   
* class that owns the field or from outside it. Any constructor in any
* derived class is allowed to set the value of the field and invoke any
* methods using it. As for methods, only those annotated as
* {@code @Const} may be invoked using the field. The field may only be
* passed as a parameter to a method if the method annotates the 
* corresponding formal parameter as {@code @Const}
* 
* When applied to a local variable, ensures neither the block where the
* variable is declared or any nested block will alter the value of that 
* local variable. The local variable may be defined only once, at any
* point where it is in scope. Only methods annotated as
* {@code @Const} may be invoked using this variable, and the variable 
* may only be passed as a parameter to another method if said method
* annotates its corresponding formal parameter as {@code @Const}
*
*/
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD,
ElementType.LOCAL_VARIABLE})
@Inherited
public @interface Const
{

}

现在是@Out

/**
* The formal parameter annotated with {@code @Out} must be undefined in 
* the scope of the caller, and it's the responsibility of the method to
* define it. If allowNull is true, the parameter can be explicitly set
* to null in the body of the method.
*/
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.PARAMETER)
public @interface Out
{
    boolean allowNull() default false;
}

编辑:我正在尝试将其实现为Eclipse插件,但是我完全不知道如何阅读手册。我编写了一个插件,其中包含访问AST和访问方法和字段的基本逻辑。然后我创建了一堆虚拟注释,我的插件应该检测到它们,然后我尝试打印结果,但我甚至不知道该期望什么。我的插件是“增量构建”插件。这是它的代码,如果有人可以看一下并向我解释一些东西,那就太好了。我在这个API中完全迷失了。

https://github.com/Starless2001/Plugin-for-Eclipse


2
说实话,我认为要做到你想要的,你需要一个支持用户定义插件的编译器。但是我不知道有没有这样的编译器存在。从编译器编写者的角度来看,这似乎是一件非常困难的事情。 - ajb
3
您可以定义一个注解处理器,您的注解保留期为 SOURCE。然后,您需要创建一个 Processor,并在 META-INF/services/javax.annotation.processing.Processor 中定义您处理器实现的完全限定名称以将其附加到您的项目中。您还需要启用注解处理。缺点是您必须在错误/警告出现之前保存文件,而不是像内置的编译时检查一样立即出现。 - Dioxin
2
这篇博客文章描述了Project Lombok如何钩入编译过程:http://notatube.blogspot.com/2010/11/project-lombok-trick-explained.html - user41871
查看注释处理器生成编译错误 的示例,了解使用注释处理进行编译时检查的方法。 这是我之前写的另一篇文章,当时我在想为什么警告/错误直到保存文件后才能显示。它包含一个编译时注释处理器的小例子。 - Dioxin
@VinceEmigh 但是我能否使用注解处理器访问整个项目的AST呢?因为它们似乎只作用于一个文件。我不想(也确信我没有能力)自己构建AST。为了使我的想法实现,我必须检查是否使用@Const注释的内容即使从其他文件或依赖项中访问也将保持不可变。 - FinnTheHuman
显示剩余10条评论
1个回答

13

javac编译器支持用户定义的插件,称为注解处理器,可以完成您想要的功能。您可以将注解视为语言扩展。

定义public @interface Immutable { ... }定义语法:您可以在程序中编写的@Immutable注解。注解处理器(编译器插件)定义语义:它强制执行语义规则,并在程序违反规则时发出编译器警告。

一个使编写这种注解处理器变得容易的框架是Checker Framework,其中包含@NonNull@Immutable等注解的定义。下面是两个关于如何使用Checker Framework验证代码的教程:教程1教程2

普通的Java注解处理在每个声明上都会被调用,例如类、字段、方法和方法参数,并且普通的Java不允许注解处理器访问程序的完整AST。您可以将Checker Framework看作是扩展了Java注解处理能力的库。它让您访问每个类的完整AST,并允许您为程序中的每个语句定义规则。因此,当语句在@Immutable对象上调用非@Const方法时,您的注解处理器可以发出警告。

您的注解处理器应该是模块化的,每次处理一个类。注解处理器可以访问当前类的AST,以及它使用的所有类的签名,包括注解。注解处理提供了这些信息(但不是整个项目的AST)。


我会用这种方式来实现。感谢所有的帮助。我不喜欢检查器框架,因为它速度慢且不完整。你需要多个“检查器”才能定义只是不变性。一旦准备好了,我会发布出去,谁知道,也许有人会觉得有用。 - FinnTheHuman
听起来很不错。请分享你的结果。 - mernst

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接