3

I have a class MyStack<T> which defines the following

public T[] toArray(){
  int s=size();
  @SuppressWarnings("unchecked")
  T[] result=(T[])new Object[s];
  Node n=first;
  for (int i=0; i<s; i++){
     result[i]=n.data;
     n=n.next;
  }
  return result;
}

Since this returns an array of type T, I would think that if I declared this instance: MyStack<String> s=new MyStack<>, that the following would be perfectly valid: String[] test=s.toArray(). I think this because since s is of type String, toArray should return an array of type String, since String has basically been substituted in for every T in this class (only for this particular instantiation, I know). The only way this runs without errors is if I do this: Object[] test=s.toArray().

Why is this?

user50210
  • 93
  • 2
  • 9

2 Answers2

2

In a word, type erasure. Taken from the Java website:

Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded. The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods.

What this means is that, when your code is compiled, MyStack<String> is compiled into MyStack<Object>. This is to make sure that generics do not incur an overhead by needing to create new classes. How does this apply to you? Well..

MyStack<String> s = new MyStack<>();

is converted into..

MyStack<Object> s = new MyStack<>();

Now, this means that when you call the toArray method, the only type that can be guarenteed is the Object type. The compiler can't be sure that everything it returns is of type String, so it won't let you treat it as a String, due to the strong typing in Java. So, what is the only variable type left?

Object[] array = s.toArray();

Extra Reading

christopher
  • 24,892
  • 3
  • 50
  • 86
  • 'Because erasure' does not actually answer the question here. It only explains why the exception is thrown from a weird spot. – Radiodef Nov 04 '14 at 19:41
  • I guess I'm slightly confused then. If the best we can do is say that `s` is a stack filled with `Objects`, which all other classes descend from, why even bother with the generic types? What am I missing here? – user50210 Nov 05 '14 at 05:22
  • @user50210 The problem is that you are trying to cast `Object[]` in to a `String[]` and it's not a valid cast. – Radiodef Nov 05 '14 at 16:48
1

Well, hold on a minute. Suppose your hypothesis were correct that String were substituted for every T.

Would the following cast be valid?

String[] result = (String[])new Object[s];

No, it would not. We can be sure that a new Object[] is not a String[].

Now sometimes you will see something like (T[])new Object[n] but it only works because the cast actually becomes erased inside the generic class. (It is a deceptive idiom.)

When the class gets compiled, what actually happens is that references to T are replaced with its upper bound (probably Object unless you had something like <T extends ...>):

public Object[] toArray(){
  int s=size();
  Object[] result=new Object[s];
  Node n=first;
  for (int i=0; i<s; i++){
     result[i]=n.data;
     n=n.next;
  }
  return result;
}

And the cast is moved to the call site:

MyStack stack = new MyStack();
String[] arr = (String[])stack.toArray();

So in fact, while the cast is erased inside the class, the cast does happen once the value is returned to outside the class, where ClassCastException is thrown.

The inability to instantiate arrays (and objects in general) generically is why the Collections framework defines their toArray method to take the return array as an argument. A simple version of this for you would be like the following:

public T[] toArray(T[] inArray){
    int s = size();

    Node n = first;
    for (int i = 0; i < s; i++){
        inArray[i] = n.data;
        n = n.next;
    }

    return inArray;
}

For some ideas on how to create an array generically, you may see 'How to create a generic array in Java?'; however you will need the caller to pass some argument to the method.

Community
  • 1
  • 1
Radiodef
  • 35,285
  • 14
  • 78
  • 114