2010-10-15 9 views
9

ExecutorImp étend AbstractExecutor qui extrait la même exécution des logiques de ses implémenteurs (le AbstractExcutor) devrait connaître une autre classe se liant à l'implémenteur (dans l'exemple, il est la classe utilisateur):Comment obtenir le type générique lors de l'exécution?

import java.lang.reflect.InvocationTargetException; 
import java.util.ArrayList; 

abstract class AbstractExecutor<E> { 
    public void execute() throws Exception { 
     ArrayList<E> list = new ArrayList<E>(); 
     // here I want to get the real type of 'E' 
     Class cl = this.getClass().getTypeParameters()[0].getGenericDeclaration().getClass(); 
     Object o = cl.getConstructor(String.class).newInstance("Gate"); 
     list.add((E) o); 
     System.out.println(format(list)); 
    } 
    public abstract String format(ArrayList<E> list); 
    public abstract String getType(); 
} 

public class ExectorImp<E> extends AbstractExecutor<User> { 
    @Override 
    public String getType() { 
     return "user"; 
    } 
    @Override 
    public String format(ArrayList<User> list) { 
     StringBuffer sb = new StringBuffer(); 
     for (User u : list) { 
      sb.append(u.toString() + " "); 
     } 
     return sb.toString(); 
    } 
    public static void main(String[] args) throws Exception { 
     new ExectorImp().execute(); 
    } 
} 
class User { 
    String name; 
    public User(String name) { 
     this.name = name; 
    } 
} 

SO, quel est le problème avec mes codes?

+0

Peut-être que cette réponse peut vous aider à régler votre problème. http: // stackoverflow.com/questions/6624113/get-type-nom-pour-generic-parameter-of-generic-class/19454610 # 19454610 –

Répondre

12

Il y a une certaine confusion ici. En raison de l'effacement de type que vous ne pouvez pas obtenir des informations de type du moteur d'exécution de type paramétrées comme:

Class<E> cls = E.getClass(); // Error. 
E e = new E(); // Error. 

Cependant, vous pouvez obtenir compiletime informations de type paramétrés de la classe, le champ et la déclaration de la méthode par ParameterizedType#getActualTypeArguments().

abstract class AbstractExecutor<E> { 

    public void execute() throws Exception { 
     List<E> list = new ArrayList<E>(); 
     Class<E> cls = (Class<E>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; 
     E e = cls.getConstructor(String.class).newInstance("Gate"); 
     list.add(e); 
     System.out.println(format(list)); 
    } 

    // ... 
} 

Mise à jour: pour savoir si cela est recommandé ou non, même si cela va fonctionner, ceci est sensible à la durée d'exécution des problèmes à chaque fois que des changements mineurs dans la déclaration de classe se produisent. En tant que développeur, vous devez le documenter correctement. Comme une alternative complètement différente, vous pouvez utiliser le polymorphisme.

abstract class AbstractExecutor<E> { 

    public void execute() throws Exception { 
     List<E> list = new ArrayList<E>(); 
     E e = create("Gate"); 
     list.add(e); 
     System.out.println(format(list)); 
    } 

    public abstract E create(String name); 

    // ... 
} 

et mettre en œuvre UserExecutor en conséquence.

class UserExecutor extends AbstractExecutor<User> { 

    @Override 
    public User create(String name) { 
     return new User(name); 
    } 

    // ... 
} 
+0

Merci beaucoup, et merci à tous les gars de cette page. La réponse de BalusC est ce que je voulais. Cependant, je me demande si mon code est recommandé? Voici mon contexte d'application: http: //forums.oracle.com/forums/message.jspa? MessageID = 7006003 # 7006003 – hguser

6

Je pense que vous devriez utiliser getActualTypeParameters; comme getTypeParameters ne se réfère pas à ce qui a été mis dans votre instanciation actuelle à la place de E, mais à E lui-même (pour décrire comment est-il borné, etc.). Pour obtenir le code ParameterizedType, vous devez d'abord utiliser getGenericSuperclass.

mise à jour: mais ce qui précède ne fonctionne que si l'objet courant est dérivé d'une classe générique avec l'argument générique instancié, comme:

class StringList extends ArrayList<String> { 
    public Type whatsMyGenericType() { 
     return ((ParameterizedType)getGenericSuperClass()).getActualTypeParameters()[0]; 
    } 
} 

devrait revenir String.class.

+4

Juste pour être précis: getGenericSuperclass() donnera des résultats significatifs pour vous seulement si la classe n'est pas un générique class, donc: new LinkedList () .getClass(). getGenericSuperclass() ne sera pas suffisant pour vous, mais: nouveau LinkedList () {}. getClass(). getGenericSuperclass() est OK - remarqué la différence? En ajoutant {} j'ai créé la sous-classe de LinkedList. – iirekm

+0

@iirekm vrai, alors je suppose qu'il n'y a aucun moyen pour un objet de détecter comment il a été déclaré de manière réfléchie, mais cela pourrait être fait "de l'extérieur" (en utilisant 'Field.getGenericType()') ... – fortran

2

Je ne pense pas que vous pourriez obtenir le type générique à l'exécution. Le type générique est une restriction qui s'applique à la compilation. Comme je me souviens à l'exécution il n'y a pas de différence entre une collection générique et une collection sans un type générique.

+0

true: lisez la solution par 'fortran' et mon commentaire. – iirekm

+2

He, je pensais qu'une fois aussi ... L'effacement signifie qu'au moment de la compilation, vous pouvez ignorer les restrictions génériques car le bytecode exécutant les deux instances est le même, mais les informations sur la façon dont les instances ont été déclarées sont conservées. – fortran

1

L'approche habituelle pour résoudre le problème consiste à modifier légèrement le code. Définir le constructeur sur la classe de base acceptant le paramètre Class<E>. Affectez ce paramètre au champ interne.

Dans la sous-classe, définissez le constructeur sans paramètre et appelez super(User.class) à partir de là. De cette façon, vous connaîtrez la classe d'argument sans beaucoup de surcharge pour les clients des sous-classes.