2015-06-16 84 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獲得的結果。請注意,綁定僅在字節碼可訪問時纔可用,所以在這方面並不比反射好。但它優於反思,因爲我們可以區分原始實例和使用鑽石算子的實例。 –