1. Overview
1.概述
These days, it’s hard to imagine Java without annotations, a powerful tool in the Java language.
这些天来,很难想象没有注解的Java,这是Java语言中的一个强大工具。
Java provides a set of built-in annotations. Additionally, there are plenty of annotations from different libraries. We can even define and process our own annotations. We can tune these annotations with attribute values, however, these attribute values have limitations. Particularly, an annotation attribute value must be a constant expression.
Java提供了一组内置注解。此外,还有大量来自不同库的注解。我们甚至可以定义和处理我们自己的注解。我们可以用属性值来调整这些注解,然而,这些属性值有局限性。特别是,一个注释的属性值必须是一个常量表达式。
In this tutorial, we’re going to learn some reasons for that limitation and look under the hood of the JVM to explain it better. We’ll also take a look at some examples of problems and solutions involving annotation attribute values.
在本教程中,我们将了解这种限制的一些原因,并在JVM的引擎盖下看一看,以更好地解释它。我们还将看一看一些涉及注解属性值的问题和解决方案的例子。
2. Java Annotation Attributes Under the Hood
2.罩子下的Java注释属性
Let’s consider how Java class files store annotation attributes. Java has a special structure for it called element_value. This structure stores a particular annotation attribute.
让我们考虑一下Java类文件如何存储注解属性。Java有一个特殊的结构,叫做element_value。这个结构存储一个特定的注释属性。
The structure element_value can store values of four different types:
结构element_value可以存储四个不同类型的值。
- a constant from the pool of constants
- a class literal
- a nested annotation
- an array of values
So, a constant from an annotation attribute is a compile-time constant. Otherwise, the compiler wouldn’t know what value it should put into the constant pool and use as an annotation attribute.
因此,来自注释属性的常量是一个编译时常量。否则,编译器就不知道应该把什么值放到常量池中并作为注释属性使用。
The Java specification defines operations producing constant expressions. If we apply these operations to compile-time constants, we’ll get compile-time constants.
Java规范定义了产生常量表达式的操作。如果我们将这些操作应用于编译时常量,我们会得到编译时常量。
Let’s assume we have an annotation @Marker that has an attribute value:
让我们假设我们有一个注释@Marker,它有一个属性value。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Marker {
String value();
}
For example, this code compiles without errors:
例如,这段代码的编译没有错误。
@Marker(Example.ATTRIBUTE_FOO + Example.ATTRIBUTE_BAR)
public class Example {
static final String ATTRIBUTE_FOO = "foo";
static final String ATTRIBUTE_BAR = "bar";
// ...
}
Here, we define an annotation attribute as a concatenation of two strings. A concatenation operator produces a constant expression.
在这里,我们把一个注释属性定义为两个字符串的连接。连接运算符产生一个常数表达式。
3. Using Static Initializer
3.使用静态初始化器
Let’s consider a constant initialized in a static block:
让我们考虑在static块中初始化的常量。
@Marker(Example.ATTRIBUTE_FOO)
public class Example {
static final String[] ATTRIBUTES = {"foo", "Bar"};
static final String ATTRIBUTE_FOO;
static {
ATTRIBUTE_FOO = ATTRIBUTES[0];
}
// ...
}
It initializes the field in the static block and tries to use that field as an annotation attribute. This approach leads to a compilation error.
它在static块中初始化了该字段,并试图将该字段用作注释属性。这种方法导致了编译错误。。
First, the variable ATTRIBUTE_FOO has static and final modifiers, but the compiler can’t compute that field. The application computes it at runtime.
首先,变量ATTRIBUTE_FOO有static和final修饰符,但编译器不能计算这个字段。应用程序在运行时计算它。
Second, annotation attributes must have an exact value before the JVM loads the class. However, when the static initializer runs, the class is already loaded. So, this limitation makes sense.
其次,注解属性必须在JVM加载类之前有一个确切的值。然而,当static初始化器运行时,该类已经被加载。所以,这种限制是有道理的。
The same error shows up when in the field initialization. This code is incorrect for the same reason:
在字段初始化中,也出现了同样的错误。由于同样的原因,这段代码是不正确的。
@Marker(Example.ATTRIBUTE_FOO)
public class Example {
static final String[] ATTRIBUTES = {"foo", "Bar"};
static final String ATTRIBUTE_FOO = ATTRIBUTES[0];
// ...
}
How does the JVM initialize ATTRIBUTE_FOO? Array access operator ATTRIBUTES[0] runs in a class initializer. So, ATTRIBUTE_FOO is a runtime constant. It’s not defined at compile-time.
JVM是如何初始化ATTRIBUTE_FOO的?阵列访问操作符ATTRIBUTES[0]在类初始化器中运行。所以,ATTRIBUTE_FOO是一个运行时常量。它不是在编译时定义的。
4. Array Constant as an Annotation Attribute
4.作为注释属性的数组常数
Let’s consider an array annotation attribute:
让我们考虑一个数组注释属性。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Marker {
String[] value();
}
This code will not compile:
这段代码将不会被编译。
@Marker(value = Example.ATTRIBUTES)
public class Example {
static final String[] ATTRIBUTES = {"foo", "bar"};
// ...
}
First, although the final modifier protects the reference from being changed, we can still modify array elements.
首先,尽管final修饰符可以保护引用不被改变,我们仍然可以修改数组元素。
Second, array literals can’t be runtime constants. The JVM sets each element up in the static initializer — a limitation we described earlier.
第二,数组字头不能成为运行时常量。JVM会在静态初始化器中设置每个元素–这是我们之前描述的一个限制。
Finally, a class file stores values of each element of that array. So, the compiler calculates each element of the attribute array, and it happens at compile-time.
最后,一个类文件存储了该数组中每个元素的值。所以,编译器计算属性数组的每个元素,而且是在编译时发生的。
Thus, we can only specify an array attribute each time:
因此,我们每次只能指定一个数组属性。
@Marker(value = {"foo", "bar"})
public class Example {
// ...
}
We can still use a constant as a primitive element of an array attribute.
我们仍然可以使用一个常量作为数组属性的原始元素。
5. Annotations in a Marker Interface: Why Doesn’t It Work?
5.标识器界面中的注释 为什么它不起作用?
So, if an annotation attribute is an array, we have to repeat it each time. But we would like to avoid this copy-paste. Why don’t we make our annotation @Inherited? We could add our annotation to a marker interface:
所以,如果一个注释属性是一个数组,我们每次都要重复它。但是我们想避免这种复制粘贴。为什么我们不把我们的注解变成@Inherited呢?我们可以把我们的注释添加到marker接口。
@Marker(value = {"foo", "bar"})
public interface MarkerInterface {
}
Then, we could make the classes that require this annotation implement it:
然后,我们可以让需要这个注解的类实现它。
public class Example implements MarkerInterface {
// ...
}
This approach won’t work. The code will compile without errors. However, Java doesn’t support annotation inheritance from interfaces, even if the annotations have the @Inherited annotation itself. So, a class implementing the marker interface won’t inherit the annotation.
这种方法是行不通的。代码的编译不会出错。然而,Java不支持从接口继承注解,即使注解本身具有@Inherited注解。所以,实现标记接口的类不会继承该注解。
The reason for this is the problem of multiple inheritance. Indeed, if multiple interfaces have the same annotation, Java can’t choose one.
其原因是多重继承的问题。事实上,如果多个接口有相同的注解,Java就不能选择一个。
So, we can’t avoid this copy-paste with a marker interface.
所以,我们不能用一个标记界面来避免这种复制粘贴。
6. Array Element as an Annotation Attribute
6.作为注释属性的数组元素
Suppose we have an array constant and we use this constant as an annotation attribute:
假设我们有一个数组常数,我们使用这个常数作为注释属性。
@Marker(Example.ATTRIBUTES[0])
public class Example {
static final String[] ATTRIBUTES = {"Foo", "Bar"};
// ...
}
This code won’t compile. Annotation parameters must be a compile-time constant. But, as we considered before, an array is not a compile-time constant.
这段代码是不会被编译的。注释参数必须是一个编译时常量。但是,正如我们之前所考虑的,一个数组不是一个编译时常量。
Moreover, an array access expression is not a constant expression.
此外,数组访问表达式不是一个常量表达式。
What if we had a List instead of an array? Method calls do not belong to the constant expressions. Thus, using the get method of the List class results in the same error.
如果我们有一个List而不是一个数组呢?方法调用不属于常量表达式。因此,使用List类的get方法会导致同样的错误。
Instead, we should explicitly refer to a constant:
相反,我们应该明确地引用一个常数。
@Marker(Example.ATTRIBUTE_FOO)
public class Example {
static final String ATTRIBUTE_FOO = "Foo";
static final String[] ATTRIBUTES = {ATTRIBUTE_FOO, "Bar"};
// ...
}
This way, we specify the annotation attribute value in the string constant, and the Java compiler can unambiguously find the attribute value.
这样,我们在字符串常量中指定注解属性值,而Java编译器可以毫不含糊地找到属性值。
7. Conclusion
7.结语
In this article, we looked through the limitations of annotation parameters. We considered some examples of problems with annotation attributes. We also discussed the JVM internals in the context of these limitations.
在这篇文章中,我们研究了注释参数的局限性。我们考虑了一些关于注解属性问题的例子。我们还讨论了JVM内部在这些限制方面的问题。
In all examples, we used the same classes for constants and annotations. However, all these limitations hold for the cases where the constant comes from another class.
在所有的例子中,我们对常量和注释使用了相同的类。然而,所有这些限制都适用于常量来自其他类的情况。