2009-05-18 18 views
18

Nel tentativo di vedere se riesco a ripulire un po 'del mio codice matematico, principalmente di matrice, sto cercando di utilizzare Java Generics. Ho il seguente metodo:Java Generics e numeri

private <T> T[][] zeroMatrix(int row, int col) { 
    T[][] retVal = (T[][])new Object[row][col]; 
    for(int i = row; i < row; i++) { 
     for(int j = col; j < col; j++) { 
      retVal[i][j] = 0; 
     } 
    } 
    return retVal; 
} 

La linea retVal [i] [j] = 0 è quello mi ha causato mal di testa. L'obiettivo della linea è per inizializzare l'array con la rappresentazione T 0. ho tentato di fare ogni sorta di cose con esso: (T è definito nella classe come T estende Number)

retVal[i][j] = (T)0; 
retVal[i][j] = new T(0); 

Il l'unica cosa che funziona è

retVal[i][j] = (T)new Object(0); 

Quale non è quello che voglio.

È possibile? C'è un modo più semplice per rappresentare una matrice NxM di qualsiasi tipo di numero (incluso potenzialmente BigDecimal) o sono bloccato?

+1

Non ho molto da aggiungere a questa discussione, ma grazie per aver fornito una domanda che ha provocato risposte così grandiose. Non sono soddisfatto dell'implementazione dei generici da parte di Java, ma ora li capisco meglio. –

risposta

1

Gli array di riferimento (di riferimento) non funzionano bene con i generici. In questo caso, anche gli array potrebbero essere inefficienti. Stai creando una serie di array, quindi non c'è controllo indiretto e dei limiti non necessari. Meglio fare una classe Matrix<T>. È inoltre possibile aggiungere allo Matrix un riferimento a un'istanza di T che rappresenta uno zero.

+1

Questo è in realtà il codice dalla mia matrice . –

+0

Suggerisco di utilizzare Number [] o List come tipo "array". java.util si trova solo un po 'in disordine essendo scritto con un array generico contraffatto. Avrai bisogno di un metodo come ('private Number [] [] zeroMatrix (int row, int col, Number zero)' o 'private Elenco zeroMatrix (riga int, int col, T zero)'). –

1

Generics e array non corrispondono molto bene. La creazione di un array generico non è consentita in quanto non sarebbe tipologica. Deriva dal fatto che se Sub è un sottotipo di Super, quindi Sub [] è un sottotipo di Super [], che non è il caso di tipi generici; per due tipi distinti, Type1 e Type2, List non è né un sottotipo né un supertipo di List. (Java efficace copre ciò nel capitolo 5, elemento 25).

-1

L'uso di Java della cancellazione per implementare i generici significa che si avranno problemi a creare un tipo generico.

ne dite di usare nulla per rappresentare 0

retVal[i][j] = null; 

è quindi possibile assegnare qualsiasi tipo che si desidera l'array in seguito.

+0

Non mi piace questa soluzione poiché null non è zero in Java – dfa

2

In Java il tipo viene cancellato in fase di esecuzione, quindi è necessario passare un altro argomento per ottenere il tipo in fase di esecuzione.

Quello potrebbe essere il valore per inizializzare gli array con o la classe da utilizzare.

Se si decide di passare la classe, quindi avere una mappa della classe da valorizzare per memorizzare un valore zero per ogni tipo.

È quindi possibile utilizzare java.util.Arrays.fill per riempire la matrice:

private static HashMap<Class<?>, Object> ZEROS = new HashMap<Class<?>,Object>(); 

static { 
    ZEROS.put(Integer.class, Integer.valueOf(0)); 
    ... 
} 

private static <T extends Number> T[][] zeroMatrix (Class<T> type, int rows, int cols) { 
    @SuppressWarnings("unchecked") 
    T[][] matrix = (T[][]) java.lang.reflect.Array.newInstance(type, rows, cols); 
    Object zero = ZEROS.get(type); 

    for (T[] row : matrix) 
     java.util.Arrays.fill(row,zero); 

    return matrix; 
} 

Integer[][] matrix = zeroMatrix (Integer.class, 10, 10); 

Tuttavia, se le prestazioni sono da remoto una preoccupazione che non si vuole essere utilizzando i valori in scatola per il codice numerico.

Davvero non si vuole provare a utilizzare null per zero: triplicherà la complessità di tutti gli altri percorsi nel codice. Sebbene si possa ottenere una classe di supporto numerico che fornisca l'addizione e la moltiplicazione dei vari tipi di numeri in scatola, la quantità di complessità che si risparmia sarà molto piccola se confrontata con due o tre matrici primitive e un paio di numeri grandi, in particolare se si utilizza un sistema di template (ad es. task di sostituzione di formiche o XSLT) per generare il codice sorgente.

+0

@Bill al momento della compilazione il tipo generico è disponibile per il controllo del tipo - quindi non viene cancellato in fase di compilazione, ma dopo la compilazione. Il compilatore lo cancella dal codice passato alla JVM, quindi in fase di runtime viene cancellato. –

1

Penso che tu stia combattendo una battaglia persa. Anche se risolvi questo problema, come pensi di risolvere addizioni, sottrazioni, ecc.? La classe numero non è una superclasse molto utile, e l'unico metodo utile è doubleValue().

Lo zero può essere definito come identità in aggiunta o uno zero in moltiplicazione, ma senza una definizione generica di addizione o moltiplicazione, una definizione generica di zero è improbabile.

Se si desidera questo, è possibile che sia meglio attenersi a BigDecimal per qualsiasi cosa, ma naturalmente che avrà delle penalizzazioni prestazionali associate.

L'altra opzione ovvia sarebbe lasciare l'array con inizializzazione nulla e quindi modificare l'altro codice per considerare null come zero.

3

deve essere nullo anziché zero.

Se si vuole mettere effettivamente in là l'equivalente 0 per oggetto T è necessario fornire una fabbrica di T. Qualcosa di simile a questo:

interface Factory<T> { 
    T getZero();  
} 

e si dovrebbe fare il metodo come questo:

private <T> T[][] zeroMatrix(int row, int col, Factory<T> factory) { 
    T[][] retVal = (T[][])new Object[row][col]; 
    for(int i = row; i < row; i++) { 
     for(int j = col; j < col; j++) { 
      retVal[i][j] = factory.getZero(); 
     } 
    } 

    return retVal; 
} 

Si dovrebbe anche avere implementazioni corrette per la fabbrica:

class IntegerFactory implements Factory<Integer> { 
    Integer getZero() { 
     return new Integer(0); 
    } 
} 

Nor inoltre, si dovrebbe inserire lo getMatrix(int row, int column) nell'implementazione di fabbrica per restituire effettivamente un array tipizzato corretto.

+0

Questo si rompe in fase di esecuzione ovviamente. Non puoi lanciare un oggetto [] [] su Integet [] []. –

+0

Prova ad aggiungere limiti modello, ad es.

+0

getZero() dovrebbe memorizzare nella cache o semplicemente usare Integer.valueOf() – dfa

1

È necessario considerare che i generici vengono utilizzati solo in fase di compilazione per il controllo di sicurezza del tipo. Queste informazioni vengono perse in fase di esecuzione, quindi non è possibile utilizzare il box automatico su retVal [i] [j] = 0; poiché Java non può auto-boxare per digitare Numero o Oggetto.

Se si passa il valore che si desidera impostare, funzionerà. Ecco un esempio veloce:

private <T> T[][] fillMatrix(int row, int col, T value) { 
    T[][] retVal = (T[][])new Object[row][col]; 
    for(int i = 0; i < row; i++) { 
     for(int j = 0; j < col; j++) { 
      retVal[i][j] = value; 
     } 
    } 
    return retVal; 
} 

Btw, for (int i = fila; i < fila; i ++) e per (int j = col; j < Col; j ++) non sarà mai ciclo in modo c'è un altro problema con il vostro codice.

modifica: Non è possibile eseguire il cast a un risultato diverso da Object [] [], poiché questo è il tipo di array effettivo.

+0

Forse un << per (T [] t1: retVal) per (T t2: t1) t2 = valore; >> potrebbe funzionare. – ATorras

+2

solleva: java.lang.ClassCastException: [[Ljava.lang.Object; non può essere lanciato su [[Ljava.lang.Integer; – dfa

+0

+1 Ahi! Hai ragione: – ATorras

12
<T extends Number> T[][] zeroMatrix(Class<? extends Number> of, int row, int col) { 
    T[][] matrix = (T[][]) java.lang.reflect.Array.newInstance(of, row, col); 
    T zero = (T) of.getConstructor(String.class).newInstance("0"); 
    // not handling exception  

    for (int i = 0; i < row; i++) { 
     for (int j = 0; j < col; 
      matrix[i][j] = zero; 
     } 
    } 

    return matrix; 
} 

di utilizzo:

BigInteger[][] bigIntegerMatrix = zeroMatrix(BigInteger.class, 3, 3); 
    Integer[][] integerMatrix = zeroMatrix(Integer.class, 3, 3); 
    Float[][] floatMatrix = zeroMatrix(Float.class, 3, 3); 
    String[][] error = zeroMatrix(String.class, 3, 3); // <--- compile time error 
    System.out.println(Arrays.deepToString(bigIntegerMatrix)); 
    System.out.println(Arrays.deepToString(integerMatrix)); 
    System.out.println(Arrays.deepToString(floatMatrix)); 

EDIT

una matrice generica:

public static <T> T[][] fillMatrix(Object fill, int row, int col) { 
    T[][] matrix = (T[][]) Array.newInstance(fill.getClass(), row, col); 

    for (int i = 0; i < row; i++) { 
     for (int j = 0; j < col; j++) { 
      matrix[i][j] = (T) fill; 
     } 
    } 

    return matrix; 
}  

Integer[][] zeroMatrix = fillMatrix(0, 3, 3); // a zero-filled 3x3 matrix 
String[][] stringMatrix = fillMatrix("B", 2, 2); // a B-filled 2x2 matrix 
+0

che suona bene, non conosco i generici abbastanza bene da dire per certo, ma se funziona, deve essere corretto. :) –

+0

Umm .... aspetta un minuto. Non ho problemi di correttezza, ma ho un problema con l'efficienza. Dovresti inserire la nuova sostanza ("0") all'inizio, ad es. T zero = .... newInstance ("0") e assegna la matrice [i] [j] = zero nel ciclo. –

+0

(solo problematico se T è una classe mutabile e non credo che lo siano) –

8

Array e Generics non fanno giocare bene insieme:

"Le matrici sono covarianti, il che significa che una serie di riferimenti di supertipo è un supertipo di una matrice di riferimenti di sottotipo.Cioè, Object[] è un supertipo di String[] e una matrice di stringhe sono accessibili attraverso una variabile di riferimento di tipo Object[] "

vedere il Java Generics FAQ:.

1

Se vuoi davvero usare i generici, puoi fare qualcosa di simile a questo

private <T extends Number> T[][] zeroMatrix(int row, int col, Class<T> clazz) throws InstantiationException, IllegalAccessException, 
     IllegalArgumentException, InvocationTargetException 
{ 
    T[][] retVal = (T[][]) Array.newInstance(clazz, new int[] { row, col }); 
    for (int i = 0; i < row; i++) 
    { 
     for (int j = 0; j < col; j++) 
     { 
      Constructor<T> c = clazz.getDeclaredConstructors()[0]; 
      retVal[i][j] = c.newInstance("0"); 
     } 
    } 

    return retVal; 
} 

Esempio:

zeroMatrix(12, 12, Integer.class); 
1

ho sollevato un related question which also asked about performance issues, che fa riferimento alla tua domanda. Il consenso era chiaro sul fatto che il refactoring di Generics ha avuto un notevole impatto sulle prestazioni e quindi è necessario attenersi ai primitivi se ciò è importante (per me lo fa).