Feeds:
Posts
Comments

Archive for April, 2012

Let’s take a very simple code example just to understand where I am heading to:

public class Tester {

  public static void main(String[] args) {

    String[] strings = {"alpha", "beta"};
    Integer[] integers = {1, 2, 3};
    Long[] longs = {31L, 76L};
    Short[] shorts = {7, 8, 9};

    printAnArrayOfAnything(strings);
    printAnArrayOfAnything(integers);
    printAnArrayOfAnything(longs);
    printAnArrayOfAnything(shorts);

  }

  private static void printAnArrayOfAnything(Object[] objects) { 
   
    StringBuilder out = new StringBuilder("");
    for (Object obj : objects)
      out.append(obj.getClass().getSimpleName()).append("=")
         .append(obj).append(" ");
    System.out.println(out.toString());

  }
}

The output would be:

String=alpha String=beta
Integer=1 Integer=2 Integer=3
Long=31 Long=76
Short=7 Short=8 Short=9

The method printAnArrayOfAnything() can process any of the declared arrays. This is because in Java, arrays of objects are co-variant. This means that the added assignment statements are accepted by the compiler:

public class Tester {

  public static void main(String[] args) {

    String[] strings = {"alpha", "beta"};
    Integer[] integers = {1, 2, 3};
    Long[] longs = {31L, 76L};
    Short[] shorts = {7, 8, 9};

    Object[] objects = integers;
             objects = longs;
             objects = shorts;

Otherwise said Integer[] is an Object[]
Equally Long[], Short[] or an array of any Object sub-class is an Object[]

This is why, even though the method expects a parameter of type Object[], an array of any component, sub type of Object, will be accepted.

Now, the problem with co-variance is illustrated by the following example:


public class Tester {

  public static void main(String[] args) {

    Integer[] integers = {1, 2, 3};
    Object[] objects = integers;

    objects[0] = "A string";
  }
}

This code compiles just fine. Nothing prevents you from putting a String in an Object array, right?

But, in fact, objects points to an array of Integer. So putting a String in it would corrupt this array.

This kind of programming error is unfortunately caught only at run time in Java.

It will raise the following Exception:

Exception in thread "main" java.lang.ArrayStoreException: java.lang.String

In Java arrays, the type of the element is kept in an instance variable, making at least such a run time safety net available.

We will see that conversly, in generic types, the type parameters being erased at run time, forces such generic type to be non covariant (or rigid).

On the other end, what can you do if you deal with arrays of primitive types instead of sub-classes of Object?

public class Tester {

  public static void main(String[] args) {

    int[] ints = {10, 20, 30};
    long[] longArray = {67L, 23L};
    short[] shortArray = {87, 79, 9};

    printAnArrayOfAnything(ints);   // does not compile
    printAnArrayOfAnything(longs);  // does not compile
    printAnArrayOfAnything(shorts); // does not compile

  }

  private static void printAnArrayOfAnything(Object[] objects) {
    
      StringBuilder out = new StringBuilder("");
      for (Object obj : objects)
        out.append(obj.getClass().getSimpleName()).append("=")
           .append(obj).append(" ");
      System.out.println(out.toString());

  }
}

Obviously, neither int[], long[] or short[] conform to Object[], since int, long or short are just NOT Objects.

What we would need is a super-type T of int, long or short and use it as the type of the parameter of the method, like this: printAnArrayOfAnything(T[] items).

But such of super-type just does not exist. Of course we could use Object as a super type of int[], short[], long[] etc, but with an Object we cannot iterate to print the elements of the underlying array.

So, should we renounce to have a unique method to print all these different arrays of primitives? Not exactly, but we need to convert each array of primitives to an array of Object:

public class Tester {

  public static void main(String[] args) {

    String[] strings = {"alpha", "beta"};
    Integer[] integers = {1, 2, 3};
    Long[] longs = {31L, 76L};
    Short[] shorts = {7, 8, 9};

    printAnArrayOfAnything(strings);
    printAnArrayOfAnything(integers);
    printAnArrayOfAnything(longs);
    printAnArrayOfAnything(shorts);

    int[] ints = {10, 20, 30};
    long[] longArray = {67L, 23L};
    short[] shortArray = {87, 79, 9};

    printAnArrayOfAnything(ints);
    printAnArrayOfAnything(longArray);
    printAnArrayOfAnything(shortArray);

  }

  private static void printAnArrayOfAnything(Object param) {

    Object[] objects;

    if (param instanceof Object[])
      objects = (Object[]) param;
    else
      objects = toWrapperArray(param);

    StringBuilder out = new StringBuilder("");
    for (Object obj : objects)
      out.append(obj.getClass().getSimpleName()).append("=")
         .append(obj).append(" ");
        
    System.out.println(out.toString());

  }

  public static Object[] toWrapperArray(Object array) {

    if (Array.getLength(array) == 0)
      return new Object[0];

    Class wrapperClass = Array.get(array, 0).getClass();
    Object[] objArray = (Object[]) Array.newInstance(wrapperClass,
                                   Array.getLength(array));
    int i = 0;
    for (Object o : objArray) {
      objArray[i] = Array.get(array, i);
      i++;
    }
    return objArray;
  }
}

Here, we change the type of the method parameter to Object and not Object[], to be more general and encompasses array of primitives (line 24).

The method toWrapperArray() is called when dealing with arrays of primitives. Its duty is to transform the array of primitives received, to an array of the corresponding wrapper class instances.

For example int[] will be transformed to Integer[], short[] to Short[] etc.

For doing so, some static methods of the class Array are very handy to handle an array even so it has been declared as a simple Object.

The method Array.get() is a replacement to T[i] for example. Moreover, Array.get() returns the element as a wrapper instance. i. e. it will return an Integer instance if called on an int[]

This way we can get the wrapper class of the array primitive element and are able to build an array of the wrapper class instances.

The drawback of this method is that the array must not be empty, otherwise Array.get() rises an ArrayIndexOutOfBoundsException.

In this occurrence, we simply return an empty Object[] instead of an empty Integer[], or Long[], which is not perfectly correct.

Alternatively, we could do this:

public class Tester {

  public static void main(String[] args) {

    boolean[] bools = {true, false};
    char[] chars = {'a', 'b', 'c'};
    byte[] bytes = {};
    short[] shortArray = {87, 79, 9};
    int[] ints = {10, 20, 30};
    long[] longArray = {67L, 23L};
    float[] floats = {1.2f, 3.78f, 3.14f};
    double[] doubles = {3 / 4d, 1 / 3d};

    printAnArrayOfAnything(bools);
    printAnArrayOfAnything(chars);
    printAnArrayOfAnything(bytes);
    printAnArrayOfAnything(shortArray);
    printAnArrayOfAnything(ints);
    printAnArrayOfAnything(longArray);
    printAnArrayOfAnything(floats);
    printAnArrayOfAnything(doubles);

  }

  private static void printAnArrayOfAnything(Object param) {

    Object[] objects;

    if (param instanceof Object[])
      objects = (Object[]) param;
    else
      objects = toWrapperArray(param);

    StringBuilder out = new StringBuilder(
                            objects.getClass()
                                   .getComponentType()
                                   .getSimpleName()
                            + "[");

    for (Object obj : objects)
      out.append(obj).append(", ");

    if (objects.length > 0)
      out.setLength(out.length() - 2);

    out.append("]");

    System.out.println(out.toString());

  }

  public static Object[] toWrapperArray(Object array) {

    Class primitiveClass = array.getClass().getComponentType();
    Class wrapperClass = primitiveToWrapper.get(primitiveClass);
    Object[] objArray = (Object[]) Array.newInstance(wrapperClass,
                                   Array.getLength(array));
    int i = 0;
    for (Object o : objArray) {
      objArray[i] = Array.get(array, i);
      i++;
    }
    return objArray;
  }

  private static Map<Class, Class> primitiveToWrapper 
                                      = new HashMap<Class, Class>();

  static {
    primitiveToWrapper.put(Boolean.TYPE, Boolean.class);
    primitiveToWrapper.put(Character.TYPE, Character.class);
    primitiveToWrapper.put(Byte.TYPE, Byte.class);
    primitiveToWrapper.put(Short.TYPE, Short.class);
    primitiveToWrapper.put(Integer.TYPE, Integer.class);
    primitiveToWrapper.put(Long.TYPE, Long.class);
    primitiveToWrapper.put(Float.TYPE, Float.class);
    primitiveToWrapper.put(Double.TYPE, Double.class);
  }
}

This output would be:


Boolean[true, false]
Character[a, b, c]
Byte[]
Short[87, 79, 9]
Integer[10, 20, 30]
Long[67, 23]
Float[1.2, 3.78, 3.14]
Double[0.75, 0.3333333333333333]

This way, the array’s component type is correct even though the array is empty.

Unfortunately, there is no way to obtain directly the wrapper class from a primitive class (or at least I never found it!).
However, there is the field TYPE in every wrapper class that indicates the corresponding primitive class. We take advantage of this to build a correspondance map.

In a coming post, we will explore polymorphic aspects of generic types like collections and the impact of having covariant type parameters or not.

Finally we will compare Java and Scala design on these aspects.

Stay tuned !

Read Full Post »