2008-12-19 16 views
5

Mam metodę fetchObjects(String), która ma zwrócić tablicę obiektów biznesowych Contract. Parametr className mówi mi, jakie obiekty biznesowe powinienem zwrócić (oczywiście nie ma to sensu w tym skonstruowanym przypadku, ponieważ już powiedziałem, że zwrócę Contract s, ale to w zasadzie sytuacja, którą mam w moim prawdziwym scenariuszu). Dostaję więc zbiór wpisów skądś i ładuję klasę pozycji kolekcji (której typ jest określony przez className).Odlewanie do klasy, która jest określana w czasie wykonywania

Teraz potrzebuję skonstruować tablicę, aby wrócić, więc używam metody Set 's toArray(T[]). Używając refleksji, zbudowałem sobie pustą tablicę kontraktów. Ale, to daje mi wartość statycznego typu Object! Następnie muszę rzucić go do odpowiedniego typu, który w tym przypadku jest Contract[] (patrz "gwiazdka podkreślona" część w wykazie poniżej).

Moje pytanie brzmi: Czy jest jakiś sposób, i jak oddać do Contract[] jak ja w szczegóły, ale ustalaniu rodzaju elementów Array (Contract) tylko przez className (lub entriesType)? Innymi słowy, to, co chciałbym zrobić, to zasadniczo rzutowanie w następujący sposób: (entriesType[]) valueWithStaticTypeObject, gdzie entriesType należy zastąpić klasą określoną parametrem classname, tj. Contract.

Czy jest to w jakiś sposób niemożliwe lub może być zrobione w jakiś sposób? Może używając generycznych?

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

Dzięki.


Aktualizacja: Stosując metodę typu bezpieczny toArray, wykonane z CodeIdol, zaktualizowałem moją fetchObjects metoda tak:

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

Co muszę zrobić, aby pozbyć się tego błędu kompilatora cytowanego w komentarzu? Czy muszę bezwzględnie określać Set<Contract> w typie zwrotu mojej metody getEntrySet, aby mogło to działać? Dzięki za wszelkie wskazówki.

+0

Jak postępować, gdy klasa entriesType nie jest podklasą kontraktu? –

+0

entries entries * nie * koniecznie podtyp kontraktu. Może to być Pet, Friend, Hobby - wszystko, co możesz mieć w kolekcji. W moim przykładowym przypadku klient będzie miał kolekcję umów. –

Odpowiedz

7

Możesz używać klasy jako parametru zamiast nazwy klasy.

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: (po przeczytać komentarz Yang)

Nie, nie można używać typu rodzajowego z wartością zmiennej.

+0

To imponujące! Chodzi o to, że * nie chcę * w sztywny sposób określać klasy kontraktu (obiekt biznesowy mógłby równie dobrze być House lub Pet, w zależności od parametru method className).Właściwą klasę należy ustalić na podstawie parametru className. –

+0

Skoro osoba dzwoniąca musi wiedzieć, jakiego typu obiektu oczekuje, to dlaczego nie przekazać obiektu klasy zamiast nazwy? Nie wiem, jaką korzyść ma ta nazwa. –

+0

Cf. mój komentarz do odpowiedzi Milhousa. –

1

Więc dlaczego nie skorzystać

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())); 
} 

Note. Zmodyfikowaliśmy kod z góry.

+0

Myślę, że on może chcieć, że generics jest oparty na wartości "String className", bez wpisanego/generics/klasy parametru. –

+0

Dokładnie. W prawdziwym scenariuszu muszę użyć odbicia, aby uzyskać nazwę typu, np. Dostaję "Kontrakt" z refleksji, a następnie wczytam klasę o tej nazwie. –

+3

Nie można typować na typ nieznany podczas kompilacji. Typecasting jest problemem podczas kompilacji. Generics jest po prostu sposobem parametryzacji typecast, aby umożliwić kompilatorowi propagowanie informacji o typie z jednej części kodu do drugiej. Ale wciąż musi być znany kompilatorowi. –