2016-09-08 11 views
5

Wywołanie java.lang.reflect.Type.toString() zapewnia bardzo ładne reprezentację typów generycznych:Class.forName równoważne do tworzenia ParameterizedType jest z String

@Test 
public void typeToStringWithTypeToken() { 
    assertEquals("java.util.List<java.lang.String>", new TypeToken<List<String>>() {}.getType().toString()); 
} 

Co potrzebne jest odwrotnością Type.toString() metody, czyli metodę, która może tworzyć Type S z danym ciągiem reprezentacja:

public static Type parseTypeString(String typeString) throws ClassNotFoundException { 
    if (typeString.indexOf('<') == -1) { 
     return Class.forName(typeString); 
    } 
    // ??? how to implement rest 
    throw new IllegalStateException("not implemented"); 
} 

które mogą przekazać następujące testy z wyspecjalizowanych typów:

@Test 
public void justSimpleClassName() throws Exception { 
    assertEquals(Integer.class, parseTypeString("java.lang.Integer")); 
} 

@Test 
public void listOfInteger() throws Exception { 
    assertEquals(new TypeToken<List<Integer>>() {}.getType(), parseTypeString("java.util.List<java.lang.Integer>")); 
} 

@Test 
public void complexType() throws Exception { 
    assertEquals(new TypeToken<Map<List<Integer>, Set<String>>>() {}.getType(), parseTypeString("java.util.Map<java.util.List<java.lang.Integer>, java.util.Set<java.lang.String>>")); 
} 

Nie mogę znaleźć biblioteki lub pytania SO, które rozwiązują ten problem.

Odpowiedz

2

Cóż ... Można to zrobić samemu za pomocą na przykład antlr4 stosując następujące gramatyki

type returns[ClassBuilder value] 
    : cls=CLASS   { $value = ClassBuilder.parse($cls.text); } 
    | cls=CLASS   { $value = ClassBuilder.parse($cls.text); } 
     LT head=type  { $value.add($head.value); } 
     (COMMA tail=type { $value.add($tail.value); })* GT 
    ; 

GT : '>' 
    ; 

LT : '<' 
    ; 

COMMA 
    : ',' 
    ; 

CLASS 
    : ('a'..'z'|'A'..'Z') ('a'..'z'|'A'..'Z'|'0'..'9'|'$'|'.'|'_')* 
    ; 

Gdzie ClassBuilder wygląda

public class ClassBuilder { 
    private final Class<?> clazz; 
    private final List<ClassBuilder> parameters = new ArrayList<>(); 

    public ClassBuilder(final Class<?> clazz) { 
     this.clazz = clazz; 
    } 

    public static ClassBuilder parse(String clazz) { 
     try { 
      return new ClassBuilder(Class.forName(clazz)); 
     } catch (ClassNotFoundException e) { 
      // do better handling here 
      throw new IllegalStateException(e); 
     } 
    } 

    public void add(ClassBuilder builder) { 
     parameters.add(builder); 
    } 

    public Type build() { 
     // class is not parametrized 
     if (parameters.isEmpty()) { 
      return clazz; 
     } 

     return ParameterizedTypeImpl.make(
       clazz, 
       parameters.stream() 
          .map(ClassBuilder::build) 
          .toArray(Type[]::new), 
       null); 
    } 
} 

i wreszcie analizować ciąg

CharStream stream = 
    new ANTLRInputStream(
     "java.util.Map<java.util.List<java.lang.Integer>, java.util.Set<java.lang.String>>" 
    ); 

TokenStream tokenStream = 
    new CommonTokenStream(new ParametrizedTypeLexer(stream)); 
ParametrizedTypeParser parser = 
    new ParametrizedTypeParser(tokenStream); 

assertEquals(
     new TypeToken<Map<List<Integer>, Set<String>>>() {}.getType(), 
     parser.type().value.build() 
); 

Możesz zobaczyć działający przykład: here.

UWAGA

CLASS reguła lexer mogą być nieco niedokładne. Ten parser stosuje się tylko do klas nieprostych i typów sparametryzowanych, ale oczywiście rozszerzasz go również o obsługę typów wieloznacznych.

+0

To wspaniała odpowiedź! Przetestuję to i udzielę dalszych informacji zwrotnych. Ludzie mogą potrzebować zauważyć trudną część implementacji, używając 'ParameterizedTypeImpl' z ** private ** API ([ParameterizedTypeImpl.java] (http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/ 687fd7c7986d/src/share/classes/sun/reflect/generics/reflectiveObjects/ParameterizedTypeImpl.java)). Domyślam się, że ludzie w Javie próbują zniechęcić nas zwykłych śmiertelników wykonujących czarną magię, nie dostarczając publicznej implementacji 'ParameterizedType'. – b10y

+0

@ b10y afaik jest implementacja ParameterizedType w guava, ale nie ma problemu z jej implementacją za pomocą rąk – vsminkov

1

Dane wyjściowe java.lang.reflect.Type.toString() są zależne od implementacji.
Zachowanie, które widzisz, jest częścią Gonsona ParameterizedTypeImpl
Ze względu na wymazywanie typów, klasy w Javie nie mają parametrów typów w czasie wykonywania.
Numer Type firmy gson nie odpowiada rzeczywistemu załadowanemu Class.

Aby uzyskać dodatkowe informacje, zobacz Get Type of ParameterizedType from generic.

Powiązane problemy