Java Generics FAQs - Generic And Parameterized Types
Generic
And Parameterized Types
© Copyright 2004-2022 by Angelika Langer. All
Rights Reserved.
Generic And Parameterized Types
Fundamentals
What
is a parameterized or generic type?
A generic type is a type with formal
type parameters. A parameterized type is an instantiation of a generic
type with actual type arguments.
|
A
generic type
is a reference type that has one
or more type parameters. These type parameters are later replaced by type
arguments when the generic type is instantiated (or
declared
).
Example (of a generic type):
interface Collection<E> {
public void add (E x);
public Iterator<E> iterator();
}
The interface
Collection
has one type parameter
E
.
The type parameter
E
is a place holder that will later be replaced
by a type argument when the generic type is instantiated and used. The
instantiation of a generic type with actual type arguments is called a
parameterized
type
.
Example (of a parameterized type):
Collection<String> coll = new LinkedList<String>();
The declaration
Collection<String>
denotes a parameterized
type, which is an instantiation of the generic type
Collection
,
where the place holder
E
has been replaced by the concrete type
String
. |
LINK TO THIS
|
GenericTypes.FAQ001
|
REFERENCES
|
What
is a type parameter?
|
How
do I define a generic type?
Like a regular type, but with a type
parameter declaration attached.
|
A generic type is a reference type that has one or more
type parameters. In the definition of the generic type, the type parameter
section follows the type name. It is a comma separated list of identifiers
and is delimited by angle brackets.
Example (of a generic type):
class Pair<X,Y> {
private X first;
private Y second;
public Pair(X a1, Y a2) {
first = a1;
second = a2;
}
public X getFirst() { return first; }
public Y getSecond() { return second; }
public void setFirst(X arg) { first = arg; }
public void setSecond(Y arg) { second = arg; }
}
The class
Pair
has two type parameters
X
and
Y
.
They are replaced by type arguments when the generic type
Pair
is instantiated. For instance, in the declaration
Pair<String, Date>
the type parameter
X
is replaced by the type argument
String
and
Y
is replaced by
Date
.
The scope of the identifiers
X
and
Y
is
the
entire definition of the class. In this scope the two type parameters
X
and
Y
are used like they were types (with some restrictions). In the example
above, the type parameters are used as the argument and return type of
instance methods and the types of instance fields.
Type parameters can be declared with bounds. Bounds give access to
methods of the unknown type that the type parameter stands for. In our
example, we do not invoke any methods of the unknown types
X
and
Y
.
For this reason, the two type parameters are unbounded. |
LINK TO THIS
|
GenericTypes.FAQ002
|
REFERENCES
|
What
is a type parameter?
What
is a bounded type parameter?
What
is a type parameter bound?
|
Are
there any types that cannot have type parameters?
All types, except enum types, anonymous
inner classes and exception classes, can be generic..
|
Almost all reference types can be generic. This includes
classes, interfaces, nested (static) classes, nested interfaces, inner
(non-static) classes, and local classes.
The following types cannot be generic:
Anonymous inner classes
. They can implement a parameterized interface
or extend a parameterized class, but they cannot themselves be generic
classes. A generic anonymous class would be nonsensical. Anonymous
classes do not have a name, but the name of a generic class is needed for
declaring an instantiation of the class and providing the type arguments.
Hence, generic anonymous classes would be pointless.
Exception types
. A generic class must not directly or indirectly
be derived from class
Throwable
. Generic exception or error
types are disallowed because the exception handling mechanism is a runtime
mechanism and the Java virtual machine does not know anything about Java
generics. The JVM would not be capable of distinguishing between
different instantiations of a generic exception type. Hence, generic exception
types would be pointless.
Enum types
. Enum types cannot have type parameters. Conceptually,
an enum type and its enum values are static. Since type parameters
cannot be used in any static context, the parameterization of an enum type
would be pointless. |
LINK TO THIS
|
GenericTypes.FAQ003
|
REFERENCES
|
Can
I use generic / parameterized types in exception handling?
Why
are generic exception and error types illegal?
Why
are generic enum types illegal?
|
How
is a generic type instantiated?
By providing a type argument per type
parameter.
|
In order to use a generic type we must provide one type
argument per type parameter that was declared for the generic type. The
type argument list is a comma separated list that is delimited by angle
brackets and follows the type name. The result is a so-called parameterized
type.
Example (of a generic type):
class Pair<X,Y> {
private X first;
private Y second;
public Pair(X a1, Y a2) {
first = a1;
second = a2;
}
public X getFirst() { return first; }
public Y getSecond() { return second; }
public void setFirst(X arg) { first = arg; }
public void setSecond(Y arg) { second = arg; }
}
If we want to use the generic type
Pair
we must specify the type
arguments that shall replace the place holders
X
and
Y
.
A type argument can be a concrete reference type, such as
String
,
Long
,
Date
,
etc.
Example (of a concrete parameterized type):
public void printPair(
Pair<String,Long>
pair) {
System.out.println("("+pair.getFirst()+","+pair.getSecond()+")");
}
Pair<String,Long>
limit =
new
Pair<String,Long>
("maximum",1024L);
printPair(limit);
The instantiation
Pair<String,Long>
is a concrete parameterized
type and it can be used like a regular reference type (with a couple of
restrictions that are discussed later). In the example, we have been
using the concrete parameterized type as argument type of a method, as
type of a reference variable, and in a
new
expression for creation
of an object.
In addition to concrete instantiation there so-called
wildcard instantiations
.
They do not have concrete types as type arguments, but so-called
wildcards
.
A wildcard is a syntactic construct with a "
?
" that denotes not
just one type, but a family of types. In its simplest form a wildcard
is just a question mark and stands for "all types".
Example (of a wildcard parameterized type):
public void printPair(
Pair<?,?>
pair) {
System.out.println("("+pair.getFirst()+","+pair.getSecond()+")");
}
Pair<?,?>
limit = new
Pair<String,Long>
("maximum",1024L);
printPair(limit);
The declaration
Pair<?,?>
is an example of a wildcard parameterized
type, where both type arguments are wildcards. Each question mark stands
for a
separate
representative from the family of "all types".
The resulting family of instantiations comprises
all
instantiations
of the generic type
Pair
. (Note: the concrete type arguments
of the family members need
not
be identical; each "
?
" stands
for a separate type.) A reference variable or method parameter whose type
is a wildcard parameterized type, such as
limit
and
pair
in the example, can refer to any member of the family of types that the
wildcard denotes.
It is permitted to leave out the type arguments altogether and not specify
type arguments at all. A generictype without type arguments is called
raw
type
and is only allowed for reasons of compatibility with non-generic
Java code. Use of raw types is discouraged. The Java Language
Specification even states that it is possible that future versions of the
Java programming language will disallow the use of raw types. |
LINK TO THIS
|
GenericTypes.FAQ004
|
REFERENCES
|
What
is a type argument?
Which
types are permitted as type arguments?
What
is a wildcard?
What
is a concrete parameterized type?
What
is a wildcard parameterized type?
Can
I use a concrete parameterized type like any other type?
Can
I use a wildcard parameterized like any other type?
What
is the raw type?
|
Why
do instantiations of a generic type share the same runtime type?
Because of type erasure.
|
The compiler translates generic and parameterized types
by a technique called
type erasure
. Basically, it elides all
information related to type parameters and type arguments. For instance,
the parameterized type
List<String>
is translated to type
List
,
which is the so-called
raw type
. The same happens for the
parameterized type
List<Long>
; it also appears as
List
in the bytecode.
After translation by type erasure, all information regarding type parameters
and type arguments has disappeared. As a result, all instantiations of
the same generic type share the same runtime type, namely the raw type.
Example (printing the runtime type of two parameterized types):
System.out.println("runtime type of ArrayList<String>:
"+ne
w ArrayList<String>().getClass());
System.out.println("runtime type of ArrayList<Long>
: "+new ArrayList<Long>().g
etClass());
prints:
runtime type of
ArrayList<String>
:
class java.util.
ArrayList
runtime type of
ArrayList<Long>
: class java.util.
ArrayList
The example illustrates that
ArrayList<String>
and
ArrayList<Long>
share the runtime type
ArrayList
. |
LINK TO THIS
|
GenericTypes.FAQ005
|
REFERENCES
|
How
does the compiler translate Java generics?
What
is type erasure?
What
is the raw type?
|
Can
I cast to a parameterized type?
Yes, you can, but under certain circumstances
it is not type-safe and the compiler issues an "unchecked" warning.
|
All instantiations of a generic type share the same runtime
type representation, namely the representation of the raw type. For instance,
the instantiations of a generic type
List
, such as
List<Date>
,
List<String>
,
List<Long>
,
etc. have different static types at compile time, but the same dynamic
type
List
at runtime.
A cast consists of two parts:
-
a static type check performed by the compiler at compile time and
-
a dynamic type check performed by the virtual machine at runtime.
The static part sorts out nonsensical casts, that cannot succeed, such
as the cast from
String
to
Date
or from
List<String>
to
List<Date>
.
The dynamic part uses the runtime type information and performs a type
check at runtime. It raises a
ClassCastException
if the
dynamic type of the object is not the target type (or a subtype of the
target type) of the cast. Examples of casts with a dynamic part are the
cast from
Object
to
String
or from
Object
to
List<String>
.
These are the so-called downcasts, from a supertype down to a subtype.
Not all casts have a dynamic part. Some casts are just static casts
and require no type check at runtime. Examples are the casts between
primitive types, such as the cast from
long
to
int
or
byte
to
char
. Another example of static casts are the so-called
upcasts, from a subtype up to a supertype, such as the casts from
String
to
Object
or from
LinkedList<String>
to
List<String>
.
Upcasts are casts that are permitted, but not required. They are
automatic conversions that the compiler performs implicitly, even without
an explicit cast expression in the source code, which means, the cast is
not required and usually omitted. However, if an upcast appears somewhere
in the source code then it is a purely static cast that does not have a
dynamic part.
Type casts with a dynamic part are potentially unsafe, when the target
type of the cast is a parameterized type. The runtime type information
of a parameterized type is non-exact, because all instantiations of the
same generic type share the same runtime type representation. The virtual
machine cannot distinguish between different instantiations of the same
generic type. Under these circumstances the dynamic part of a cast
can succeed although it should not.
Example (of unchecked cast):
void m1() {
List<Date> list = new ArrayList<Date>();
...
m2(list);
}
void m2(Object arg) {
...
List<String> list =
(List<String>)
arg;
// unchecked warning
...
m3(list);
...
}
void m3(List<String> list) {
...
String s = list.get(0);
//
ClassCastException
...
}
The cast from
Object
to
List<String>
in method
m2
looks like a cast to
List<String>
, but actually is a cast from
Object
to the raw type
List
. It would succeed even if the object referred
to were a
List<Date>
instead of a
List<String>
.
After this successful cast we have a reference variable of type
List<String>
which refers to an object of type
List<Date>
. When we retrieve
elements from that list we would expect
String
s, but in fact we
receive
Date
s - and a
ClassCastException
will occur in
a place where nobody had expected it.
We are prepared to cope with
ClassCastException
s when there
is a cast expression in the source code, but we do not expect
ClassCastException
s
when we extract an element from a list of strings. This sort of unexpected
ClassCastException
is
considered a violation of the type-safety principle. In order to
draw attention to the potentially unsafe cast the compiler issues an "unchecked"
warning when it translates the dubious cast expression.
As a result, the compiler emits "unchecked" warnings for every dynamic
cast whose target type is a parameterized type. Note that an upcast
whose target type is a parameterized type does
not
lead to an "unchecked"
warning, because the upcast has no dynamic part. |
LINK TO THIS
|
GenericTypes.FAQ006
|
REFERENCES
|
Why
do instantiations of the same generic type share the same runtime type?
What
does type-safety mean?
What
is the type erasure of a parameterized type?
|
Can
I use parameterized types in exception handling?
Can
generic types have static members?
Concrete Instantiations
What
is a concrete parameterized type?
Is
List<Object> a supertype of List<String>?
Can
I use a concrete parameterized type like any other type?
Can
I create an array whose component type is a concrete parameterized type?
No, because it is not type-safe.
|
Arrays are covariant, which means that an array of supertype
references is a supertype of an array of subtype references. That
is,
Object[]
is a supertype of
String[]
and a string
array can be accessed through a reference variable of type
Object[]
.
Example (of covariant arrays):
Object[] objArr = new String[10]; // fine
objArr[0] = new String();
In addition, arrays carry runtime type information about their component
type, that is, about the type of the elements contained. The runtime
type information regarding the component type is used when elements are
stored in an array in order to ensure that no "alien" elements can be inserted.
Example (of array store check):
Object[] objArr = new String[10];
objArr[0] = new Long(0L); // compiles; fails at runtime with ArrayStoreException
The reference variable of type
Object[]
refers to a
String[]
,
which means that only strings are permitted as elements of the array.
When an element is inserted into the array, the information about the array's
component type is used to perform a type check - the so-called
array
store check.
In our example the array store check will fail because
we are trying to add a
Long
to an array of
String
s.
Failure of the array store check is reported by means of a
ArrayStoreException
.
Problems arise when an array holds elements whose type is a concrete
parameterized type. Because of type erasure, parameterized types do not
have exact runtime type information. As a consequence, the array
store check does not work because it uses the dynamic type information
regarding the array's (non-exact) component type for the array store check.
Example (of array store check in case of parameterized component type):
Pair<Integer,Integer>[] intPairArr = new Pair<Integer,Integer>[10];
// illegal
Object[] objArr = intPairArr;
objArr[0] = new Pair<String,String>("",""); // should fail,
but would succeed
If arrays of concrete parameterized types were allowed, then a reference
variable of type
Object[]
could refer to a
Pair<Integer,Integer>[]
,
as shown in the example. At runtime an array store check must be performed
when an array element is added to the array. Since we are trying to add
a
Pair<String,String>
to a
Pair<Integer,Integer>[]
we
would expect that the type check fails. However, the JVM cannot detect
any type mismatch here: at runtime, after type erasure,
objArr
would have the dynamic type
Pair[]
and the element to be stored
has the matching dynamic type
Pair
. Hence the store check succeeds,
although it should not.
If it were permitted to declare arrays that holds elements whose type
is a concrete parameterized type we would end up in an unacceptable situation.
The array in our example would contain different types of pairs instead
of pairs of the same type. This is in contradiction to the expectation
that arrays hold elements of the same type (or subtypes thereof).
This undesired situation would most likely lead to program failure
some time later, perhaps when a method is invoked on the array elements.
Example (of subsequent failure):
Pair<Integer,Integer>[] intPairArr = new Pair<Integer,Integer>[10];
// illegal
Object[] objArr = intPairArr;
objArr[0] = new Pair<String,String>("",""); // should fail,
but would succeed
Integer i = intPairArr[0].getFirst(); // fails at runtime with ClassCastException
The method
getFirst
is applied to the first element of the array
and it returns a
String
instead of an
Integer
because
the first element in the array
intPairArr
is a pair of strings,
and not a pair of integers as one would expect. The innocent-looking assignment
to the
Integer
variable
i
will fail with a
ClassCastException
,
although no cast expression is present in the source code. Such an
unexpected
ClassCastException
is considered a violation of type-safety.
In order to prevent programs that are not type-safe all arrays holding
elements whose type is a concrete parameterized type are illegal. For the
same reason, arrays holding elements whose type is a wildcard parameterized
type are banned, too. Only arrays with an unbounded wildcard parameterized
type as the component type are permitted. More generally, reifiable
types are permitted as component type of arrays, while arrays with a non-reifiable
component type are illegal. |
LINK TO THIS
|
GenericTypes.FAQ104
|
REFERENCES
|
What
does type-safety mean?
Can
I declare a reference variable of an array type whose component type is
a concrete parameterized type?
Can
I create an array whose component type is a wildcard parameterized type?
Why
is it allowed to create an array whose component type is an unbounded wildcard
parameterized type?
What
is a reifiable type?
|
Can
I declare a reference variable of an array type whose component type is
a concrete parameterized type?
Yes, you can, but you should not, because
it is neither helpful nor type-safe.
|
You can declare a reference variable of an array type whose
component type is a concrete parameterized type. Arrays of such a type
must not be created. Hence, this reference variable cannot refer
to an array of its type. All that it can refer to is
null
,
an array whose component type is a non-parameterized subtype of the concrete
parameterized type, or an array whose component type is the corresponding
raw type. Neither of these cases is overly useful, yet they are permitted.
Example (of an array reference variable with parameterized component
type):
Pair<String,String>[]
arr = null;
//
fine
arr =
new Pair<String,String>[2]
;
//
error: generic array creation
The code snippet shows that a reference variable of type
Pair<String,String>[]
can
be declared, but the creation of such an array is rejected. But we
can have the reference variable of type
Pair<String,String>[]
refer to an array of a non-parameterized subtype.
Example (of another array reference variable with parameterized component
type):
class Name extends Pair<String,String> { ... }
Pair<String,String>[]
arr =
new
Name[2]
;
// fine
Which raises the question: how useful is such an array variable if it never
refers to an array of its type? Let us consider an example.
Example (of an array reference variable refering to array of subtypes;
not recommended):
void printArrayOfStringPairs(
Pair<String,String>[]
pa) {
for (Pair<String,String> p : pa)
if (p != null)
System.out.println(p.getFirst()+"
"+p.getSecond());
}
Pair<String,String>[]
createArrayOfStringPairs() {
Pair<String,String>[] arr
=
new
Name[2];
arr[0] = new Name("Angelika","Langer");
//
fine
arr[1] = new Pair<String,String>("a","b");
//
fine
(causes ArrayStoreException)
return arr;
}
void extractStringPairsFromArray(
Pair<String,String>[]
arr) {
Name name =
(Name)
arr[0];
//
fine
Pair<String,String> p1 = arr[1];
//
fine
}
void test() {
Pair<String,String>[]
arr =
createArrayOfStringPairs
();
printArrayOfStringPairs
(arr);
extractStringPairsFromArray(arr);
}
The example shows that a reference variable of type
Pair<String,String>[]
can refer to an array of type
Name[]
, where
Name
is a
non-parameterized subtype of
Pair<String,String>
. However,
using a reference variable of type
Pair<String,String>[]
offers
no advantage over using a variable of the actual type
Name[]
.
Quite the converse; it is an invitation for making mistakes.
For instance, in the
createArrayOfStringPairs
method the compiler
would permit code for insertion of elements of type
Pair<String,String>
into the array though the reference variable of type
Pair<String,String>[]
.
Yet, at runtime, this insertion will always fail with an
ArrayStoreException
because
we are trying to insert a
Pair
into a
Name[]
. The
same would happen if we tried to insert a raw type Pair into the array;
it would compile with an "unchecked" warning and would fail at runtime
with an
ArrayStoreException
. If we used
Name[]
instead of
Pair<String,String>[]
the debatable insertions would
not compile in the first place.
Also, remember that a variable of type
Pair<String,String>[]
can
never refer to an array that contains elements of type
Pair<String,String>
.
When we want to recover the actual type of the array elements, which is
the subtype
Name
in our example, we must cast down from
Pair<String,String>
to
Name
,
as is demonstrated in the
extractStringPairsFromArray
method.
Here again, using a variable of type
Name[]
would be much clearer.
Example (improved):
void printArrayOfStringPairs(
Pair<String,String>[]
pa) {
for (Pair<String,String> p : pa)
if (p != null)
System.out.println(p.getFirst()+"
"+p.getSecond());
}
Name[]
createArrayOfStringPairs()
{
Name[]
arr =
new
Name[2]
;
arr[0] = new Name("Angelika","Langer");
//
fine
arr[1] = new Pair<String,String>("a","b");
//
error
return arr;
}
void extractStringPairsFromArray(
Name[]
arr) {
Name name = arr[0];
// fine
(needs
no cast)
Pair<String,String> p1 = arr[1];
//
fine
}
void test() {
Name[]
arr =
createArrayOfStringPairs
();
printArrayOfStringPairs
(arr);
extractStringPairsFromArray(arr);
}
Since an array reference variable whose component type is a concrete parameterized
type can never refer to an array of its type, such a reference variable
does not really make sense. Matters are even worse than in the example
discussed above, when we try to have the variable refer to an array of
the raw type instead of a subtype. First, it leads to numerous "unchecked"
warnings because we are mixing use of raw and parameterized type.
Secondly, and more importantly, this approach is not type-safe and suffers
from all the deficiencies that lead to the ban of arrays of concrete instantiation
in the first place.
No matter how you put it, you should better refrain from using array
reference variable whose component type is a concrete parameterized type.
Note, that the same holds for array reference variable whose component
type is a
wildcard
parameterized type. Only array reference variable
whose component type is an
unbounded wildcard
parameterized type
make sense. This is because an unbounded wildcard parameterized type is
a reifiable type and arrays with a reifiable component type can be created;
the array reference variable can refer to an array of its type and the
deficiencies discussed above simply do not exist for unbounded wildcard
arrays.
|
LINK TO THIS
|
GenericTypes.FAQ104A
|
REFERENCES
|
What
does type-safety mean?
Can
I create an array whose component type is a concrete parameterized type?
Can
I declare a reference variable of an array type whose component type is
a bounded wildcard parameterized type?
Can
I create an array whose component type is a wildcard parameterized type?
Can
I declare a reference variable of an array type whose component type is
an unbounded wildcard parameterized type?
Why
is it allowed to create an array whose component type is an unbounded wildcard
parameterized type?
What
is a reifiable type?
|
How
can I work around the restriction that there are no arrays whose component
type is a concrete parameterized type?
You can use arrays of raw types, arrays
of unbounded wildcard parameteriezd types, or collections of concrete parameteriezd
types as a workaround.
|
Arrays holding elements whose type is a concrete parameterized
type are illegal.
Example (of illegal array type):
static void test() {
Pair<Integer,Integer>[]
intPairArr = new
Pair<Integer,Integer>[10]
;
//
error
addElements(intPairArr);
Pair<Integer,Integer> pair = intPairArr[1];
Integer i = pair.getFirst();
pair.setSecond(i);
}
static void addElements(
Object[]
objArr) {
objArr[0] = new Pair<Integer,Integer>(0,0);
objArr[1] = new Pair<String,String>("","");
// should fail with ArrayStoreException
}
The compiler prohibits creation of arrays whose component type is a concrete
parameterized type, like
Pair<Integer,Integer>
in our example.
We discussed in the preceding entry why is it reasonable that the compiler
qualifies a
Pair<Integer,Integer>[]
as illegal. The key problem is that compiler and runtime system must
ensure that an array is a
homogenous
sequence of elements of the
same type. One of the type checks, namely the array-store-check performed
by the virtual machine at runtime, fails to detect the offending insertion
of an alien element. In the example the second insertion in the
addElements
method should fail, because were are adding a pair of strings to an array
of integral values, but it does not fail as expected The reasons
were discussed in the preceding entry.
If we cannot use arrays holding elements whose type is a concrete parameterized
type, what do we use as a workaround?
Let us consider 3 conceivable workarounds:
-
array of raw type
-
array of unbounded wildcard parameterized type
-
collection instead of array
Raw types and unbounded wildcard parameterized type are permitted as
component type of arrays. Hence they would be alternatives.
Example (of array of raw type):
static void test() {
Pair[]
intPairArr =
new
Pair[10]
;
addElements(intPairArr);
Pair<Integer,Integer> pair = intPairArr[1];
//
unchecked
warning
Integer i = pair.getFirst();
// fails with ClassClassException
pair.setSecond(i);
}
static void addElements(Object[] objArr) {
objArr[0] = new Pair<Integer,Integer>(0,0);
objArr[1] = new Pair<String,String>("","");
// should fail, but succeeds
}
Use of the raw type, instead of a parameterized type, as the component
type of an array, is permitted. The downside is that we can stuff
any type of pair into the raw type array. There is no guarantee that
a
Pair[]
is homogenous in the sense that it contains only pairs
of the same type. Instead the
Pair[]
can contain a mix of
arbitrary pair types.
This has numerous side effects. When elements are fetched from
the
Pair[]
only raw type
Pair
references are received.
Using raw type
Pair
s leads to unchecked warnings invarious situations,
for instance, when we try to access the pair member or, like in the example,
when we assign the
Pair
to the more specific
Pair<Integer,Integer>
,
that we really wanted to use.
Let us see whether an array of an unbounded wildcard parameterized type
would be a better choice.
Example (of array of unbounded wildcard parameterized type):
static void test() {
Pair<?,?>[]
intPairArr
= new
Pair<?,?>[10]
;
addElements(intPairArr);
Pair<Integer,Integer> pair = intPairArr[1];
//
error
Integer i = pair.getFirst();
pair.setSecond(i);
}
static void addElements(Object[] objArr) {
objArr[0] = new Pair<Integer,Integer>(0,0);
objArr[1] = new Pair<String,String>("","");
// should fail, but succeeds
}
error: incompatible types
found : Pair<?,?>
required: Pair<java.lang.Integer,java.lang.Integer>
Pair<Integer,Integer>
pair = intPairArr[1];
^
A
Pair<?,?>[]
contains a mix of arbitrary pair types; it is
not homogenous and semantically similar to the raw type array
Pair[]
.
When we retrieve elements from the array we receive references of type
Pair<?,?>
,
instead of type
Pair
in the raw type case. The key difference
is that the compiler issues an error for the wildcard pair where it issues
"unchecked" warnings for the raw type pair. In our example, we cannot
assign the the
Pair<?,?>
to the more specific
Pair<Integer,Integer>
,
that we really wanted to use. Also, various operations on the
Pair<?,?>
would be rejected as errors.
As we can see, arrays of raw types and unbounded wildcard parameterized
types are very different from the illegal arrays of a concrete parameterized
type. An array of a concrete wildcard parameterized type would be a
homogenous
sequence of elements of the exact same type. In constrast, arrays of raw
types and unbounded wildcard parameterized type are
heterogenous
sequences of elements of different types. The compiler cannot prevent that
they contain different instantiations of the generic type.
By using arrays of raw types or unbounded wildcard parameterized types
we give away the static type checks that a homogenous sequence would come
with. As a result we must use explicit casts or we risk unexpected
ClassCastException
s.
In the case of the unbounded wildcard parameterized type we are additionally
restricted in how we can use the array elements, because the compiler prevents
certain operations on the unbounded wildcard parameterized type.
In essence, arrays of raw types and unbounded wildcard parameterized types
are semantically very different from what we would express with an array
of a concrete wildcard parameterized type. For this reason they are
not a good workaround and only acceptable when the superior efficiency
of arrays (as compared to collections) is of paramount importance.
While arrays of concrete parameterized types are illegal, collections
of concrete parameterized types are permitted.
Example (using collections):
static void test() {
ArrayList<Pair<Integer,Integer>>
intPairArr = new
ArrayList<Pair<Integer,Integer>>(10)
;
addElements(intPairArr);
Pair<Integer,Integer> pair = intPairArr.
get
(1);
Integer i = pair.getFirst();
pair.setSecond(i);
}
static void addElements(
List<?>
objArr) {
objArr.
add
(0,new Pair<Integer,Integer>(0,0));
//
error
objArr.
add
(1,new Pair<String,String>("",""));
//
error
}
error: cannot find symbol
symbol : method add(int,Pair<java.lang.Integer,java.lang.Integer>)
location: interface java.util.List<capture of ?>
objArr.add(0,new Pair<Integer,Integer>(0,0));
^
error: cannot find symbol
symbol : method add(int,Pair<java.lang.String,java.lang.String>)
location: interface java.util.List<capture of ?>
objArr.add(1,new Pair<String,String>("",""));
^
A collection of a concrete parameterized type is a
homogenous
sequence
of elements and the compiler prevents any attempt to add alien elements
by means of static type checks. To this regard it is semantically
similar to the illegal array, but otherwise collections are very different
from arrays. They have different operations; no index operator, but
get
and
add
methods. They have different type relationships;
arrays are covariant, while collections are not. They are not as efficient
as arrays; they add overhead in terms of memory footprint and performance.
By using collections of concrete parameterized types as a workaround for
the illegal array type many things change in your implementation.
The different type relationships, for instance, can be observed in the
example above and it renders method
addElements
pointless.
Using arrays we declared the argument type of the
addElements
method as type
Object[]
so that the method would accept all types
of arrays. For the collections there is no such supertype as an
Object[]
.
Type
Collection<?>
, or type
List<?>
in our example,
comes closest to what the
Object[]
is for arrays. But wildcard
instantiations of the collection types give only limited access to the
collections' operations. In our example, we cannot insert any elements
into the collection of integer pairs through a reference of type
List<?>
.
A method like
addElements
does not make any sense any longer;
we would need a method specifically for a collection of
Pair<Integer,Integer>
instead. In essence, you must design your APIs differently, when
you work with collections instead of arrays.
The most compelling argument against collections is efficiency; arrays
are without doubt more efficient. The argument in favor of collections
is type safety; the compiler performs all necessary type checks to ensure
that the collection is a homogenous sequence.
|
LINK TO THIS
|
GenericTypes.FAQ105
|
REFERENCES
|
What
is a reifiable type?
What
is an unbounded wildcard?
What
is an unbounded wildcard parameterized type?
What
is the raw type?
Can
I create an array whose component type is a wildcard parameterized type?
Why
is it allowed to create an array whose component type is an unbounded wildcard
parameterized type?
What
is the difference between the unbounded wildcard parameterized type and
the raw type?
|
Why
is there no class literal for concrete parameterized types?
Because parameterized type has no exact
runtime type representation.
|
A class literal denotes a
Class
object that represents
a given type. For instance, the class literal
String.class
denotes
the
Class
object that represents the type
String
and
is identical to the
Class
object that is returned when method
getClass
is
invoked on a S
tring
object. A class literal can be used for runtime
type checks and for reflection.
Parameterized types lose their type arguments when they are translated
to byte code during compilation in a process called
type erasure
.
As a side effect of type erasure, all instantiations of a generic
type share the same runtime representation, namely that of the corresponding
raw
type
. In other words, parameterized types do not have type representation
of their own. Consequently, there is no point in forming class literals
such as
List<String>.class
,
List<Long>.class
and
List<?>.class
,
since no such
Class
objects exist. Only the raw type
List
has a
Class
object that represents its runtime type. It is referred
to as
List.class
. |
LINK TO THIS
|
GenericTypes.FAQ106
|
REFERENCES
|
What
is type erasure?
What
is the raw type?
|
Raw Types
What
is the raw type?
The generic type without any type arguments.
|
The generic type without any type arguments, like
Collection
,
is called
raw type
.
The raw type is assignment compatible with all instantiations of the
generic type. Assignment of an instantiation of a generic type to
the corresponding raw type is permitted without warnings; assignment of
the raw type to an instantiation yields an "unchecked conversion" warning.
Example (of assignment compatibility):
ArrayList
rawList = new ArrayList();
ArrayList<String> stringList = new ArrayList<String>();
rawList = stringList;
stringList = rawList;
//
unchecked
warning
The "unchecked" warning indicates that the compiler does not know whether
the raw type
ArrayList
really contains strings. A raw type
ArrayList
can in principle contain any type of object and is similar to a
ArrayList<Object>
. |
LINK TO THIS
|
GenericTypes.FAQ201
|
REFERENCES
|
Why
are raw types permitted?
Can
I use a raw type like any other type?
How
does the raw type relate to instantiations of the corresponding generic
type?
|
Why
are raw types permitted?
To facilitate interfacing with non-generic
(legacy) code.
|
Raw types are permitted in the language predominantly to
facilitate interfacing with non-generic (legacy) code.
If, for instance, you have a non-generic legacy method that takes a
List
as
an argument, you can pass a parameterized type such as
List<String>
to
that method. Conversely, if you have a method that returns a
List
,
you can assign the result to a reference variable of type
List<String>
,
provided you know for some reason that the returned list really is a list
of strings.
Example (of interfacing with legacy code using raw types):
class SomeLegacyClass {
public void setNames(List c) { ... }
public List getNames() { ... }
}
final class Test {
public static void main(String[] args) {
SomeLegacyClass obj = new SomeLegacyClass();
List<String> names = new LinkedList<String>();
... fill list ...
obj.setNames(names);
names = obj.getNames();
//
unchecked warning
}
}
A
List<String>
is passed to the
setNames
method that
asks for an argument of the raw type
List
. The conversion
from a
List<String>
to a
List
is safe because
a method that can handle a heterogeneous list of objects can certainly
cope with a list of strings.
The
getNames
method returns a raw type
List
, which
we assign to a variable of type
List<String>
. The compiler
has not enough information to ensure that the list returned really is a
list of strings. Despite of that, the compiler permits the conversion
from the raw type
List
to the more specific type
List<String>
,
in order to allow this kind of mixing of non-generic and generic Java code.
Since the conversion from
List
to
List<String>
is
not type-safe, the assignment is flagged as an "unchecked assignment".
The use of raw types in code written after the introduction of genericity
into the Java programming language is discouraged. According to the Java
Language Specification, it is possible that future versions of the Java
programming language will disallow the use of raw types. |
LINK TO THIS
|
GenericTypes.FAQ202
|
REFERENCES
|
What
are raw types?
Can
I use a raw type like any other type?
How
does the raw type relate to instantiations of the corresponding generic
type?
|
Can
I use a raw type like any other type?
Yes, but certain uses will result in
"unchecked" warnings.
|
Raw types can be used like regular types without any restrictions,
except that certain uses will result in "unchecked" warnings.
Example (of a parameterized type):
interface Copyable<T> {
T copy();
}
final class Wrapped
<Elem extends Copyable<Elem>>
{
private
Elem
theObject;
public Wrapped(
Elem
arg)
{ theObject = arg.copy(); }
public void setObject(
Elem
arg) { theObject = arg.copy(); }
public
Elem
getObject()
{ return theObject.copy(); }
public boolean equals(Object other) {
if (other == null) return false;
if (! (other instanceof Wrapped)) return
false;
return (this.theObject.equals(((Wrapped)other).theObject));
}
}
Methods or constructors of a raw type have the signature that they would
have after type erasure. A method or constructor call to a raw type
generates an unchecked warning if the erasure changes the argument types.
Example (same as above - after type erasure):
interface Copyable {
Object copy();
}
final class Wrapped {
private
Copyable
theObject;
public Wrapped(
Copyable
arg) { theObject = arg.copy(); }
public void setObject(
Copyable
arg) { theObject = arg.copy(); }
public
Copyable
getObject()
{ return theObject.copy(); }
public boolean equals(Object other) {
if (other == null) return false;
if (! (other instanceof Wrapped)) return
false;
return (this.theObject.equals(((Wrapped)other).theObject));
}
}
Invocation of a method or constructor, whose argument type changed in the
course of type erasure is unsafe and is flagged as an "unchecked" operation.
For instance, the method
setObject
has the signature
void
setObject(Copyable)
after type erasure and its invocation results
in an "unchecked" warning. The invocation is unsafe because the compiler
cannot ensure that the argument passed to the method is compatible to the
"erased" type that the type parameter
Elem
stands for.
Example (using the raw type):
class MyString implements Copyable<MyString> {
private StringBuilder buffer;
public MyString(String s) { buffer = new StringBuilder(s);
}
public MyString copy() { return new MyString(buffer.toString());
}
...
}
class Test {
private static void test(
Wrapped
wrapper) {
wrapper.
setObject
(new MyString("Deutsche
Bank"));
//
unchecked warning
Object s = wrapper.
getObject
();
}
public static void main(String[] args) {
Wrapped<MyString>
wrapper = new Wrapped<MyString>(new MyString("Citibank"));
test(wrapper);
}
}
If the method's argument type is not changed by type erasure, then the
method call is safe. For instance, the method
getObject
has the signature
Copyable getObject(void)
after type erasure
and its invocation is safe and warning-free.
Fields of a raw type have the type that they would have after type erasure.
A field assignment to a raw type generates an unchecked warning if erasure
changes the field type. In our example, the field
theObject
of the raw type
Wrapped
is changed by type erasure and is of type
Copyable
after type erasure.
If the
theObject
field were public and we could assign
to it, the assignment would be unsafe because the compiler cannot ensure
that the value being assigned really is of type
Elem
. Yet
the assignment is permitted and flagged as an "unchecked" assignment. Reading
the field is safe and does not result in a warning. |
LINK TO THIS
|
GenericTypes.FAQ203
|
REFERENCES
|
What
is type erasure?
How
does the raw type relate to instantiations of the corresponding generic
type?
Can
I use a type parameter as part of its own bounds?
|
Wildcard Instantiations
What
is a wildcard parameterized type?
An instantiation of a generic type where
the type argument is a wildcard (as opposed to a concrete type).
|
A wildcard parameterized type is an instantiation of a
generic type where at least one type argument is a wildcard. Examples
of wildcard parameterized types are
Collection<?>
,
List<?
extends Number>
,
Comparator<? super String>
and
Pair<String,?>
.
A wildcard parameterized type denotes a family of types comprising concrete
instantiations of a generic type. The kind of the wildcard being
used determines which concrete parameterized types belong to the family.
For instance, the wildcard parameterized type
Collection<?>
denotes the family of all instantiations of the
Collection
interface
regardless of the type argument. The wildcard parameterized type
List<?
extends Number>
denotes the family of all list types where the element
type is a subtype of
Number
. The wildcard parameterized
type
Comparator<? super String>
is the family of all instantiations
of the
Comparator
interface for type argument types that are supertypes
of
String
.
A wildcard parameterized type is not a concrete type that could appear
in a
new
expression. A wildcard parameterized type is similar
to an interface type in the sense that reference variables of a wildcard
parameterized type can be declared, but no objects of the wildcard parameterized
type can be created. The reference variables of a wildcard parameterized
type can refer to an object that is of a type that belongs to the family
of types that the wildcard parameterized type denotes.
Examples:
Collection<?> coll = new ArrayList<String>();
List<? extends Number> list = new ArrayList<Long>();
Comparator<? super String> cmp = new RuleBasedCollator("<
a< b< c< d");
Pair<String,?> pair = new Pair<String,String>();
Counter Example:
List<? extends Number> list = new ArrayList<String>();
// error
Type
String
is not a subtype of
Number
and consequently
ArrayList<String>
does
not belong to the family of types denoted by
List<? extends Number>
.
For this reason the compiler issues an error message. |
LINK TO THIS
|
GenericTypes.FAQ301
|
REFERENCES
|
What
is a wildcard?
Can
I use a wildcard parameterized type like any other type?
|
What
is the unbounded wildcard parameterized type?
An instantiation of a generic type where
all type arguments are the unbounded wildcard "
?
".
|
Examples of unbounded wildcard parameterized types are
Pair<?,?>
and
Map<?,?>
.
The unbounded wildcard parameterized type is assignment compatible with
all
instantiations of the correspinding generic type. Assignment of another
instantiation to the unbounded wildcard instantiation is permitted without
warnings; assignment of the unbounded wildcard instantiation to another
instantiation is illegal.
Example (of assignment compatibility):
ArrayList
<?>
anyList = new ArrayList<Long>();
ArrayList<String> stringList = new ArrayList<String>();
anyList = stringList;
stringList = anyList;
//
error
The unbounded wildcard parameterized type is kind of the supertype of all
other instantiations of the generic type: "subtypes" can be assigned to
the "unbounded supertype", not vice versa. |
LINK TO THIS
|
GenericTypes.FAQ302
|
REFERENCES
|
How
do unbounded wildcard instantiations of a generic type relate to other
instantiations of the same generic type?
|
What
is the difference between the unbounded wildcard parameterized type and
the raw type?
Which
methods and fields are accessible/inaccessible through a reference variable
of a wildcard parameterized type?
It depends on the kind
of wildcard.
|
Using an object through a reference variable of a wildcard
parameterized type is restricted. Consider the following class:
Example (of a generic class):
class Box<T> {
private T t;
public Box(T t) { this.t = t; }
public void put(T t) { this.t = t;}
public T take() { return t; }
public boolean equalTo(Box<T> other) { return this.t.equals(other.t);
}
public Box<T> copy() { return new Box<T>(t); }
}
When we use a reference variable of a wildcard instantiation of type
Box
to access methods and fields of the referenced object the compiler would
reject certain invocations.
Example (of access through a wildcard parameterized type):
class Test {
public static void main(String[] args) {
Box<?>
box = new Box<String>("abc");
box.put("xyz");
//
error
box.put(null);
//
ok
String s = box.take();
//
error
Object o = box.take();
//
ok
boolean equal = box.equalTo(box);
//
error
equal = box.equalTo(new Box<String>("abc"));
//
error
Box<?> box1 = box.copy();
//
ok
Box<String> box2 = box.copy();
//
error
}
}
In a wildcard parameterized type such as
Box<?>
the type of
the field and the argument and the return types of the methods would be
unknown. It is like the field t would be of type "
?
" and
the
put
method would take an argument of type "
?
" and
the
take
method would return a "
?
" and so on.
In this situation the compiler does not let us assign anything to the
field or pass anything to the
put
method. The reason is that the
compiler cannot make sure that the object that we are trying to pass as
an argument to a method is of the expected type, since the expected type
is unknown. Similarly, the compiler does not know of which type the field
is and cannot check whether we are assigning an object of the correct type,
because the correct type is not known.
In contrast, the
take
method can be invoked and it returns
an object of an unknown type, which we can assign to a reference variable
of type
Object
.
Similar effects can be observed for methods such as like
equalTo
and
copy
, which have a parameterized argument or return
type and the type parameter
T
appears as type argument of the
parameterized argument or return type.
Consider a generic class with methods that use the type parameter in
the argument or return type of its methods:
Example (of a generic class):
class Box
<T>
{
private T t;
public Box(T t) { this.t = t; }
public Box(
Box<? extends
T
>
box) { t = box.t; }
...
public boolean equalTo(
Box<
T
>
other) { return this.t.equals(other.t); }
public
Box<
T
>
copy()
{ return new Box<T>(t); }
public
Pair<
T
,
T
>
makePair() { return new Pair<T,T>(t,t); }
public
Class<? extends
T
>
getContentType() { ... }
public int compareTo(
Comparable<? super
T
>
other)
{ return other.compareTo(t); }
}
The type parameter
T
can appear as the type argument of a parameterized
argument or return type, like in method
makePair
, which returns
a
Pair<T,T>
. But it can also appear as part of the type argument
of a parameterized argument or return type, namely as bound of a wildcard,
like in method
geteContentType
, which returns a value of type
Class<?
extends T>
. Which methods can or must not be invoked
through a wildcard instantiation depends not only on the type of the wildcard
instantiation (unbounded or bounded with upper or lower bound), but also
on the use of the type parameter (as type argument or as wildcard bound).
The restriction are fairly complex in detail, because they depend on
the type of the wildcard (unbounded or bounded with upper or lower bound).
So far we have only seen
Box<?>
, that is, the unbounded wildcard
instantiation. Which fields and methods are accessible through references
of other wildcard instantiations? In addition, the rules depend on
the way in which a method uses the type parameter in the method signatures
(as the type of an argument or the return type or as the type argument
of a parameterized argument or return type). A comprehensive discussion
can be found in the FAQ entries listed in the reference section below. |
LINK TO THIS
|
GenericTypes.FAQ304
|
REFERENCES
|
Which
methods and fields are accessible/inaccessible through a reference variable
of a wildcard parameterized type?
Which
methods that use the type parameter in the argument or return type are
accessible in an unbounded wildcard parameterized type?
Which
methods that use the type parameter in the argument or return type are
accessible in an upper bound wildcard parameterized type?
Which
methods that use the type parameter in the argument or return type are
accessible in a lower bound wildcard parameterized type?
Which
methods that use the type parameter as type argument of a parameterized
argument or return type are accessible in a wildcard parameterized type?
Which
methods that use the type parameter as upper wildcard bound in a parameterized
argument or return type are accessible in a wildcard parameterized type?
Which
methods that use the type parameter as lower wildcard bound in a parameterized
argument or return type are accessible in a wildcard parameteriezd type?
In
a wildcard parameterized type, can I read and write fields whose type is
the type parameter?
|
Can
I use a wildcard parameterized type like any other type?
No. A wildcard parameterized type
is not a type in the regular sense (different from a non-parameterized
class/interface or a raw type).
|
Wildcard parameterized types can be used for typing (like
non-parameterized classes and interfaces):
-
as argument and return types of methods
-
as type of a field or local reference variable
-
as component type of an array
-
as type argument of other parameterized types
-
as target type in casts
Wildcard parameterized type can NOT be used for the following purposes
(different from non-parameterized classes and interfaces):
-
for creation of objects
-
for creation of arrays (except unbounded wildcard)
-
in exception handling
-
in instanceof expressions (except unbounded wildcard)
-
as supertypes
-
in a class literal
|
LINK TO THIS
|
GenericTypes.FAQ305
|
REFERENCES
|
|
Can
I create an object whose type is a wildcard parameterized type?
No, not directly.
|
Objects of a wildcard parameterized
type are not particularly useful, mainly because there is not much you
can do with the object. You can access an object of a wildcard parameterized
type only through a reference of that wildcard parameterized type, and
such a reference gives only restricted access to the referenced object.
Basically, the wildcard parameterized type is too abstract to be useful.
For this reason, the creation of objects of a wildcard parameterized type
is discouraged: it is illegal that a wildcard parameterized type appears
in a
new
expression.
Example (of illegal creation of
objects of a wildcard
parameterized type
):
ArrayList<String> list = new ArrayList<String>();
... populate the list ...
ArrayList<?> coll1 =
new ArrayList
<?>
();
//
error
ArrayList<?> coll2 =
new ArrayList
<?>
(10);
//
error
ArrayList<?> coll3 =
new ArrayList
<?>
(list);
//
error
The compiler rejects all attempts to create an object
of the wildcard type
ArrayList<?>
.
In a way, a wildcard parameterized type is like an interface type:
you can declare reference variables of the type, but you cannot create
objects of the type. A reference variable of an interface type or
a wildcard parameterized type can refer to an object of a compatible type.
For an interface, the compatible types are the class (or enum) types that
implement the interface. For a wildcard parameterized type, the compatible
types are the concrete instantiations of the corresponding generic type
that belong to the family of instantiations that the wildcard denotes.
Example (comparing interface and wildcard
parameterized
type
):
Cloneable clon1 = new Date();
Cloneable clon2 = new Cloneable();
//
error
ArrayList<?> coll1 =
new ArrayList
<String>
();
ArrayList<?> coll2 =
new ArrayList
<?>
();
//
error
The code snippet above illustrates the similarity
between an interface and a wildcard parameterized type, using the interface
Cloneable
and the wildcard parameterized type
ArrayList<?>
as examples.
We can declare reference variables of type
Cloneable
and
ArrayList<?>
,
but we must not create objects of type
Cloneable
and
ArrayList<?>
.
Interestingly, the compiler's effort to prevent
the creation of objects of a wildcard parameterized type can be circumvented.
It is unlikely that you will ever want to create an object of a wildcard
parameterized type, but should you ever need one, there's the workaround
(see
TechnicalDetails.FAQ609
).
|
LINK TO THIS
|
GenericTypes.FAQ306
|
REFERENCES
|
What
is a wildcard parameterized type?
Which
methods and fields are accessible/inaccessible through a reference variable
of a wildcard type?
Can
I use a wildcard instantiation like any other type?
What
is type argument inference?
Is
it really impossible to create an object whose type is a wildcard parameterized
type?
|
Can
I create an array whose component type is a wildcard parameterized type?
No, because it is not type-safe.
|
The rationale is the same as for concrete parameterized
types: a wildcard
parameterized type
, unless
it is an unbounded wildcard
parameterized type
,
is a non-reifiable type and arrays of non-reifiable types are not type-safe.
The array store check cannot be performed reliably because a wildcard
parameterized
type
that is not an unbounded wildcard
parameterized
type
has a non-exact runtime type.
Example (of the consequences):
Object[] numPairArr = new Pair<? extends Number,? extends
Number>[10]; // illegal
numPairArr[0] = new Pair<Long,Long>(0L,0L);
// fine
numPairArr[0] = new Pair<String,String>("",""); // should fail,
but would succeed
The array store check would have to check whether the pair added to the
array is of type
Pair<? extends Number,? extends Number>
or
of a subtype thereof. Obviously, a
Pair<String,String>
is not
of a matching type and should be rejected with an
ArrayStoreException
.
But the array store check does not detect any type mismatch, because the
JVM can only check the array's runtime component type, which is
Pair[]
after type erasure, against the element's runtime type, which is
Pair
after type erasure. |
LINK TO THIS
|
GenericTypes.FAQ307
|
REFERENCES
|
What
does type-safety mean?
Can
I create an array whose component type is a concrete parameterized type?
Why
is it allowed to create an array whose component type is an unbounded wildcard
parameterized type?
What
is a reifiable type?
|
Can
I declare a reference variable of an array type whose component type is
a bounded wildcard parameterized type?
Yes, you can, but you should not, because
it is neither helpful nor type-safe.
|
The rationale is the same as for concrete parameterized
types: a wildcard
parameterized type
, unless
it is an unbounded wildcard
parameterized type
,
is a non-reifiable type and arrays of non-reifiable types must not be created.
Hence it does not make sense to have a reference variable of such an array
type because it can never refer to array of its type. All that it
can refer to is
null
, an array whose component type is a non-parameterized
subtype of the instantiations that belong to the type family denoted by
the wildcard, or an array whose component type is the corresponding raw
type. Neither of these cases is overly useful, yet they are permitted.
Example (of an array reference variable with wildcard parameterized
component type):
Pair<? extends Number,? extends Number>[]
arr
= null;
// fine
arr =
new Pair<? extends Number,? extends
Number>[2]
;
// error: generic
array creation
The code snippet shows that a reference variable of type
Pair<?
extends Number,? extends Number>[]
can be declared, but the creation
of such an array is illegal. But we can have the reference variable
of type
Pair<? extends Number,? extends Number>[]
refer to
an array of a non-parameterized subtype of any of the concrete instantiations
that belong to the type family denoted by
Pair<? extends Number,?
extends Number>
. (Remember, wildcard
parameterized
types
cannot be used as supertypes; hence a non-parameterized subtype
must be a subtype of a concrete
parameterized type
.)
Example (of another array reference variable with parameterized component
type):
class Point extends Pair<Double,Double> { ... }
Pair<? extends Number,? extends Number>[]
arr =
new
Point[2]
;
// fine
Using a reference variable of type
Pair<? extends Number,? extends
Number>[]
offers no advantage over using a variable of the actual
type
Point[]
. Quite the converse; it is an invitation for
making mistakes.
Example (of an array reference variable refering to array of subtypes;
not recommended):
Pair<? extends Number,? extends Number>[]
arr
=
new Point[2];
arr[0] = new Point(-1.0,1.0);
//
fine
arr[1] = new Pair<Number,Number>(-1.0,1.0);
//
fine
(causes ArrayStoreException)
arr[2] = new Pair<Integer,Integer>(1,2);
//
fine
(causes ArrayStoreException)
The compiler permits code for insertion of elements of type
Pair<
Number,Number
>
or
Pair<Integer,Integer>
into
the array through the reference variable of type
Pair<?
extends
Number,? extends Number>[]
. Yet, at runtime, this insertion will always
fail with an
ArrayStoreException
because we are trying to insert
a
Pair
into a
Point[]
. The debatable insertions
would be flagged as errors and thereby prevented if we used the actual
type of the array, namely
Point[]
instead of
Pair<?extends
Number,? extends Number>[]
.
In essence, you should better refrain from using array reference variable
whose component type is a wildcard parameterized type. Note, that
the same holds for array reference variable whose component type is a
concrete
parameterized type. Only an array reference variable whose component type
is an
unbounded wildcard
parameterized type make sense. This is
because an unbounded wildcard
parameterized type
is a reifiable type and arrays with a reifiable component type can be created;
the array reference variable can refer to an array of its type and the
deficiencies discussed above simply do not exist for unbounded wildcard
arrays.
|
LINK TO THIS
|
GenericTypes.FAQ307A
|
REFERENCES
|
What
does type-safety mean?
Can
I create an array whose component type is a wildcard parameterized type?
Can
I declare a reference variable of an array type whose component type is
a concrete parameterized type?
Can
I create an array whose component type is a concrete parameterized type?
Can
I declare a reference variable of an array type whose component type is
an unbounded wildcard parameterized type?
Why
is it allowed to create an array whose component type is an unbounded wildcard
parameterized type?
What
is a reifiable type?
|
Why
is it allowed to create an array whose component type is an unbounded wildcard
parameterized type?
Because it is type-safe.
|
The rationale is related to the rule for other instantiations
of a generic type: an unbounded wildcard
parameterized
type
is a reifiable type and arrays of reifiable types are type-safe,
in contrast to arrays of non-reifiable types, which are not safe and therefore
illegal. The problem with the unreliable array store check (the reason
for banning arrays with a non-reifiable component type) does not occur
if the component type is reifiable.
Example (of array of unbounded wildcard
parameterized
type
):
Object[] pairArr = new
Pair<?,?>[10]
;
// fine
pairArr[0] = new
Pair
<Long,Long>(0L,0L);
// fine
pairArr[0] = new
Pair
<String,String>("",""); // fine
pairArr[0] = new
ArrayList
<String>();
// fails with ArrayStoreException
The array store check must check whether the element added to the
array is of type
Pair<?,?>
or of a subtype thereof. In
the example the two pairs, although of different type, are perfectly acceptable
array elements. And indeed, the array store check, based on the non-exact
runtime type
Pair
, accepts the two pairs and correctly sorts out
the "alien"
ArrayList
object as illegal by raising an
ArrayStoreException
.
The behavior is exactly the same as for an array of the raw type, which
is not at all surprising because the raw type is a reifiable type as well. |
LINK TO THIS
|
GenericTypes.FAQ308
|
REFERENCES
|
What
is a reifiable type?
What
does type-safety mean?
What
is the raw type?
Can
I create an array whose component type is a concrete parameterized type?
Can
I create an array whose component type is a wildcard parameterized type?
|
Can
I declare a reference variable of an array type whose component type is
an unbounded wildcard parameterized type?
Yes.
|
An array reference variable whose component type is an
unbounded wildcard parameterized type (such as
Pair<?,?>[]
)
is permitted and useful. This is in contrast to array reference variables
with a component type that is a concrete or bounded wildcard
parameterized
type
(such as
Pair<Long,Long>[]
or
Pair<? extends
Number,? extends Number>[]
); the array reference variable is permitted,
but not overly helpful.
The difference stems from the fact that an unbounded wildcard
parameterized
type
is a reifiable type and arrays with a reifiable component type
can be created. Concrete and bounded wildcard
parameterized
types
are
non
-reifiable types and arrays with a non-reifiable
component type can
not
be created. As a result, an array variable
with a reifiable component type can refer to array of its type, but this
is not possible for the non-reifiable component types.
Example (of array reference variables with parameterized component types):
Pair<?,?>[]
arr
=
new Pair<?,?>[2]
;
// fine
Pair<? extends Number,? extends Number>[]
arr
=
new Pair<? extends Number,?
extends Number>[2]
;
// error:
generic array creation
Pair<Double,Double>[]
arr
=
new Pair<Double,Double>[2]
;
// error: generic array creation
The examples above demonstrate that unbounded wildcard
parameterized
types
are permitted as component type of an array, while other instantiations
are not permitted. In the case of a non-reifiable component type
the array reference variable can be declared, but it cannot refer to an
array of its type. At most it can refer to an array of a non-parameterized
subtype (or an array of the corresponding raw type), which opens opportunities
for mistakes, but does not offer any advantage. |
LINK TO THIS
|
GenericTypes.FAQ308A
|
REFERENCES
|
What
is a reifiable type?
Can
I create an array whose component type is a wildcard parameterized type?
Can
I declare a reference variable of an array type whose component type is
a concrete parameterized type?
Can
I create an array whose component type is a concrete parameterized type?
Why
is it allowed to create an array whose component type is an unbounded wildcard
parameterized type?
Can
I declare a reference variable of an array type whose component type is
a bounded wildcard parameterized type?
|
Can
I derive from a wildcard parameterized type?
No, a wildcard parameterized type
is not a supertype.
|
Let us scrutinize an example and see why a wildcard
parameterized
type
cannot be a supertype. Consider the generic interface
Comparable
.
Example (of a generic interface):
interface Comparable<T> {
int compareTo(T arg);
}
If it were allowed to subtype from a wildcard instantiation of
Comparable
,
neither we nor the compiler would know what the signature of the
compareTo
method would be.
Example (of illegal use of a wildcard
parameterized
type
as a supertype):
class MyClass implements
Comparable
<?>
{
//
error
public int compareTo(
???
arg) { ... }
}
The signatures of methods of a wildcard
parameterized
type
are undefined. We do not know what type of argument the
compareTo
method is supposed to accept. We can only subtype from concrete instantiations
of the
Comparable
interface, so that the signature of the
compareTo
method is well-defined.
Example (of legal use of a concrete
parameterized
type
as a supertype):
class MyClass implements
Comparable
<MyClass>
{ // fine
public int compareTo(
MyClass
arg) { ... }
}
Note that the raw type is, of course, acceptable as a supertype, different
from the wildcard
parameterized types
including
the unbounded wildcard
parameterized type
.
Example (of legal use of a raw type as a supertype):
class MyClass implements
Comparable
{
// fine
public int compareTo(
Object
arg) { ... }
}
|
LINK TO THIS
|
GenericTypes.FAQ309
|
REFERENCES
|
What
is the raw type?
What
is a wildcard parameterized type?
What
is the unbounded wildcard parameterized type?
What
is the difference between the unbounded wildcard parameterized type and
the raw type?
|
Why
is there no class literal for wildcard parameterized types?
Because a wildcard parameterized type
has no exact runtime type representation.
|
The rationale is the same as for concrete parameterized
types.
Wildcard parameterized types lose their type arguments when they are
translated to byte code in a process called
type erasure
. As a side
effect of type erasure, all instantiations of a generic type share
the same runtime representation, namely that of the corresponding
raw
type
. In other words, parameterized types do not have type representation
of their own. Consequently, there is no point to forming class literals
such as
List<?>.class
,
List<? extends Number>.class
and
List<Long>.class
, since no such
Class
objects
exist. Only the raw type
List
has a
Class object
that represents its runtime type. It is referred to as
List.class
. |
LINK TO THIS
|
GenericTypes.FAQ310
|
REFERENCES
|
What
is type erasure?
What
is the raw type?
Why
is there no class literal for concrete parameterized types?
|
CONTENT
PREVIOUS
NEXT
INDEX
|