2

Since java does not have a generic array, I am using the regular trick of casting Object array to a type parameter. This is working fine when I have a formal type parameter like <T> but not when I use bounded type parameter <T extends something>.

Following Code using formal type works fine

public class Deck <T> {
    private T [] cards;
    private int size;

    public Deck () {
        cards = (T []) new Object[52];
        size = 0;
    }
}

public class BlackJackGame {
    Deck<BlackJackCard> deck;

    public BlackJackGame() {
        deck = new Deck<>();
        populate (deck);
        deck.shuffle();
    }
}

public class BlackJackCard extends Card {
}

Following code using bounded type throws error

public class Deck <T extends Card> {
    private T [] cards;
    private int size;

    public Deck () {
        cards = (T []) new Object[52];
        size = 0;
    }
}

public class BlackJackGame {
    Deck<BlackJackCard> deck;

    public BlackJackGame() {
        deck = new Deck<>();
        populate (deck);
        deck.shuffle();
    }
}

public class BlackJackCard extends Card {
}
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [LCard;
    at Deck.<init>(Deck.java:10)
    at BlackJackGame.<init>(BlackJackGame.java:5)
abb
  • 145
  • 2
  • 19
  • Look up "type erasure" and then what `` is erased to and what `` is erased to. That will help you understand the subject and understand the issue here. – Tom Sep 21 '19 at 00:21
  • Possible duplicate of [Java generics type erasure: when and what happens?](https://stackoverflow.com/questions/339699/java-generics-type-erasure-when-and-what-happens) – Tom Sep 21 '19 at 00:25
  • The second dupe covering bounded types and raw types: [What is the erasure of a bounded generic type?](//stackoverflow.com/q/42398260) – Tom Sep 21 '19 at 00:25
  • Now you know why the compiler flags `(T[])` with an “unchecked cast” warning. That cast is not safe. Are you not permitted to use a List? – VGR Sep 21 '19 at 03:08

1 Answers1

6

This example remind me early days, when I'm reading about Generics in "Effective java" book ...

First thing first, here is java generics golden rule : don't mix arrays and generics, because you get a good chance to produce unsafe code. Your code mixes generics (e.g. T, T extends Card) with arrays (e.g. T [] cards). Then, you got unsafe code in runtime.

This is one safe way (prefer lists over arrays):

class Deck <T extends Card> {
    private List<T> cards;

    public Deck () {
        cards = new ArrayList()<>;
    }

}

Now, to answer your question you should go back to some basics first in java:

1- Arrays are co-variant constructs

2- Generics are invariant constructs

3- Element Type is reified in arrays (reification)

4- Parameter Type is erased in Generic (Type erasure)

No worries, let put aside scary concepts, and check what happened with your example:

  • The Formal type T is erased in Runtime.

  • It means it's completely removed in the bytecode.

  • In 1st example the T is just replaced with Object, because it's the nearest class to it (in terms of inheritance) so,

cards = (T []) new Object[52]

is translated to

cards = (Object []) new Object[52];

which is safe.

  • In 2nd example the T is bounded to Card and it becomes , hence, the nearest class to it (in terms of inheritance) so,
cards = (T []) new Object[52]

is translated to

cards = (Card []) new Object[52];

Since Object is not a subtype of Card, you got a Runtime cast exception.

  • Can you please elaborate, why does the java do this 'nearest class' thing while adding casts? – abb Sep 25 '19 at 00:14