问题 类型是可分配的,是子类型的误解


在使用Java 6 API编写注释处理器时,我遇到了以特定方式处理所有Maps的需求,但我显然误解了API的目的或如何调用它。这是让我不开心的代码:

import javax.lang.model.element.Element;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.annotation.processing.ProcessingEnvironment;
...

public String doThing(Element el, ProcessingEnvironment processingEnv) {
    // Utilities from the ProcessingEnvironment
    Types typeUtils = processingEnv.getTypeUtils();
    Elements elementUtils = processingEnv.getElementUtils();

    // The type of the element I'm handling
    TypeMirror elType = el.asType();

    // Compare the element's type to Map
    TypeMirror mapType = elementUtils.getTypeElement("java.util.Map").asType();

    System.out.println(elType + " > " + mapType + " = " + typeUtils.isSubtype(elType, mapType));
    System.out.println(mapType + " > " + elType + " = " + typeUtils.isSubtype(mapType, elType));
    System.out.println(elType + " > " + mapType + " = " + typeUtils.isAssignable(elType, mapType));
    System.out.println(mapType + " > " + elType + " = " + typeUtils.isAssignable(mapType, elType));

    // Compare the element's type to HashMap
    TypeMirror hashmapType = elementUtils.getTypeElement("java.util.HashMap").asType();

    System.out.println(elType + " > " + hashmapType + " = " + typeUtils.isSubtype(elType, hashmapType));
    System.out.println(hashmapType + " > " + elType + " = " + typeUtils.isSubtype(hashmapType, elType));
    System.out.println(elType + " > " + hashmapType + " = " + typeUtils.isAssignable(elType, hashmapType));
    System.out.println(hashmapType + " > " + elType + " = " + typeUtils.isAssignable(hashmapType, elType));


    // Compare the element's type to Object
    TypeMirror objectType = elementUtils.getTypeElement("java.lang.Object").asType();

    System.out.println(elType + " > " + objectType + " = " + typeUtils.isSubtype(elType, objectType));
    System.out.println(objectType + " > " + elType + " = " + typeUtils.isSubtype(objectType, elType));
    System.out.println(elType + " > " + objectType + " = " + typeUtils.isAssignable(elType, objectType));
    System.out.println(objectType + " > " + elType + " = " + typeUtils.isAssignable(objectType, elType));
}

鉴于此,这是它的输出:

java.util.HashMap<K,V> > java.util.Map<K,V> = false
java.util.Map<K,V> > java.util.HashMap<K,V> = false
java.util.HashMap<K,V> > java.util.Map<K,V> = false
java.util.Map<K,V> > java.util.HashMap<K,V> = false

java.util.HashMap<K,V> > java.util.HashMap<K,V> = true
java.util.HashMap<K,V> > java.util.HashMap<K,V> = true
java.util.HashMap<K,V> > java.util.HashMap<K,V> = true
java.util.HashMap<K,V> > java.util.HashMap<K,V> = true

java.util.HashMap<K,V> > java.lang.Object = true
java.lang.Object > java.util.HashMap<K,V> = false
java.util.HashMap<K,V> > java.lang.Object = true
java.lang.Object > java.util.HashMap<K,V> = false

这对我来说非常有意义,除了我希望HashMap元素可以分配给Map的第一个块,我希望HashMap是Map的子类型。

我在这里想念的是什么?


10464
2017-10-05 15:32


起源



答案:


我怀疑这是因为类型变量。 HashMap<String, String> 可分配给 Map<String, String> 但是没有类型变量的具体实例化,你无法确定是否是任意的 HashMap<A,B> 可分配给 Map<X,Y>

如果使用通配符实例化变量,那么您应该得到预期的结果

DeclaredType wildcardMap = typeUtils.getDeclaredType(
    elementUtils.getTypeElement("java.util.Map"),
    typeUtils.getWildcardType(null, null),
    typeUtils.getWildcardType(null, null));

这将为您提供类型镜像 Map<?,?>这一切 HashMap 实例化可分配给。


9
2017-10-05 16:18



你知道,我想,并且我认为,因为通用字母匹配,类型将匹配,但我想不。谢谢! - Patrick
整个方法和@Patrick的实现过于复杂。只是用 types.isAssignable(type, types.erasure(baseGenerifiedType)) (例如,检查类型是否可分配给raw HashMap 代替 HashMap<?, ?>) - user1643723


答案:


我怀疑这是因为类型变量。 HashMap<String, String> 可分配给 Map<String, String> 但是没有类型变量的具体实例化,你无法确定是否是任意的 HashMap<A,B> 可分配给 Map<X,Y>

如果使用通配符实例化变量,那么您应该得到预期的结果

DeclaredType wildcardMap = typeUtils.getDeclaredType(
    elementUtils.getTypeElement("java.util.Map"),
    typeUtils.getWildcardType(null, null),
    typeUtils.getWildcardType(null, null));

这将为您提供类型镜像 Map<?,?>这一切 HashMap 实例化可分配给。


9
2017-10-05 16:18



你知道,我想,并且我认为,因为通用字母匹配,类型将匹配,但我想不。谢谢! - Patrick
整个方法和@Patrick的实现过于复杂。只是用 types.isAssignable(type, types.erasure(baseGenerifiedType)) (例如,检查类型是否可分配给raw HashMap 代替 HashMap<?, ?>) - user1643723


更新(2016年3月):根据@ user1643723的评论,似乎有一个 types.erasure(TypeMirror) 我在2012年没有意识到的图书馆功能。


基于 伊恩的回答,我现在使用以下方法来匹配基本类型,就像我在问题中为Map描述的那样。

TypeElement COLLECTION = elementUtils.getTypeElement("java.util.Collection");
TypeElement MAP = elementUtils.getTypeElement("java.util.Map");
TypeElement VOID = elementUtils.getTypeElement("java.lang.Void");
WildcardType WILDCARD_TYPE_NULL = typeUtils.getWildcardType(null, null);
Map<String,DeclaredType> cachedParentTypes = new HashMap<String, DeclaredType>();

...

public static boolean isA(TypeMirror type, TypeElement typeElement) {

    // Have we used this type before?
    DeclaredType parentType = cachedParentTypes.get(typeElement.getQualifiedName().toString());
    if (parentType == null) {
        // How many generic type parameters does this typeElement require?
        int genericsCount = typeElement.getTypeParameters().size();

        // Fill the right number of types with nulls
        TypeMirror[] types = new TypeMirror[genericsCount];
        for (int i = 0; i < genericsCount; i++) {
            types[i] = WILDCARD_TYPE_NULL;
        }

        // Locate the correct DeclaredType to match with the type
        parentType = typeUtils.getDeclaredType(typeElement, types);

        // Remember this DeclaredType
        cachedParentTypes.put(typeElement.getQualifiedName().toString(), parentType);
    }

    // Is the given type able to be assigned as the typeElement?
    return typeUtils.isAssignable(type, parentType);
}

我调用的就像

if (isA(elType, VOID)) {
    isVoid = true;
} else if (isA(elType, COLLECTION) || elType.getKind() == TypeKind.ARRAY) {
    isCollectionOrArray = true;
} else if (isA(elType, MAP)){
    isMap = true;
}

2
2017-10-06 19:16