Java 检查类型是否为泛型

Java 检查类型是否为泛型,java,generics,Java,Generics,我试图实现一个代码分析器,它将另一个Java文件作为输入。对于每个变量声明,我想检查变量所属的类型是否为泛型。有没有一个简单的方法可以做到这一点 例如,我希望它: isGenericType("HashSet") -> true isGenericType("int") -> false 我可以创建一个包含所有泛型类型的注册表,但问题是如果我实现一个自定义泛型类型,那么每次都必须更新注册表。有什么简单的解决方法吗?即使HashSet可以是泛型的,类型HashSet本身(没有)也是原

我试图实现一个代码分析器,它将另一个Java文件作为输入。对于每个变量声明,我想检查变量所属的类型是否为泛型。有没有一个简单的方法可以做到这一点

例如,我希望它:

isGenericType("HashSet") -> true
isGenericType("int") -> false

我可以创建一个包含所有泛型类型的注册表,但问题是如果我实现一个自定义泛型类型,那么每次都必须更新注册表。有什么简单的解决方法吗?

即使
HashSet
可以是泛型的,类型
HashSet
本身(没有
)也是原始类型。因此,我将采用扫描声明变量的实际类型的方法,可能对该类型应用正则表达式,以查看尖括号是否存在:

isGenericType --> matches the pattern [a-zA-Z_\$][\w\$]*<[a-zA-Z_\$][\w\$]*>
isGenericType-->与模式[a-zA-Z\$][\w\$]匹配*
如果要严格说明有效标识,可以在以下位置检查其定义:

标识符:

IdentifierChars,但不是关键字或BooleanLiteral或NullLiteral

识别卡:

JavaLetter{JavaLetterOrdGit}

JavaLetter:

作为“Java字母”的任何Unicode字符

JavaLetterOrdGit:

“Java字母或数字”的任何Unicode字符

“Java字母”是方法
character.isJavaIdentifierStart(int)
返回true的字符

“Java字母或数字”是方法
character.isJavaIdentifierPart(int)
返回true的字符


我认为这与您正在寻找的类似:

它为
java.util.HashSet
打印
true

但是
java.lang.Object
false

public class TestGenerics
{
    public static boolean isGenericType(String s) throws ClassNotFoundException
    {
        Class c = Class.forName(s);
        return c.getTypeParameters().length > 0;
    }

    public static void main(String[] args) throws ClassNotFoundException 
    {
        System.out.println(isGenericType("java.util.HashSet"));
        System.out.println(isGenericType("java.lang.Object"));
    }
}

这将按对象、类或类名进行测试

更新:基于有用的注释对该代码进行的几次修订提供了一些有趣的测试用例。然而,问题的正确解决最终取决于问题的定义。请参阅先前的修订和注释

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;

public class GenericTest
{
    public static void main(String[] args)
    {
        try
        {
            new GenericTest().testAll();
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }

        System.exit(0);
    }

    public void testAll() throws ClassNotFoundException, InstantiationException, IllegalAccessException
    {
        Object a = new HashMap<String, Object>();
        Object b = new HashMap();
        int c = 0;

        isGeneric(a);
        System.out.println("\n");
        isGeneric(b);
        System.out.println("\n");
        isGeneric(c);
        System.out.println("\n");
        isGeneric("java.util.HashMap");
        System.out.println("\n");
        isGeneric("java.lang.Integer");
        System.out.println("\n");

        isGeneric(new TestA());
        System.out.println("\n");
        isGeneric(new TestB());
        System.out.println("\n");
        isGeneric(new TestB<String>());
        System.out.println("\n");
        isGeneric(new TestC());
        System.out.println("\n");
        isGeneric(new TestD());
        System.out.println("\n");
        isGeneric(new TestE());
        System.out.println("\n");

        return;
    }

    public static void isGeneric(String className) throws ClassNotFoundException, InstantiationException, IllegalAccessException
    {
        GenericTest.isGeneric(Class.forName(className));
        return;
    }

    public static boolean isGeneric(Object o)
    {
        return isGeneric(o.getClass());
    }

    public static boolean isGeneric(Class<?> c)
    {
        boolean hasTypeParameters = hasTypeParameters(c);
        boolean hasGenericSuperclass = hasGenericSuperclass(c);
//      boolean hasGenericSuperinterface = hasGenericSuperinterface(c);
//      boolean isGeneric = hasTypeParameters || hasGenericSuperclass || hasGenericSuperinterface;
        boolean isGeneric = hasTypeParameters || hasGenericSuperclass;

        System.out.println(c.getName() + " isGeneric: " + isGeneric);

        return isGeneric;
    }

    public static boolean hasTypeParameters(Class<?> c)
    {
        boolean flag = c.getTypeParameters().length > 0;
        System.out.println(c.getName() + " hasTypeParameters: " + c.getTypeParameters().length);
        return flag;
    }

    public static boolean hasGenericSuperclass(Class<?> c)
    {
        Class<?> testClass = c;

        while (testClass != null)
        {
            Type t = testClass.getGenericSuperclass();

            if (t instanceof ParameterizedType)
            {
                System.out.println(c.getName() + " hasGenericSuperclass: " + t.getClass().getName());
                return true;
            }

            testClass = testClass.getSuperclass();
        }

        return false;
    }

    public static boolean hasGenericSuperinterface(Class<?> c)
    {
        for (Type t : c.getGenericInterfaces())
        {
            if (t instanceof ParameterizedType)
            {
                System.out.println(c.getName() + " hasGenericSuperinterface: " + t.getClass().getName());
                return true;
            }
        }

        return false;
    }

    public interface TestX<X> { }

    public interface TestY extends TestX<String> { }

    public class TestA implements TestY { }

    public class TestB<V> extends TestA { }

    public class TestC extends TestB<String> { }

    public class TestD extends TestA { }

    public class TestE extends TestC { }
}

其他答案似乎侧重于使用正则表达式或反射,但这似乎需要一个解析器。这就是我建议你使用的

例如,Eclipse有其Java开发工具(JDT)插件,它为您提供了正确执行此操作所需的所有解析工具。您所要做的就是要求JDT为您提供一个启用绑定的抽象语法树(AST)。无论何时声明变量,都会为声明的变量类型获得一个ITypeBinding,为实例化的类型获得另一个ITypeBinding(如果在声明中实例化了变量)

ITypeBinding有一些方法可以告诉你它是否是泛型的、参数化的等等

您还将获得类型参数,以备需要

还有其他java解析器选项,但这是我熟悉的一个

=============================================

使用EclipseJDT的特定结果

案例1:
HashSet h1=newhashset()

案例2:
HashSet h2=new HashSet()

案例3:
HashSet h3=newhashset()

正如目前所理解的,本练习的目标是将案例1识别为非泛型(它是原始的),将案例2和3识别为泛型(它们具有类型参数,但在案例2中,类型参数是隐含的)

仅查看从
新哈希集生成的节点…

这三种情况都会生成ClassInstanceCreation实例

案例1和案例2有0个类型参数,而案例3有1个类型参数

案例1的ITypeBinding标记为raw,而案例2和案例3的ITypeBinding标记为参数化这构成了成功,因为我们有办法区分案例1和案例2和案例3

案例1的ITypeBinding有0个类型参数,而案例2和案例3的ITypeBinding中都有1个类型参数。注意,案例2在AST本身中有0个类型参数,但绑定有一个类型参数。这允许我们区分显式类型参数和菱形运算符的使用

完成此任务所需的所有信息都可以从AST和绑定中获得


现在坏消息来了:绑定只有在字节码可用时才可用,也就是说,这不是一个简单的文本分析练习。

正则表达式应该考虑到类名可以包含数字0-9、uu下划线和美元符号。如果不是,
[a-zA-Z\$]+[a-zA-Z0-9\$]*[\w]*
?@MusicMaster我的正则表达式不是为了精确,只是陈述想法。我的正则表达式以
[a-zA-Z\$]+
开头,表示必须至少有一个字母、美元符号或下划线。@MusicMaster同样,正则表达式可能不是最好的方法。标识符可以有一些“特殊”Unicode字符。是否要标识声明中包含一个或多个类型参数的类型?或者识别类型的泛型用法?例如,正如@manouti所观察到的,HashSet是用类型参数定义的,但可以原始方式使用。那么,您真的希望使用原始哈希集的变量声明作为泛型进行计算吗?不,谢谢您指出这一点。我只想在HashSet是用类型化参数定义的情况下返回true。这个答案要求代码分析器的类加载器能够访问被分析程序使用的所有字节码。这个答案虽然简短,除非@Erick G.Hagstrom指出它也可以访问字节码,否则不适用于自定义类型。理想情况下,我希望在java源文件本身上运行此代码分析器,因为可执行文件很糟糕!这将检查超类是否为参数化类型。OP想知道类本身是否是“泛型的”(这可能意味着参数化,我不确定)
java.util.HashMap hasTypeParameters: 2
java.util.HashMap hasGenericSuperclass: sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
java.util.HashMap isGeneric: true


java.util.HashMap hasTypeParameters: 2
java.util.HashMap hasGenericSuperclass: sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
java.util.HashMap isGeneric: true


java.lang.Integer hasTypeParameters: 0
java.lang.Integer isGeneric: false


java.util.HashMap hasTypeParameters: 2
java.util.HashMap hasGenericSuperclass: sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
java.util.HashMap isGeneric: true


java.lang.Integer hasTypeParameters: 0
java.lang.Integer isGeneric: false


GenericTest$TestA hasTypeParameters: 0
GenericTest$TestA isGeneric: false


GenericTest$TestB hasTypeParameters: 1
GenericTest$TestB isGeneric: true


GenericTest$TestB hasTypeParameters: 1
GenericTest$TestB isGeneric: true


GenericTest$TestC hasTypeParameters: 0
GenericTest$TestC hasGenericSuperclass: sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
GenericTest$TestC isGeneric: true


GenericTest$TestD hasTypeParameters: 0
GenericTest$TestD isGeneric: false


GenericTest$TestE hasTypeParameters: 0
GenericTest$TestE hasGenericSuperclass: sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
GenericTest$TestE isGeneric: true