2015-06-16 46 views
8

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

例如,我想为它:

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

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

+1

您是否想要在声明中标识具有一个或多个类型参数的类型?或者确定一种类型的通用用法?例如,正如@manouti所观察到的,HashSet是用一个类型参数定义的,但可以以一种原始的方式使用。那么你真的想要一个使用原始HashSet的变量声明来评估为泛型吗? –

+0

不,谢谢你指出。我只想在使用类型化参数定义HashSet时返回true。 – akhiljain

回答

6

即使HashSet可以通用,类型HashSet本身(无<T>)是原始类型。因此,我将与扫描的实际类型声明的变量,也许施加一个正则表达式的类型看的办法去,如果尖括号存在:

isGenericType --> matches the pattern [a-zA-Z_\$][\w\$]*<[a-zA-Z_\$][\w\$]*> 

如果要严格地占有效IDENTIFER ,你可以在Java Language Specification检查其定义:

标识符:

IdentifierChars而不是关键字或博oleanLiteral或NullLiteral

IdentifierChars:

JavaLetter {JavaLetterOrDigit}

JavaLetter:

任何Unicode字符是一个 “Java的字母”

JavaLetterOrDigit:

这是一个 “Java的字母或数字的”

任何Unicode字符

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

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

+1

正则表达式应该考虑到类名可以包含数字0-9,_下划线和$美元符号。 – MusicMaster

+1

如果不是,[a-zA-Z _ \ $] + [a-zA-Z0-9 _ \ $] * [\ w] * <[\ w] * [a-zA-Z _ \ $] + [a-zA-Z0-9 _ \ $] * [\ w] *>'? – MusicMaster

+0

@MusicMaster我的正则表达式并不是完全准确的,只是说明这个想法。 – manouti

5

我认为这是类似于你在找什么:

它打印truejava.util.HashSet

falsejava.lang.Object

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")); 
    } 
} 
+2

此答案要求代码分析器的类加载器可以访问被分析程序使用的所有字节代码。 –

+0

这个答案虽然简短,但对于自定义类型不起作用,除非@Erick G. Hagstrom指出它也可以访问字节码。我想理想地在java源文件本身上运行这个代码分析器,因为可执行文件只是IMO而已! – akhiljain

4

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

更新:该代码基于有用的评论几次修订提供了一些有趣的测试用例。但是,最终问题的恰当解决方案取决于问题的确切定义。请参阅之前的修订和评论。

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 { } 
} 

运行上面的代码的结果:

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 
+1

这将检查超类是否是ParameterizedType。 OP想知道类本身是否是“Generic”(可能意味着参数化,我还不确定)。为什么不使用c instanceof ParameterizedType? –

+2

@ ErickG.Hagstrom好点。您对'getGenericSuperclass()'是正确的,但是,对于原始类:'不兼容的条件操作数类型Class和ParameterizedType',c instanceof ParameterizedType会导致编译错误。我修改了我的例子,根据泛型参数的数量进行测试,如euginioy提供的答案。但是,一个完整的解决方案也可能会测试超类(请参阅上面的TestC的情况,其中“不是泛型”,但其超类是通用的)。 – vallismortis

+1

好吧,很高兴知道c instanceof ParameterizedType不工作。使用$的类型参数是有意义的。但是如果你想测试超类,你不想测试整个层次结构(超类的超类,实现的接口和它们的超类)吗? –

3

其他的答案似乎把重点放在使用正则表达式或反射,但什么这似乎要求是一个分析器。所以这就是我推荐你使用的。

举例来说,Eclipse拥有自己的Java开发工具(JDT)插件中提供了所有的你需要做的权利解析工具。您只需要让JDT为您提供启用绑定的抽象语法树(AST)。每当一个变量被声明时,你会得到一个ITypeBinding作为变量的声明类型,另一个作为实例化类型(如果变量在声明中被实例化)。

而且ITypeBinding有告诉你,无论是通用的,参数化方法等

您还可以获得类型的参数,如果你需要的。

还有其他的Java解析器的选择为好,但这是我所熟悉的人。

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

HashSet<String> h1 = new HashSet();

案例2:HashSet<String> h2 = new HashSet<>();

案例3:

使用Eclipse JDT

案例1的具体结果HashSet<String> h3 = new HashSet<String>();

按照目前的了解,这项工作的目的是识别案例1 as不是通用的(它是原始的),以及情况2和3是通用的(它们具有类型参数,但是在情况2中隐含了类型参数)。

new HashSet...

三个案例产生的节点仅仅看制作ClassInstanceCreation实例。

情况1和情况2具有0类型参数,而外壳3具有1个类型参数。

的ITypeBinding案例1的被标记为原始的,而对于情况2和3它们被标记参数。 这构成了成功,因为我们有一种区分案例1和案例2和案例3的方法。

案例1的ITypeBinding有0个类型参数,而案例2和3在ITypeBinding中都有1个类型参数。请注意,情况2在AST本身中有0个类型参数,但绑定有一个类型参数。这允许区分明确的类型参数和钻石操作符的使用。

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

现在这里有个坏消息:绑定仅在字节代码可用时才可用,即这不是简单的文本分析练习。

+0

我其实尝试使用Lombok解析器来获取AST。但它们似乎没有TypeParameters的单独节点。另外,我基本上想要的以下三种类型的实例之间进行区分: '的HashSet H =新的HashSet()' '的HashSet H =新的HashSet <>()' '的HashSet H =新的HashSet () ' 我已经看过的解析器会将前两种情况减少到AST中的相同表示形式 – akhiljain

+1

我更新了答案,以向您展示可以从Eclipse JDT中的ITypeBinding获得的结果。请注意,绑定仅在字节码可访问时才可用,所以在这方面并不比反射好。但它优于反思,因为我们可以区分原始实例和使用钻石算子的实例。 –