Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Casting to generic variable

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?

like image 521
Petr Beneš Avatar asked Jul 19 '16 07:07

Petr Beneš


1 Answers

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

like image 114
Hulk Avatar answered Oct 18 '22 22:10

Hulk