4

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;
}                    
Geek
  • 26,489
  • 43
  • 149
  • 227
  • What about [wikipedia](http://en.wikipedia.org/wiki/Scalar_(computing)) ? Generally it's a mathematical term for non-vector value. – default locale Feb 20 '13 at 18:02

3 Answers3

5

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;
    }
}
lbalazscs
  • 17,474
  • 7
  • 42
  • 50
  • Thanks for clearing the doubt on Scalar types ? Can you answer my second question ? – Geek Feb 20 '13 at 18:13
  • @ruakh: you might want to check out my updated answer with a new theory :) – lbalazscs Feb 20 '13 at 20:20
  • @ruakh Yes, the problem is specific to arrays. The confusion comes from the fact, that the OP asked two unrelated questions (first: what is a scalar? - actually this was originally the only question). But I'll reformulate my answer :) – lbalazscs Feb 20 '13 at 21:42
  • @ruakh I copied your theory. Also feel free to edit the answer, I guess you have the necessary rights :) – lbalazscs Feb 20 '13 at 22:24
5

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.

irreputable
  • 44,725
  • 9
  • 65
  • 93
  • 1. That "colossal mistake" was inevitable because of the type erasure. 2. As far as I understand Effective Java, the author is sure that the first workaround is correct because *"The array in question (elements) is stored in a private field and never returned to the client or passed to any other method."* – lbalazscs Feb 20 '13 at 19:13
  • erasure is the colossal mistake. some still hope one day java can get rid of erasure (which will break the first workaround). some don't care and write code like the first workaround which make it increasingly costly to get rid of erasure. – irreputable Feb 20 '13 at 19:26
  • I see and +1. I updated my answer with my understanding of your answer. – lbalazscs Feb 20 '13 at 20:19
  • 1
    "It happens to work at runtime on today's JVMs, however, we are not supposed to count on that to work forever. " It works based on how the Java language is currently specified. If the language is changed in the future, then all bets are off. – newacct Feb 20 '13 at 22:06
  • Also, erasure is not a "mistake". A class or method that is "generic" is by definition supposed to work equally for all types (within given bounds), and not care about what those particular types are. The need to care about what the types are are an indication of improper use of generics. – newacct Feb 20 '13 at 22:09
  • @newacct: Re: your first comment: Sorry, but you're mistaken. The spec clearly indicates that nonreifiable types may be a temporary feature (see [section 4.7 "Reifiable Types"](http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.7)). In a similar vein, it "strongly discourage[s]" the use of raw types, saying that future versions might forbid them (see [section 4.8 "Raw Types"](http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.8)). So irreputable's statement is quite right: the spec tells us not to rely on this stuff continuing to work in future Javas. – ruakh Feb 20 '13 at 22:37
  • newacct is correct that JLS(§5.5) clearly allows the cast to succeed at runtime, so we can count on it to work. what I heard was that the language spec is carefully written to allow full reification to be introuced later. but with sections like that that allows programs to rely on erasure to work, I don't see how. – irreputable Feb 20 '13 at 22:49
  • @irreputable: No, newacct is not correct. The spec clearly allows the cast to succeed at runtime, but the spec *also* clearly indicates that we shouldn't use this sort of thing and that future versions of the spec might not allow it. – ruakh Feb 20 '13 at 22:54
  • @ruakh - I cannot respect that kind of cop-out in a specification. essentially it tries to shift the blame to the users, while Sun is the one that created the problem. Sun did not have enough resources and they need to release generics ahead of C#, so we got this half baked stuff. – irreputable Feb 20 '13 at 23:07
  • @newacct - erasure is a mistake, just count how many generics questions on stackoverflow that are rooted in erasure. some of them are dumb questions, but most are based on legit and natural needs that are crushed by erasure. – irreputable Feb 20 '13 at 23:12
  • @irreputable: I'm not saying that you need to respect the cop-out, I'm just saying that your original statement, "we are not supposed to count on that to work forever", is in fact correct according to the spec, and that newacct's objection is erroneous. – ruakh Feb 20 '13 at 23:22
  • @ruakh - in any case, the "don't care" camp is winning, and erasure probably will stay with Java forever. – irreputable Feb 20 '13 at 23:27
  • @irreputable: Yup, I agree. – ruakh Feb 20 '13 at 23:28
1

A scalar type in this case means a non array type.

AlexWien
  • 28,470
  • 6
  • 53
  • 83