tags:

views:

813

answers:

5

I'm playing around with some code katas and trying to get a better understanding of java generics at the same time. I've got this little method that prints arrays like I like to see them and I have a couple of helper methods which accept an array of 'things' and an index and returns the array of the 'things' above or below the index (it's a binary search algorithm).

Two questions,

#1 Can i avoid the cast to T in splitBottom and splitTop? It doesn't feel right, or I'm going about this the wrong way (don't tell me to use python or something .. ;) )

#2 Do I have to write seperate methods to deal with primitive arrays or is there a better solution?

public class Util {

    public static <T> void print(T[] array) {
        System.out.print("{");
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i]);
            if (i < array.length - 1) {
                System.out.print(", ");
            }
        }
        System.out.println("}");
    }

    public static <T> T[] splitTop(T[] array, int index) {
        Object[] result = new Object[array.length - index - 1];
        System.arraycopy(array, index + 1, result, 0, result.length);
        return (T[]) result;
    }

    public static <T> T[] splitBottom(T[] array, int index) {
        Object[] result = new Object[index];
        System.arraycopy(array, 0, result, 0, index);
        return (T[]) result;
    }

    public static void main(String[] args) {

        Integer[] integerArray = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        print(integerArray);
        print(splitBottom(integerArray, 3));
        print(splitTop(integerArray, 3));

        String[] stringArray = {"one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"};
        print(stringArray);
        print(splitBottom(stringArray, 3));
        print(splitTop(stringArray, 3));

        int[] intArray = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        // ???
    }
}
+5  A: 

Generics don't handle primitives in a consistent fashion. This is because Generics are not like templates in C++, it is just a compile time addition to a single class.

When generic are compiled, you end up with Object[] in the above example as the implementing type. As int[] and byte[] etc, do not extend Object[] you cannot use them inter-changeably even if the code involved would be identical (again generics are not templates)

The only class int[] and Object[] share is Object. You can write the above methods Object as the type (see System.arraycopy, Array.getLength, Array.get, Array.set)

Peter Lawrey
+1  A: 

Question 1: Casting arrays doesn't work like you expect. A String is an Object, but a String array isn't an Object array.

Try to use something like:

public static <T> T[] splitTop(T[] array, int index) {
    T[] result = Arrays.copyOfRange(array, index + 1, array.length);
    return result;
}

Question 2: For arrays of primitives my function obviously doesn't work either. There is no elegant solution to it - look at for example the Arrays library which have several copies of essentially the same method for each primitive array type.

waxwing
You can't do that with generics—that results in an incompatible type error.
htw
@htw can you explain more?
Bedwyr Humphreys
+2  A: 

1 Can i avoid the cast to T in splitBottom and splitTop? It doesn't feel right, or I'm going about this the wrong way (don't tell me to use python or something .. ;) )

Not only can you not avoid it, but you shouldn't do it. In Java, different types of arrays are actually different runtime types. An array that was created as an Object[] cannot be assigned to a variable of AnythingElse[]. The cast there will not fail immediately, because in generics the type T is erased, but later it will throw a ClassCastException when code tries it to use it as a Something[] as you promised them, but it is not.

The solution is to either use the Arrays.copyOf... methods in Java 6 and later, or if you are using an earlier version of Java, use Reflection to create the correct type of array. For example,

T[] result = (T[])Array.newInstance(array.getClass().getComponentType(), size);

2 Do I have to write seperate methods to deal with primitive arrays or is there a better solution?

It is probably best to write separate methods. In Java, arrays of primitive types are completely separate from arrays of reference types; and there is no nice way to work with both of them.

It is possible to use Reflection to deal with both at the same time. Reflection has Array.get() and Array.set() methods that will work on primitive arrays and reference arrays alike. However, you lose type safety by doing this as the only supertype of both primitive arrays and reference arrays is Object.

newacct
+1  A: 

Java does not allow the construction of generic arrays in a type-safe manner. Use a generic sequence type instead (such as java.util.List, for example).

Here's how I would write your test program, using a generic container class fj.data.Stream:

import fj.data.Stream;
import static fj.data.Stream.range;

// ...

public int[] intArray(Stream<Integer> s) {
  return s.toArray(Integer.class).array()
}

public static void main(String[] args) {
  Stream<Integer> integerStream = range(1, 10);
  print(intArray(integerStream));
  print(intArray(integerStream.take(3)));
  print(intArray(integerStream.drop(3)));

  // ...
}
Apocalisp
A: 

You've got two problems with what you are trying to accomplish.

First of all, you are trying to use primitive types, which do not actually inherit from Object. This will mess up things. If you really need to do this, explicitly use Integer rather than int, etc.

The second and greater problem is that Java generics have type erasure. This means that at runtime, you cannot actually refer to the type of the generic. This was done to allow you to mix generic-supporting and non generic-supporting code, and ended up (IMHO) being a major source of headache for Java developers and another proof that generics should have been in Java from day 1. I suggest you read the part in the tutorial about it, it will make this issue clearer.

Uri