11

I understand generic and casting but I dont understand generic casting. I thought I can cast only to specific type up or down through inheritence tree but this proved me wrong:

ArrayList<?> cislo = (ArrayList<? extends Number>) new ArrayList<Integer>();

This might not be the best example but hopefully you will get my point. How does this work? To what type its get cast?

Petr Beneš
  • 303
  • 3
  • 15
  • 1
    At run time there is no cast at all, as the generic information is lost (type erasure). At compile time you tell the compiler to handle that `ArrayList` as an `ArrayList extends Number>` and be quiet about it (except the warning, basically it's the same as if you'd cast a `Number` variable to `Integer` - you tell the compiler you know what you're doing). The assignment to `ArrayList>` works in any case. – Thomas Jul 19 '16 at 07:32
  • With Generics, casting should not be necessary at all. If you feel like casting a Generic type, perhaps you should check if there is not a "clean" solution without cast just by using the huge flexibility Generics have. I am sure that many people are willung to help if you have a specific example. – martinhh Jul 19 '16 at 08:20
  • I do have specific example. I work with framwork which allows to annotate JUnit test to select storage I want to send results to e.g `@ReesmoConfiguration(storage = RestApiStorage.class)` and in the abstract superclass Storage i found factory method `newInstance(Object configuration){ Class extends Storage> clazz = null; if (Bool.FALSE.equals(Property.ENABLED.get(configuration))) { clazz = DummyStorage.class; } else { clazz = (Class extends Storage>) Property.STORAGE.get(configuration); } if (clazz.isAssignableFrom(DummyStorage.class)) { return new DummyStorage(); }}` – Petr Beneš Jul 19 '16 at 09:05
  • Ok this is basically `Class extends Storage> clazz = (Class extends Storage>) Property.STORAGE.get(configuration);` - this will work if the Object returned by `get` is the class-object of something that is a subtype of `Storage. The cast is necessary if the return type of `get` is a supertype of that (e.g. `Object` or some interface) – Hulk Jul 19 '16 at 09:43

1 Answers1

4

The cast is unnecessary.

The assignments

ArrayList<?> list = new ArrayList<Integer>();
ArrayList<? extends Number> list2 = new ArrayList<Integer>();

don't require any explicit casting.

Wildcard types are "more general"/"less specific" types than concrete types, and upper bounded wildcard types (like ? extends Number) are more specific than unbounded ones (?).

More information on the relation between wildcard types can be found in the Oracle Tutorial on Wildcards and Subtyping.

The relevant part of the JLS specifying this is 4.10.2. Subtyping among Class and Interface Types

Given a generic type declaration C (n > 0), the direct supertypes of the parameterized type C, where Ti (1 ≤ i ≤ n) is a type, are all of the following:

  • D, where D is a generic type which is a direct supertype of the generic type C and θ is the substitution [F1:=T1,...,Fn:=Tn].
  • C, where Si contains Ti (1 ≤ i ≤ n) (§4.5.1).

[...]

which refers to 4.5.1. Type Arguments of Parameterized Types

A type argument T1 is said to contain another type argument T2, written T2 <= T1, if the set of types denoted by T2 is provably a subset of the set of types denoted by T1 under the reflexive and transitive closure of the following rules (where <: denotes subtyping (§4.10)):

  • ? extends T <= ? extends S if T <: S

  • ? extends T <= ?

[...]

So by this definition ArrayList<? extends Number> is a supertype of ArrayList<Integer> and ArrayList<?> is a supertype of any ArrayList<> except the raw type.

Assignment to a variable of a type that is a supertype does not require casting.


A cast is necessary if you want to assign something of a different type:

Object list = new ArrayList<Integer>();
//...somewhere else - the compiler does not know that list is always an ArrayList, but we tell it that we know what we are doing
List<? extends Number> numbers = (List<? extends Number>) list; //unchecked cast warning, but works

After that cast, you can e.g. get elements from the list and treat them as Numbers. The cast will fail at runtime if you assign something else to the list reference which is not a subtype of List<? extends Number>

    Object list = new HashSet<Integer>();
    List<? extends Number> numbers = (List<? extends Number>) list; //unchecked cast warning

fails at runtime throwing

java.lang.ClassCastException: java.util.HashSet cannot be cast to java.util.List


The problems start to arise when the generic type does not match:

List<String> stringList = new ArrayList<String>();
stringList.add("hi");
Object list = stringList;
List<? extends Number> numbers = (List<? extends Number>) list; //unchecked cast warning, but (sadly) works

Number n = numbers.get(0); //fails: java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Number

This happens because at runtime, the Erasure of the list-types matches. See also the tutorial on erasure

Hulk
  • 6,399
  • 1
  • 30
  • 52
  • "The cast is unnecessary." - as I said could not be the best example. I find jls really hard to read, but your explanation really helped, thx. Additional info can be found here http://stackoverflow.com/questions/2776975/how-can-i-add-to-list-extends-number-data-structures – Petr Beneš Jul 19 '16 at 17:57