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