From Effective Java Item 26 Favour Generic types
All other things being equal, it is riskier to suppress an unchecked cast to an array type than to a scalar type, which would suggest the second solution. But in a more realistic generic class than Stack, you would probably be reading from the array at many points in the code, so choosing the second solution would require many casts to E rather than a single cast to E[],which is why the first solution is used more commonly [Naftalin07, 6.7].
What does the author mean by scalar type
here and what is he trying to convey here ? What is option 1 considered more dangerous than option 2?
The code :
// The elements array will contain only E instances from push(E).
// This is sufficient to ensure type safety, but the runtime
// type of the array won't be E[]; it will always be Object[]!
@SuppressWarnings("unchecked")
public Stack() {
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}
VS
// Appropriate suppression of unchecked warning
public E pop() {
if (size == 0)
throw new EmptyStackException();
// push requires elements to be of type E, so cast is correct
@SuppressWarnings("unchecked") E result =
(E) elements[--size];
elements[size] = null; // Eliminate obsolete reference
return result;
}
Ideally we want to write
E[] elements;
public Stack()
{
elements = new E[DEFAULT_INITIAL_CAPACITY];
}
Unfortunately, Java made a colossal mistake and didn't allow that. So we need workarounds.
This workaround
E[] elements;
public Stack()
{
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}
is theoretically wrong in the Java type system, since an Object[]
is not a subtype of E[]
. It happens to work at runtime on today's JVMs, however, we are not supposed to count on that to work forever. Well, actually, people do count on that, and I don't see any chance in hell that it'll change. So nobody cares.
(correction: actually the language spec §5.5 specifically allows the cast to work at runtime, therefore the code is not wrong per spec. nevertheless, it is too hackish, it is not part of "normal" type system, its correctness is based on some compromises that we dont' really want to learn.)
That 2nd workaround is correct, both practically and theoretically
Object[] elements;
public Stack()
{
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public push(E e)
{
...
elements[size++] = e;
}
public E pop()
{
...
E result = (E)element[size--];
}
the casting from Object to E is correct, since the program logic ensures that it must be an E
.
A scalar type in this example is a single value as opposed to an array which consists of multiple values, like a mathematical vector. E[] is the array, and E is the scalar.
My original thought was that Joshua Bloch thinks that it is riskier to suppress the unchecked cast warning in the case of arrays because it is more complicated to prove that nothing wrong will happen with the type-safety of your code.
Another opinion worth considering was mentioned by ruakh in the comments: "I would have thought that it's not so much about complexity of proof, as about detecting the mistake when there's a bug. I think there will generally be less "distance" between an erroneous-but-unchecked cast to (E) and a subsequent implicit cast that raises an ClassCastException, than if a cast to (E[]) were used instead"
And a third opinion (If I understand correctly, this is what irreputable wants to point out in his answer, and in any case this is my new opinion) is that the array cast is "risky" because this array could not be used outside this class. (E[])
is an unchecked cast: because of the type erasure the runtime cannot really check the correctness of this (incorrect) cast. We get away with a dirty trick, but if some method returned this array as E[], and it would be assigned to an E[] in a client class, it would still fail at runtime with ClassCastException:
public class Test {
public static void main(String[] args) {
Stack<String> stack = new Stack<String>();
String[] array = stack.getArray(); // ClassCastException at runtime here!
}
}
class Stack<E> {
E[] elements;
public Stack() {
elements = (E[]) new Object[10];
}
// oh no, our dirty-tricky array escapes!
E[] getArray() {
return elements;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With