tags:

views:

1090

answers:

12

In an attempt to see if I can clean up some of my math code, mostly matrix stuff, I am trying to use some Java Generics. I have the following method:

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

The line retVal[i][j] = 0 is the one causing me headaches. The goal of the line is to initialize the array with the T representation of 0. I've attempted to do all sorts of things with it: (T is defined in the class as T extends Number)

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

The only thing that works is

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

Which is not what I want.

Is this possible? Is there an easier way to represent an NxM matrix of any type of Number(including potentially BigDecimal), or am I stuck?

+1  A: 

Ye olde (reference) arrays do not play well with generics. In this case arrays are also likely to be inefficient. You are creating an array of arrays, so there is unnecessary indirection and bounds checking. Better to make a class Matrix<T>. You may also want to add to the Matrix an reference to an instance of T that represents a zero.

Tom Hawtin - tackline
This is actually code from my Matrix<T extends Number> class.
Scott Lemke
I suggest using Number[] or List<T> as the "array" type. java.util just gets itself in a bit of a mess by being written with a forged generic arrays. You'll need a method like (`private Number[][] zeroMatrix(int row, int col, Number zero)` or `private <T> List<T> zeroMatrix(int row, int col, T zero)`).
Tom Hawtin - tackline
+1  A: 

Generics and arrays don't match very well. Creating a generic array is not allowed since it would not be typesafe. It stems from the fact that if Sub is a subtype of Super, then Sub[] is a subtype of Super[], which is not the case of generic types; for any two distinct types Type1 and Type2, List is neither a subtype or a supertype of List. (Effective Java covers this in chapter 5, item 25).

JesperE
A: 

Java's use of erasure to implement generics means that you're going to have trouble new'ing a generic type.

How about using null to represent 0

retVal[i][j] = null;

You can then assign any type you want to the array later on.

Glen
I don't like this solution since null is not zero in Java
dfa
+2  A: 

In Java the type is erased at runtime, so you need to pass in another argument to get the type at runtime.

That could either be the value to initialise the arrays with, or the class to use.

If you choose to pass in the class, then have a Map of class to value to store a zero value for each type.

You then can use java.util.Arrays.fill to fill the array:

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

However, if performance is remotely a concern you don't want to be using boxed values for numeric code.

You really don't want to try and use null for zero - it will treble the complexity of all other paths in your code. Although you might get away with a numeric support class which would provide addition and multiplication of the various boxed number types, the amount of complexity you save will be very little compared with providing two or three primitive matrices and a couple of big-number ones, particularly if you use a templating system (eg ant's replace task or XSLT) to generate the source code.

Pete Kirkham
@Bill at compile time the generic type is available for type checking - therefore it is not erased at compile time, but after compile time. The compiler erases it from the code passed to the JVM, therefore at runtime it is erased.
Pete Kirkham
+1  A: 

I think you are fighting a losing battle. Even if you solve this, how are you planning on solving addition, subtraction etc? The number class is not a very useful superclass, and about the only useful method is doubleValue().

Zero can be defined as the identity in addition or a zero in multiplication, but without a generic definition of addition or multiplication, a generic definition of zero is unlikely.

If you want this then you might be better just sticking with BigDecimal for everything, but of course that will have associated performance penalties.

The other obvious option would be to leave the array with null initialisation, and then change your other code to treat null as zero.

Nick Fortescue
+3  A: 

it should be null instead of zero.

If you want to actually put in there the equivalent 0 for object T you will need to provide a factory of T. Something like this:

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

and you should make the method like this:

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

You should also have proper implementations for the factory:

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

Normally you would put the getMatrix(int row, int column) in the factory implementation too in order to actually return a proper typed array.

Toader Mihai Claudiu
This breaks at runtime obviously. You cannot cast an Object[][] to Integet[][].
bruno conde
Try adding template bounds, e.g. <T extends SomeInterfaceThatHasAGetZeroMethod>
Jason S
getZero() should caching or just use Integer.valueOf()
dfa
@bruno You can actually cast it. Try it :-)
Toader Mihai Claudiu
@dfa Depends on the performance requirements :-).
Toader Mihai Claudiu
@Jason Why .. There is no need and it will force me to always use T's that implement the interface (which rules out Integer as a class since it's final)
Toader Mihai Claudiu
this code throws java.lang.ClassCastException: [[Ljava.lang.Object; cannot be cast to [[Ljava.lang.Integer; since I want to be able to write Integer[][] matrix = zeroMatrix(...)
dfa
@dfa :-) Yes .. this is true. The propery solution would be for the factory to actually build the array (as i said in the last paragraph before). But if the caller expects an Object[][] it works.
Toader Mihai Claudiu
just replace (T[][])new Object[row][col]; with java.lang.reflect.Array.newInstance(factory.getZero().getClass(), row, col);
dfa
or just check my answer below
dfa
+1  A: 

You need to consider that generics are only used at compile-time for type safety checking. This information is lost at runtime so you can't use auto-boxing on retVal[i][j] = 0; since Java can't auto-box to type Number or Object.

If you pass in the value you want to set, it will work. Here's a quick sample:

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 = row; i < row; i++) and for(int j = col; j < col; j++) will never loop so there's another problem with your code.

edit: You won't be able to cast the result to something other than Object[][] though because that's the actual array type.

Alexandre Beaulieu
Maybe a <<for(T[] t1: retVal) for(T t2: t1) t2 = value;>> could work.
ATorras
it raises: java.lang.ClassCastException: [[Ljava.lang.Object; cannot be cast to [[Ljava.lang.Integer;
dfa
+1 Ouch! You are right
ATorras
If you get: java.lang.ClassCastException that because you tried to cast the returned array to something other than Object[][]. As one poster mentionned Arrays and Generics don't play well together.
Alexandre Beaulieu
+10  A: 
<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;
}

usage:

    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

a generic matrix:

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
dfa
that sounds about right, I don't know generics well enough to say for certain but if It Just Works Right then it must be correct. :)
Jason S
Umm.... wait a minute. I don't have a problem with correctness, but I do have a problem with efficiency. You should put the newInstance("0") at the beginning, e.g. T zero = ....newInstance("0"), and assign matrix[i][j]=zero in the loop.
Jason S
(only problematic if T is a mutable class and I don't think those are)
Jason S
fixed, thanks =)
dfa
Your approach is more generic, more readable and more compact. It looks like the typical teacher's answer ;-)
ATorras
+8  A: 

Arrays and Generics do not play well together:

"Arrays are covariant, which means that an array of supertype references is a supertype of an array of subtype references. That is, Object[] is a supertype of String[] and a string array can be accessed through a reference variable of type Object[] ."

see the Java Generics FAQ:

bendin
+1  A: 

If you really want to use generics, you could do something like this

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

Example:

zeroMatrix(12, 12, Integer.class);
Agora
A: 

Looks like what I wanted to do is not (easily) doable. Thanks for all the great responses.

Scott Lemke
+1  A: 

I raised a related question which also asked about performance issues which referenced your question. The consensus was clear that refactoring to Generics had a considerable performance hit and so you should stick with primitives if this matters (for me it does).

peter.murray.rust