2009-11-19 3 views
5

J'ai défini une méthode dans une classe:Réflexion Java: Que contient ma collection?

public void setCollection(Collection<MyClass>); 

et dans une autre classe

public void setCollection(Collection<OtherClass>); 

(et vraiment, beaucoup de classes similaires)

Tous sont en cours avec le même superclasse, et j'ai une méthode dans une classe de support où je veux appeler cette méthode et la définir avec des éléments du type de classe correct. Maintenant, je peux obtenir que je mise une collection en faisant

Method setter = ...; 
Class<?> paramClass = setter.getParameterTypes()[0]; // Is Collection in this case 
if(paramClass.equals(Collection.class)) { 
    HashSet col = new HashSet(); 
    // fill the set with something 
    setter.invoke(this, col); 
} 

Mais comment puis-je savoir quelle classe les objets de cette collection doivent appartenir?

Vive

Nik

Répondre

9
Method.getGenericParameterTypes(); 

renvoie un tableau de types acceptés par le paramètre. La complexité augmente exponentiellement à partir de là.

Dans votre cas, cela fonctionnerait:

Method m = Something.class.getMethod("setCollection", Collection.class); 
    Class<?> parameter = (Class<?>) ((ParameterizedType) m.getGenericParameterTypes()[0]).getActualTypeArguments()[0]; 

Mais il y a beaucoup de potentiel gotchas là, selon la façon dont le paramètre a été déclaré. Si c'est un simple comme dans votre exemple, génial. Si ce n'est pas le cas, il y a un tas de types dont vous devez tenir compte, à la fois dans la méthode getGenericParameterTypes() et dans la méthode getActualTypeArguments(). Il devient très poilu et moche très vite.

+0

Merci beaucoup, cela a résolu mon problème! La complexité ajoutée ici va réduire ma complexité globale, alors je prends mon chapeau pour vous et je vous remercie sincèrement. :-) – niklassaers

1

Java implémente génériques avec l'effacement de type. Le compilateur utilise ces informations de type uniquement pour les contrôles de sécurité, le code d'octet ne contient aucune information sur les types génériques. Par conséquent, le runtime ne le sait pas non plus, vous ne pouvez donc pas le récupérer par réflexion.

Vous pouvez trouver des informations supplémentaires here.

Mise à jour:

Pour me corriger, considérer cette méthode:

public <T> String doSomething(T first, int second) { 
    return first.toString(); 
} 

Dans le code octet, cela aura la signature suivante (notez le type T):

<T:Ljava/lang/Object;>(TT;I)Ljava/lang/String 

Donc ce que j'ai dit ci-dessus n'est vrai qu'avec cette exception.

0

Vous semblez déjà connaître le setter. Donc, il me semble que, dans ce cas, MyClass et OtherClass (pas la collection, mais les éléments) partagent quelque chose d'une interface similaire. Dans ce cas, vous n'avez pas vraiment besoin de connaître le type exact.

0

Comme déjà expliqué, le 'Type de collection' est effacé, lors de l'exécution ce n'est rien d'autre qu'une collection de types d'objets.

Si vous êtes autorisé à modifier légèrement la signature du message dans vos sous-classes pour

public void setCollection(Collection<T> aCollection, Class<T> elementType); 

vous pouvez alors passer un second paramètre qui contient les informations de la « Collection Type », utilisation serait comme:

public void setCollection(Collection<MyClass> myClassCollection, 
          Class<MyClass> clazz); 

et de l'utiliser:

Collection<MyClass> myClassCollection = new ArrayList<MyClassCollection>(); 
setCollection(myClassCollection, MyClass.class); 

Mais d'autre part - usu ally vous n'avez pas à vous en soucier car le type est effacé à l'exécution et vous pouvez mettre n'importe quel objet dans une collection paramétrée, tant que vous n'êtes pas attrapé par le compilateur;)

2

Je considérerais déplacer les méthodes setCollection dans leurs propres classes et ensuite faire quelque chose de plus comme ceci (vous pouvez le changer pour créer la collection entière à la fois au lieu de le faire comme un élément à la fois).

Ceci élimine la réflexion et assure une protection contre la frappe lors de la compilation. Je pourrais mal comprendre ce que vous essayez de faire, mais je pense que je l'ai compris. La méthode "init" serait la méthode d'aide (au moins si je comprends ce que vous voulez).

public class Main 
{ 
    public static void main(String[] args) 
    { 
     List<Car> car; 
     List<Bar> bar; 

     car = new ArrayList<Car>(); 
     bar = new ArrayList<Bar>(); 
     init(car, new CarCreator()); 
     init(bar, new BarCreator()); 
    } 

    private static <T> void init(final List<T> list, 
           final Creator<T> creator) 
    { 
     for(int i = 0; i < 10; i++) 
     { 
      final T instance; 

      instance = creator.newInstance(i); 
      list.add(instance); 
     } 
    } 
} 

interface Foo 
{  
} 

class Bar implements Foo 
{ 
} 

class Car implements Foo 
{ 
} 

interface Creator<T> 
{ 
    T newInstance(int i); 
} 

class BarCreator 
    implements Creator<Bar> 
{ 
    public Bar newInstance(final int i) 
    { 
     return (new Bar()); 
    } 
} 

class CarCreator 
    implements Creator<Car> 
{ 
    public Car newInstance(final int i) 
    { 
     return (new Car()); 
    } 
}