【问题标题】:Java 8 TYPE_USE annotations not behaving if array is annotated inside List or Map如果数组在 List 或 Map 中注释,Java 8 TYPE_USE 注释不起作用
【发布时间】:2015-02-12 17:49:21
【问题描述】:

我正在尝试编写一个带有注释的简单验证库,该库使用 Java 8 中的新 TYPE_USE 目标。访问这些东西的方式非常复杂,并且给我留下了两个非常混乱的代码,它们的功能完全相同,但也与他们实际要做的事情非常相关。所以我决定创建一组简单的类来以非常简单和直观的方式保存这些信息。基本上有 TypedClass,它是一个类包装器,但它可以转换为 ListClass(集合或数组)或 MapClass 以作为 TypedClass 访问其组件(或键/值)。最重要的部分是应该转换的这个助手:

static TypedClass<?> create(Field f) {
    final TypeResolver typeResolver = new TypeResolver();
    ResolvedType type = typeResolver.resolve(f.getGenericType());
    return create(f.getAnnotations(), type, f.getAnnotatedType());
}

private static TypedClass<?> create(Annotation[] annotations, ResolvedType type, AnnotatedType at) {
    if (type.isArray()) {
        AnnotatedType childAnnotatedType = ((AnnotatedArrayType) at).getAnnotatedGenericComponentType();
        ResolvedType childType = type.getArrayElementType();
        return new ListClass<>(type.getErasedType(), annotations, create(at.getAnnotations(), childType, childAnnotatedType));
    } else if (type.isInstanceOf(Collection.class)) {
        AnnotatedType childAnnotatedType = ((AnnotatedParameterizedType) at).getAnnotatedActualTypeArguments()[0];
        ResolvedType childType = type.typeParametersFor(Collection.class).get(0);
        return new ListClass<>(type.getErasedType(), annotations, createForGenerics(childType, childAnnotatedType));
    } else if (type.isInstanceOf(Map.class)) {
        AnnotatedType[] att = ((AnnotatedParameterizedType) at).getAnnotatedActualTypeArguments();
        List<ResolvedType> types = type.typeParametersFor(Map.class);
        TypedClass<?> key = createForGenerics(types.get(0), att[0]);
        TypedClass<?> value = createForGenerics(types.get(1), att[1]);
        return new MapClass<>(type.getErasedType(), annotations, key, value);
    }
    return new TypedClass<>(type.getErasedType(), annotations);
}

private static TypedClass<?> createForGenerics(ResolvedType childType, AnnotatedType childAnnotatedType) {
    return create(childAnnotatedType.getAnnotations(), childType, childAnnotatedType);
}

这里我也是使用 com.fasterxml.classmate 中的 ResolvedType,它只是用来在 Java 中的 Type 和 Class 之间架起桥梁,这也是一种痛苦。我认为这与问题无关,因为生成的结构还可以,只是注释放错了位置。

它似乎工作得很好,除非 List 或 Map 中有一个数组。例如:

@First("array") List<@First("string") String> @First("list") [] arrayOfListOfString;

工作正常(注释与相应的类型匹配)。但是当我解析时

List<@First("int[]") Integer @First("int") []> listOfArrayOfInteger;

数组和整数都与@First("int") 注释相关联。

我已经调试了我的代码,但在任何地方都找不到对 @First("int[]") 注释的引用。我要么错过了一些非常简单的东西,要么没有任何意义。该代码似乎适用于其他所有场景,即使是更复杂的场景。几周以来我一直在尝试这样做,最近才找到我认为可行的解决方案。原来我之前的两种耦合方法也不适用于这种情况(没有这个特定的测试)。所以现在我很困。

怎么了?

【问题讨论】:

    标签: java arrays generics annotations java-8


    【解决方案1】:

    在您对create 的初始调用中,您传递了字段 注释,但没有传递AnnotatedType at 的注释。因此,方法create负责调用at.getAnnotations()。如果您查看您的实现,您会发现它仅在数组情况下才会这样做。在所有其他情况下,您的方法会将逻辑切换为“传入的注释数组是与at 关联的数组”。

    问题是您的第一个示例似乎是偶然的。您没有显示@First 注释的声明,但我怀疑@Targets、ElementType.FIELD ElementType.TYPE_USE 都允许使用它。在这种情况下,表单的声明

    @First("array") List… [] fieldName;
    

    不明确,注释将被记录为bothfieldName 的字段注释和List…[] 类型的注释。因此,在第一个示例中无法识别一个注释数组在递归期间丢失的事实,因为它恰好与字段注释匹配。但是,一旦只允许 TYPE_USE 而不是 FIELD 目标的注释,您的代码甚至不适用于您的第一个示例。

    因此,您必须决定传入的注释数组是与at 参数关联的还是与周围上下文关联的。如果两者都关联起来会更容易,因为在这种情况下,您可以通过让方法检索该数组而不是调用者来完全摆脱该参数。

    你应该记住:

    1. 如果你想记录递归类型的所有类型注解字段注解,你将比类型节点多一个注解数组
    2. 如果你想主要处理TYPE_USE注解,你根本不需要处理字段注解

    这是一个查找所有注释的简单直接的解析代码:

    public static void fullType(Field f) {
        AnnotatedType at = f.getAnnotatedType();
        fullType("\t", at);
        System.out.println(f.getName());
    }
    public static void fullType(String header, AnnotatedType at) {
        final boolean arrayType = at instanceof AnnotatedArrayType;
        if(arrayType) {
            fullType(header+"\t",
                ((AnnotatedArrayType)at).getAnnotatedGenericComponentType());
        }
        for(Annotation a: at.getAnnotations())
            System.out.println(header+a);
        if(arrayType) {
            System.out.println(header+"[]");
        }
        else if(at instanceof AnnotatedParameterizedType) {
            AnnotatedParameterizedType apt = (AnnotatedParameterizedType)at;
            System.out.println(header
                +((ParameterizedType)apt.getType()).getRawType().getTypeName());
            System.out.println(header+'<');
            String subHeader=header+"\t";
            for(AnnotatedType typeArg:
                apt.getAnnotatedActualTypeArguments())
                fullType(subHeader, typeArg);
            System.out.println(header+'>');
        }
        else if(at instanceof AnnotatedTypeVariable) {
            // when appearing in a Field’s type, it refers to Class’ type variables
            System.out.println(header+at.getType().getTypeName());
        }
        else if(at instanceof AnnotatedWildcardType) {
            System.out.println(header+"?");
            final AnnotatedWildcardType awt = (AnnotatedWildcardType)at;
            AnnotatedType[] bounds=awt.getAnnotatedLowerBounds();
            if(bounds==null || bounds.length==0) {
                bounds=awt.getAnnotatedUpperBounds();
                if(bounds==null || bounds.length==0) return;
                System.out.println(header+"extends");
            }
            else System.out.println(header+"super");
            header+="\t";
            for(AnnotatedType b: bounds) fullType(header, b);
        }
        else {
            assert at.getType().getClass()==Class.class;
            System.out.println(header+at.getType().getTypeName());
        }
    }
    

    它与您的示例字段完美配合。

    【讨论】:

    • 您说的非常正确,先生!虽然我必须保留 TypeUsage.FIELD,因为我在其他字段修饰符之前添加了注释,但是如果我在之后添加它们,我就不再需要它了。事实上,正是这个问题导致我得到了错误的代码。你的完美无瑕;谢谢!
    • 实际上,我做了更多的测试,您的代码似乎不适用于多维数组。例如,用fullType 方法解析这个@Ann("float") float @Ann("float[]") [] @Ann("float[][]") [] matrix;;它将切换第一个和第二个数组的注释。在我看来,它应该是我输入的方式,而不是相反,但如果有理由我可以假设解析是正确的,我必须在代码中切换我的注释......
    • 参见oracle.com/technetwork/articles/java/…“应用类型注释”部分
    • 原来如此。这整件事让我非常非常困惑。我想我会改变我的测试然后:)
    【解决方案2】:

    你说:

    @First("array") List<@First("string") String> @First("list") [] arrayOfListOfString;
    

    工作正常(注释与相应的类型匹配)。

    这在我看来是错误的。诸如

    之类的类型
    @English String @NonEmpty []
    

    读作“非空英文字符串数组”。

    现在让我们考虑你的例子(我已经稍微简化了):

    @First("array") List @First("list") [] arrayOfList;
    

    您有一个@First("list") 数组,其中每个元素都是一个@First("array") 列表。那是你要的吗?这不是名字所暗示的。

    我不知道您在 listOfArrayOfInteger 案例中的代码有什么问题,但鉴于您给出的一个示例似乎不正确。

    【讨论】:

    • 我理解你的观点,我同意你的观点,应该是这样,但我认为不是。正如在问题/25220215 中已经向我指出的那样,结果@English String @NonEmpty [] 不正确,应该是@NonEmpty String @English []。这个想法是:第一个注释适用于整个领域 - 这就是它一直的方式。中间的那个与这对括号有关 - 但不是明显的方式。你会认为括号代表数组,但实际上它们代表字符串,因为当你索引[i] 时,你会得到一个字符串。或者我又错了:(
    • @English String @NonEmpty [] 是正确的。如果有人推荐了@NonEmpty String @English [],那他们就错了(而且他们不熟悉 Java 8 规范)。 method 注释适用于整个字段(而不适用于类型)并出现在声明的最开头,以及诸如public 之类的修饰符。相比之下,type 注释出现在类型之前并适用于该类型。所以,在@Deprecated @English String @NonEmpty [] 中; @Deprecated 适用于变量字,@English 适用于String@NonEmpty 适用于数组。
    • 内部 javac 表示与规范不同。数组类型的 javac 表示很粗糙,您必须做一些工作才能在它和语义之间进行转换。
    • 你是对的;这就是为什么强烈建议不要同时使用TYPE_USEFIELD/METHOD 的注解。当此类注释的值同时应用于数组类型的元素类型和整个字段/方法时,会导致混淆。但是在这种令人困惑的行为中,Java 是一致的:顺序无关紧要,例如@English public @Deprecated String @NonEmpty [] 是一个正确的声明,其中@English 适用于组件类型String(假设它是为TYPE_USE 声明的),@Deprecated 适用于整个字段。
    猜你喜欢
    • 2020-05-06
    • 2017-02-13
    • 2014-08-14
    • 2015-05-13
    • 2023-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多