4

Question is similar to this Why do we have to use an intermediary variable for @SuppressWarnings("unchecked")?, but I couldn't glean a solution from that one.

As an exercise, I'm building a hash-table construct from scratch. So I wrote a class LinkedListDemo auxiliary to HashTableDemo; the LinkedListDemo class tests absolutely fine. Particularly, the following subroutine in causes no ClassCastException errors at run-time:

    public LinkedListDemo<S, T> reverse() {

        if (count == 0)
            return null;

        @SuppressWarnings("unchecked")
        S[] keys = (S[]) new Object[count];
        @SuppressWarnings("unchecked")
        T[] vals = (T[]) new Object[count];

        ListNode<S, T> runner = head;
        for (int k = 0; k < count; k++) {

            keys[k] = runner.pair.getKey();
            vals[k] = runner.pair.getValue();

            runner = runner.next;

        }

        ArrayUtils.reverse(keys);
        ArrayUtils.reverse(vals);

        return new LinkedListDemo<S, T>(keys, vals);

    }

Whereas the following, in my HashTable class does:

public class HashTableDemo<S, T> {

    private LinkedListDemo<S, T>[] table = (LinkedListDemo<S, T>[]) new Object[10]; 

// more code...

}

Does anyone know how Java's HashMap class circumvents this issue, and/or how I can? I tried creating an intermediate variable within a constructor as suggested in the above link - but it didn't work.

Community
  • 1
  • 1
yangmillstheory
  • 925
  • 10
  • 28
  • 1
    You *should* be getting ClassCastExceptions at runtime. Maybe you're not testing thoroughly. The assignment `S[] keys = (S[]) new Object[count];` is only legal if `S` is `Object`. It will fail if `S` is, say `String`. – Nathaniel Waisbrot Jun 02 '13 at 04:26
  • 1
    @NathanielWaisbrot Not true, since `S` is erased to `Object` - this is the meaning of an *unchecked* cast. – Paul Bellora Jun 02 '13 at 04:58

1 Answers1

3

This will compile:

@SuppressWarnings("unchecked") // this is okay because [insert reasoning here]
private LinkedListDemo<S, T>[] table =
        (LinkedListDemo<S, T>[]) new LinkedListDemo<?, ?>[10];

The reason your code errors is because an Object[] is not a LinkedListDemo[], similarly to how an Object is not a LinkedListDemo.

The earlier unchecked casts work because S[] and T[] have been erased to Object[] at runtime.

In general, arrays of parameterized types are discouraged because their very existence is unsafe. This is because:

  • Arrays are covariant - a LinkedListDemo<S, T>[] is an Object[]. So one could assign table to an Object[] variable and then assign a String as an element - this would be legal at compile time but would at least fail at runtime with an ArrayStoreException, since arrays do runtime type checking.
  • Generic type information is not available at runtime because it's been erased by the compiler. One could once again assign table to an Object[] variable but this time assign a LinkedListDemo<String, Integer> as an element. At runtime, the array would only see that a plain LinkedListDemo had been added, so it would not fail right away if the generic types were wrong. This might lead to unexpected ClassCastExceptions at some later time.

See my answer here for a fuller explanation and example of this issue. See also this great explanation by Angelika Langer's Generics FAQ: Can I create an array whose component type is a concrete parameterized type?

The takeaway is that it will be much easier and safer to simply use a List<LinkedListDemo<S, T>> instead. If you ultimately decide to stick to using LinkedListDemo<S, T>[], make sure that it is carefully hidden as an implementation detail and stay cognizant about how it can be misused.

A couple of other misc notes:

  • @SuppressWarnings("unchecked") doesn't do anything but suppress the unchecked cast compiler warning. It's essentially saying "trust me I know what I'm doing" - but you still need to be careful that an unchecked cast isn't being misused. See this post for more info: How do I address unchecked cast warnings?
  • Like all core API classes, the source for HashMap is available so you should feel free to look over it if you want to understand its inner workings better.
Community
  • 1
  • 1
Paul Bellora
  • 51,514
  • 17
  • 127
  • 176
  • Thanks Paul; but by extension, why isn't LinkedListDemo similarly erased at run-time? – yangmillstheory Jun 02 '13 at 04:42
  • 1
    @VictorAlvarez `LinkedListDemo` is a *reifiable* type, meaning it's still available at runtime. `LinkedListDemo[]` gets erased to `LinkedListDemo[]`. – Paul Bellora Jun 02 '13 at 04:49
  • Is it reifiable because it itself holds type parameters, as opposed to S and T which are themselves type parameters? Thanks - – yangmillstheory Jun 02 '13 at 05:00
  • 1
    @VictorAlvarez It's reifiable because it's *not* itself a generic type argument or type parameter. Generic type arguments get erased, e.g. `LinkedListDemo[]` or `LinkedListDemo[]` to `LinkedListDemo[]`. Type parameters get erased, e.g. `S` to `Object`. – Paul Bellora Jun 02 '13 at 05:13
  • If Class is a type-parametrized class, then Class[] is erased to Class[] at runtime, as you say. But how does the computer interpret a parametrized class with no parameter, as in "Class[]"? – yangmillstheory Jun 02 '13 at 17:34
  • 1
    @VictorAlvarez When type erasure happens, the appropriate casts are added into the compiled bytecode. So given `Class c`, which is going to erase to `Class c`, the code `MyType obj = c.newInstance();` compiles into something like `MyType obj = (MyType)c.newInstance();` (except in bytecode). See this post for more info: [Java generics - type erasure - when and what happens](http://stackoverflow.com/questions/339699/java-generics-type-erasure-when-and-what-happens). – Paul Bellora Jun 02 '13 at 18:32