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?

4
  • 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<Integer> 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. Commented Jul 19, 2016 at 7: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. Commented Jul 19, 2016 at 8: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(); }} Commented Jul 19, 2016 at 9: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) Commented Jul 19, 2016 at 9:43

1 Answer 1

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

Sign up to request clarification or add additional context in comments.

1 Comment

"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 stackoverflow.com/questions/2776975/…

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.