2008-12-19 4 views
5

J'ai une méthode fetchObjects(String) qui devrait renvoyer un tableau de Contract objets métier. Le paramètre className me dit quel type d'objets métier je devrais retourner (bien sûr cela n'a pas de sens dans ce cas, car j'ai déjà dit que je vais retourner Contract s, mais c'est essentiellement la situation que j'ai dans mon scénario réel). Donc, je reçois l'ensemble des entrées de quelque part et charge la classe des entrées de la collection (dont le type est spécifié par className).Coulée vers une classe qui est déterminée au moment de l'exécution

Maintenant, j'ai besoin de construire le tableau à retourner, donc j'utilise la méthode toArray(T[]) de Set. En utilisant la réflexion, je me construis un tableau de contrats vide. Mais, cela me donne une valeur de type statique Object! Donc, je dois ensuite le mouler au type approprié, qui dans ce cas est Contract[] (voir la partie «souligné par un astérisque» dans la liste ci-dessous).

Ma question est: Est-il possible, et comment, de caster Contract[] comme je le fais dans la liste, mais déterminer le type des éléments du tableau (Contract) que par className (ou entriesType)? En d'autres termes, ce que je voudrais faire est essentiellement de lancer comme ceci: (entriesType[]) valueWithStaticTypeObject, où entriesType est remplacé par la classe spécifiée par le biais du paramètre classname, c'est-à-dire Contract. Est-ce que c'est en quelque sorte intrinsèquement impossible, ou peut-il être fait d'une manière ou d'une autre? Peut-être en utilisant des génériques?

package xx.testcode; 

import java.util.HashSet; 
import java.util.Set; 

class TypedArrayReflection { 

    public static void main(String[] args) { 
     try { 
      Contract[] contracts = fetchObjects("Contract"); 
      System.out.println(contracts.length); 
     } catch (ClassNotFoundException e) {} 
    } 

    static Contract[] fetchObjects(String className) throws ClassNotFoundException { 
     Class<?> entriesType = Class.forName("xx.testcode."+className); 
     Set<?> entries = ObjectManager.getEntrySet(className); 
     return entries.toArray( 
       (Contract[]) java.lang.reflect.Array.newInstance(
       /********/   entriesType, entries.size())); 
    } 
} 

class Contract { } // business object 

class ObjectManager { 
    static Set<?> getEntrySet(String className) { 
     if (className.equals("Contract")) 
      return new HashSet<Contract>(); 
     return null; // Error 
    } 
} 

Merci.


Mise à jour: En utilisant la méthode de type sécurisé toArray, pris de CodeIdol, je mis à jour ma méthode fetchObjects ainsi:

static Contract[] fetchObjects(String className) throws ClassNotFoundException { 
    Class<?> entriesType = Class.forName("xx.testcode."+className); 
    Set<?> entries = ObjectManager.getEntrySet(className); 
    return toArray(entries, entriesType); // compile error 
    // -> "method not applicable for (Set<capture#3-of ?>, Class<capture#4-of ?>)" 
} 

public static <T> T[] toArray(Collection<T> c, Class<T> k) { 
    T[] a = (T[]) java.lang.reflect.Array.newInstance(k, c.size()); 
    int i = 0; 
    for (T x : c) 
     a[i++] = x; 
    return a; 
} 

Que dois-je faire pour se débarrasser de l'erreur du compilateur cité dans le commentaire? Dois-je absolument spécifier Set<Contract> dans le type de retour de ma méthode getEntrySet pour que cela fonctionne? Merci pour les pointeurs.

+0

Comment gérer lorsque la classe entriesType n'est pas une sous-classe de Contract? –

+0

entriesType n'est pas un sous-type de Contract. Ce pourrait être Pet, Friend, Hobby - tout ce que vous pourriez avoir une collection de. Dans mon exemple, un client aurait une collection de contrats. –

Répondre

7

Vous pouvez utiliser la classe comme paramètre plutôt que comme nom de classe.

static <T extends Contract> T[] buildArray(Class<T> clazz){ 
    ArrayList<T> l=new ArrayList<T>(); 
    return l.toArray((T[]) java.lang.reflect.Array.newInstance(clazz, l.size())); 
    } 

EDIT: (après lecture commentaire Yang)

Non, Vous ne pouvez pas utiliser le type générique avec la valeur d'une variable.

+0

C'est assez impressionnant! Cependant, le fait est que je ne veux pas spécifier de manière codée la classe Contract (l'objet métier pourrait tout aussi bien être House ou Pet, en fonction du paramètre de méthode className).La bonne classe doit être déterminée à partir du paramètre className. –

+0

Puisque l'appelant devrait savoir quel type d'objet il attend, de toute façon, pourquoi ne pas passer dans l'objet Class au lieu du nom? Je ne comprends pas quel avantage le nom a. –

+0

Cf. mon commentaire à la réponse de Milhous. –

1

Alors pourquoi ne pas utiliser

static <T> T[] buildArray(Class<T> clazz){ 
ArrayList<T> l=new ArrayList<T>(); 
return l.toArray((T[]) java.lang.reflect.Array.newInstance(clazz, l.size())); 
} 

Remarque. Modifié le code d'en haut.

+0

Je pense qu'il peut vouloir que les génériques soient basés sur la valeur de "String className", sans un paramètre typed/generics/class. –

+0

Exactement. Dans le scénario réel, j'ai besoin d'utiliser la réflexion pour obtenir le nom du type, par ex. Je reçois "Contrat" ​​de la réflexion et charge ensuite la classe avec ce nom. –

+3

Vous ne pouvez pas effectuer un typage vers un type inconnu lors de la compilation. Typecasting est un problème de compilation. Generics est juste un moyen de paramétrer le transtypage, de laisser le compilateur propager l'information de type d'une partie du code à l'autre. Mais il doit encore être connu du compilateur. –

Questions connexes