18

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
  • 1
    I don't have much to add to this discussion, but thank you for providing a question which provoked such great answers. I'm not happy with Java's implementation of generics, but I understand them better now. Commented May 18, 2009 at 14:59

11 Answers 11

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

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
Sign up to request clarification or add additional context in comments.

4 Comments

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. :)
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.
(only problematic if T is a mutable class and I don't think those are)
Your approach is more generic, more readable and more compact. It looks like the typical teacher's answer ;-)
8

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:

Comments

3

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.

9 Comments

This breaks at runtime obviously. You cannot cast an Object[][] to Integet[][].
Try adding template bounds, e.g. <T extends SomeInterfaceThatHasAGetZeroMethod>
getZero() should caching or just use Integer.valueOf()
@bruno You can actually cast it. Try it :-)
just replace (T[][])new Object[row][col]; with java.lang.reflect.Array.newInstance(factory.getZero().getClass(), row, col);
|
2

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.

1 Comment

@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.
1

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.

2 Comments

This is actually code from my Matrix<T extends Number> class.
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)).
1

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

Comments

1

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.

Comments

1

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

Comments

1

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.

3 Comments

Maybe a <<for(T[] t1: retVal) for(T t2: t1) t2 = value;>> could work.
it raises: java.lang.ClassCastException: [[Ljava.lang.Object; cannot be cast to [[Ljava.lang.Integer;
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.
1

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

Comments

-1

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.

1 Comment

I don't like this solution since null is not zero in Java

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.