2015-01-25 7 views
7

Sto cercando di ragionare sui generici in fase di esecuzione. Esistono numerose librerie eccellenti (ad es., gentyref, ClassMate e Guava). Tuttavia, il loro utilizzo è un po 'sopra la mia testa.Come si genera un'espressione generica accurata per un campo di classe Java?

In particolare, voglio estrarre un'espressione che corrisponde a un campo particolare nel contesto di una sottoclasse.

Ecco un esempio utilizzando gentyref:

import com.googlecode.gentyref.GenericTypeReflector; 

import java.lang.reflect.Field; 
import java.lang.reflect.Type; 

public class ExtractArguments { 

    public static class Thing<T> { 
    public T thing; 
    } 

    public static class NumberThing<N extends Number> extends Thing<N> { } 

    public static class IntegerThing extends NumberThing<Integer> { } 

    public static void main(final String... args) throws Exception { 
    final Field thing = Thing.class.getField("thing"); 

    // naive type without context 
    Class<?> thingClass = thing.getType(); // Object 
    System.out.println("thing class = " + thingClass); 
    Type thingType = thing.getGenericType(); // T 
    System.out.println("thing type = " + thingType); 
    System.out.println(); 

    // exact types without adding wildcard 
    Type exactThingType = GenericTypeReflector.getExactFieldType(thing, Thing.class); 
    System.out.println("exact thing type = " + exactThingType); 
    Type exactNumberType = GenericTypeReflector.getExactFieldType(thing, NumberThing.class); 
    System.out.println("exact number type = " + exactNumberType); 
    Type exactIntegerType = GenericTypeReflector.getExactFieldType(thing, IntegerThing.class); 
    System.out.println("exact integer type = " + exactIntegerType); 
    System.out.println(); 

    // exact type with wildcard 
    final Type wildThingType = GenericTypeReflector.addWildcardParameters(Thing.class); 
    final Type betterThingType = GenericTypeReflector.getExactFieldType(thing, wildThingType); 
    System.out.println("better thing type = " + betterThingType); 
    final Type wildNumberType = GenericTypeReflector.addWildcardParameters(NumberThing.class); 
    final Type betterNumberType = GenericTypeReflector.getExactFieldType(thing, wildNumberType); 
    System.out.println("better number type = " + betterNumberType); 
    final Type wildIntegerType = GenericTypeReflector.addWildcardParameters(IntegerThing.class); 
    final Type betterIntegerType = GenericTypeReflector.getExactFieldType(thing, wildIntegerType); 
    System.out.println("better integer type = " + betterIntegerType); 
    System.out.println(); 

    System.out.println("desired thing type = T"); 
    System.out.println("desired number thing type = N extends Number"); 
    System.out.println("desired integer thing type = Integer"); 
    } 

} 

Ed ecco l'output:

thing class = class java.lang.Object 
thing type = T 

exact thing type = class java.lang.Object 
exact number type = class java.lang.Object 
exact integer type = class java.lang.Integer 

better thing type = capture of ? 
better number type = capture of ? 
better integer type = class java.lang.Integer 

desired thing type = T 
desired number thing type = N extends Number 
desired integer thing type = Integer 

So che l'oggetto betterThingTypeType (un gentyref-specific implementation) è più sofisticato di quello che viene mostrato da toString() Qui. Ma suppongo di dover invocare di nuovo getExactFieldType con un non jolly Type per ottenere quello che sto cercando.

Il mio requisito principale è che ho bisogno di un'espressione che potrebbe diventare parte di un file sorgente generato dal codice che possa essere compilato con successo, o almeno compilato con modifiche minime. Sono aperto a utilizzare qualsiasi libreria sia la migliore per il lavoro.

+0

non ho una risposta completa, ma sono riuscito a ottenere il risultato desiderato per l'esempio dato utilizzando la riflessione, vedi [questa pasta] (http: // pastebin.com/snt7ZtsF) (esempio eseguibile). Il codice potrebbe fornire alcuni spunti nella direzione per una risposta adeguata. – vanOekel

risposta

5

Per ottenere questo tipo di informazioni, è necessario determinare se un tipo effettivo o meno (ad esempio Integer) è stato fornito al parametro di tipo generico. In caso contrario, sarà necessario ottenere il nome del parametro del tipo, come è noto nella classe necessaria, insieme a eventuali limiti.

Questo risulta piuttosto complicato. Ma prima, passiamo ad alcune delle tecniche e dei metodi di riflessione che useremo nella soluzione.

In primo luogo, Field's getGenericType() method restituisce le informazioni Type necessarie. Qui, Type può essere un semplice Class se una classe reale viene fornita come tipo, ad es. Integer thing;, oppure può essere un TypeVariable, che rappresenta un parametro di tipo generico come lo hai definito in Thing, ad es. T thing;.

Se si tratta di un tipo generico, allora avremo bisogno di sapere quanto segue:

  • In quale classe di questo tipo è stato originariamente dichiarato. Questo viene richiamato con Field's getDeclaringClass method.
  • In ciascuna sottoclasse, dalla classe originale che ha dichiarato Field, quali argomenti tipo sono stati forniti nella clausola extends. Questi argomenti di tipo possono essere tipi reali come Integer oppure possono essere parametri di tipo generico della propria classe. Complicando argomenti, questi parametri di tipo possono essere chiamati in modo diverso e potrebbero essere dichiarati in un ordine diverso rispetto alla superclasse. I dati della clausola extends possono essere recuperati chiamando Class's getGenericSuperclass() method, che restituisce un Type che può essere un semplice Class, ad esempio Object oppure potrebbe essere un ParameterizedType, ad es. Thing<N> o NumberThing<Integer>.
  • I parametri di tipo di una classe possono essere richiamati con Class's getTypeParameters() method, che restituisce un array di TypeVariable s.
  • Da un TypeVariable è possibile estrarre il nome, ad es.T e i limiti, come una matrice di oggetti Type, ad es. Number per N extends Number.

Per il parametro di tipo generico, abbiamo bisogno di tenere traccia di quali sottoclasse argomenti di tipo corrispondono al parametro originale di tipo generico, giù attraverso la gerarchia delle classi, fino a che non sia raggiungere l'originale Class, in cui riportiamo il parametro di tipo generico con qualsiasi limite, o raggiungiamo un oggetto effettivo Class, in cui riportiamo la classe.

Ecco un programma, basato sulle classi, che riporta le informazioni desiderate.

Deve creare uno Stack di Class es, passando dalla classe originale fino alla classe che dichiara il campo. Quindi apre le classi, scendendo lungo la gerarchia delle classi. Trova l'argomento type nella classe corrente che corrisponde al parametro type della classe precedente, annotando eventuali modifiche al nome del parametro di tipo e nuove posizioni del nuovo argomento di tipo fornito dalla classe corrente. Per esempio. T diventa N extends Number passando da Thing a NumberThing. Le iterazioni del ciclo si interrompono quando l'argomento type è una classe effettiva, ad es. Integer o se abbiamo raggiunto la classe originale, nel qual caso riportiamo il nome del parametro tipo e qualsiasi limite, ad es. N extends Number.

Ho incluso anche un paio di classi addizionali, Superclass e Subclass, dove Subclass inverte l'ordine degli argomenti di tipo generico che sono dichiarati in Superclass, per fornire ulteriori test. Ho anche incluso SpecificIntegerThing (non generico), come un caso di test in modo che l'iterazione si fermi a IntegerThing, per segnalare Integer, prima che raggiunga SpecificIntegerThing nello stack.

// Just to have some bounds to report. 
import java.io.Serializable; 
import java.util.RandomAccess; 

// Needed for the implementation. 
import java.lang.reflect.*; 
import java.util.Arrays; 
import java.util.Stack; 

public class ExtractArguments { 

    public static class Thing<T> { 
     public T thing; 
    } 

    public static class NumberThing<N extends Number> extends Thing<N> {} 

    public static class IntegerThing extends NumberThing<Integer> {} 

    public static class SpecificIntegerThing extends IntegerThing {} 

    public static class Superclass<A extends Serializable, B> { 
     public A thing; 
    } 

    // A and B are reversed in the extends clause! 
    public static class Subclass<A, B extends RandomAccess & Serializable> 
     extends Superclass<B, A> {} 

    public static void main(String[] args) 
    { 
     for (Class<?> clazz : Arrays.asList(
       Thing.class, NumberThing.class, 
       IntegerThing.class, SpecificIntegerThing.class, 
       Superclass.class, Subclass.class)) 
     { 
     try 
     { 
      Field field = clazz.getField("thing"); 
      System.out.println("Field " + field.getName() + " of class " + clazz.getName() + " is: " + 
        getFieldTypeInformation(clazz, field)); 
     } 
     catch (NoSuchFieldException e) 
     { 
      System.out.println("Field \"thing\" is not found in class " + clazz.getName() + "!"); 
     } 
     } 
    } 

Il metodo getFieldTypeInformation fa il lavoro con la pila.

private static String getFieldTypeInformation(Class<?> clazz, Field field) 
    { 
     Type genericType = field.getGenericType(); 
     // Declared as actual type name... 
     if (genericType instanceof Class) 
     { 
     Class<?> genericTypeClass = (Class<?>) genericType; 
     return genericTypeClass.getName(); 
     } 
     // .. or as a generic type? 
     else if (genericType instanceof TypeVariable) 
     { 
     TypeVariable<?> typeVariable = (TypeVariable<?>) genericType; 
     Class<?> declaringClass = field.getDeclaringClass(); 
     //System.out.println(declaringClass.getName() + "." + typeVariable.getName()); 

     // Create a Stack of classes going from clazz up to, but not including, the declaring class. 
     Stack<Class<?>> stack = new Stack<Class<?>>(); 
     Class<?> currClass = clazz; 
     while (!currClass.equals(declaringClass)) 
     { 
      stack.push(currClass); 
      currClass = currClass.getSuperclass(); 
     } 
     // Get the original type parameter from the declaring class. 
     int typeVariableIndex = -1; 
     String typeVariableName = typeVariable.getName(); 
     TypeVariable<?>[] currTypeParameters = currClass.getTypeParameters(); 
     for (int i = 0; i < currTypeParameters.length; i++) 
     { 
      TypeVariable<?> currTypeVariable = currTypeParameters[i]; 
      if (currTypeVariable.getName().equals(typeVariableName)) 
      { 
       typeVariableIndex = i; 
       break; 
      } 
     } 

     if (typeVariableIndex == -1) 
     { 
      throw new RuntimeException("Expected Type variable \"" + typeVariable.getName() + 
        "\" in class " + clazz + "; but it was not found."); 
     } 

     // If the type parameter is from the same class, don't bother walking down 
     // a non-existent hierarchy. 
     if (declaringClass.equals(clazz)) 
     { 
      return getTypeVariableString(typeVariable); 
     } 

     // Pop them in order, keeping track of which index is the type variable. 
     while (!stack.isEmpty()) 
     { 
      currClass = stack.pop(); 
      // Must be ParameterizedType, not Class, because type arguments must be 
      // supplied to the generic superclass. 
      ParameterizedType superclassParameterizedType = (ParameterizedType) currClass.getGenericSuperclass(); 
      Type currType = superclassParameterizedType.getActualTypeArguments()[typeVariableIndex]; 
      if (currType instanceof Class) 
      { 
       // Type argument is an actual Class, e.g. "extends ArrayList<Integer>". 
       currClass = (Class) currType; 
       return currClass.getName(); 
      } 
      else if (currType instanceof TypeVariable) 
      { 
       TypeVariable<?> currTypeVariable = (TypeVariable<?>) currType; 
       typeVariableName = currTypeVariable.getName(); 
       // Reached passed-in class (bottom of hierarchy)? Report it. 
       if (currClass.equals(clazz)) 
       { 
        return getTypeVariableString(currTypeVariable); 
       } 
       // Not at bottom? Find the type parameter to set up for next loop. 
       else 
       { 
        typeVariableIndex = -1; 
        currTypeParameters = currClass.getTypeParameters(); 
        for (int i = 0; i < currTypeParameters.length; i++) 
        { 
        currTypeVariable = currTypeParameters[i]; 
        if (currTypeVariable.getName().equals(typeVariableName)) 
        { 
         typeVariableIndex = i; 
         break; 
        } 
        } 

        if (typeVariableIndex == -1) 
        { 
        // Shouldn't get here. 
        throw new RuntimeException("Expected Type variable \"" + typeVariable.getName() + 
         "\" in class " + currClass.getName() + "; but it was not found."); 
        } 
       } 
      } 
     } 
     } 
     // Shouldn't get here. 
     throw new RuntimeException("Missed the original class somehow!"); 
    } 

Il metodo getTypeVariableString aiuta a generare il nome del parametro tipo ed eventuali limiti.

// Helper method to print a generic type parameter and its bounds. 
    private static String getTypeVariableString(TypeVariable<?> typeVariable) 
    { 
     StringBuilder buf = new StringBuilder(); 
     buf.append(typeVariable.getName()); 
     Type[] bounds = typeVariable.getBounds(); 
     boolean first = true; 
     // Don't report explicit "extends Object" 
     if (bounds.length == 1 && bounds[0].equals(Object.class)) 
     { 
     return buf.toString(); 
     } 
     for (Type bound : bounds) 
     { 
     if (first) 
     { 
      buf.append(" extends "); 
      first = false; 
     } 
     else 
     { 
      buf.append(" & "); 
     } 
     if (bound instanceof Class) 
     { 
      Class<?> boundClass = (Class) bound; 
      buf.append(boundClass.getName()); 
     } 
     else if (bound instanceof TypeVariable) 
     { 
      TypeVariable<?> typeVariableBound = (TypeVariable<?>) bound; 
      buf.append(typeVariableBound.getName()); 
     } 
     } 
     return buf.toString(); 
    } 
} 

Questo l'output:

Field thing of class ExtractArguments$Thing is: T 
Field thing of class ExtractArguments$NumberThing is: N extends java.lang.Number 
Field thing of class ExtractArguments$IntegerThing is: java.lang.Integer 
Field thing of class ExtractArguments$SpecificIntegerThing is: java.lang.Integer 
Field thing of class ExtractArguments$Superclass is: A extends java.io.Serializable 
Field thing of class ExtractArguments$Subclass is: B extends java.util.RandomAccess & java.io.Serializable 
+0

Grazie, mi fa molto piacere che tu abbia il tempo di approfondire e spiegare anche alcuni dettagli dell'API generici. Esaminerò la tua risposta in dettaglio nei prossimi giorni e pubblicherò in seguito la posizione della mia soluzione finale su GitHub, a beneficio di chiunque altro sia interessato. Saluti! – ctrueden

+0

Grazie ancora per l'implementazione. Ho iniziato a scrivere test contro di esso e ho scoperto che un caso che non gestisce è generico annidato. Ho iniziato a provare a estendere il codice per supportarli, ma mi sembra in modo preponderante come una reinvenzione delle ruote, quindi ho un messaggio [sull'elenco degli utenti di ClassMate] (https://groups.google.com/d/msg/java-classmate -users/J4AvRCLvsZg/INoVQnN-2zYJ) chiedendo se questa libreria supporta già questo tipo di cose. Seguirò di nuovo qui mentre imparo di più. – ctrueden