Satchel
Satchel

Reputation: 21

Java Generics and type transform

I just start learning Generics of Java. It confused me when I try to read some sample examples, just as below. I know it's something related to the type erasure, but I can not figure out what's going wrong.

package graph;
import java.util.*;

public class JustTest<T> {  
    public static void main (String[] args) {
        ArrayQueue<String> q = new ArrayQueue<String>(); 
        q.offer(new String("string_1"));
        q.offer(new String("string_2"));

        Object[] ob = q.getArray();
        System.out.println(ob);

        // The lines below went wrong when running
        // Error information: 
        //java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
        String[] str = q.getArray();
        System.out.println(str);
    }
}


class ArrayQueue<T> {

    private static final int MINIMUM_SIZE = 1024;

    private T[] array = (T[]) new Object[MINIMUM_SIZE];
    private int lastIndex = 0;

    public T[] getArray () {
        return array;
    }

    public T getElement () {
        return array[0];
    }

    public boolean offer(T value) {

        array[lastIndex % array.length] = value;
        lastIndex++;
        return true;
    }
}

Upvotes: 2

Views: 104

Answers (3)

developer_hatch
developer_hatch

Reputation: 16224

Update

I missed up something last time, if you are learning generics, just picked up a little twisted example, you probably need to start with some classes like Pair, but well.

I Made this changes to your ArrayQueue class:

The @SuppressWarnings is added to avoid the error casting, because we know that the cast we made here manually is right, so, suppress this warning is fine in this case.

I also added two constructors, one parameter and two parameters, the first parameter is the Class<T> c is the type you will say it will have in the moment you create it, and the second one is if you want another sizes for your array.

in this line:

array = (T[]) Array.newInstance(c, MINIMUM_SIZE);

is how I made possible to create the generic array, with the Array.newInstance method, you can tell the class and the size, and the type is given as parameter, that's the trick.

@SuppressWarnings("unchecked")
class ArrayQueue<T> {

  private static final int MINIMUM_SIZE = 1024;
  private T[] array;
  private int lastIndex = 0;

  public ArrayQueue(Class<T> c){
    array = (T[]) Array.newInstance(c, MINIMUM_SIZE);
  }

  public ArrayQueue(Class<T> c, int size){
    array = (T[]) Array.newInstance(c, size);
  }

  public T[] getArray () {
    return array;
  }

  public T getElement () {
    return array[0];
  }

  public boolean offer(T value) {

    array[lastIndex % array.length] = value;
    lastIndex++;
    return true;
  }
}

And in the main class, for testing it:

  public static void main(String[] args)
  {
        ArrayQueue<String> q = new ArrayQueue<String>(String.class); 
        q.offer(new String("string_1"));
        q.offer(new String("string_2"));

        String[] ob = q.getArray();
        System.out.println(ob[0]);
        System.out.println(ob[1]);

        // The lines below went wrong when running
        // Error information: 
        //java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
        String[] str = q.getArray();
        System.out.println(str[0]);
  }

Upvotes: 1

Jorn Vernee
Jorn Vernee

Reputation: 33845

The problem lies with the unchecked cast in your ArrayQueue class:

private T[] array = (T[]) new Object[MINIMUM_SIZE];

You basically say here that an Object[] is actually a T[], but this cast is 'unchecked' so there is never a check at runtime. If there was, this cast would fail, since an Object[] is not a T[] unless T is Object. But since the cast is unchecked, the program doesn't crash on this line.

The program crashes on the line where the compiler inserts an implicit cast, at the point where T is a known concrete type:

String[] str = q.getArray();

Here you go from T[] to String[], so the compiler will insert an implicit cast here:

String[] str = (String[]) q.getArray();

And this is where it blows up, since your earlier cast to T[] was faulty.

With the interface you currently have, the most convenient solution would be to pass a reference to a constructor of T[], so you can actually create a T[]:

class ArrayQueue<T> {

    private static final int MINIMUM_SIZE = 1024;

    private T[] array;

    ...

    public ArrayQueue(IntFunction<T[]> arrayCons) {
        array = arrayCons.apply(MINIMUM_SIZE);
    }

...

ArrayQueue<String> q = new ArrayQueue<>(String[]::new);  // passing a constructor reference

Or use a similar solution to what API Collections do, using an Object[] instead of a T[] array, and require a reference to a T[] constructor when calling getArray:

class ArrayQueue<T> {

    private static final int MINIMUM_SIZE = 1024;

    private Object[] array = new Object[MINIMUM_SIZE];

    ...

    public T[] getArray(IntFunction<T[]> arrayCons) {
        T[] ret = arrayCons.apply(size()); // implement size
        System.arrayCopy(array, firstIndex, ret, 0, size());
        return ret;
    }

    public T getElement() {
        return (T) array[0]; // a safe unchecked cast, since you only put Ts into the array.
    }
}

Upvotes: 1

ItamarG3
ItamarG3

Reputation: 4122

During runtime, Ts are replaced by Object, unless there is a bounding type. This means that

private T[] array = (T[]) new Object[MINIMUM_SIZE];

actually becomes

private Object[] array = new Object[MINIMUM_SIZE];

So when you call getArray, you get an array of Object, which can't be assigned a reference of type String[]. Hence the ClassCastException.

Each element in the array can, by itself, be cast from Object to String (specifically because it's String), but the array as a whole can't be cast.

Which is why you get

[Ljava.lang.Object; cannot be cast to [Ljava.lang.String;

The [L means it's an array.

To get them as strings, you can do this thing:

String[] str = Arrays.toList(q.getArray()).stream().map(Object::toString()).collect(Collectors.toList()).toArray();

Which should work.

Upvotes: 0

Related Questions