注释 Lambda 表达式的函数接口

2024-03-26

Java 8 引入了两者拉姆达表达式 http://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html and 类型注释 http://docs.oracle.com/javase/tutorial/java/annotations/type_annotations.html.

使用类型注释,可以定义如下 Java 注释:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_USE)
public @interface MyTypeAnnotation {
    public String value();
}

然后可以在任何类型引用上使用此注释,例如:

Consumer<String> consumer = new @MyTypeAnnotation("Hello ") Consumer<String>() {
    @Override
    public void accept(String str) {
        System.out.println(str);
    }
};

这是一个完整的示例,它使用此注释来打印“Hello World”:

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.util.Arrays;
import java.util.List;
import java.util.function.Consumer;

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);
            }
        });
    }

    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;
            }
        }
        for (String str : list) {
            if (annotation != null) {
                System.out.print(annotation.value());
            }
            consumer.accept(str);
        }
    }
}

输出将是:

Hello World! 
Hello Type Annotations!

在 Java 8 中,还可以用 lambda 表达式替换本例中的匿名类:

public static void main(String[] args) {
    List<String> list = Arrays.asList("World!", "Type Annotations!");
    testTypeAnnotation(list, p -> System.out.println(p));
}

但由于编译器推断 lambda 表达式的 Consumer 类型参数,因此无法再注释创建的 Consumer 实例:

testTypeAnnotation(list, @MyTypeAnnotation("Hello ") (p -> System.out.println(p))); // Illegal!

可以将 lambda 表达式转换为 Consumer,然后注释转换表达式的类型引用:

testTypeAnnotation(list,(@MyTypeAnnotation("Hello ") Consumer<String>) (p -> System.out.println(p))); // Legal!

但这不会产生期望的结果,因为创建的 Consumer 类不会使用强制转换表达式的注释进行注释。输出:

World!
Type Annotations!

两个问题:

  1. 有没有什么方法可以像注释相应的匿名类一样注释 lambda 表达式,以便获得上例中预期的“Hello World”输出?

  2. 在示例中,我确实转换了 lambda 表达式并注释了转换类型:是否有任何方法可以在运行时接收此注释实例,或者此类注释是否始终隐式限制为 RetentionPolicy.SOURCE?

这些示例已使用 javac 和 Eclipse 编译器进行了测试。

Update

我尝试了 @assylias 的建议,改为注释参数,这产生了一个有趣的结果。这是更新后的测试方法:

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;
                }
            }
        }
    }
    for (String str : list) {
        if (annotation != null) {
            System.out.print(annotation.value());
        }
        consumer.accept(str);
    }
}

现在,在注释匿名类的参数时,还可以产生“Hello World”结果:

public static void main(String[] args) {
    List<String> list = Arrays.asList("World!", "Type Annotations!");
    testTypeAnnotation(list, new Consumer<String>() {
        @Override
        public void accept(@MyTypeAnnotation("Hello ") String str) {
            System.out.println(str);
        }
    });
}

但注释参数确实not适用于 lambda 表达式:

public static void main(String[] args) {
    List<String> list = Arrays.asList("World!", "Type Annotations!");
    testTypeAnnotation(list, (@MyTypeAnnotation("Hello ") String str) ->  System.out.println(str));
}

有趣的是,使用 lambda 表达式时,也无法接收参数名称(使用 javac 参数编译时)。但我不确定这种行为是否是有意为之,lambda 的参数注释是否尚未实现,或者这是否应该被视为编译器的错误。


深入挖掘后Java SE 8 最终规范 http://download.oracle.com/otndocs/jcp/java_se-8-fr-eval-spec/index.html我能够回答我的问题。

(1)回答我的第一个问题

有没有类似于注释的方法来注释 lambda 表达式 相应的匿名类,因此得到预期的“Hello 上例中的 World” 输出?

No.

注释时Class Instance Creation Expression (§15.9)如果是匿名类型,则注释将存储在匿名类型的扩展接口或扩展类的类文件中。

对于下面的匿名接口注解

Consumer<String> c = new @MyTypeAnnotation("Hello ") Consumer<String>() {
    @Override
    public void accept(String str) {
        System.out.println(str);
    }
};

然后可以在以下位置访问类型注释runtime通过致电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(){/*empty body!*/};

类型注释也可以访问runtime通过致电Class#getAnnotatedSuperclass():

MyTypeAnnotation a = c.getClass().getAnnotatedSuperclass().getAnnotation(MyTypeAnnotation.class);

这种类型注释是not对于 lambda 表达式来说是可能的。

附带说明一下,这种注释对于正常的类实例创建表达式也是不可能的,如下所示:

Consumer<String> c = new @MyTypeAnnotation("Hello ") MyClass();

在这种情况下,类型注释将存储在方法信息结构 http://docs.oracle.com/javase/specs/jvms/se5.0/html/ClassFile.doc.html方法的,表达式出现的位置,而不是作为类型本身(或其任何超类型)的注释。

这个区别很重要,因为存储在 method_info 中的注释将not可以在运行时通过 Java 反射 API 进行访问。当查看生成的字节码时ASM http://asm.ow2.org/,差异如下所示:

匿名接口实例创建上的类型注释:

@Java8Example$MyTypeAnnotation(value="Hello ") : CLASS_EXTENDS 0, null
// access flags 0x0
INNERCLASS Java8Example$1

普通类实例创建上的类型注释:

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 bug 有关:类型注释清理 https://bugs.openjdk.java.net/browse/JDK-8027181。此外,Eclipse 编译器尚未在类文件中存储相关的 Runtime[In]VisibleTypeAnnotations 属性 - 相应的 bug 可以在此处找到:Lambda 参数名称和注释不会出现在类文件中。 https://bugs.eclipse.org/bugs/show_bug.cgi?id=430571

(3)回答我的第二个问题

在示例中,我确实转换了 lambda 表达式并进行了注释 转换类型:有什么方法可以接收这个注释实例 在运行时,或者这样的注释总是隐式限制为 保留政策.SOURCE?

当注释强制转换表达式的类型时,此信息也会存储在类文件的 method_info 结构中。对于方法代码内类型注释的其他可能位置也是如此,例如if(c instanceof @MyTypeAnnotation Consumer)。目前还没有公共的 Java 反射 API 来访问这些代码注释。但由于它们存储在类文件中,因此至少有可能在运行时访问它们 - 例如通过使用外部库读取类的字节码,例如ASM http://asm.ow2.org/.

实际上,我设法让我的“Hello World”示例使用像这样的强制转换表达式

testTypeAnnotation(list,(@MyTypeAnnotation("Hello ") Consumer<String>) (p -> System.out.println(p)));

通过使用 ASM 解析调用方法字节码。但该代码非常老套且效率低下,人们可能永远不应该在生产代码中做这样的事情。无论如何,为了完整起见,这里是完整的工作“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.Opcodes;
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(Opcodes.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(Opcodes.ASM5) {
                            int lineNumber;
                            String type;
                            public void visitLineNumber(int line, Label start) {
                                this.lineNumber = line;
                            };
                            public void visitTypeInsn(int opcode, String type) {
                                if (opcode == Opcodes.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(Opcodes.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;
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

注释 Lambda 表达式的函数接口 的相关文章

随机推荐

  • Python 3 和二进制文件的 base64 编码

    我是 Python 新手 确实有一个问题困扰着我 我使用以下代码获取 zip 文件的 Base64 字符串表示形式 with open C Users Mario Downloads exportTest1 zip rb as file z
  • 停止 Eclipse/Java 运行多个实例

    我不是 java 专家或 eclipse 专家 目前我正在开发一个项目 我需要经常调试 测试 我使用 eclipse run 按钮 但是当我不关闭程序 eclipse java 时 它会再次打开 第二个窗口 这是一个带有 swing jfr
  • 使用 Windows Installer 取消 Windows 服务安装/卸载会导致服务安装/卸载不完整

    我已经使用 VS 2017 为 Windows 服务创建了一个 Windows Installer Windows 服务内部有一个项目安装程序和服务安装程序类 当我中途取消卸载过程时 该过程会删除 Windows 服务 但不会更新数据 因此
  • 前置声明有哪些危险?

    我刚刚接受采访 有人问我什么是 前瞻性声明 然后有人问我是否存在与前瞻性声明相关的危险 我无法回答第二个问题 在网上搜索并没有出现任何有趣的结果 那么 有人知道使用前向声明有什么危险吗 好吧 除了重复的问题之外 标准中至少有一个痛处 如果你
  • 从 String java 或 javascript 中修剪 和
    标签

    我想使用 java 或 javascript 从字符串中删除开头和结尾的 br 和 nbsp 标签 输入和所需的输出托管在 jsfiddle net 中 因为 Stackoverflow 不允许我发布 html 内容 http jsfidd
  • Java Swing 中的 PNG 渲染不良(颜色深度低)

    我目前正在使用 swing 在 java 中设计吃豆人 我有使用以下语句在屏幕上绘制的 PNG 图像 wall new ImageIcon GamePanel class getResource wall png getImage g2d
  • 如何使用 PowerShell 将“date-1”格式设置为 mm-dd-yyyy?

    一个人如何得到date 1并在 PowerShell 中将其格式化为 mm dd yyyy 示例 如果今天是 2013 年 11 月 1 日 我的代码中需要 10 31 2013 我以前使用过 AddDays 1 但我似乎无法让它与任何格式
  • 启动 PyQt 应用程序而不阻塞主线程

    我正在向现有应用程序添加 PyQT UI 我必须初始化QAppliaction通过我在主线程上从该应用程序收到的回调 我无法从回调中同步执行此操作 因为我最终必须调用app exec 这是阻塞的 阻止现有应用程序继续运行 显然 生成常规线程
  • 将 JSON 数组读入类似 Julia DataFrame 的类型

    给定一个 JSON 文件 JSON 包会愉快地解析它 但如果我想把它作为DataFrame 或任何其他柱状数据结构 获得它的好方法是什么 目前 例如 我有 using JSON using DataFrames json str color
  • 使用 powershell 从多台计算机中提取注册表值

    我和一位同事正在尝试创建一个 powershell 脚本 该脚本使用一个 CSV 文件 其中包含公司网络上所有计算机的名称 并使用这些名称连接到远程注册表并提取特定值 这是我们目前所拥有的 strMachineName import csv
  • MySQL存储过程中用于选择数据的if语句

    我有下面的 SQL 代码 DELIMITER CREATE PROCEDURE test new procedure queryString VARCHAR 255 BEGIN SELECT tempValue COUNT FROM tes
  • 被类覆盖的扩展方法不会给出警告

    我在另一个线程中进行了讨论 发现类方法优先于具有相同名称和参数的扩展方法 这很好 因为扩展方法不会劫持方法 但假设您已向第三方库添加了一些扩展方法 public class ThirdParty public static class Th
  • pdf-writer 无效的多字节字符 (US-ASCII) 期望 keywords_end 内容 = "%PDF-#{@version}\n%âãÏÓ\n" Rails 3

    我在尝试运行的协作项目中遇到了 pdf writer gem 的问题 我正在使用RVM Ruby 1 9 2p180 Rails 3 1 0 on Ubuntu 10 10 我创建了一个新的gemset使用 Bundler 安装所需的 ge
  • 如何模拟 Object.getClass?

    我正在开发一个 Java 项目 想要为 DTO 中的 equals 方法编写单元测试 在 equals 方法中 有一个 getClass 方法由两个被测试对象调用 我想模拟这个 但我不知道它想要什么类型的对象 我试过 when mockRo
  • 带有窗口服务的网络打印

    我有一个朋友用窗口窗体做了同样的事情 我需要的是将其作为窗口服务 Window 服务程序 打印控制器服务 将用作中央打印机控制器 根据情况 我将打印作业发送到网络上的不同打印机 打印机安装在USB上 已经可以进行测试打印 但是当我列出已安装
  • HttpContext.Current.User.Identity.Name 始终为 string.Empty

    您好 我使用自定义 MembershipProvider 我想在应用程序场景中知道当前用户名 但是当我尝试访问 HttpContext Current User Identity Name 时 它 总是返回 string Empty if
  • 限制事件队列中的事件

    In a 最近的问题 https stackoverflow com questions 36492268 nodejs running out of memory processing csv files 我概述了在处理大量 csv 文件
  • 用于搜索某个单词/字符串是否包含在实体字段之一的 JPQL 查询

    基本上 这类似于查看句子中是否存在某个单词 有实体帖子 public class Post implements Serializable Id GeneratedValue strategy IDENTITY Column name po
  • 将公式向下填充到列中的最后一行

    我试图将单元格 M3 中的公式绘制到数据集的末尾 我使用 L 列作为基础来确定最后一个包含数据的单元格 我的公式是两个单元格的串联 它们之间有一个文本逗号 我的公式是 G3 L3 我希望Excel能画出这个公式 单元格 M4 将是 G4 L
  • 注释 Lambda 表达式的函数接口

    Java 8 引入了两者拉姆达表达式 http docs oracle com javase tutorial java javaOO lambdaexpressions html and 类型注释 http docs oracle com