Jes*_*per 26 java generics annotations
定义Java接口时,可以使用类型参数声明方法,例如:
public interface ExampleInterface {
<E extends Enum<E>> Class<E> options();
}
Run Code Online (Sandbox Code Playgroud)
同样的事情在注释中不起作用.例如,这是非法的:
public @interface ExampleAnnotation {
<E extends Enum<E>> Class<E> options();
}
Run Code Online (Sandbox Code Playgroud)
我可以通过使用原始类型得到我想要的东西Enum:
public @interface ExampleAnnotation {
@SuppressWarnings("rawtypes")
Class<? extends Enum> options();
}
Run Code Online (Sandbox Code Playgroud)
究竟是什么原因导致无法使用类型参数声明注释属性?
irr*_*ble 19
我认为这是可能的,但它需要对语言规范进行大量补充,这是不合理的.
首先,对于enum示例,您可以使用Class<? extends Enum<?>> options.
存在另一个问题Class<? extends Enum> options:既然Enum.class是一个Class<Enum>这是一个Class<? extends Enum>,它是合法的options=Enum.class
这不可能发生Class<? extends Enum<?>> options,因为Enum它不是Enum<?>杂乱的原始类型治疗中的一个相当偶然的事实的子类型.
回到一般问题.由于有限的属性类型中,Class唯一一个具有类型参数,并且通配符通常足够表达,因此您的关注点不值得解决.
让我们进一步概括问题,假设有更多的属性类型,并且在许多情况下通配符不够强大.例如,让我们说Map是允许的,例如
Map<String,Integer> options();
options={"a":1, "b":2} // suppose we have "map literal"
Run Code Online (Sandbox Code Playgroud)
假设我们希望attrbite类型Map<x,x>适用于任何类型x.这不能用通配符来表达 - Map<?,?>而不是Map<x,y>任何通配符x,y.
一种方法是允许类型的类型参数:<X>Map<X,X>.这实际上非常有用.但这是打字系统的一个重大变化.
另一种方法是重新解释注释类型中方法的类型参数.
<X> Map<X,X> options();
options={ "a":"a", "b":"b" } // infer X=String
Run Code Online (Sandbox Code Playgroud)
在当前对方法类型参数,推理规则,继承规则等的理解中,这根本不起作用.我们需要更改/添加很多东西才能使它工作.
在任何一种方法中,如何交付X注释处理器都是一个问题.我们必须发明一些额外的机制来携带实例的类型参数.
oll*_*ins 11
The Java™语言规范第三版说:
通过其上下文无关语法对注释类型声明施加以下限制:
Java语言规范的第9.6节描述了注释.其中一句话是:
如果在注释类型中声明的方法的返回类型是除以下之一之外的任何类型,则是编译时错误:其中一种基本类型,String,Class和任何类的调用,枚举类型(§8.9) ),注释类型或前述类型之一的数组(第10节).如果在注释类型中声明的任何方法具有覆盖等同于在类Object或接口annotation.Annotation中声明的任何公共或受保护方法的签名,则它也是编译时错误.
然后它说以下,这是我认为这个问题的关键:
请注意,这与泛型方法的禁止不冲突,因为通配符消除了对显式类型参数的需要.
所以它建议我应该使用通配符,并且不需要类型参数.为了摆脱原始类型Enum,我只需要Enum<?>在他的回答中使用无可争议的建议:
public @interface ExampleAnnotation {
Class<? extends Enum<?>> options();
}
Run Code Online (Sandbox Code Playgroud)
可能允许类型参数会打开一堆蠕虫,因此语言设计者决定简单地禁止它们,因为你可以通过通配符得到你需要的东西.
他们想引入注释,以便人们只能将它们用作、、、井注释。并阻止开发人员将逻辑放入其中。即开始使用注释进行编程,在我看来,这可能会使 Java 看起来像一种非常不同的语言。因此,Java 语言规范中出现了上下文无关语法注释。
注释类型声明因其上下文无关语法而受到以下限制:
Run Code Online (Sandbox Code Playgroud)Annotation type declarations cannot be generic. No extends clause is permitted. (Annotation types implicitly extend annotation.Annotation.) Methods cannot have any parameters Methods cannot have any type parameters
(http://java.sun.com/docs/books/jls/third_edition/html/interfaces.html)
为了更好地理解我的意思,看看这个 JVM 黑客做了什么: http://ricken.us/research/xajavac/
他创建And、Or注释作为指令,并使用它们处理其他注释。无价!
诚然,我在这里参加聚会迟到了,但我自己在这个确切的问题上挣扎了很长一段时间,我想对此添加一个稍微不同的看法。
注意:这是一个相当长的答案,您可能不需要阅读它,除非您对 JVM 的底层细节感兴趣,或者您正在从事在 JVM 之上实现新编程语言的业务。
首先,Java语言和作为其底层平台的Java虚拟机之间存在差异。Java 语言受 Java 语言规范的约束,一些人已经在他们的答案中引用了该规范。JVM 受 Java 虚拟机规范管辖,除了 Java 之外,它还支持其他几种编程语言,例如 Scala、Ceylon、Xtend 和 Kotlin。JVM 充当所有这些语言的共同点,因此,它必须比基于它的语言更加宽松。
现有答案中引用的限制是 Java 语言的限制,而不是 JVM 的限制。大多数情况下,这些限制在 JVM 级别并不存在。
例如,假设您想要定义如下内容(最后解释了为什么要这样做):
@Retention(RUNTIME)
public @interface before
{
class<? extends Runnable> code() default @class(Initializer.class);
}
public @interface class<T>
{
Class<T> value();
}
public class Initializer extends Runnable
{
@Override
public void run()
{
// initialization code
}
}
Run Code Online (Sandbox Code Playgroud)
显然不可能用 Java 编写这段代码,因为(a)它涉及一个具有类型参数的注释,并且(b)因为该注释被调用class(使用小写c),这是 Java 中的保留关键字。
然而,使用像ByteBuddy这样的代码生成框架,确实可以通过编程方式创建相应的 JVM 字节码:
import java.lang.annotation.Annotation
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import net.bytebuddy.ByteBuddy
import net.bytebuddy.description.annotation.AnnotationDescription
import net.bytebuddy.description.annotation.AnnotationValue
import net.bytebuddy.description.modifier.Visibility
import net.bytebuddy.description.type.TypeDefinition
import net.bytebuddy.description.type.TypeDescription
import net.bytebuddy.description.type.TypeDescription.Generic
import net.bytebuddy.dynamic.DynamicType.Unloaded
import net.bytebuddy.dynamic.scaffold.TypeValidation
import net.bytebuddy.implementation.StubMethod
import static java.lang.annotation.RetentionPolicy.RUNTIME
import static net.bytebuddy.description.type.TypeDescription.Generic.Builder.parameterizedType
import static net.bytebuddy.description.type.TypeDescription.Generic.OfWildcardType.Latent.boundedAbove
import static net.bytebuddy.description.type.TypeDescription.CLASS
import static net.bytebuddy.matcher.ElementMatchers.named
class AnnotationWithTypeParameter
{
def void createAnnotationWithTypeParameter()
{
val ByteBuddy codeGenerator = new ByteBuddy().with(TypeValidation.DISABLED)
val TypeDefinition T = TypeDescription.Generic.Builder.typeVariable("T").build
val TypeDefinition classT = TypeDescription.Generic.Builder.parameterizedType(CLASS, T).build
val Unloaded<? extends Annotation> unloadedAnnotation = codeGenerator
.makeAnnotation
.merge(Visibility.PUBLIC)
.name("class")
.typeVariable("T")
.defineMethod("value", classT, Visibility.PUBLIC)
.withoutCode
.make
val TypeDescription classAnnotation = unloadedAnnotation.typeDescription
val Unloaded<Runnable> unloadedRunnable = codeGenerator
.subclass(Runnable).merge(Visibility.PUBLIC).name("Initializer")
.method(named("run")).intercept(StubMethod.INSTANCE)
.make
val TypeDescription typeInitializer = unloadedRunnable.typeDescription
val AnnotationDescription.Builder a = AnnotationDescription.Builder.ofType(classAnnotation)
.define("value", typeInitializer)
val AnnotationValue<?, ?> annotationValue = new AnnotationValue.ForAnnotationDescription(a.build)
val TypeDescription classRunnable = new TypeDescription.ForLoadedType(Runnable)
val Generic.Builder classExtendsRunnable = parameterizedType(classAnnotation, boundedAbove(classRunnable.asGenericType, classRunnable.asGenericType))
val Retention runtimeRetention = new Retention()
{
override Class<Retention> annotationType() {Retention}
override RetentionPolicy value() {RUNTIME}
}
val Unloaded<? extends Annotation> unloadedBefore = codeGenerator
.makeAnnotation
.merge(Visibility.PUBLIC)
.name("before")
.annotateType(runtimeRetention)
.defineMethod("code", classExtendsRunnable.build, Visibility.PUBLIC)
.defaultValue(annotationValue)
.make
#[unloadedBefore, unloadedAnnotation, unloadedRunnable].forEach[load(class.classLoader).loaded]
// ...or alternatively something like: .forEach[saveIn(new File("/tmp"))]
}
}
Run Code Online (Sandbox Code Playgroud)
(上面的代码是用Xtend语法编写的,但可以很容易地转换为常规 Java)
简而言之,此代码将创建一个参数化注释 ( @class<T>) 并将其用作另一个注释 ( @before) 的属性,其中类型参数绑定到? extends Runnable。forEach[load(...)]通过将 替换为 a forEach[saveIn(...)](生成实际的类文件)并在同一文件夹中编译一个小型 Java 测试程序,可以轻松验证生成代码的有效性:
import java.lang.reflect.Method;
import java.lang.annotation.Annotation;
public class TestAnnotation
{
@before
public static void main(String[] arg) throws Exception
{
Method main = TestAnnotation.class.getDeclaredMethod("main", String[].class);
@SuppressWarnings("unchecked")
Class<? extends Annotation> beforeAnnotation = (Class<? extends Annotation>)Class.forName("before");
Annotation before = main.getAnnotation(beforeAnnotation);
Method code = before.getClass().getDeclaredMethod("code");
Object classAnnotation = code.invoke(before);
System.err.println(classAnnotation);
}
}
Run Code Online (Sandbox Code Playgroud)
测试程序将显示包装在@class注释中的预期初始值设定项类:
@class(value=class Initializer)
Run Code Online (Sandbox Code Playgroud)
为了更好地理解它实现了什么(和没有实现),通过以下方式反汇编一些生成的类文件很有用javap -c -v:
Classfile /private/tmp/class.class
Last modified Feb 28, 2020; size 265 bytes
MD5 checksum f57e09ce9d174a6943f7b09704cbdea3
public interface class<T extends java.lang.Object> extends java.lang.annotation.Annotation
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
Constant pool:
#1 = Utf8 class
#2 = Class #1 // class
#3 = Utf8 <T:Ljava/lang/Object;>Ljava/lang/Object;Ljava/lang/annotation/Annotation;
#4 = Utf8 java/lang/Object
#5 = Class #4 // java/lang/Object
#6 = Utf8 java/lang/annotation/Annotation
#7 = Class #6 // java/lang/annotation/Annotation
#8 = Utf8 value
#9 = Utf8 ()Ljava/lang/Class;
#10 = Utf8 ()Ljava/lang/Class<TT;>;
#11 = Utf8 Signature
{
public abstract java.lang.Class<T> value();
descriptor: ()Ljava/lang/Class;
flags: ACC_PUBLIC, ACC_ABSTRACT
Signature: #10 // ()Ljava/lang/Class<TT;>;
}
Signature: #3 // <T:Ljava/lang/Object;>Ljava/lang/Object;Ljava/lang/annotation/Annotation;
Run Code Online (Sandbox Code Playgroud)
上面的代码显示类型参数T在类和方法级别正确反映,并且在属性的签名中也正确显示value。
Classfile /private/tmp/before.class
Last modified Feb 28, 2020; size 382 bytes
MD5 checksum d2166167cf2adb8989a77dd320f9f44b
public interface before extends java.lang.annotation.Annotation
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
Constant pool:
#1 = Utf8 before
#2 = Class #1 // before
#3 = Utf8 java/lang/Object
#4 = Class #3 // java/lang/Object
#5 = Utf8 java/lang/annotation/Annotation
#6 = Class #5 // java/lang/annotation/Annotation
#7 = Utf8 Ljava/lang/annotation/Retention;
#8 = Utf8 value
#9 = Utf8 Ljava/lang/annotation/RetentionPolicy;
#10 = Utf8 RUNTIME
#11 = Utf8 code
#12 = Utf8 ()Lclass;
#13 = Utf8 ()Lclass<+Ljava/lang/Runnable;>;
#14 = Utf8 Lclass;
#15 = Utf8 LInitializer;
#16 = Utf8 Signature
#17 = Utf8 AnnotationDefault
#18 = Utf8 RuntimeVisibleAnnotations
{
public abstract class<? extends java.lang.Runnable> code();
descriptor: ()Lclass;
flags: ACC_PUBLIC, ACC_ABSTRACT
Signature: #13 // ()Lclass<+Ljava/lang/Runnable;>;
AnnotationDefault:
default_value: @#14(#8=c#15)}
RuntimeVisibleAnnotations:
0: #7(#8=e#9.#10)
Run Code Online (Sandbox Code Playgroud)
对注释的反汇编@before再次表明,具体类型参数 ( ? extends Runnable) 已正确记录在实际方法签名和Signature属性中。
因此,如果您有一种能够进行参数化注释的语言,那么字节码将保留编译时保证类型安全所需的所有信息。话虽这么说(即类型安全主要在 Java 的编译时强制执行),我不认为 JVM 级别有任何东西会阻止未扩展的类被Runnable分配为code属性的默认值。@before注解(但同样,检测和防止这种情况是编译器的工作)。
最后,房间里的一个大问题是:为什么有人想要做这一切?我实际上并不是从头开始编写所有这些代码,只是为了为已经回答的问题提供一个晦涩的答案。我上面粘贴的代码来自(稍微编辑过的)基于 JVM 的编程语言的测试用例。这种语言中的注释经常需要携带代码(以对包含代码的类的引用的形式)。这是实现类似于Xtend 编程语言中的活动注释功能的语言功能的必要条件。现在,由于java.lang.Class是有效的注释属性类型,因此可以通过直接使用类文字来实现。然而,这会直接暴露 Java API,这是不可取的,因为它会产生紧密耦合。如果类文字要包装在其他属性类型中,则它必须是另一个注释,并且如果我们不想在此过程中丢失类型信息,则该注释需要有一个类型参数来携带该信息。
因此,长话短说,参数化注释是可能的(在 JVM 上,而不是在 Java 中),并且在某些用例中您需要它们,但实际上这只会对 JVM 语言实现者感兴趣。
顺便说一句,另一位发帖者谈到“使用注释进行编程”并不是 Java 中真正的预期功能,但我强烈建议您查看 Xtend 的活动注释功能。正是如此,即“使用注释进行编程”,一旦掌握了它,它就是一个非常强大的语言功能。