在深入研究Java SE 8最终规范之后,我可以回答我的问题。
(1)回答我的第一个问题
有没有什么方法可以像注释相应的匿名类一样注释lambda表达式,因此可以在上面的示例中获得预期的“ Hello World”输出?
没有。
注释
Class Instance Creation expression(§15.9)匿名类型的时,注释将存储在类文件中,用于扩展接口或匿名类型的扩展类。
对于以下匿名接口注释
Consumer<String> c = new @MyTypeAnnotation("Hello ") Consumer<String>() { @Override public void accept(String str) { System.out.println(str); }};然后可以在 运行时 通过调用
Class#getAnnotatedInterfaces()以下方式访问类型注释:
MyTypeAnnotation a = c.getClass().getAnnotatedInterfaces()[0].getAnnotation(MyTypeAnnotation.class);
如果使用这样的空主体创建一个匿名类:
class MyClass implements Consumer<String>{ @Override public void accept(String str) { System.out.println(str); }}Consumer<String> c = new @MyTypeAnnotation("Hello ") MyClass(){};类型注释也可以在 运行时 通过调用来访问
Class#getAnnotatedSuperclass():
MyTypeAnnotation a = c.getClass().getAnnotatedSuperclass().getAnnotation(MyTypeAnnotation.class);
这种类型的注释是 没有 可能的lambda表达式。
附带说明一下,这种注释对于普通的类实例创建表达式也是不可能的:
Consumer<String> c = new @MyTypeAnnotation("Hello ") MyClass();在这种情况下,类型注释将存储在发生表达式的方法的method_info结构中,而不是类型本身(或其任何超类型的注释)。
这种差异很重要,因为Java反射API在运行时将 无法
访问存储在method_info中的注释。使用ASM查看生成的字节码时,区别如下:
在创建匿名接口实例时输入Annotation:
@Java8Example$MyTypeAnnotation(value="Hello ") : CLASS_EXTENDS 0, null// access flags 0x0INNERCLASS Java8Example$1
在普通的类实例创建时输入Annotation:
NEW Java8Example$MyClass@Java8Example$MyTypeAnnotation(value="Hello ") : NEW, null
在第一种情况下,注释与 内部类 相关联,在第二种情况下,注释与方法字节代码内的 实例创建 表达式相关联。
(2)回应@assylias的评论
您也可以尝试(@MyTypeAnnotation(“ Hello”)String s)->
System.out.println(s),尽管我尚未设法访问注释值…
是的,根据Java
8规范,这实际上是可能的。但是,目前尚无法通过Java反射API接收lambda表达式的形式参数的类型注释,这很可能与以下JDK错误有关:类型注释清除。此外,Eclipse编译器尚未在类文件中存储相关的Runtime
[In] VisibleTypeAnnotations属性-
在此处找到了相应的错误:Lambda参数名称和注释未将其添加到类文件中。
(3)回答我的第二个问题
在示例中,我确实强制转换了lambda表达式并注释了强制转换的类型:在运行时是否有任何方法可以接收此批注实例,或者这样的批注始终隐式地限于RetentionPolicy.SOURCE?
注释强制转换表达式的类型时,此信息也将存储在类文件的method_info结构中。对于方法代码(例如)中的类型注释的其他可能位置也是如此
if(cinstanceof @MyTypeAnnotationConsumer)。当前没有公共Java反射API可以访问这些代码注释。但是由于它们存储在类文件中,因此至少有可能在运行时访问它们-
例如,通过使用外部库(如ASM)读取类的字节码。
实际上,我设法使我的“ Hello World”示例与强制转换表达式类似
testTypeAnnotation(list,(@MyTypeAnnotation("Hello ") Consumer<String>) (p -> System.out.println(p)));通过使用ASM解析调用方法的字节码。但是代码非常hacky,效率低下,可能永远都不要在生产代码中做类似的事情。无论如何,仅出于完整性考虑,下面是完整的“
Hello World”示例:
import java.lang.annotation.Annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;import java.lang.reflect.AnnotatedType;import java.lang.reflect.Method;import java.net.URL;import java.util.Arrays;import java.util.List;import java.util.function.Consumer;import org.objectweb.asm.AnnotationVisitor;import org.objectweb.asm.ClassReader;import org.objectweb.asm.ClassVisitor;import org.objectweb.asm.Label;import org.objectweb.asm.MethodVisitor;import org.objectweb.asm.Oppres;import org.objectweb.asm.TypePath;import org.objectweb.asm.TypeReference;public class Java8Example { @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE_USE) public @interface MyTypeAnnotation { public String value(); } public static void main(String[] args) { List<String> list = Arrays.asList("World!", "Type Annotations!"); testTypeAnnotation(list, new @MyTypeAnnotation("Hello ") Consumer<String>() { @Override public void accept(String str) { System.out.println(str); } }); list = Arrays.asList("Type-Cast Annotations!"); testTypeAnnotation(list,(@MyTypeAnnotation("Hello ") Consumer<String>) (p -> System.out.println(p))); } public static void testTypeAnnotation(List<String> list, Consumer<String> consumer){ MyTypeAnnotation annotation = null; for (AnnotatedType t : consumer.getClass().getAnnotatedInterfaces()) { annotation = t.getAnnotation(MyTypeAnnotation.class); if (annotation != null) { break; } } if (annotation == null) { // search for annotated parameter instead loop: for (Method method : consumer.getClass().getMethods()) { for (AnnotatedType t : method.getAnnotatedParameterTypes()) { annotation = t.getAnnotation(MyTypeAnnotation.class); if (annotation != null) { break loop; } } } } if (annotation == null) { annotation = findCastAnnotation(); } for (String str : list) { if (annotation != null) { System.out.print(annotation.value()); } consumer.accept(str); } } private static MyTypeAnnotation findCastAnnotation() { // foundException gets thrown, when the cast annotation is found or the search ends. // The found annotation will then be stored at foundAnnotation[0] final RuntimeException foundException = new RuntimeException(); MyTypeAnnotation[] foundAnnotation = new MyTypeAnnotation[1]; try { // (1) find the calling method StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); StackTraceElement previous = null; for (int i = 0; i < stackTraceElements.length; i++) { if (stackTraceElements[i].getMethodName().equals("testTypeAnnotation")) { previous = stackTraceElements[i+1]; } } if (previous == null) { // shouldn't happen return null; } final String callingClassName = previous.getClassName(); final String callingMethodName = previous.getMethodName(); final int callingLineNumber = previous.getLineNumber(); // (2) read and visit the calling class ClassReader cr = new ClassReader(callingClassName); cr.accept(new ClassVisitor(Oppres.ASM5) { @Override public MethodVisitor visitMethod(int access, String name,String desc, String signature, String[] exceptions) { if (name.equals(callingMethodName)) { // (3) visit the calling method return new MethodVisitor(Oppres.ASM5) { int lineNumber; String type; public void visitLineNumber(int line, Label start) { this.lineNumber = line; }; public void visitTypeInsn(int oppre, String type) { if (oppre == Oppres.CHECKCAST) { this.type = type; } else{ this.type = null; } }; public AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { if (lineNumber == callingLineNumber) { // (4) visit the annotation, if this is the calling line number AND the annotation is // of type MyTypeAnnotation AND it was a cast expression to "java.util.function.Consumer" if (desc.endsWith("Java8Example$MyTypeAnnotation;") && this.type != null && this.type.equals("java/util/function/Consumer")) { TypeReference reference = new TypeReference(typeRef); if (reference.getSort() == TypeReference.CAST) {return new AnnotationVisitor(Oppres.ASM5) { public void visit(String name, final Object value) { if (name.equals("value")) { // Heureka! - we found the Cast Annotation foundAnnotation[0] = new MyTypeAnnotation() { @Override public Class<? extends Annotation> annotationType() { return MyTypeAnnotation.class; } @Override public String value() { return value.toString(); } }; // stop search (Annotation found) throw foundException; } };}; } } } else if (lineNumber > callingLineNumber) { // stop search (Annotation not found) throw foundException; } return null; }; }; } return null; } }, 0); } catch (Exception e) { if (foundException == e) { return foundAnnotation[0]; } else{ e.printStackTrace(); } } return null; }}


