Reputation: 27385
I'm reading J. Bloch's effective Java and now I'm at the arrays vs lists section. Here is an example of unchecked cast he provided:
interface Function<T> {
T apply(T arg1, T arg2);
}
public class Main{
public static void main( String[] args ){
Function<String> f = null;
List<String> str = Arrays.asList("asd");
//staff
reduce(str, f, ""); //E's deduced to String. Where is type-unsafe?
}
static <E> E reduce(List<E> list, Function<E> f, E initVal) {
E[] snapshot = (E[]) list.toArray(); // Unchecked cast
E result = initVal;
for (E e : snapshot)
result = f.apply(result, e);
return result;
}
}
He said that the method is not type-safe and we can easily get the ClassCastException
. But I don't see how. Where's the type-unsafe, the type variable E
will always be deduced to the appropriate type so we're no worried about class-cast-exeption.
Couldn't you give an example with throwing ClassCastException
?
Upvotes: 15
Views: 3179
Reputation: 197
Object[] toArray() Returns an array containing all of the elements in this list in proper sequence (from first to last element).
We are casting this to E[] inferring generics so the cast is unchecked because jvm doesnt know what type E will be so the warning.
Say for example, E is String type(as in code by you). And we are trying to cast Object[] to String[], which can very well be Object[] to Integer[] for some other case. This validity cant be tested at compile/run time by jvm so the issue.
public static void main( String[] args ){
List<String> str = Arrays.asList("asf");
//staff
System.out.println(reduce(str, 2)); //E's deduced to String. Where is type-unsafe?
}
static <E, T> E reduce(List<E> list, T initVal) {
Object snapshot = list.size(); // Unchecked cast
return (E) snapshot;
}
This will create class cast exception.
Upvotes: 4
Reputation: 48404
The list.toArray
idiom you employ here is not parametrized by an array of your List
's parametrized type, so it returns Object[]
.
For instance, with your List<String> str
, you can invoke: String[] foo = str.toArray(new String[str.size()]);
without casting.
The problem here is that, due to Java generics' design, you can never initialize a new E[]
, hence you have to cast to (E[])
.
I cannot see this throwing a ClassCastException
ever as is.
As others have mentioned, the "cosmetic" workaround is to add @SuppressWarnings("unchecked")
before your toArray
invocation, which will suppress the warning.
Upvotes: 4
Reputation: 100209
There's no compile-time guarantee that list.toArray()
will return the array of type E[]
. Moreover it almost always returns an array of type Object[]
. Thus depending on the later usage of this array you may have a ClassCastException
. For example, consider the following code:
public static void main( String[] args ){
List<String> str = Collections.singletonList("asd");
String[] array = test(str);
}
static <E> E[] test(List<E> list) {
E[] snapshot = (E[]) list.toArray(); // Unchecked cast
return snapshot;
}
Here you return this E[]
array and the receiver expects that String[]
array is returned. But actually it's Object[]
array, thus you will get the ClassCastException
in main
method after the returned generic type is implicitly cast to the String[]
.
In your code you can be sure that the array is used in safe way. But compiler is not smart enough to make this analysis, so it just warns you.
Upvotes: 13
Reputation: 3048
There is nothing wrong with your cast, but with Java Generics:
static <E> E reduce(List<E> list, Function<E> f, E initVal) {
@SuppressWarnings({"unchecked"}) //nevermind
E[] snapshot = (E[]) list.toArray(); //Unchecked cast
E result = initVal;
for (E e : snapshot)
result = f.apply(result, e);
return result;
}
Upvotes: 3