views:

128

answers:

6

I get a ClassCastException when eclipse suggested that my code should be like that..

I have a class named Kort.

ArrayList<Kort> kort = new ArrayList<Kort>();

then I use toArray(), and eclipse suggest it should look like: Kort[] array = (Kort[])kort.toArray();

But it gives me this exception: ClassCastException ! :(

A: 

Ignore the fix suggestion.

OscarRyz
well i dont know how it should look.. :S
Johannes
oic :) Ignore my answer then ;)
OscarRyz
+2  A: 

Use

kort.toArray(new Kort[kort.size()]);
Robert Munteanu
+2  A: 

What you can do is:

    Kort[] array = kort.toArray(new Kort[kort.size()]);

That is considered better, as it keeps the type safety on the array. If you don't care/want that, then just ignore the suggestion. The toArray() method without parameters returns an Object[] array.

Yishai
Because Type Erasure is ... annoying.
RHSeeger
that worked, I'll use that.. thx!
Johannes
@RHSeeger - actually, type erasure is only peripherally connected to this. The distinction between toArray() and toArray(Object) has been part of the Collections framework since its introduction. See my answer for a detailed explanation.
Stephen C
@Stephen C: yes, and for compatibility's sake that could not be fixed, which is why we have type erasure now. I am sure if we had generics from the get-go, toArray() would have been designed to return the "properly" typed array.
Thilo
@Thilo: well maybe. But even if they sacrificed backwards compatibility, I think it would *still* be impossible to implement a `toArray()` that returned the right type ... using generics as they are currently defined.
Stephen C
+3  A: 

My suggestion is:

kort.toArray(new Kort[0])

Technically this might be fractionally slower than giving the correct size. However, you don't need to mention the collection variable name twice, so less chance of making a mistake. It's also easier to read. As a bonus, it also works with concurrent collections, where calling size doesn't really make a great deal of sense.

However, the best approach is to avoid using arrays of references if you possibly can.

Tom Hawtin - tackline
Interestingly IDEA by default gives you a warning on such syntax, so I have gotten in the habit of specifying the size, but you are right it is a micro-optimization, and apparently not a very good one either
Yishai
+2  A: 

The root of the problem is the difference between the Object[] toArray() and Object[] toArray(Object[]) methods in the java.util.Collection API.

The first form allocates an array of the right size to hold the members of the collection and then assigns the member references into the array. Because the collection class doesn't know what the type of the members actually is (see note below), the API specifies that the result is an instance of Object[]; i.e. as if it was allocated using new Object[size].

The second form takes an array as a parameter that will (normally) be where the collection member references are stored. Thus, the program determines what the type of the result will be by passing an array of the required type. If the supplied array is big enough to hold the collection elements, the toArray method will return the supplied array instance. If not, a new array is allocated with the same type as the supplied array instance. (This can done reflectively using Array.newInstance(clazz, size) where clazz is the supplied array's component class. Note that the type parameter is not used to do this ... and it cannot be used.)

So what is happening is that Eclipse is not smart enough to know that the real correction to your mistake is to use a different method overload. In order to make this correction, it would need to "know" about the specific semantics of the two forms of the toArray method. That's a tall order IMO.

The lesson is that Eclipse's suggested corrections are Just Suggestions ... and not guaranteed to be the right fix.

Note: The collection class does not know the appropriate array class to create in the toArray() case because of type erasure. Information about how the type was instantiated is not available to the class that implements toArray. But considering that it is generally possible to insert into a Collection<T> something that is not a T (by ignoring "unsafe typing" compiler warnings) it is maybe a good thing that the result type is an Object[]!

This problem predates generic types; the two forms of the method have been present since the introduction of the collections framework in Java 1.2.

EDIT: in a comment @Thilo suggested that this problem would not have arisen if Java had supported generics from the start. My answer to that is that it didn't happen that way, and we cannot say what would have happened if they had. But we can say that with Java generics as they are currently defined it is would be impossible to do this without redesigning the collection APIs.

Specifically, a generic class cannot figure out what actual type has been used as a type parameter for a given instance. Without this information, it cannot know the correct array type to instantiate. In practice, if you want a generic type to create instances of the type parameter or some related type, you have to design the APIs so that the relevant methods have runtime access to the actual parameter type's Class object. For example, the actual Class object could be supplied as a constructor parameter.

Stephen C
At a minimum the toArray() method would just take a class object and create the array itself, even with type erasure. And it could ensure the class object is the right type for the collection, such as toArray(Class<? super T> type);
Yishai
@Yishai - agreed, but that is not much different to the existing `<T> T[] toArray(T[] a)` method. I guess that the Sun folks decided that the (relatively minor) advantages of a new overload for `toArray` were outweighed by the compatibility issues it would introduce.
Stephen C
A: 

Another advantage of kort.toArray(new Kort[kort.size()]); over (Kort[])kort.toArray(); is that in case your kort collection is empty, the resulting to array shall be a zero length array of object type which will throw an exception if typecasted to Kort. Using kort.toArray(new Kort[kort.size()]); ensures that even if the collection is empty still the returned array is of desired type.

Prashant Gupta