2008-12-19 6 views
5

Ich habe eine Methode fetchObjects(String), von der erwartet wird, dass sie ein Array von Contract Geschäftsobjekten zurückgibt. Der Parameter className sagt mir, welche Art von Geschäftsobjekten ich zurückgeben sollte (natürlich ergibt das in diesem konstruierten Fall keinen Sinn, weil ich bereits gesagt habe, dass ich 0 zurücksenden werde, aber es ist im Grunde die Situation, die ich in meinem realen Szenario habe). So bekomme ich die Menge der Einträge von irgendwo und lade die Klasse der Einträge der Sammlung (deren Typ von className angegeben wird).Umwandlung in eine Klasse, die zur Laufzeit bestimmt wird

Jetzt muss ich das Array erstellen, um zurückzukehren, so dass ich Set 's toArray(T[]) Methode verwenden. Mit Reflektion baue ich mir ein leeres Contracts-Array. Aber, das gibt mir einen Wert von statischen Typ Object! Als nächstes muss ich es auf den passenden Typ umwandeln, der in diesem Fall Contract[] ist (siehe "asterisk-unterstrichen" Teil in der Liste unten).

Meine Frage ist: Gibt es eine Möglichkeit, und wie man Contract[] werfen, wie ich in der Liste zu tun, aber die Art der Array-Elemente (Contract) nur durch className (oder entriesType) Bestimmung? Mit anderen Worten, was ich tun möchte, ist im Grunde Casting wie folgt: (entriesType[]) valueWithStaticTypeObject, wo EinträgeTyp durch die Klasse ersetzt werden, die durch den classname Parameter, d. H. Contract spezifiziert wird.

Ist das irgendwie von Natur aus unmöglich oder kann es irgendwie gemacht werden? Vielleicht mit Generika?

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

Danke.


Update: Mit der typsichere Methode toArray, genommen von CodeIdol, ich aktualisierte meine fetchObjects Methode so:

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

Was ich von dem Compiler-Fehler tun, tun müssen, um loszuwerden, zitiert im Kommentar? Muss ich unbedingt Set<Contract> im Rückgabetyp meiner getEntrySet Methode angeben, damit das funktioniert? Danke für irgendwelche Hinweise.

+0

Wie behandeln Sie, wenn die Klasse entriesType keine Unterklasse von Contract ist? –

+0

entriesType ist * not * notwendigerweise ein Subtyp des Vertrags. Es könnte Haustier, Freund, Hobby sein - alles, was Sie vielleicht eine Sammlung von haben. In meinem Beispielfall würde ein Kunde eine Sammlung von Verträgen haben. –

Antwort

7

Sie können die Klasse als Parameter und nicht den Klassennamen verwenden.

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: (nach dem Lesen Yang Kommentar)

Nein, Sie können nicht generischen Typen mit dem Wert einer Variablen verwenden.

+0

Das ist ziemlich beeindruckend! Die Sache ist jedoch, dass ich die Contract-Klasse nicht fest codieren möchte (das Business-Objekt könnte genauso gut House oder Pet sein, abhängig vom Methodenparameter className).Die richtige Klasse muss anhand des className-Parameters ermittelt werden. –

+0

Da der Aufrufer wissen müsste, welche Art von Objekt er sowieso erwartet, warum nicht das Klassenobjekt anstelle des Namens übergeben? Ich verstehe nicht, welchen Vorteil der Name hat. –

+0

Vgl. mein Kommentar zu Milhous 'Antwort. –

1

Warum also nicht

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

Hinweis verwenden. Modifizierte den Code von oben.

+0

Ich glaube, er möchte, dass die Generika auf dem Wert von "String className" basiert, ohne einen typisierten/generics/class-Parameter. –

+0

Genau. Im realen Szenario muss ich Reflektion verwenden, um den Namen des Typs zu erhalten, z. Ich bekomme "Vertrag" von der Reflexion und lade dann die Klasse mit diesem Namen. –

+3

Sie können den Typ nicht in einen Typ eingeben, der zur Kompilierungszeit unbekannt ist. Typecasting ist ein Problem bei der Kompilierung. Generics ist nur eine Möglichkeit, die Typumwandlung zu parametrisieren, damit der Compiler Typinformationen von einem Teil des Codes an einen anderen weitergeben kann. Aber es muss dem Compiler noch bekannt sein. –