Java Generics FAQs - Programming With Java Generics
Practicalities
- Programming With Java Generics
© Copyright 2004-2022 by Angelika Langer. All
Rights Reserved.
Programming With Generics
Using
Generic Types and Methods
Should
I prefer parameterized types over raw types?
Yes, using parameterized types has various
advantages and is recommended, unless you have a compelling reason
to prefer the raw type.
|
It is permitted to use generic types without type arguments,
that is, in their raw form. In principle, you can entirely ignore Java
Generics and use raw types throughout your programs. It is, however,
recommended that type arguments are provided when a generic type is used,
unless there is a compelling reason not to do so.
Providing the type arguments rather than using the raw type has a couple
of advantages:
-
Improved readability.
An instantiation with type arguments
is more informative and improves the readability of the source code.
-
Better tool support
. Providing type arguments enables development
tools to support you more effectively: IDEs (= integrated develepment environments)
can offer more precise context-sensitive information; incremental compilers
can flag type errors the moment you type in the incorrect source code.
Without providing type arguments the errors would go undetected until
you start testing your program.
-
Fewer ClassCastExceptions.
Type arguments enable
the compiler to perform static type checks to ensure type safety at compile
time, as opposed to dynamic type checks performed by the virtual machine
at runtime. As a result there are fewer opportunities for the program
to raise a
ClassCastException
.
-
Fewer casts.
More specific type informations is available when type
arguments are provided, so that hardly any casts are needed compared to
the substantial number of casts that clutter the source code when raw types
are used.
-
No unchecked warnings.
Raw types lead to "unchecked"
warning, which can be prevented by use of type arguments.
-
No future deprecation.
The Java Language Specification states that
raw types might be deprecated in a future version of Java, and might ultimately
be withdrawn as a language feature.
Raw types have an advantage, too:
-
Zero learning effort.
If you ignore Java Generics and use raw types
everywhere in you program you need not familiarize yourself with new language
features or learn how to read any puzzling error messages.
Advantages that are no advantages:
-
Improved Performance
. Especially C++ programmers might expect
that generic programs are more efficient than non-generic programs, because
C++ templates can boost runtime efficiency. However, if you take
a look under the hood of the Java compiler and study how the compiler translates
generic source code to byte code you realize that Java code using parameterized
types does not perform any faster than non-generic programs.
|
LINK TO THIS
|
Practicalities.FAQ001
|
REFERENCES
|
How
does the compiler translate Java generics?
What
is an "unchecked" warning?
What
is the benefit of using Java generics?
|
Why
shouldn't I mix parameterized and raw types, if I feel like it?
Because it is poor style and highly confusing
to readers of your source code.
|
Despite of the benefits of parameterized types you might
still prefer use of raw types over using pre-defined generic types in their
parameterized form, perhaps because the raw types look more familiar. To
some extent it is a matter of style and taste and both styles are permitted.
No matter what your preferences are: be consistent and stick to it.
Either ignore Java generics and use raw type in all places, or take advantage
of the improved type-safety and provide type arguments in all places. Mixing
both styles is confusing and results in "unchecked" warnings that can and
should be avoided.
Naturally, you have to mix both styles when you interface with source
code that was written before the advent of Java generics. In these cases
you cannot avoid the mix and the inevitable "unchecked" warnings. However,
one should never have any "unchecked" warnings in code that is written
in generic style and does not interface with non-generic APIs.
Here is a typical beginner's mistake for illustration.
Example (of poor programming style):
List
<String>
list = new ArrayList
<String>
();
Iterator
iter = list.iterator();
String s =
(String)
iter.next();
...
Beginners often start out correctly providing type arguments and suddenly
forget, in the heat of the fighting, that methods of parameterized types
often return other parameterized types. This way they end up with
a mix of generic and non-generic programming style, where there is no need
for it. Avoid mistakes like this and provide type arguments in
all
places.
Example (corrected):
List
<String>
list = new ArrayList
<String>
();
Iterator
<String>
iter = list.iterator();
String s = iter.next();
...
Here is an example of a code snippet that produces avoidable "unchecked"
warnings.
Example (of avoidable "unchecked" warning):
void f(Object obj) {
Class
type = obj.getClass();
Annotation a = type.getAnnotation(Documented.class);
//
unchecked warning
...
}
warning: [unchecked] unchecked call to <A>getAnnotation(java.lang.Class<A>)
as a member of the raw type java.lang.Class
Annotation a = type.getAnnotation(Documented.class);
^
The
getClass
method returns an instantiation of class
Class
,
namely
Class<? extends X>
, where
X
is the erasure
of the static type of the expression on which
getClass
is called.
In the example, the parameterization of the return type is ignored and
the raw type
Class
is used instead. As a result, certain
method calls, such as the invocation of
getAnnotation
, are flagged
with an "unchecked" warning.
In general, it is recommended that type arguments are provided unless
there is a compelling reason not to do so. In case of doubt, often
the unbounded wildcard
parameterized type
is the best
alternative to the raw type. It is sematically equivalent, eliminates
"unchecked" warnings and yields to error messages if their use is unsafe.
Example (corrected):
void f(Object obj) {
Class
<?>
type = obj.getClass();
Annotation a = type.getAnnotation(Documented.class);
...
}
|
LINK TO THIS
|
Practicalities.FAQ002
|
REFERENCES
|
What
is the benefit of using Java generics?
What
does type-safety mean?
What
is an "unchecked" warning?
What
is the raw type?
What
is a parameterized or generic)type?
How
is a generic type instantiated?
What
is an unbounded wildcard parameterized type?
|
Should
I use the generic collections or stick to the old non-generic collections?
Provide type arguments when you use collections;
it improves clarity and expressiveness of your source code.
|
The JDK collection framework has been re-engineered. All
collections are generic types since Java 5.0. In principle, you can
choose whether you want to use the pre-defined generic collections in their
parameterized or raw form. Both is permitted, but use of the parameterized
form is recommended because it improves the readability of your source
code.
Let us compare the generic and non-generic programming style and see
how they differ.
Example (of non-generic style):
final class HtmlProcessor {
public static
Collection
process(
Collection
files) {
Collection imageFileNames = new TreeSet();
for (Iterator i = files.iterator(); i.hasNext(); ) {
URI uri =
(URI)
i.next();
Collection tokens = HtmlTokenizer.tokenize(new File(uri));
imageFileNames.addAll(ImageCollector.collect(tokens));
//
unchecked warning
}
return imageFileNames;
}
}
final class ImageCollector {
public static
Collection
collect(
Collection
tokens) {
Set images = new TreeSet();
for (Iterator i = tokens.iterator(); i.hasNext(); ) {
HtmlToken tok =
(HtmlToken)
i.next();
if (tok.getTag().3("img") && tok.hasAttribute("src")) {
Attribute attr = tok.getAttribute("src");
images.add(attr.getValue());
//
unchecked warning
}
}
return images;
}
}
From the code snippet above it is relatively difficult to tell what
the various collections contain. This is typical for non-generic
code. The raw type collections do not carry information regarding
their elements. This lack of type information also requires that
we cast to the alledged element type each time an element is retrieved
from any of the collections. Each of these casts can potentially
fail at runtime with a
ClassCastException
.
ClassCastException
s
are a phenomenon typical to non-generic code.
If we translate this non-generic source code with a Java 5.0 compiler,
we receive "unchecked" warnings when we invoke certain operations
on the raw type collections. We would certainly ignore all these
warnings, or suppress them with the
SuppressWarnings
annotation.
Example (of generic counterpart):
final class HtmlProcessor {
public static
Collection<String>
process(
Collection<URI>
files) {
Collection<String> imageFileNames = new TreeSet<String>();
for (URI uri : files) {
Collection<HtmlToken> tokens = HtmlTokenizer.tokenize(new File(uri));
imageFileNames.addAll(ImageCollector.collect(tokens));
}
return imageFileNames;
}
}
final class ImageCollector {
public static
Collection<String>
collect(
Collection<HtmlToken>
tokens)
{
Set<String> images = new TreeSet<String>();
for (HtmlToken tok : tokens) {
if (tok.getTag().equals("img") && tok.hasAttribute("src"))
{
Attribute attr = tok.getAttribute("src");
images.add(attr.getValue());
}
}
return images;
}
}
From the generic source code we can easily tell what type of elements
are stored in the various collections. This is one of the benefits of generic
Java: the source code is substantially more expressive and captures more
of the programmer's intent. In addition it enables the compiler to perform
lots of type checks at compile time that would otherwise be performed at
runtime. Note that we got rid of all casts. As a consequence there
will be no runtime failure due to a
ClassCastException
.
This is a general rule in Java 5.0: if your source code compiled
without any warnings then there will be no unexpected
ClassCastException
s
at runtime. Of course, if your code contains explicit cast expressions
any exceptions resulting from these casts are not considered unexpected.
But the number of casts in your source code will drop substantially with
the use of generics.
|
LINK TO THIS
|
Practicalities.FAQ003
|
REFERENCES
|
package
java.util
Should
I prefer parameterized types over raw types?
What
is the benefit of using Java generics?
What
is an "unchecked" warning?
How
can I disable or enable unchecked warnings?
What
is the SuppressWarnings annotation?
What
is the raw type?
What
is a parameterized or generic type?
How
is a generic type instantiated?
|
What
is a checked collection?
A view to a regular collection that performs
a runtime type check each time an element is inserted.
|
Despite of all the type checks that the compiler performs
based on type arguments in order to ensure type safety it is still possible
to smuggle elements of the wrong type into a generic collection.
This can happen easily when generic and non-generic code is mixed.
Example (of smuggling an alien into a collection):
class Legacy {
public static List create() {
List rawList = new ArrayList();
rawList.add("abc");
//
unchecked warning
...
return rawList;
}
public static void insert(List rawList) {
...
rawList.add(new
Date
());
//
unchecked warning
...
}
}
class Modern {
private void someMethod() {
List<
String
>
stringList = Legacy.create();
// unchecked
warning
Legacy.insert(stringList);
Unrelated.useStringList(stringList);
}
}
class Unrelated {
public static void useStringList(List<String> stringList)
{
...
String s = stringList.get(1);
//
ClassCastException
...
}
}
An "alien"
Date
object is successfully inserted into a list of
strings. This can happen inadvertantly when a parameterized type
is passed to a piece of legacy code that accepts the corresponding raw
type and then adds alien elements. The compiler can neither detect
nor prevent this kind of violation of the type safety, beyond issuing an
"unchecked" warning when certain methods of the raw type are invoked. The
inevitable type mismatch will later show up in a potentially unrelated
part of the program and will mainfest itself as an unexpected
ClassCastException
.
For purposes of diagnostics and debugging JDK 5.0 adds a set of “checked”
views to the collection framework (see
java.util.Collections
),
which can detect the kind of problem explained above. If a checked
view is used instead of the original collection then the error is reported
at the correct location, namely when the "alien" element is inserted.
Example (of using a checked collection):
class Legacy {
public static List create() {
List rawList = new ArrayList();
rawList.add("abc");
//
unchecked warning
...
return rawList;
}
public static void insert(List rawList) {
...
rawList.add(new Date());
//
ClassCastException
...
}
}
class Modern {
private void someMethod() {
List<String> stringList
=
Collections.checkedList
(L
egacy.create()
,String.class)
;
//
unchecked warning
Legacy.insert(stringList);
Unrelated.useStringList(stringList);
}
}
class Unrelated
public static void useStringList(List<String> stringList)
{
...
String s = stringList.get(1);
...
}
}
The checked collection is a view to an underlying collection, similar to
the unmodifiable and synchronized views provided by class
Collections
.
The purpose of the checked view is to detect insertion of "aliens" and
prevent it by throwing a
ClassCastException
in case the element
to be inserted is of an unexptected type. The expected type of the
elements is provided by means of a
Class
object when the checked
view is created. Each time an element is added to the checked collection
a runtime type check is performed to make sure that element is of an acceptable
type. Here is a snippet of the implementation of the checked
view for illustration.
Example (excerpt from a checked view implementation):
public class Collections {
public static <E> Collection<E> checkedCollection(Collection<E>
c,
Class<E> type
) {
return new CheckedCollection<E>(c, type);
}
private static class CheckedCollection<E> implements
Collection<E> {
final Collection<E> c;
final Class<E> type;
CheckedCollection(Collection<E> c, Class<E>
type) {
this.c = c;
this.type = type;
}
public boolean add(E o){
if (!
type.isInstance
(o))
throw new ClassCastException();
return c.add(o);
}
}
}
The advantage of using a checked view is that the error is reported at
the correct location. The downside of using a checked collection is the
performance overhead of an additional dynamic type check each time an element
is inserted into the collection.
The error detection capabilities of the checked view are somewhat limited.
The type check that is performed when an element is inserted into a checked
collection is performed at runtime - using the runtime type representation
of the expected element type. If the element type is a parameterized
type the check cannot be exact, because only the raw type is available
at runtime. As a result, aliens can be inserted into a checked collection,
although the checked collection was invented to prevent exactly that.
Example (of limitations of checked collections):
class Legacy {
public static List legacyCreate() {
List rawList = new ArrayList();
rawList.add(new Pair("abc","xyz"));
//
unchecked warning
...
return rawList;
}
public static void legacyInsert(List rawList) {
...
rawList.add(new
Pair(new
Date(),"Xmas")
);
//
unchecked warning
...
}
}
class Modern {
private void someModernMethod() {
List<
Pair<String,String>
>
stringPairs
=
Collections.checkedList(
legacyCreate()
,Pair.class)
;
//
unchecked warning
Legacy.insert(stringPairs);
Unrelated.useStringPairs(stringPairs);
}
}
class Unrelated {
public static void useStringPairs(List<Pair<String,String>>
stringPairList) {
...
String s = stringPairList.get(1).getFirst();
//
ClassCastException
...
}
}
The checked view can only check against the raw type
Pair
and
cannot prevent that an alien pair of type
Pair<Date,String>
is inserted into the checked view to a collection of
Pair<String,String>
.
Remember, parameterized types do not have an exact runtime type representation
and there is not class literal for a parameterized type that we could provide
for creation of the checked view.
Note, that a checked view to a collection of type
Pair<String,String>
cannot be created without a warning.
Example:
Lis
t<Pair<String,String>> stringPairs
= Collections.checkedList
(new
ArrayList<
Pair<String,String>
>
()
,
Pair
.class);
//
error
Lis
t<Pair<String,String>> stringPairs
= Collections.checkedList
(
(List<Pair>)
(new
ArrayList<
Pair<String,String>
>
())
,
Pair
.class);
//
error
Lis
t<Pair<String,String>> stringPairs
= Collections.checkedList
(
(List)
(new
ArrayList<
Pair<String,String>
>
())
,
Pair
.class);
//
unchecked warning
We cannot create a checked view to a parameterized type such as
List<Pair<String,String>>
,
because it is required that we supply the runtime type representation of
the collection's element type as the second argument to the factory method
Collections.checkedList
.
The element type
Pair<String,String>
does not have a runtime
type representation of its own; there is no such thing as
Pair<String,String>.class
.
At best, we can specify the raw type
Pair
as the runtime type
representation of the collection's element type. But this is the
element type of a collection of type
List<Pair>
, not of a
List<Pair<String,String>>
.
This explains why we have to add a cast. The natural cast would
be to type
List<Pair>
, but the conversion from
ArrayList<Pair<String,String>>
to
List<Pair>
is not permitted. These two types a inconvertible because they are instantiations
of the same generic type for different type arguments.
As a workaround we resort to the raw type
List
, because the
conversion
ArrayList<Pair<String,String>>
to
List
is permitted for reasons of compatibility. Use of the raw type results
in the usual "unchecked" warnings. In this case the compiler complains
that we pass a raw type
List
as the first arguments to the
Collections.checkedList
method,
where actually a
List<Pair>
is exptected.
In general, we cannot create a checked view to an instantiation
of a collection whose type argument is a parameterized type (such as
List<Pair<String,String>>
).
This is only possible using debatable casts, as demonstrated above.
However, it is likely that checked collections are used in cases where
generic and non-generic legacy code is mixed, because that is the situation
in which alien elements can be inserted into a collection inadvertantly.
In a mixed style context, you might not even notice that you work around
some of the compiler's type checks, when you create a checked view, because
you have to cope with countless "unchecked" warnings anyway.
The point to take home is that checked views provide a certain safety
net for collections whose element type is a raw type, but fails to provide
the same kind of safety for collections whose element type is a parameterized
type. |
LINK TO THIS
|
Practicalities.FAQ004
|
REFERENCES
|
class
java.util.Collections
What
is an "unchecked" warning?
What
is the raw type?
What
happens when I mix generic and non-generic code?
How
do I pass type information to a method so that it can be used at runtime?
How
do I perform a runtime type check whose target type is a type parameter?
Why
is there no class literal for concrete parameterized types?
How
does the compiler translate Java generics?
What
is type erasure?
What
is the type erasure of a parameterized type?
|
What
is the difference between a Collection<?> and a Collection<Object>?
Collection<Object>
is a heterogenous
collection, while
Collection<?>
is a homogenous collection
of elements of the same unknown type.
|
The type
Collection<Object>
is a
heterogenous
collection of objects of different types. It's a mixed bag and can
contain elements of all reference types.
The type
Collection<?>
stands for a representative from
the family of types that are instantiations of the generic interface
Collection
,
where the type argument is an arbitrary reference type. For instance,
it refers to a
Collection<Date>
, or a
Collection<String>
,
or a
Collection<Number>
, or even a
Collection<Object>
.
A
Collection<?>
is a
homogenous
collection in the
sense that it can only contain elements that have a common unknown supertype,
and that unknown supertype might be more restrictive than
Object
.
If the unknown supertype is a
final
class then the collection
is truly homogenous. Otherwise, the collection is not really homogenous
because it can contain objects of different types, but all these types
are subtypes of the unknown supertype. For instance, the
Collection<?>
might
stand for
Collection<Number>
, which is homogenous in the sense
that it contains numbers and not apples or pears, yet it can contain a
mix of elements of type
Short
,
Integer
,
Long
,
etc.
A similar distinction applies to bounded wildcards, not just the unbounded
wildcard "
?
".
A
List<Iterable>
is a concrete parameterized type.
It is a mixed list of objects whose type is a subtype of
Iterable
.
I can contain an
ArrayList
and a
TreeSet
and a
SynchronousQueue
,
and so on.
A
List<? extends Iterable>
is a wildcard parameterized type
and stands for a representative from the family of types that are instantiations
of the generic interface
List
, where the type argument is a subtype
of
Iterable
, or
Iterable
itself. Again, the list
is truly homogenous if the unknown subtype of
Iterable
is a
final
class. Otherwise, it is a mix of objects with a common unknown supertype
and that supertype itself is a subtype of
Iterable
. For
example,
List<? extends Iterable>
might stand for
List<Set>
,
which is homogenous in the sense that it contains sets and not lists or
queues. Yet the
List<Set>
can be heterogenous because it might
contain a mix of
TreeSet
s and
HashSet
s. |
LINK TO THIS
|
Practicalities.FAQ005
|
REFERENCES
|
What
is a concrete parameterized type?
What
is a wildcard parameterized type?
|
How
do I express that a collection is a mix of objects of different types?
Using wildcard instantiations of the
generic collections.
|
Occasionally, we want to refer to sequences of objects
of different types. An example would be a
List<Object>
or a
Object[]
.
Both denote sequences of objects of arbitrary types, because
Object
is the supertype of all reference types.
How do we express a sequence of objects not of arbitrary different types,
but of different instantiations of a certain generic type? Say, we need
to refer to a sequence of pairs of arbitrary elements. We would need the
supertype of all instantiations of the generic
Pair
type. This
supertype is the unbounded wildcard instantiation
Pair<?,?>
.
Hence a
List<Pair<?,?>>
and a
Pair<?,?>[]
would
denote sequences of pairs of different types.
|
of any type
|
of any pair type
|
collection
|
List<Object>
|
List<Pair<?,?>>
|
array
|
Object[]
|
Pair<?,?>[]
|
When we want to refer to a mixed sequence of certain types, instead
of all arbitrary types, we use the supertype of those "certain types" to
express the mixed sequence. Examples are
List<Number>
or
Number[].
The corresponding mixed sequences of instantiations of a generic type is
expressed in a similar way. A mixed sequences of pairs of numbers can be
expressed as
List<Pair<? extends Number, ? extends Number>>
or
as
Pair<? extends Number, ? extends Number>[]
.
|
of any number type
|
of any type of pair of numbers
|
collection
|
List<Number>
|
List<Pair<? extends
Number,? extends Number>>
|
array
|
Number[]
|
Pair<? extends Number,?
extends Number>[]
*)
|
|
*) Legal as the type of reference variable, but illegal
in a new expression.
|
The array type
Pair<? extends Number, ? extends Number>[]
needs
further explanation. This type would in principle denote a mixed sequence
of pairs of different type, but this array type is not overly useful. It
can only be used for declaration of reference variables, while it must
not appear in new expressions. That is, we can declare reference variables
of type
Pair<? extends Number, ? extends Number>[]
, but the
reference can never refer to an array of its type, because no such array
can be created.
Example (of illegal array creation):
Pair<? extends Number, ? extends Number>[] makeNumberPairs(int
size) {
return new Pair<? extends Number, ? extends Number>[size];
//
error
}
error: generic array creation
return new Pair<? extends Number, ? extends Number>[size];
^
By and large an array type such as
Pair<? extends Number, ? extends
Number>[]
is not particularly useful, because it cannot refer to an
array of its type. It can refer to an array of the corresponding raw type,
i.e.
Pair[],
or to an array of a non-generic subtype, e.g.
Point[]
,
where
Point
is a subclass of
Pair<Double,Double>
for
instance. In each of these cases using a reference variable of type
Pair<?
extends Number, ? extends Number>[]
offers
no advantage over using a reference variable that matches the type
of the array being refered to. Quite the converse; it is error prone and
should be avoided. This rules applies to all array types with a component
type that is a concrete or bounded wildcard parameterized type. For details
see
ParameterizedTypes.FAQ104A
and
ParameterizedTypes.FAQ307A
.
Note that arrays of unbounded wildcard parameterized types do not suffer
from this restriction. The creation of an array of an unbounded wildcard
parameterized type is permitted, because the unbounded wildcard parameterized
type is a so-called reifiable type, so that an array reference variable
with an unbounded wildcard parameterized type as its component type, such
as
Pair<?,?>[]
, can refer to an array of its type.
Example (of legal array creation):
Pair<?,?>[] makeNumberPairs(int size) {
return new Pair<?,?>[size];
// fine
}
|
LINK TO THIS
|
Practicalities.FAQ006
|
REFERENCES
|
Can
I create an object whose type is a wildcard 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?
Can
I declare a reference variable of an array type 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 declare a reference variable of an array type whose component type is
an unbounded wildcard parameterized type?
What
is a reifiable type?
|
What
is the difference between a Collection<Pair<String,Object>>, a Collection<Pair<String,?>>
and a Collection<? extends Pair<String,?>>?
All three types refer to collections
that hold pairs where the first part is a
String
and the second
part is of an arbitrary type. The differences are subtle.
|
The three parameterized types are relatively similar.
They all refer to collections that hold pairs where the first part is a
String
and the second part is of an arbitrary type.
Let us start with a comparison of the two concrete parameterized types
Collection<Pair<String,Object>>
and
Collection<Pair<String,?>>
.
The both contain pairs where the first part is a
String
.
The individual pairs stored in the collection can for instance contain
a
String
and a
Date
, or a
String
and an
Object
,
or a
String
and a
String
. The difference lies in the
types of the pairs that can be added to the two collections.
Example (using a
Collection<Pair<String,Object>>
):
Collection<
Pair<String,
Object
>
>
c = new ArrayList<Pair<String,Object>>();
c.add(new Pair<String,
Date
>
("today", new Date()));
// error:
illegal argument type
c.add(new Pair<String,
Object
>("today",
new Date()));
// fine
c.add(new Pair<String,
String
>("name","Pete
Becker"));
// error: illegal
argument type
c.add(new Pair<String,
Object
>(
"name","Pete
Becker"
));
// fine
The example demonstrates that only pairs of type
Pair<String,Object>
can be added to a
Collection<Pair<String,Object>>
.
A
Collection<Pair<String,Object>>
is a homogenous collections
of elements of the same type. The individual pairs may contain different
things, as long as the type of the pair is
Pair<String,Object>
.
For instance, a pair may consist of a
String
and a
Date
,
but it must not be of type
Pair<String,Date>
.
Example (using a
Collection<Pair<String,?>>
):
Collection<
Pair<String,
?
>
>
c = new ArrayList<Pair<String,?>>();
c.add(new Pair<String,
Date
>
("today", new Date()));
// fine
c.add(new Pair<String,
Object
>("today",
new Date()));
// fine
c.add(new Pair<String,
String
>("name","Pete
Becker"));
// fine
c.add(new Pair<String,
Object
>(
"name","Pete
Becker"
));
// fine
The example illustrates that a
Collection<Pair<String,?>>
accepts
all types of pairs as long as the first type argument is
String
.
For instance, a pair of type
Pair<String,Date>
is
accepted.
A
Collection<Pair<String,?>>
is a heterogenous
collections of elements of the similar types.
The key difference between a
Collection<Pair<String,Object>>
and
a
Collection<Pair<String,?>>
is that the first contains
elements of the same type and the latter contains elements of different
similar types.
The type
Collection<? extends Pair<String,?>>
is fundamentally
different. It is a wildcard parameterized type, not a concrete parameterized
type. We simply do not know what exactly a reference variable of
the wildcard type refers to.
Example
(using a
Collection<? extends
Pair<String,?>>
):
Collection<
? extends
Pair<String,
?
>
> c = new ArrayList<Pair<String,?>>();
c.add(new Pair<String,
Date
>
("today", new Date()));
// error:
add method must not be called
c.add(new Pair<String,
Object
>("today",
new Date()));
// error: add
method must not be called
c.add(new Pair<String,
String
>("name","Pete
Becker"));
// error: add method
must not be called
c.add(new Pair<String,
Object
>(
"name","Pete
Becker"
));
// error: add method
must not be called
The type
Collection<? extends Pair<String,?>>
stands
for a representative from the family of all instantiations of the generic
type
Collection
where the type argument is a subtype of type
Pair<String,?>
.
This type family includes members such as
Pair<String,String>
,
Pair<String,Object>
,
Pair<String,?
extends Number>
, and
Pair<String,?>
itself .
Methods like
add
must not be invoked through a reference of
a wildcard type. This is because the
add
method takes an argument
of the unknown type that the wildcard stands for. Using the variable
c
of the wildcard type
Collection<? extends Pair<String,?>>
,
nothing can be added to the collection. This does not mean that the
collection being refered to does not contain anything. We just do
not know what exactly the type if the collection is and consequently we
do not know what type of elements it contains. All we know is that
is contains pairs where the first part is a
String
. But
we do not know of which type the second part of the pair is, or whether
or not all pairs are of the same type.
So far, we've silently assumed that
Pair
is a
final
class. What if it has subtypes? Say, it has a subtype
class SubTypeOfPair<X,Y>
extends Pair<X,Y>
.
In that case, a
Collection<Pair<String,Object>> may
not
only contain objects of type
Pair<String,Object>
, but also
objects of type
SubTypeOfPair<String,Object>
.
A
Collection<Pair<String,?>> may
not only contain objects
of different pair types such as
Pair<String,Date>
and
Pair<String,Object>
,
but also objects of subtypes of those, such as
SubTypeOfPair<String,Date>
and
SubTypeOfPair<String,Object>
.
The type
Collection<? extends Pair<String,?>>
stands
for a representative from the family of all instantiations of the generic
type
Collection
where the type argument is a subtype of type
Pair<String,?>
.
This type family is now even larger. It does not only include members
such as
Pair<String,String>
,
Pair<String,Object>
,
Pair<String,?
extends Number>
, and
Pair<String,?>
itself, but also type
such as
SubTypeOfPair<String,String>
,
SubTypeOfPair<String,Object>
,
SubTypeOfPair<String,?
extends Number>
, and
SubTypeOfPair<String,?>
.
|
LINK TO THIS
|
Practicalities.FAQ006A
|
REFERENCES
|
What
is a bounded wildcard?
Which
methods and fields are accessible/inaccessible through a reference variable
of a wildcard type?
What
is the difference between a Collection<?> and a Collection<Object>?
Which
super-subset relationships exist among wildcards?
|
How
can I make sure that a wildcard that occurs repeatedly in the same scope
stands for the same type?
In general you can't.
|
If the same wildcard appears repeatedly, each occurrence
of the wildcard stands for a potentially different type. There is no way
to make sure that the same wildcard represents the same type.
Example (using the same wildcard repeatedly):
Pair<
?
,
?
> couple = new Pair<
String
,
String
>("Orpheus","Eurydike");
Pair<
?
,
?
> xmas
= new Pair<
String
,
Date
>("Xmas",
new Date(104,11,24));
There is nothing you can do to make sure that a reference variable of type
Pair<?,?>
represents a pair of elements of the same type.
Depending on the circumstances there might be work-arounds that achieve
this goal. For instance, if the type
Pair<?,?>
is the
type of a method argument, it might be possible to generify the method
to ensure that the method argument is a pair of elements of the same type.
For instance, the following method
void someMethod(Pair<
?
,
?
> pair) { ...
}
accepts all types of pairs. It is mostly equivalent to the following
generic method:
<
X
,
Y
> void someMethod(Pair<
X
,
Y
>
pair) { ... }
In order to make sure that only pairs of elements of the same type are
passed to the method, the method can be generified as follows:
<
T
> void someMethod(Pair<
T
,
T
>
pair) { ... }
Now it is guaranteed that the method accepts only pairs of elements of
the same type. |
LINK TO THIS
|
Practicalities.FAQ007
|
REFERENCES
|
What
is a wildcard parameterized type?
If
a wildcard appears repeatedly in a type argument section, does it stand
for the same type?
|
Using Generic
Methods
Why
doesn't method overloading work as I expect it?
Because
there is only one byte code representation of each generic type or method.
|
When
you invoke an overloaded method and pass an argument to the method whose
type is a type variable or involves a type variable, you might observe
surprising results. Let us study an example.
Example (of invocation of an overloaded method):
static void overloadedMethod(
Object
o) {
System.out.println("overloadedMethod(Object)
called");
}
static void overloadedMethod(
String
s) {
System.out.println("overloadedMethod(String)
called");
}
static void overloadedMethod(
Integer
i) {
System.out.println("overloadedMethod(Integer)
called");
}
static <T> void genericMethod(T t) {
overloadedMethod
(t)
;
//
which method is called?
}
public static void main(String[] args) {
genericMethod(
"abc"
);
}
We have several overloaded versions of a method. The overloaded method
is invoked by a generic method which passes an argument of type
T
to the overloaded method. Eventually the generic method is called
and a string is passed as an argument to the generic method. One might
expect that inside the generic method the string version of the overloaded
method is invoked, because the method argument is a string. This,
however, is wrong.
The program prints:
overloadedMethod(
Object
) called
How can this happen? We pass an argument of type
String
to the overloaded method and yet the version for type
Object
is
called. The reason is that the compiler creates only one byte code representation
per generic type or method and maps all instantiations of the generic type
or method to that one representation.
In our example the generic method is translated to the following representation:
void genericMethod(
Object
t) {
overloadedMethod(t);
}
Considering this translation, it should be obvious why the
Object
version of the overloaded method is invoked. It is entirely irrelevant
what type of object is passed to the generic method and then passed along
to the overloaded method. We will always observe a call of the
Object
version of the overloaded method.
More generally speaking: overload resolution happens at compile
time, that is, the compiler decides which overloaded version must be called.
The compiler does so when the generic method is translated to its unique
byte code representation. During that translation type erasure is
performed, which means that type parameters are replaced by their leftmost
bound or
Object
if no bound was specified. Consequently,
the leftmost bound or Object determines which version of an
overloaded method is invoked. What type of object is passed to the
method at runtime is entirely irrelevant for overload resolution.
Here is another even more puzzling example.
Example (of invocation of an overloaded method):
public final class GenericClass<T> {
private void overloadedMethod(
Collection<?>
o) {
System.out.println("overloadedMethod(Collection<?>)");
}
private void overloadedMethod(
List<Number>
s) {
System.out.println("overloadedMethod(List<Number>)");
}
private void overloadedMethod(
ArrayList<Integer>
i) {
System.out.println("overloadedMethod(ArrayList<Integer>)");
}
private void method(List<T> t) {
overloadedMethod(t)
;
// which method is called?
}
public static void main(String[] args) {
GenericClass<Integer>
test = new GenericClass<Integer>();
test.method(new
ArrayList<Integer>
());
}
}
The program prints:
overloadedMethod(Collection<?>)
One might have expected that version for
ArrayList<Integer>
would be invoked, but that again is the wrong expectation. Let us
see what the compiler translates the generic class to.
Example (after type erasure):
public final class GenericClass {
private void overloadedMethod(
Collection
o) {
System.out.println("overloadedMethod(Collection<?>)");
}
private void overloadedMethod(
List
s)
{
System.out.println("overloadedMethod(List<Number>)");
}
private void overloadedMethod(
ArrayList
i) {
System.out.println("overloadedMethod(ArrayList<Integer>)");
}
private void method(List t) {
overloadedMethod(t)
;
}
public static void main(String[] args) {
GenericClass test =
new GenericClass();
test.method(new
ArrayList
());
}
}
One might mistakenly believe that the compiler would decide that the
List
version of the overloaded method is the best match. But that would
be wrong, of course. The
List
version of the overloaded
method was originally a version that takes a
List<Number>
as
an argument, but on invocation a
List<T>
is passed, where
T
can be any type and need not be a
Number
. Since
T
can
be any type the only viable version of the overloaded method is the version
for
Collection<?>
.
Conclusion:
Avoid passing type variables to overloaded methods. Or, more precisely,
be careful when you pass an argument to an overloaded method whose type
is a type variable or involves a type variable. |
LINK
TO THIS
|
Practicalities.FAQ050
|
REFERENCES
|
How
does the compiler translate Java generics?
What
is type erasure?
What
is method overriding?
What
is method overloading?
What
is a method signature?
What
is the @Override annotation?
What
are override-equivalent signatures?
When
does a method override its supertype's method?
What
is overload resolution?
|
Why
doesn't method overriding work as I expect it?
Because
the decision regarding overriding vs. overloading is based on the generic
type, not on any instantiation thereof.
|
Sometimes,
when you believe you override a method inherited from a supertype you inadvertantly
overload instead of override the inherited method. This can lead
to surprising effects. Let us study an example.
Example (of overloading):
class Box
<T>
{
private T theThing;
public Box(
T
t)
{ theThing = t; }
public void reset(
T
t) { theThing = t; }
...
}
class WordBox<
S extends CharSequence
> extends Box<
String
>
{
public WordBox(
S
t) { super(t.toString().toLowerCase());
}
public void reset(
S
t) { super.reset(t.toString().toLowerCase());
}
...
}
class Test {
public static void main(String[] args) {
WordBox<String> city
= new WordBox<String>("Skogland");
city.reset("Stavanger");
//
error: ambiguous
}
}
error: reference to reset is
ambiguous,
both method reset(T) in Box<String> and method reset(T) in WordBox<String>
match
city.reset("Stavanger");
^
In this example, one might be tempted to believe that the method
WordBox<String>.reset(String)
overrides the superclass method
Box<String>.reset(String)
.
After all, both methods have the same name and the same parameter types.
Methods with the same name and the same parameter types in a super- and
a subtype are usually override-equivalent. For this reason, we might
expect that the invocation of the
reset
method in the
Test
class leads to the execution of the
WordBox<String>.reset(String)
method. Instead, the compiler complains about an ambiguous method
call. Why?
The problem is that the subclass's
reset
method does not override
the superclass's
reset
method, but overloads it instead.
You can easily verify this by using the
@Override
annotation.
Example (of overloading):
class Box
<T>
{
private T theThing;
public Box(
T
t)
{ theThing = t; }
public void reset(
T
t) { theThing = t; }
...
}
class WordBox
<S extends CharSequence>
extends Box
<String>
{
public WordBox(
S
t) { super(t.toString().toLowerCase());
}
@Override
public void reset(
S
t) { super.reset(t.toString().toLowerCase());
}
...
}
error: method does not override
a method from its superclass
@Override
^
When a method is annotated by an
@Override
annotation,
the compiler issues an error message if the annotated method does not override
any of its supertype's methods. If it does not override, then it
overloads or hides methods with the same name inherited from its supertype.
In our example the
reset
method in the generic
WordBox<S
extends CharSequence>
class overloads the
reset
method in
the parameterized
Box<String>
class.
The overloading happens because the two methods have different signatures.
This might come as a surprise, especially in the case of the instantation
WordBox<String>
,
where the two
reset
methods have the same name and the same parameter
type.
The point is that the compiler decides whether a subtype method overrides
or overloads a supertype method when it compiles the generic subtype, independently
of any instantiations of the generic subtype. When the compiler compiles
the declaration of the generic
WordBox<S extends CharSequence>
class, then there is no knowledge regarding the concrete type by which
the type parameter
S
might later be replaced. Based on the
declaration of the generic subtype the two
reset
methods have
different signatures, namely
reset(String)
in the supertype and
reset(S_extends_CharSequence)
in the generic subtype. These are two completely different signatures that
are not override-equivalent. Hence the compiler considers them overloading
versions of each other.
In a certain instantiation of the subtype, namely in
WordBox<String>
,
the type parameter
S
might be replaced by the concrete type
String
.
As a result both
reset
methods visible in
WordBox<String>
suddenly have the same argument type. But that does not change the
fact that the two methods still have different signatures and therefore
overload rather than override each other.
The identical signatures of the two overloading version of the
reset
method that are visible in
WordBox<String>
lead to the anbiguitiy
that we observe in our example. When the
reset
method is invoked
through a reference of type
WordBox<String>
, then the compiler
finds both overloading versions. Both versions are perfect matches,
but neither is better than the other, and the compiler rightly reports
an ambiguous method call.
Conclusion:
Be careful when you override methods, especially when generic types
or generic methods are involved. Sometimes the intended overriding
turns out to be considered overloading by the compiler, which leads to
surprising and often confusing results. In case of doubt, use the
@Override
annotation. |
LINK
TO THIS
|
Practicalities.FAQ051
|
REFERENCES
|
How
does the compiler translate Java generics?
What
is type erasure?
What
is method overriding?
What
is method overloading?
What
is a method signature?
What
is the @Override annotation?
When
does a method override its supertype's method?
Can
a method of a generic subtype override a method of a generic supertype?
|
Coping With Legacy
What
happens when I mix generic and non-generic legacy code?
The compiler issues lots of "unchecked"
warnings.
|
It is permitted that a generic class or method is used
in both its parameterized and its raw form. Both forms can be mixed
freely. However, all uses that potentially violate the type-safety
are reported by means of an "unchecked warning". In practice, you
will see a lot of unchecked warnings when you use generic types and methods
in their raw form.
Example (of mixing paramterized and raw use of a generic type):
interface
Comparable<T>
{
int compareTo(T other);
}
class SomeClass implements
Comparable
{
public int compareTo(Object other) {
...
}
}
class Test {
public static void main(String[] args) {
Comparable
x = new SomeClass();
x.compareTo(x);
//
"unchecked" warning
}
}
warning: [unchecked] unchecked call to compareTo(T) as a member
of the raw type java.lang.Comparable
x.compareTo(x);
^
The
Comparable
interface is a generic type. Its raw use
in the example above leads to "unchecked" warnings each time the
compareTo
method is invoked.
The warning is issued because the method invocation is considered a
potential violation of the type-safety guarantee. This particular
invocation of
compareTo
is not unsafe, but other methods invoked
on raw types might be.
Example (of type-safety problem when mixing parameterized and raw use):
class Test {
public static void someMethod(
List
list) {
list.add("xyz");
//
"unchecked" warning
}
public static void test() {
List<Long>
list = new
ArrayList<Long>
();
someMethod(list);
}
}
warning: [unchecked] unchecked call to add(E) as a member of the
raw type java.util.List
list.add("xyz");
^
Similar to the previous example, the invocation of the
add
method
on the raw type
List
is flagged with an "unchecked" warning.
The invocation is indeed unsafe, because it inserts a string into a list
of long values.
The compiler cannot distinguish between invocations that are safe and
those that are not. It reports "unchecked" warnings just in case
that a call might be unsafe. It applies a simple rule: every invocation
of a method of a raw type that takes an argument of the unknown type that
the class's type parameter stands for, is potentially unsafe. That
does not mean, it must be unsafe (see
Comparable.compareTo
), but
it can be unsafe (see
List.add
).
If you find that you must intermix legacy and generic code, pay close
attention to the unchecked warnings. Think carefully how you can justify
the safety of the code that gives rise to the warning. Once you've made
sure the warning is harmless suppress it using the
SuppressWarnings
annotation.
If you can re-engineer existing code or if you write new code from scratch
you should use generic types and methods in their parmeterized form and
avoid any raw use. For instance, the examples above can be "repaired"
as follows:
Example #1 (corrected):
interface
Comparable<T>
{
int compareTo(T other);
}
class SomeClass implements
Comparable
<Object>
{
public int compareTo(Object other) {
...
}
}
class Test {
public static void main(String[] args) {
Comparable
<Object>
x = new SomeClass();
x.compareTo(x);
//
fine
}
}
No "unchecked" warning occurs if the
Comparable
interface is used
in its parameterized form in all places.
Example #2 (corrected):
class Test {
public static void someMethod(
List
<String>
list) {
list.add("xyz");
//
fine
}
public static void test() {
List<Long>
list = new
ArrayList<Long>
();
someMethod(list);
//
error
}
}
error: someMethod(java.util.List<java.lang.String>) cannot be
applied to java.util.List<java.lang.Long>)
someMethod(list);
^
The "unchecked" warning in
someMethod
is no longer necessary if
the generic type
List
is used in its parameterized form as
List<String>
.
With this additional type information the compiler is now capable of flagging
the formerly undetected type-safety problem in method
test
as
an error. |
LINK TO THIS
|
Practicalities.FAQ101
|
REFERENCES
|
What
does type-safety mean?
What
is the raw type?
Can
I use a raw type like any other type?
What
is an "unchecked" warning?
How
can I disable or enable unchecked warnings?
What
is the SuppressWarnings annotation?
|
Should
I re-engineer all my existing types and generify them?
No, most likely not.
|
Not all types are inherently generic. There is no
point to turning a type into a generic type if the type does not semantically
depend on a particular unknown type that can be more adequately be expressed
by means of a type parameter.
Example (of an arbitrary non-generic type taken from package
org.w3c.dom
):
public interface NameList {
boolean contains(String str);
boolean containsNS(String namespaceURI, String name);
int getLength();
String getName(int index);
String getNamespaceURI(int index);
}
The
NameList
interface takes and returns either strings or primitive
types and there is no reason why this class should be generic in any form.
Other non-generic types would benefit from generics.
Example (of another arbitrary non-generic type):
public interface Future {
boolean cancel(boolean mayInterruptIfRunning);
Object
get();
Object
get(long
timeout, TimeUnit unit);
boolean isCancelled();
boolean isDone();
}
This interface has get methods that return
Object
references.
If these methods return the same type of object for a given instance of
type
Future
, then the interface is more precisely declared as
a generic interface.
Example (of corresponding generic type):
public interface Future
<V>
{
boolean cancel(boolean mayInterruptIfRunning);
V
get();
V
get(long timeout, TimeUnit unit);
boolean isCancelled();
boolean isDone();
}
Occasionally, the generification of one type leads to the generification
of other related types.
Example (of non-generic types taken from package
java.lang.ref
in JDK 1.4):
public class ReferenceQueue {
public ReferenceQueue() { }
public
Reference
poll()
{ ... }
public
Reference
remove(long
timeout)
throws IllegalArgumentException, InterruptedException
{ ... }
public
Reference
remove()
throws InterruptedException { ... }
}
public abstract class Reference {
private
Object
referent;
ReferenceQueue
queue;
Reference next;
Reference(
Object
referent)
{ ... }
Reference(
Object
referent,
ReferenceQueue
queue) { ... }
public void clear() { ... }
public boolean enqueue() { ... }
public
Object
get()
{ ... }
public boolean isEnqueued() { ... }
}
The abstract class
Reference
internally holds a reference of type
Object
and has methods that take and return
Object
references.
If these methods take and return the same type of object that is held internally,
then the class is more precisely declared as a generic class, namely as
Reference<T>
where
T
is the type of the referent.
When we decide to parameterize class
Reference
then we must
provide type arguments in all places where type
Reference
is used.
This affects class
ReferenceQueue
because it has methods that
return references of type
Reference
. Consequently, we would
declare class
ReferenceQueue
as a generic class, too.
Once we have generified class
ReferenceQueue
then we must return
to class
Reference
and provide type arguments in all places where
type
ReferenceQueue
is used.
Example (of corresponding generic type in JDK 5.0):
public class ReferenceQueue
<T>
{
public ReferenceQueue() { }
public
Reference
<?
extends T>
poll() { ... }
public
Reference
<?
extends T>
remove(long timeout)
throws IllegalArgumentException, InterruptedException
{ ... }
public
Reference
<?
extends T>
remove()
throws InterruptedException { ... }
}
public abstract class Reference
<T>
{
private
T
referent;
ReferenceQueue
<? super T>
queue;
Reference next;
Reference(
T
referent)
{ ... }
Reference(
T
referent,
ReferenceQueue
<?
super T>
queue) { ... }
public void clear() { ... }
public boolean enqueue() { ... }
public
T
get() { ...
}
public boolean isEnqueued() { ... }
}
This is an example where a class, namely
ReferenceQueue
, is turned
into a generic class because the types it uses are generic. This
propagation of type parameters into related types is fairly common.
For instance, the subtypes of type
Reference
(namely
PhantomReference
,
SoftReference
,
and
WeakReference
) are generic types as well. |
LINK TO THIS
|
Practicalities.FAQ102
|
REFERENCES
|
How
do I generify an existing non-generic class?
|
How
do I generify an existing non-generic type or method?
There are no carved-in-stone rules.
It all depends on the intended semantics of the generified type or method.
|
Modifying an existing type that was non-generic in the
past so that it becomes usable as a parameterized type in the future is
a non-trivial task. The generification must not break any existing code
that uses the type in its old non-generic form and it must preserve the
original non-generic type's semantic meaning.
For illustration, we study a couple of examples from the collection
framework (see package
java.util in
J2SE
1.4.2
and
J2SE
5.0
). We will generify the traditional non-generic interface
Collection
.
From the semantics of a collection it is obvious that for a homogenous
collection of elements of the same type the element type would be the type
parameter of a generic
Collection
interface.
Example (from JDK 1.4; before generification):
interface Collection {
boolean add (
Object
o);
boolean contains(
Object
o);
boolean remove (
Object
o);
...
}
These methods take an element as an argument and insert, find or extract
the element from the collection. In a generic collection the method
parameters would be of type
E
, the interface's type parameter.
Example (from JDK 5.0; after generification):
interface Collection
<E>
{
boolean add (
E
o);
boolean contains(
E
o);
boolean remove (
E
o);
...
}
However, this modification does not exactly preserve the semantics of the
old class. Before the generification it was possible to pass an arbitrary
type of object to these methods. After the generification only objects
of the "right" type are accepted as method arguments.
Example (of modified semantics):
class ClientRepository {
private Collection
<Client>
clients = new LinkedList<Client>();
...
boolean isClient(
Object
c) {
return clients.contains(c);
//
error
}
}
Passing an
Object
reference to method
contains
used to
be permitted before the generification, but no longer compiles after generification.
Seemingly, our generified type is not semantically compatible with
the original non-generic type. A more relaxed generification would
look like this.
Example (from JDK 5.0; after an alternative generification):
interface Collection
<E>
{
boolean add (
E
o);
boolean contains(
Object
o);
boolean remove (
Object
o);
...
}
Only for the
add
method now would accept the more restrictive
method parameter type
E
. Since a
Collection<E>
is supposed to contain only elements of type
E
, it is expected
and desired that insertion of an alien element is rejected at compile time.
This seemingly trivial example illustrates that decisions regarding
a "correct" generification are largely a matter of taste and style. Often,
there are several viable approaches for a generification. Which one
is "correct" depends on the specific requirements to and expectations of
the semantics of the resulting generified type. |
LINK TO THIS
|
Practicalities.FAQ103
|
REFERENCES
|
How
do I avoid breaking binary compatibility when I generify an existing type
or method?
|
Can
I safely generify a supertype, or does it affect all subtypes?
Yes,
we can generify non-generic legacy supertypes without affecting the non-generic
legacy subtypes - provided the subtype method's signature is identical
to the erasure of the supertype method's signature.
|
Assume
we have a class hierarchy of legacy types and we want to generify the supertype.
Must we also generify all the subtypes? Fortunately not. Let us consider
an example.
Example (of a hierarchy of legacy types):
c
lass Box {
private Object theThing;
public Box(Object t)
{ theThing = t; }
public void reset(
Object
t) { theThing = t; }
public
Object
get()
{ return theThing; }
...
}
class NamedBox extends Box {
private String theName;
public NamedBox(Object t,String n) { super(t); theName =
n; }
public void reset(
Object
t)
{ super.reset(t); }
public
Object
get()
{ return super.get(); }
...
}
Now we decide to generify the supertype.
Example (same as before, but with generified superclass):
class Box
<T>
{
private T theThing;
public Box(T t)
{ theThing = t; }
public void reset(
T
t)
{ theThing = t; }
public
T
get()
{ return theThing; }
...
}
class NamedBox extends Box {
private String theName;
public NamedBox(Object t,String n) { super(t); theName =
n; }
public void reset(
Object
t)
{ super.reset(t); }
public
Object
get()
{ return super.get(); }
...
}
warning: [unchecked] unchecked
call to Box(T) as a member of the raw type Box
public NamedBox(Object t,String n) { super(t); theName =
n; }
^
warning: [unchecked] unchecked call to reset(T) as a member of
the raw type Box
public void reset(Object t)
{ super.reset(t); }
^
The subclass is still considered a subtype of the now generic supertype
where the
reset
and
get
method override the corresponding
supertype methods. Inevitably, we now receive unchecked warnings whenever
we invoke certain methods of the supertype because we are now using methods
of a raw type. But other than that, the subtype is not affected by the
re-engineering of the supertype. This is possible because the signatures
of the subtype methods are identical to the erasures of the signatures
of the supertype methods.
Let us consider a slightly different generification. Say, we re-engineer
the superclass as follows.
Example (same as before, but with a different generification):
class Box
<T>
{
private T theThing;
public <S extends T> Box(S t)
{ theThing = t; }
public
<S extends T>
void reset(
S
t) { theThing = t; }
public
T
get() { return
theThing; }
...
}
class NamedBox extends Box {
private String theName;
public NamedBox(Object t,String n) { super(t); theName =
n; }
public void reset(
Object
t)
{ super.reset(t); }
public
Object
get()
{ return super.get(); }
...
}
This time the
reset
method is a generic method. Does the
subtype method
reset
still override the generic supertype method?
The answer is: yes. The subtype method's signature is still identical to
the erasure of the supertype method's signature and for this reason the
subtype method is considered an overriding method. Naturally, we
still receive the same unchecked warnings as before, but beyond that there
is no need to modify the subtype although we re-engineered the supertype.
The point to take home is that methods in a legacy subtype can override
(generic and non-generic) methods of a generic supertype as long as the
subtype method's signature is identical to the erasure of the supertype
method's signature. |
LINK
TO THIS
|
Practicalities.FAQ103A
|
REFERENCES
|
Can
a method of a non-generic subtype override a method of a generic supertype?
How does the compiler translate
Java generics?
What
is type erasure?
What
is method overriding?
What
is a method signature?
What
is a subsignature?
What
are override-equivalent signatures?
When
does a method override its supertype's method?
|
How
do I avoid breaking binary compatibility when I generify an existing type
or method?
Sometimes a dummy bound does the trick.
|
Occasionally, one must pay attention to the fact that a
generification might change the signature of some methods in the byte code.
Changing the signature will break existing code that cannot be recompiled
and relies on the binary compatibility of the old and new version of the
.class
file.
Example (before generification, taken from package
java.util
):
class Collections {
public static
Object
max(
Collection
coll) {...}
...
}
The
max
method finds the largest element in a collection and obviously
the declared return type of the method should match the element type of
the collection passed to the method. A conceivable generification
could look like this.
Example (after a naive generification):
class Collections {
public static
<T extends Comparable<?
super T>>
T
max(Collection
<?
extends T>
coll) {...}
...
}
While this generification preserves the semantics of the method, it changes
the signature of the
max
method. It is now a method with
return type
Comparable
, instead of
Object
.
Example (after type erasure):
class Collections {
public static
Comparable
max(
Collection
coll) {...}
...
}
This will break existing code that relies on the binary compatibility of
the
.class
files. In order to preserve the signature and
thus the binary compatibility, an otherwise superfluous bound can be used.
Example (after binary compatible generification, as available in package
java.util
):
class Collections {
public stati
c <T extends
Object
&
Comparable<? super T>>
T
max(Collection
<? extends
T>
coll) {...}
...
}
The leftmost bound of the type parameter is now type
Object
instead
of type
Comparable
, so that the type parameter
T
is replaced
by
Object
during type erasure.
Example (after type erasure):
class Collections {
public static
Object
max(
Collection
coll) {...}
...
}
Afterthought:
Perhaps you wonder why the hack decribed in this FAQ entry is needed.
Indeed, had the
Collections.max
method been defined as returning
a
Comparable
in the first place, no further measures, such as
adding
Object
as a type parameter bound, had been required to
preserve binary compatibility. Basically, the declared return type
Object
is a mistake in the design of this method.
If you carefully study the specification of the
Collections.max
method's functionality then you realize that all elements of the collection
are required to implement the
Comparable
interface. Consequently,
the returned object is
Comparable
, too. There is no reason
why the method should return an
Object
reference.
The only explanation one can think of is that in pre-generic Java there
was no way of ensuring by compile-time type checks that the
Collection
contains only
Comparable
objects. However, this was ensured
via runtime type checks, namely an explicit downcast in the implementation
of the method. Hence this is not really an excuse for the bug.
Note, that the runtime time type check in the pre-generic version of
the
Collections.max
method still exists in the generic version.
The former explicit cast is now an implicit one generated by the compiler.
In the generic version, this cast can never fail (unless there are unchecked
warnings), because the type parameter bound
Comparable
ensures
at compile-time that the elements in the
Collection
are
Comparable
. |
LINK TO THIS
|
Practicalities.FAQ104
|
REFERENCES
|
|
Defining
Generic Types and Methods
Which
types should I design as generic types instead of defining them as regular
non-generic types?
Types that use supertype references in
several places and where there is a correspondence between the occurrences
of these supertypre references.
|
Not all types are inherently generic, not even the majority
of the types in a program is. The question is: which types profit
from being generic types and which ones do not. This FAQ entry tries to
sketch out some guidelines.
Obvious candidates for generic types are those types that work closely
with existing generic types. For instance, when you derive from
a generic type, such as
WeakReference<T>
, then the derived
class is often generic as well.
Example (of a generic subclass):
class WeakKey
<T>
extends java.lang.ref.WeakReference
<T>
{
private int hash;
public WeakKey(
T
ref)
{ super(t); hash = t.hashcode(); }
...
public int hashcode() { return hash; }
}
The subclass
WeakKey
can be used in lieu of its superclass
WeakReference
and therefore is as generic as the superclass is.
Classes that use generic types are sometimes generic as well. For instance,
if you want to build a cache abstraction as a map of a key and an
associated value that is refered to by a soft reference, then this new
Cache
type will naturally be a generic type.
Example (of a generic cache):
class Cache
<K,V>
{
private HashMap<
K
,SoftReference<
V
>>
theCache;
...
public
V
get(
K
key) { ... }
public
V
put(
K
key,
V
value) { ... }
...
}
The
Cache
class is built on top of a
Map
and can be seen
as a wrapper around a
Map
and therefore is as generic as the
Map
itself.
On the other hand, a cache type need not necessarily be a generic type.
If you know that all keys are strings and you do not want to have different
types of caches for different types of cached values, then the
Cache
type might be a non-generic type, despite of the fact that it works closely
with the generic
Map
type.
Example (of a non-generic cache):
class Cache {
private HashMap<
String
,SoftReference<
Object
>>
theCache;
...
public
Object
get(
String
key) { ... }
public
Object
put(
String
key,
Object
value) { ... }
...
}
Both abstractions are perfectly reasonable. The first one is more
flexible. It includes the special case of a
Cache<String,Object>
,
which is the equivalent to the non-generic
Cache
abstraction.
In addition, the generic
Cache
allows for different cache types.
You could have a
Cache<Link,File>
, a
Cache<CustomerName,CustomerRecord>
,
and so on. By means of the parameterization you can put a lot more
information into the type of the cache. Other parts of your program can
take advantage of the enhanced type information and can do different things
for different types of caches - something that is impossible if you have
only one non-generic cache type.
Another indication for a generic type is that a type uses the same supertype
in several places. Consider a
Triple
class.
Conceptually, it contains three elements of the same type. It could
be implemented as a non-generic class.
Example (of a non-generic triple):
class Triple {
private
Object
t1, t2, t3;
public Triple(
Object
a1,
Object
a2,
Object
a3) {
t1 = a1;
t2 = a2;
t3 = a3;
}
public void reset(
Object
a1,
Object
a2,
Object
a3) {
t1 = a1;
t2 = a2;
t3 = a3;
}
public void setFirst(
Object
a1) {
t1 = a1;
}
public void setSecond(
Object
a2) {
t2 = a2;
}
public void setThird(
Object
a3) {
t3 = a3;
}
public
Object
getFirst() {
return a1;
}
public
Object
getSecond() {
return a2;
}
public
Object
getThird() {
return a3;
}
...
}
A triple is expected to contain three elements of the same type, like three
strings, or three dates, or three integers. It is usually not a triple
of objects of different type, and its constructors enforce these semantics.
In addition, a certain triple object will probably contain the same type
of members during its entire lifetime. It will not contain strings today,
and integers tomorrow. This, however, is not enforced in the implemention
shown above, perhaps mistakenly so.
The point is that there is a correspondence between the types of the
three fields and their type
Object
does not convey these semantics.
This correspondence - all three fields are of the same type - can be expressed
more precisely by a generic type.
Example (of a generic triple):
class Triple
<T>
{
private
T
t1, t2, t3;
public Triple(
T
a1,
T
a2,
T
a3) {
t1 = a1;
t2 = a2;
t3 = a3;
}
public void reset(
T
a1,
T
a2,
T
a) {
t1 = a1;
t2 = a2;
t3 = a3;
}
public void setFirst(
T
a1) {
t1 = a1;
}
public void setSecond(
T
a2) {
t2 = a2;
}
public void setThird(
T
a3) {
t3 = a3;
}
public
T
getFirst()
{
return a1;
}
public
T
getSecond()
{
return a2;
}
public
T
getThird()
{
return a3;
}
...
}
Now we would work with a
Triple<String>
, saying that all members
are strings and will remain strings. We can still permit variations
like in a
Triple<Number>
, where the members can be of differents
number types like
Long
,
Short
and
Integer
,
and where a
Short
member can be replaced by a
Long
member
or vice versa. We can even use
Triple<Object>
, where
everything goes. The point is that the generification allows to be
more specific and enforces homogenity.
Conclusion:
When a type uses a supertype in several places and there is a correspondence
among the difference occurrences, then this is an indication that perhas
the type should be generic.
Note, that the supertype in question need not be
Object
.
The same principle applies to supertypes in general. Consider for
instance an abstraction that uses character sequences in its implementation
and refers to them through the supertype
CharSequence
. Such an
abstraction is a candidate for a generic type.
Example (of a non-generic class using character sequences):
class CharacterStore {
private
CharSequence
theChars;
...
public CharacterProcessingClass(
CharSequence
s) {
... }
public void set(
CharSequence
s) { ... }
public
CharSequence
get() { ... }
...
}
The idea of this abstraction is: whatever the
get
method
receives is stored and later returned by the
set
method.
Again there is a correspondence between the argument type of the
set
method, the return type of the
get
method, and the type of the
private field. If they are supposed to be of the same type
then the abstraction could be more precisely expressed as a generic
type.
Example (of a generic class using character sequences):
class CharacterStore
<C extends
CharSequence>
{
private
C
theChars;
...
public CharacterStore(
C
s) { ... }
public void set(
C
s)
{ ... }
public
C
get() { ...
}
...
}
This class primarily serves as a store of a character sequence and we can
create different types of stores for different types of character sequences,
such as a
CharacterStore<String>
or a
CharacterStore<StringBuilder>
.
If, however, the semantics of the class is different, then the class
might be better defined as a non-generic type. For instance, the
purpose might be to provide a piece of functionality, such as checking
for a suffix, instead of serving as a container. In that case it
does not matter what type of character sequence is used and a generification
would not make sense.
Example (of a non-generic class using character sequences):
class SuffixFinder {
private
CharSequence
theChars;
...
public CharacterProcessingClass(
CharSequence
s) {
... }
public boolean hasSuffix(
CharSequence
suffix) { ...
}
}
In this case, the character sequence being examined could be a
CharBuffer
and the suffix to be searched for could be a
StringBuilder
, or
vice versa. It would not matter. There is no correspondence
implied between the types of the various character sequences being used
in this abstraction. Under these circumstances, the generification
does not provide any advantage.
Ultimately, it all depends on the intended semantics, whether a type
should be generic or not. Some indicators were illustrated above:
a close relationship to an existing generic type, correspondences among
references of the same supertype, the need for distinct types generated
from a generic type, and the need for enhanced type information.
In practice, most classes are non-generic, because most classes are defined
for one specific purpose and are used in one specific context. Those
classes hardly ever profit from being generic. |
LINK TO THIS
|
Practicalities.FAQ201
|
REFERENCES
|
|
Do
generics help designing parallel class hierarchies?
Yes.
|
Some hierarchies
of types run in parallel in the sense that a supertype refers to another
type and the subtype refers to a subtype of that other type. Here is an
example, where the supertype
Habitat
refers to
Animal
s
and the subtype
Aquarium
refers to
Fish
.
Overriding methods in the subtype often have to perform a type checks in
this situation, like in the example below.
Example (of parallel type hierarchies leading to dynamic type check):
abstract class Habitat {
protected Collection theAnimals;
...
public void addInhabitant(
Animal
animal) {
theAnimals.add(animal);
}
}
class Aquarium extends Habitat {
...
public void addInhabitant(
Animal
fish) {
if (fish
instanceof Fish
)
theAnimals.add(fish);
else
throw new IllegalArgumentException(fish.toString());
}
}
Aquarium a = new Aquarium();
a.addInhabitant(new Cat()); //
ClassCastException
In order to ensure that the aquarium only contains fish, the
addInhabitant
method performs an
instanceof
test. The test may fail at
runtime with a
ClassCastException
. It would be nice if the
addInhabitant
method could be declared as taking a
Fish
argument; the
instanceof
test would be obsolete then. The problem is that a
addInhabitant(Fish)
method in the
Aquarium
class would be an overloading version of
the
Habitat
's
addInhabitant(Animal)
method rather than
an overriding version thereof and this is neither intended nor corrct.
Hence, we cannot get rid of the
instanceof
test - unless we consider
generics.
This kind of type relationship among parallel type hierarchies can be
more elegantly expressed by means of generics. If the supertype
Habitat
were a generic type, then the subtype
Aquarium
would no longer
need the type check. Here is a re-engineered version of the example
above.
Example (same as above, re-engineered using generics):
abstract class Habitat
<A extends Animal>
{
protected Collection
<A>
theAnimals;
...
public void addInhabitant(
A
animal) {
theAnimals.add(animal);
}
class Aquarium extends Habitat
<Fish>
{
...
public void addInhabitant(
Fish
fish) {
// no test necessary
theAnimals.add(fish);
}
}
Aquarium a = new Aquarium();
a.addInhabitant(new Cat()); //
error:
illegal argument type
When the supertype is generic, then the subtype can derive from a certain
instantiation of the supertype. The advantage is that overriding
methods in the subtype can now be declared to take the intended type of
argument rather than a supertype argument. In the example, the
Aquarium
is a
Habitat<Fish>
, which means that the
addInhabitant
method now takes a
Fish
argument instead of an
Animal
argument. This way, the
instanceof
test is no longer necessary
and any attempt to add a non-
Fish
to the
Aquarium
will
be detected at compile-time already.
Note, that the generic version of the type hierarchy has further advantages.
Example (of parallel type hierarchies):
abstract class Habitat {
protected Collection theAnimals;
...
public Habitat(
Collection
animals) {
theAnimals = animals;
}
}
class Aquarium extends Habitat {
...
public Aquarium(
Collection
fish) {
// no type check possible
super(fish);
}
}
ArrayList animals = new ArrayList();
animals.add(new Cat());
Aquarium a = new Aquarium(animals); //
no error or exception
here
In the
Aquarium
constructor there is no way to check whether the
collection contains
Fish
or not. Compare this to the generic
solution.
Example (of parallel type hierarchies using generics):
abstract class Habitat<A extends Animal> {
protected Collection<A> theAnimals;
...
public Habitat(Collection
<A>
animals) {
theAnimals = animals;
}
}
class Aquarium extends Habitat<Fish> {
...
public Aquarium(Collection
<Fish>
fish) {
// no type check necessary
super(fish);
}
}
ArrayList<Animal> animals = new ArrayList<Animal>();
animals.add(new Cat());
Aquarium a = new Aquarium(animals); //
error:
illegal argument type
In this generic version of the type hierarchy, the
Aquarium
constructor
requires a
Collection<Fish>
as a constructor argument and this
collection of fish can be passed along to the supertype's constructor because
Aquarium
extends the supertype's instantiation
Habitat<Fish>
whose constructor
requires exactly that type of collection.
Conclusion:
Type hierarchies that run in parallel are more
easily and more reliably implemented by means of generics.
|
LINK TO THIS
|
Practicalities.FAQ201A
|
REFERENCES
|
What
is method overriding?
What
is method overloading?
|
When
would I use an unbounded wildcard parameterized type instead of a bounded
wildcard or concrete parameterized type?
When you need a reifiable type.
|
Occasionally, an unbounded wildcard parameterized type
is used because it is a so-called reifiable type and can be used in situations
where non-refiable types are not permitted.
-
One of these situations are type checks (i.e., cast or
instanceof
expressions). Non-reifiable types (i.e., concrete or bounded wildcard
parameterized type) are not permitted as the target type of a type check
or lead to "unchecked" warnings.
-
Another situation is the use of arrays. Non-reifiable types (i.e.,
concrete or bounded wildcard parameterized type) are not permitted as the
component type of an array.
Depending on the situation, the unbounded wildcard parameterized type can
substitute a concrete or bounded wildcard parameterized type in a type
check or an array in order to avoid errors or warning.
Non-reifiable types (i.e., concrete or bounded wildcard parameterized
type) are not permitted as the target type of a type check or lead to "unchecked"
warnings. A typical situation, in shich such a cast would be needed, is
the implementation of methods such as the
equals
method, that
take
Object
reference and where a cast down to the actual type
must be performed.
Example (not recommended):
class Triple<T> {
private T fst, snd, trd;
...
public boolean equals
(Object other)
{
...
Triple<T> otherTriple
=
(Triple<T>)
other;
//
warning; unchecked cast
return (this.fst.equals(otherTriple.fst)
&& this.snd.equals(otherTriple.snd)
&& this.trd.equals(otherTriple.trd));
}
}
When we replace the cast to
Triple<T>
by a cast to
Triple<?>
the warning disappears, because unbounded wildcard parameterized type are
permitted as target type of a cast without any warnings.
Example (implementation of
equals
):
class Triple<T> {
private T fst,
snd, trd;
...
public boolean equals(Object other)
{
...
Triple<?> otherTriple
=
(Triple<?>)
other;
return (this.fst.equals(otherTriple.fst)
&& this.snd.equals(otherTriple.snd)
&& this.trd.equals(otherTriple.trd));
}
}
Note, that replacing the concrete parameterized type by the wildcard parameterized
type works in this example only because we need no write access to the
fields of the referenced object referred and we need not invoke any methods.
Remember, use of the object that a wildcard reference variable refers to
is restricted. In other situations, use of a wildcard parameterized
type might not be a viable solution, because full access to the referenced
object is needed. (Such a situation can arise, for instance, when
you implement the
clone
method of a generic class.)
Non-reifiable types (i.e., concrete or bounded wildcard parameterized
type) are not permitted as the component type of an array. Here is
an example:
Example (of illegal array type):
static void test() {
Pair<Integer,Integer>[]
arr = new
Pair<Integer,Integer>[10]
;
//
error
arr[0] = new Pair<Integer,Integer>(0,0);
arr[1] = new Pair<String,String>("","");
// would fail with ArrayStoreException
Pair<Integer,Integer> pair = arr[0];
Integer i = pair.getFirst();
pair.setSecond(i);
}
The concrete parameterized type
Pair<Integer,Integer>
is illegal.
As a workaround one might consider using an array of the corresponding
unbounded wildcard parameterized type.
Example (of array of unbounded wildcard parameterized type):
static void test() {
Pair<?,?>[]
arr =
new
Pair<?,?>[10]
;
arr[0] = new Pair<Integer,Integer>(0,0);
arr[1] = new Pair<String,String>("","");
// succeeds
Pair<Integer,Integer> pair1 = arr[0];
//
error
Pair<?,?>
pair2 = arr[0];
//
ok
Integer i = pair2.getFirst();
//
error
Object o = pair2.getFirst();
//
ok
pair2.setSecond(i);
//
error
}
However, a
Pair<?,?>[]
is semantically different from the illegal
Pair<Integer,Integer>[]
.
It is not homogenous, but contains a mix of arbitrary pair types.
The compiler does not and cannot prevent that they contain different instantiations
of the generic type. In the example, I can insert a pair of strings
into what was initially supposed to be a pair of integers.
When we retrieve elements from the array we receive references of type
Pair<?,?>
.
This is demonstrated in our example: we cannot assign the
Pair<?,?>
taken from the array to the more specific
Pair<Integer,Integer>
,
that we really wanted to use.
Various operations on the
Pair<?,?>
are rejected as errors,
because the wildcard type does not give access to all operations of the
referenced object. In our example, invocation of the
set
-methods
is rejected with error messages.
Depending on the situation, an array of a wildcard parameterized type
may be a viable alternative to the illegal array of a concrete (or bounded
wildcard) parameterized type. If full access to the referenced element
is needed, this approach does not work and a better solution would be use
of a collection instead of an array. |
LINK TO THIS
|
Practicalities.FAQ202
|
REFERENCES
|
What
is a reifiable type?
How
can I avoid "unchecked cast" warnings?
How
can I work around the restriction that there are no arrays whose component
type is a concrete parameterized type?
|
When
would I use a wildcard parameterized type instead of a concrete parameterized
type?
When
would I use a wildcard parameterized type with a lower bound?
When a concrete parmeterized type would
be too restrictive.
|
Consider a class hierarchy where a the topmost superclass
implements an instantiation of the generic
Comparable
interface.
Example:
class Person implements Comparable<Person> {
...
}
class Student extends Person {
...
}
Note, the
Student
class does not and cannot implement
Comparable<Student>
,
because it would be a subtype of two different instantiations of the same
generic type then, and that is illegal (details
here
).
Consider also a method that tries to sort a sequence of subtype objects,
such as a
List<Student>
.
Example:
class Utilities {
public static <T extends Comparable<T>> void sort(List<T>
list) {
...
}
...
}
This
sort
method cannot be applied to a list of students.
Example:
List<Student> list = new ArrayList<Student>();
...
Utilities.sort(list);
//
error
The reason for the error message is that the compiler infers the
type parameter of the sort method as
T:=Student
and that class
Student
is not
Comparable<Student>
. It is
Comparable<Person>
,
but that does not meet the requirements imposed by the bound of the type
parameter of method sort. It is required that
T
(i.e.
Student
) is
Comparable<T>
(i.e.
Comparable<Student>
),
which in fact it is not.
In order to make the
sort
method applicable to a list of subtypes
we would have to use a wildcard with a lower bound, like in the re-engineered
version of the
sort
method below.
Example:
class Utilities {
public static <T extends Comparable
<?
super T
>
> void sort(List<T>
list) {
...
}
...
}
Now, we can sort a list of students, because students are comparable to
a supertype of
Student
, namely
Person
. |
LINK TO THIS
|
Practicalities.FAQ204
|
REFERENCES
|
Can
a subclass implement another instantiation of a generic interface than
any of its superclasses does?
|
How
do I recover the actual type of the
this
object in a class hierarchy?
With a
getThis()
helper method
that returns the
this
object via a reference of the exact type.
|
Sometimes we need to define a hierarchy
of classes whose root class has a field of a super type and is supposed
to refer to different subtypes in each of the subclasses that inherit the
field. Here is an example of such a situation. It is a generic
Node
class.
Example (of a class with a type mismatch - does not compile):
public abstract class Node <N extends Node<N>>
{
private final List<N> children = new ArrayList<N>();
private final N parent;
protected Node(N parent) {
this.parent = parent;
parent.children.add(this);
//
error: incompatible types
}
public N getParent() {
return parent;
}
public List<N> getChildren() {
return children;
}
}
public class SpecialNode extends Node<SpecialNode> {
public SpecialNode(SpecialNode parent) {
super(parent);
}
}
The idea of this class design is: in the subclass
SpecialNode
the list of children will contain
SpecialNode
objects and in another
subclass of
Node
the child list will contain that other subtype
of
Node
. Each node adds itself to the child list at construction
time. The debatable aspect in the design is the attempt to achieve
this addition to the child list in the superclass constructor so that the
subclass constructors can simply invoke the superclass constructor and
thereby ensure the addition of this node to the child list.
The class designer overlooked that in the
Node
superclass the
child list is of type
List<N>
, where
N
is a subtype
of
Node
. Note, that the list is NOT of type
List<Node>
.
When in the superclass constructor the
this
object is added to
the child list the compiler detects a type mismatch and issues an error
message. This is because the
this
object is of type
Node
,
but the child list is declared to contain objects of type
N
, which
is an unknown subtype of
Node
.
There are at least three different ways of solving the problem.
-
Declare the child list as a
List<Node>
and add the
this
object in the superclass constructor.
-
Declare the child list as a
List<N>
and add the
this
object in the subclass constructor.
-
Declare the child list as a
List<N>
, recover the
this
object's actual type, and add the
this
object in the superclass
constructor.
Below you find the source code for each of these solutions.
Declare the child list as a
List<Node>
and add the
this
object in the superclass constructor.
If we want to add each node to the child list in the superclass constructor
then we need to declare the child list as a
List<Node>
, because
in the superclass constructor the
this
object is of type
Node
.
The
Node
superclass is supposed to be used in a way that the
Node
reference will refer to an object of type
N
, but this is just
a convention and not reflected in the types being used. Type-wise
the
this
object is just a
Node
- at least in the context
of the superclass.
Example (problem solved using a list of supertypes):
public abstract class Node <N extends Node<N>>
{
private final List<
Node<?>
> children
= new ArrayList<
Node<?>
>();
private final N parent;
protected Node(N parent) {
this.parent = parent;
parent.children.add(this);
//
fine
}
public N getParent() {
return parent;
}
public List<
Node<?>
> getChildren() {
return children;
}
}
public class SpecialNode extends Node<SpecialNode> {
public SpecialNode(SpecialNode parent) {
super(parent);
}
}
Declare the child list as a
List<N>
and add the
this
object in the subclass constructor.
Our type mismatch problem would be solved if refrained from adding the
this
object in the superclass constructor, but defer its addition to the subclass
constructor instead. In the context of the subclass constructor the
exact type of the
this
object is known and there would be no type
mismatch any longer.
Example (problem solved by adding the
this
object in the subtype
constructor):
public abstract class Node <N extends Node<N>>
{
protected
final List<N> children = new ArrayList<N>();
private final N parent;
protected Node(N parent) {
this.parent = parent;
}
public N getParent() {
return parent;
}
public List<N> getChildren() {
return children;
}
}
public class SpecialNode extends Node<SpecialNode> {
public SpecialNode(SpecialNode parent) {
super(parent);
parent.children.add(this);
//
fine
}
}
Declare the child list as a
List<N>
, recover the
this
object's actual type, and add the
this
object in the superclass
constructor.
The problem can alternatively be solved by means of an abstract helper
method that each of the subclasses implements. The purpose of the
helper method is recovering the
this
object's actual type.
Example (problem solved by recovering the
this
object's actual
type):
public abstract class Node <N extends Node<N>>
{
private final List<N> children = new ArrayList<N>();
private final N parent;
protected abstract N getThis();
protected Node(N parent) {
this.parent = parent;
parent.children.add(
getThis()
);
//
fine
}
public N getParent() {
return parent;
}
public List<N> getChildren() {
return children;
}
}
public class SpecialNode extends Node<SpecialNode> {
public SpecialNode(SpecialNode parent) {
super(parent);
}
protected SpecialNode getThis() {
return this;
}
}
We added an abstract helper method
getThis()
that returns the
this
object with its exact type information. Each implementation of the
getThis()
method
in one of the
Node
subtypes returns an object of the specific
subtype
N
.
Usually, one would try to recover type information by means of a cast,
but in this case the target type of the cast would be the unknown type
N
.
Following this line of logic one might have tried this unsafe solution:
Example (problem solved by recovering the
this
object's actual
type - not recommended):
public abstract class Node <N extends Node<N>>
{
...
protected Node(N parent) {
this.parent = parent;
parent.children.add(
(N)this
);
//
warning: unchecked cast
}
...
}
Casts whose target type is a type parameter cannot be verified at runtime
and lead to an unchecked warning. This unsafe cast introduces the
potential for unexpected
ClassCastException
s and is best avoided.
The exact type information of the object refered to by the
this
reference is best recovered by means of overriding a
getThis()
helper method.
|
LINK TO THIS
|
Practicalities.FAQ205
|
REFERENCES
|
What
is the "getThis" trick?
|
What
is the "getThis" trick?
A way to recover the type of the
this
object in a class hierarchy.
|
The
"getThis trick"
was first published by Heinz
Kabutz in
Issue
123
of his Java Specialists' Newsletter in March 2006 and later appeared
in the book
Java
Generics and Collections
by Maurice Naftalin and Philp Wadler, who
coined the term
"getThis" trick
. It is a way to recover the
type of the
this
object - a recovery of type information
that is sometimes needed in class hierachies with a self-referential generic
supertype.
Examples of self-referential generic types are
-
abstract class Enum<E extends Enum<E>>
in the
java.lang
package of the JDK, or
-
abstract class Node <N extends Node<N>>
from entry
FAQ205
above, or
-
abstract class TaxPayer<P extends TaxPayer<P>>
in the original
example discussed by Heinz Kabutz.
Self-referential generic types are often - though not necessarily - used
to express in a supertype that its subtypes depend on themselves.
For instance, all enumeration types are subtypes of class
Enum. T
he
idea is that an enumeration type
Color
extends
Enum<Color>
,
an enumeration type
TimeUnit
extends
Enum<TimeUnit>
,
and so on. Similarly in the example discussed in entry
FAQ205
:
each node type extends class
Node
parameterized on its own type,
e.g. class
SpecialNode
extends
Node<SpecialNode>
.
Heinz Kabutz's example uses the same idea: there is a class
Employee
that extends
TaxPayer<Employee>
and a class
Company
that extends
TaxPayer<Company>
.
Let us consider an arbitrary self-referential generic type
SelfReferentialType<T
extends SelfReferentialType<T>>
. In its implementation it
may be necessary to pass the
this
reference to a method that expects
an argument of type
T
, the type parameter. The attempt results
is a compile-time error message, as illustrated below:
public abstract class SelfReferentialType<T extends
SelfReferentialType<T>> {
private SomeOtherType<T> ref;
public void aMethod() { ref.m(
this
); }
//
error: incompatible types
}
public interface SomeOtherType<E> {
void m(E arg);
}
The problem is that the
this
reference is of type
SelfReferentialType<T>
,
while the method
m
expects an argument of type
T
, which
is a subtype of type
SelfReferentialType<T>
. Since we
must not supply supertype objects where subtype objects are asked for,
the compiler rightly complains. Hence the compiler is right.
However, we as developers know that conceptually all subtypes of type
SelfReferentialType
are subtypes of type
SelfReferentialType
parameterized on their
own type. As a result, the type of the
this
reference is the type
that the type parameter
T
stands for. This is illustrated
below:
public class Subtype extends SelfReferentialType<Subtype>
{ ... }
When the inherited
aMethod
is invoked on a
Subtype
object,
then the
this
reference refers to an object of type
Subtype
and a Method expects a argument of type
T:=Subtype
.
This perfect match is true for all subtypes. Consequently, we wished
that the compiler would accept the method invocation as is. Naturally,
the compiler does not share our knowlege regarding the intended structure
of the class hierarchy and there are no language means to express that
each
Subtype
extends
SelfReferentialType<Subtype>
.
Hence we need a work-around - and this is what the "getThis" trick provides.
The "getThis" trick provides a way to recover the exact type of the
this
reference. It involves an abstract method in the self-referential
supertype that all subtypes must override. The method is typically named
getThis
.
The intended implementation of the method in the subtype is
getThis()
{ return this; }
, as illustrated below:
public abstract class SelfReferentialType<T extends
SelfReferentialType<T>> {
private SomeOtherType<T> ref;
protected abstract T getThis();
public void aMethod() { ref.m(
getThis()
); }
//
fine
}
public interface SomeOtherType<E> {
void m(E arg);
}
public class Subtype extends SelfReferentialType<Subtype> {
protected Subtype getThis() { return this; }
}
As we discussed in entry
FAQ205
,
the "getThis" trick is not the only conceivable work-around. |
LINK TO THIS
|
Practicalities.FAQ206
|
REFERENCES
|
How
do I recover the actual type of the
this
object in a class hierarchy?
|
How
do I recover the element type of a container?
By having the container carry the element
type as a type token.
|
Suppose that you are defining a pair of related interfaces
which need to be implemented in pairs:
Example (of a pair of related interfaces):
interface
Contained
{}
interface
Container<
T extends
Contained>
{
void add(T element);
List<T> elements();
}
Example (of implementations of the related interfaces):
class MyContained implements
Contained
{
private final String name;
public MyContained(String name) {this.name
= name;}
public @Override String toString() {return name;}
}
class MyContainer implements
Container<MyContained>
{
private final List<MyContained> _elements = new ArrayList<MyContained>();
public void add(MyContained element) {_elements.add(element);}
public List<MyContained> elements() {return _elements;}
}
Given these interfaces you need to write generic code which works on any
instance of these interfaces.
Example (of generic code using the pair of interfaces):
class MetaContainer {
private
Container<? extends Contained>
container;
public void setContainer(Container<? extends Contained>
container) {
this.container = container;
}
public void add(Contained element) {
container.add(element);
// error
}
public List<? extends Contained> elements() {return container.elements();}
}
error: add(capture#143 of ? extends Contained) in Container<capture#143
of ? extends Contained> cannot be applied to Contained)
container.add(element);
^
The
MetaContainer
needs to handle an unknown parameterization
of the generic
Container
class. For this reason it holds a reference
of type
Container<? extends Contained>
. Problems arise when
the container's
add()
method is invoked. Since the container's
type is a wildcard parameterization of class
Container
the compiler
does not know the container's exact type and cannot check whether the type
of the element to be added is acceptable and the element can safely be
added to the container. As the compiler cannot ensure type safety,
it issues an error message. The problem is not at all surprising:
wildcard parameterizations give only restricted access to the concrete
parameterization they refer to (see entry
GenericTypes.FAQ304
for details).
In order to solve the problem, we would have to retrieve the container's
exact type and in particular its element type. However, this is not
possible statically at compile-time. A viable work-around is adding
to the
Container
class a method that returns a type token that
represents the container's element type so that we can retrieve the element
type dynamically at run-time.
Example (of container with element type):
interface Container<T extends Contained> {
void add(T element);
List<T> elements();
Class<T> getElementType();
}
class MyContainer implements Container<MyContained> {
private final List<MyContained> _elements = new ArrayList<MyContained>();
public void add(MyContained element) {_elements.add(element);}
public List<MyContained> elements() {return _elements;}
public Class<MyContained> getElementType()
{return MyContained.class;}
}
The
MetaContainer
can then retrieve the element type from the
container by means of the container's
getElementType()
method..
Example (first attempt of re-engineering the meta container):
class MetaContainer {
private Container<? extends Contained> container;
public void setContainer(Container<? extends Contained>
container) {
this.container = container;
}
public void add(Contained element)
{
container.add(container.getElementType().cast(element));
//
error
}
public List<? extends Contained> elements() {return container.elements();}
}
error: add(capture#840 of ? extends Contained) in Container<capture#840
of ? extends Contained> cannot be applied to (Contained)
container.add(container.getElementType().cast(element));
^
Unfortunately the container is still of a type that is a wildcard parameterization
and we still suffer from the restrictions that wildcard parameterizations
come with: we still cannot invoke the container's
add()
method.
However, there is a common technique for working around this kind of restriction:
using a generic helper method (see
Practicalities.FAQ304
for details).
Example (successfully re-engineered meta container):
class MetaContainer {
private Container<? extends Contained> container;
public void setContainer(Container<? extends Contained>
container) {
this.container = container;
}
public void add(Contained element) {
_add
(container,
element);
}
private static
<T extends Contained>
void _add(
Container<T>
container, Contained element){
container.add(container.getElementType().cast(element));
}
public List<? extends Contained> elements() {return container.elements();}
}
This programming technique relies on the fact that the compiler performs
type argument inference when a generic method is invoked (see
Technicalities.FAQ401
for details). It means that the type of the
container
argument
in the helper method
_add()
is not a wildcard parameterization,
but a concrete parameterization for an unknown type that the compiler infers
when the method is invoked. The key point is that the container is
no longer of a wildcard type and we may eventually invoke its
add()
method. |
LINK TO THIS
|
Practicalities.FAQ207
|
REFERENCES
|
Which
methods and fields are accessible/inaccessible through a reference variable
of a wildcard parameterized type?
How
do I implement a method that takes a wildcard argument?
What
is the capture of a wildcard?
What
is type argument inference?
|
What
is the "getTypeArgument" trick?
A technique for recovering the type argument
from a wildcard parameterized type at run-time.
|
A reference of a wildcard type typically refers to a concrete
parameterization of the corresponding generic type, e.g. a
List<?>
refers to a
LinkedList<String>
. Yet it is impossible to retrieve
the concrete parameterization's type argument from the wildcard type. The
"getTypeArgument" trick solves this problem and enables you to retrieve
the type argument dynamically at run-time. The previous FAQ entry demonstrates
an application of this technique (see
Practicalities.FAQ207
).
Consider a generic interface and a type that implements the interface.
Example (of generic interface and implementing class):
interface
GenericType<
T
>
{
void method(T arg);
}
class
ConcreteType
implements GenericType<TypeArgument>
{
public void method(TypeArgument arg) {...}
}
Note that the interface has a method that takes the type variable as an
argument.
When you later use a wildcard parameterization of the generic interface
and need to invoke a method that takes the type variable as an argument,
the compiler will complain. This is because wildcard parameterizations
do not give full access to all methods (see entry
GenericTypes.FAQ304
for details).
Example (of using a wildcard parameterization of the generic interface):
class GenericUsage {
private
GenericType<?>
reference;
public void method(Object arg) {
reference.method(arg);
// error
}
}
error: method(capture#143 of ? extends TypeArgument) in GenericType<capture#143
of ? extends TypeArgument> cannot be applied to TypeArgument)
reference.method(arg);
^
In order to solve the problem, you add a method to the implementation of
the generic interface that return a
type
token . The type token represents the type argument of the parameterization
of the generic interface that the class implements. This way you can later
retrieve the type argument dynamically at run-time.
Example (of container with element type):
interface
GenericType<
T
>
{
void method(T arg);
Class<T> getTypeArgument();
}
class
ConcreteType
implements GenericType<TypeArgument>
{
public void method(TypeArgument arg) {...}
public Class<TypeArgument> getTypeArgument()
{return TypeArgument.class;}
}
Using the
getTypeArgument()
method you can then retrieve the type
argument even from a wildcard parameterization.
Example (of retrieving the type argument via the "getTypeArgument" trick):
class GenericUsage {
private
GenericType<?>
reference;
public void method(Object arg) {
_helper
(reference,
arg);
}
private static
<T>
void
_helper(
GenericType<T>
reference, Object arg){
reference.method(reference.getTypeArgument().cast(arg));
}
}
Note that the generic helper method
_helper()
is needed because
otherwise the interface's method would still be invoked through a reference
of a wildcard type and you would still suffer from the restrictions that
wildcard parameterizations come with. Using a generic helper method is
a common technique for working around this kind of restriction (see
Practicalities.FAQ304
for details).
The work-around relies on the fact that the compiler performs type argument
inference when a generic method is invoked (see
Technicalities.FAQ401
for details). It means that the type of the
reference
argument
in the helper method is not a wildcard parameterization, but a concrete
parameterization for an unknown type that the compiler infers when the
method is invoked. The key point is that the reference is no longer
of a wildcard type and we may eventually invoke its method.
The key point of the "getTypeArgument" trick is making available the
type argument as a type token (typically by providing a method such as
getTypeArgument()
)
so that you can retrieve the type argument at run-time even in situations
where the static type information does not provide information about the
type argument. |
LINK TO THIS
|
Practicalities.FAQ208
|
REFERENCES
|
Which
methods and fields are accessible/inaccessible through a reference variable
of a wildcard parameterized type?
How
do I implement a method that takes a wildcard argument?
What
is the capture of a wildcard?
What
is type argument inference?
How
do I recover the element type of a container?
|
Designing
Generic Methods
Why
does the compiler sometimes issue an unchecked warning when I invoke a
"varargs" method?
Because
you pass in a variable argument list of reifiable types.
|
When
you invoke a method with a variable argument list (also called
varargs
)
you will occasionally find that the compiler issues an unchecked warning.
Here is an example:
Example (of a varargs method and its invocation):
public static <E> void addAll(List<E> list,
E...
array) {
for (E element : array) list.add(element);
}
public static void main(String[] args) {
addAll(new ArrayList<String>(),
// fine
"Leonardo
da Vinci",
"Vasco de
Gama"
);
addAll(new ArrayList<Pair<String,String>>(),
//
unchecked warning
new Pair<String,String>("Leonardo","da
Vinci"),
new Pair<String,String>("Vasco","de
Gama")
);
}
warning: [unchecked] unchecked
generic array creation of type Pair<String,String>[] for varargs parameter
addAll(new ArrayList<Pair<String,String>>(),
^
The first invocation is fine, but the second invocation is flagged with
an unchecked warning. This warning is confusing because there is
no array creation expression anywhere in the source code. In order to understand,
what the compiler complains about you need to keep in mind two things:
-
Variable argument lists are translated by the compiler into an array.
-
Creation of arrays with a non-reifiable component type is not permitted.
In the example above the compiler translates the varargs parameter in the
method definition into an array parameter. Basically the method declaration
is translated to the following:
Example (of varargs method after translation):
public static <E> void addAll(List<E> list,
E[]
array) {
for (E element : array) list.add(element);
}
When the method is invoked, the compiler automatically takes the variable
number of arguments, creates an array into which it stuffs the arguments,
and passes the array to the method. The method invocations in the example
above are translated to the following:
Example (of invocation of varargs method after translation):
public static void main(String[] args) {
addAll(new ArrayList<String>(),
//
fine
new String[]
{
"Leonardo
da Vinci",
"Vasco
de Gama"
}
);
addAll(new ArrayList<Pair<String,String>>(),
//
unchecked warning
new
Pair<String,String>[]
{
new Pair<String,String>("Leonardo","da Vinci"),
new Pair<String,String>("Vasco","de Gama")
}
);
}
As you can see, the compiler creates a
String[]
for the first
invocation and a
Pair<String,String>[]
for the second invocation.
Creating a is
String[]
is fine, but creating a
Pair<String,String>[]
is not permitted.
Pair<String,String>
is not a reifiable type,
that is, it loses information as a result of type erasure and is at runtime
represented as the raw type
Pair
instead of the exact type
Pair<String,String>
. The loss of information leads to problems
with arrays of such non-reifiable component types. The reasons are illustrated
in FAQ entry
ParameterizedTypes.FAQ104
;
as usual it has to do with type safety issues.
If you were trying to create such an array of type
Pair<String,String>[]
yourself, the compiler would reject the
new
-expression with an
error message. But since it is the compiler itself that creates such
a forbidden array, it chooses to do so despite of the type safety issues
and gives you an unchecked warning to alert you to potential safety hazards.
You might wonder why the unchecked warning is needed and what peril
it tries to warn about. The example above is perfectly type-safe,
because in the method implementation the array is only read and nothing
is stored in the array. However, if a method would store something
in the array it could attempt to store an alien object in the array, like
putting a
Pair<Long,Long>
into a
Pair<String,String>[]
.
Neither the compiler nor the runtime system could prevent it.
Example (of corrupting the implicitly created varargs array; not recommended):
Pair<String,String>[] method(Pair<String,String>...
lists) {
Object[] objs = lists;
objs[0] = new Pair<String,String>("x","y");
objs[1] = new Pair<Long,Long>(0L,0L);
//
corruption !!!
return lists;
}
public static void main(String[] args) {
Pair<String,String>[] result
= method(new Pair<String,String>("Vasco","da
Gama"),
// unchecked warning
new Pair<String,String>("Leonard","da Vinci"));
for (Pair<String,String> p : result) {
String s = p.getFirst();
// ClassCastException
}
}
The implicitly created array of
String
pairs is accessed through
a reference variable of type
Object[]
. This way anything
can be stored in the array; neither the compiler nor the runtime system
can prevent that a
Pair<Long,Long>
is stored in the array of
Pair<String,String>
.
What the compiler
can
do is warning you when the implicit varargs
array is created. If you ignore the warning you will get an unexpected
ClassCastException
later at runtime.
Here is another example that illustrates the potential danger of ignoring
the warning issued regarding array construction in conjunction with variable
argument lists.
Example (of a varargs method and its invocation):
public final class Test {
static <T> T[] method_1(T
t1, T t2) {
return method_2(t1, t2);
// unchecked warning
}
static <T> T[] method_2(
T...
args) {
return args;
}
public static void main(String...
args) {
String[] strings = method_1("bad", "karma");
//
ClassCastException
}
}
warning: [unchecked] unchecked generic array creation of type T[]
for varargs parameter
return method_2(t1, t2);
^
In this example the first method calls a second method and the second method
takes a variable argument list. In order to invoke the varargs method
the compiler creates an array and passes it to the method. In this example
the array to be created is an array of type
T[]
, that is, an array
whose component type is a type parameter. Creation of such arrays
is prohibited in Java and you would receive an error message if you tried
to create such an array yourself; see
TypeParameters.FAQ202
for details.
As in the previous example, the array's component type is non-reifiable
and due to type erasure the compiler does not create a
T[]
, but
an
Object[]
instead. Here is what the compiler generates:
Example (same a above, after translation by type erasure):
public final class Test {
static
Object[]
method_1(
Object
t1,
Object
t2) {
return method_2(
new Object[] {t1, t2}
);
// unchecked warning
}
static
Object[]
method_2(
Object[]
args) {
return args;
}
public static void main(String[]
args) {
String[] strings =
(String[])
method_1("bad",
"karma");
//
ClassCastException
}
}
The unchecked warning is issued to alert you to the potential risk of type
safety violations and unexpected
ClassCastException
s. In
the example, you would observe a
ClassCastException
in the
main()
method where two strings are passed to the first method. At runtime,
the two strings are stuffed into an
Object[]
; note,
not
a
String[]
. The second method accepts the
Object[]
as
an argument, because after type erasure
Object[]
is its
declared parameter type. Consequently, the second method returns an
Object[]
,
not
a
String[]
, which is passed along as the first method's return
value. Eventually, the compiler-generated cast in the
main()
method
fails, because the return value of the first method is an
Object[]
and no
String[]
.
Again, the problem is that calling the varargs method requires creation
of a array with a non-reifiable component type. In the first example,
the array in question was a
Pair<String,String>[]
; in the second
example, it was a
T[]
. Both are prohibited in Java because
they can lead to type safety problems.
Conclusion:
It is probably best to avoid providing objects
of non-reifiable types where a variable argument list is expected.
You will always receive an unchecked warning and unless you know exactly
what the invoked method does you can never be sure that the invocation
is type-safe.
|
LINK
TO THIS
|
Practicalities.FAQ300
|
REFERENCES
|
What
does type-safety mean?
What
is a reifiable 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?
Why
is it allowed to create an array whose component type is an unbounded wildcard
parmeterized type?
Can
I create an array whose component type is a type parameter?
|
What
is a "varargs" warning?
A warning that the compiler issues for
the definition of certain methods with a variable argument list.
|
Certain methods with a variable arguments list (called
a
varargs method
) lead to unchecked warnings when they are invoked.
This can occur if the declared type of the variable argument is non-reifiable,
e.g. if it is a parameterized type or a type variable. Since Java
7 the compiler does not only give an unchecked warning when such a method
is invoked, but also issues a warning for the definition of such a method.
In order to distinguish between the warning issued for the definition of
a debatable varargs method and the warning issued at the call site of such
a method we will refer to the warning at the definition site as a
varargs
warning
.
Here is an example:
Example (of a varargs warning):
public static <E> void addAll(List<E> list,
E...
array) {
// varargs warning
for (E element : array) list.add(element);
}
public static void main(String[] args) {
addAll(new ArrayList<Pair<String,String>>(),
// unchecked warning
new Pair<String,String>("Leonardo","da
Vinci"),
new Pair<String,String>("Vasco","de
Gama")
);
}
warning: [unchecked] Possible heap pollution from parameterized
vararg type E
public static
<E> void addAll(List<E> list, E... array) {
^
warning: [unchecked] unchecked generic array creation for varargs
parameter of type Pair<String,String>[]
addAll(new ArrayList<Pair<String,String>>(),
^
The
addAll()
method has a variable argument list
E...
.
The type of the variable argument is
E
which is a type variable.
When the
addAll()
method is invoked then the type variable
E
is replaced by the parameterized type
Pair<String,String>
in
the example above, which leads to an unchecked warning. The details
regarding this unchecked warning are explained in
Practicalities.FAQ300
.
In order to alert the provider of the
addAll()
method (rather
than its caller) to the trouble the method might later cause on invocation,
the compiler gives a varargs warning for the method definition. This
warning was introduced in Java 7.
The reason for the additional warning is that the caller of a varargs
method cannot do anything about the unchecked warning. At best he
can blindly suppress the unchecked warning with a
@SuppressWarnings("unchecked")
annotation, which is hazardous because the caller cannot know whether the
unchecked warning is justified or not. Only the method's provider
can judge whether the unchecked warning can safely be ignored or whether
it will lead to subsequent errors due to heap pollution (see
Technicalities.FAQ050
).
For this reason the provider of a varargs method is responsible for deciding
whether the unchecked warning on invocation of the method can be ignored
or not.
With a varargs warning the compiler tries to tell the provider of a
varargs method: invocation of your method can lead to type safety
issues and subsequent errors in form of unexpected
ClassCastExceptions
exceptions
(collectively called
heap pollution
). |
LINK TO THIS
|
Practicalities.FAQ300A
|
REFERENCES
|
Why does
the compiler sometimes issue an unchecked warning when I invoke a "varargs"
method?
What
is a reifiable type?
What
is heap pollution?
When
does heap pollution occur?
How
can I suppress a "varargs" warning?
What
is the SuppressWarnings annotation?
|
How
can I suppress a "varargs" warning?
By using a
@SafeVarargs
annotation.
|
A varargs warning can be suppressed using the
@SafeVarargs
annotation. When we use this annotation on a method with a variable
argument list the compiler will not only suppress the varargs warning for
the method definition, but also the unchecked warnings for the method invocations.
Here is an example, first without the annotation:
Example (of a varargs warning):
public static <E> void addAll(List<E> list,
E...
array) {
// varargs warning
for (E element : array) list.add(element);
}
public static void main(String[] args) {
addAll(new ArrayList<Pair<String,String>>(),
// unchecked warning
new Pair<String,String>("Leonardo","da
Vinci"),
new Pair<String,String>("Vasco","de
Gama")
);
}
warning: [unchecked] Possible heap pollution from parameterized
vararg type E
public static
<E> void addAll(List<E> list, E... array) {
warning: [unchecked] unchecked generic array creation for varargs
parameter of type Pair<String,String>[]
addAll(new ArrayList<Pair<String,String>>(),
^
Here is the same example, this time with the annotation:
Example (of a suppressed varargs warning):
@SafeVarargs
public static <E> void addAll(List<E> list,
E...
array) {
// fine
for (E element : array) list.add(element);
}
public static void main(String[] args) {
addAll(new ArrayList<Pair<String,String>>(),
// fine
new Pair<String,String>("Leonardo","da
Vinci"),
new Pair<String,String>("Vasco","de
Gama")
);
}
The
@SafeVarargs
annotation for the
addAll()
method eliminates
both warnings.
The
@SafeVarargs
annotation is only permitted on methods that
cannot be overridden, i.e.,
static
methods,
final
instance methods,
constructors, and
private
instance methods (since Java 9).
As usual, you must not suppress warnings unless you are absolutely sure
that they can safely be ignored. See
Practicalities.FAQ300C
for details on suppressing the varargs warnings.
|
LINK TO THIS
|
Practicalities.FAQ300B
|
REFERENCES
|
What
is the SuppressWarnings annotation?
Why does
the compiler sometimes issue an unchecked warning when I invoke a "varargs"
method?
What
is a "varargs" warning?
When
should I refrain from suppressing a "varargs" warning?
|
When
should I refrain from suppressing a "varargs" warning?
When the varargs method in question can
lead to heap pollution.
|
Suppressing a warning is always hazardous
and should only be attempted when the warning can with certainty
be considered harmless and no heap pollution will ever occur. In
all other situations you shoud refrain from suppressing any warnings.
Regarding suppression of a varargs warning: The provider of a varargs
method may only suppress the warning if
-
the varargs method does not add any elements to the array that the compiler
creates for the variable argument list, or
-
if the method adds an element to the array that the compiler creates for
the variable argument list, the element must be type-compatible to the
array's component type.
Example (of a harmless varargs method):
@SafeVarargs
public static <E> void addAll(List<E> list, E... array) {
for (E element : array) list.add(element);
}
public static void main(String... args) {
addAll(new ArrayList<Pair<String,String>>(),
new Pair<String,String>("Leonardo","da Vinci"),
new Pair<String,String>("Vasco","de Gama")
);
}
The
addAll()
method only reads the array that the compiler created
for the variable argument
E... array
. No heap pollution
can occur; this method is harmless; the varargs warning can be ignored
and therefor safely suppressed.
Example (of an incorrect and harmful varargs method):
public static Pair<String,String>[] modify(Pair<String,String>...
lists) {
// varargs warning
Object[] objs = lists;
objs[0] = new Pair<String,String>("x","y");
objs[1] = new Pair<Long,Long>(0L,0L);
// corruption !!!
return lists;
}
public static void main(String... args) {
Pair<String,String>[] result
= modify(new Pair<String,String>("Vasco","da Gama"),
//
unchecked warning
new Pair<String,String>("Leonard","da Vinci"));
for (Pair<String,String> p : result) {
String s = p.getFirst();
// ClassCastException
}
}
warning: [unchecked] Possible heap pollution from parameterized
vararg type Pair<String,String>
private static Pair<String,String>[]
modify(Pair<String,String>... lists) {
^
warning: [unchecked] unchecked generic array creation for varargs
parameter of type Pair<String,String>[]
= modify(new Pair<String,String>("Vasco","da Gama"),
^
The method
modify()
is plain wrong and should be corrected.
It adds a
Pair<Long,Long>
to an array that is supposed to contain
only elements of type
Pair<String,String>
and as a result the
heap is polluted. The compiler issues a warning for the method definition
as such, but does not flag the offending assignment as an error.
The invocation of method
modify()
also leads to a warning.
If all these warnings are ignored, an unexpected
ClassCastException
can occur.
While the varargs method in the example above is blatantly wrong, the
situation can be far more subtle. Here is an example:
Example (of another incorrect and harmful varargs method):
public static <T> T[] method_1(T t1, T t2) {
return method_2(t1, t2);
// unchecked warning
}
public static <T> T[] method_2(T... args) {
//
varargs warning
return args;
}
public static void main(String... args) {
String[] strings = method_2("bad", "karma");
//
fine
strings = method_1("bad", "karma");
// ClassCastException
}
warning: [unchecked] unchecked generic array creation for varargs
parameter of type T[]
return method_2(t1, t2);
^
warning: [unchecked] Possible heap pollution from parameterized
vararg type T
static <T> T[] method_2(
T... args) { // varargs warning
^
Method
method_2()
is a generic method and has a variable argument
list of type
T...
, where
T
is the type variable. As long
as the varargs method is directly called, nothing bad will happen; the
compiler infers that
T
is
String
in our example and returns
an array of type
String[]
.
If the varargs method is called from another generic method such as
method_1()
,
then the compiler will pass two arguments of type
Object
as arguments
to method
method_2()
due to type erasure. It will then infer
that
T
is
Object
in our example and returns an array
of type
Object[]
, which subsequently leads to an unexpected
ClassCastException
.
In this situation the question is: who is to blame? Is the varargs
method incorrect, or is it incorrectly used? It is debatable whether
it is a good idea to provide a method such as
method_2()
where
a type variable appears in the variable argument list. In any case,
suppressing the varargs warning is not advisable because this method can
lead to heap pollution as demonstrated above.
In general, it is very difficult to decide whether the varargs warning
can safely be suppressed. Whenever a non-reifiable type appears in
the variable argument list, an array with a non-reifiable component type
is created by the compiler. This is always hazardous. As soon
as this array becomes accessible, heap pollution can occur. As a
consequence,
you can only safely suppress a varargs warning if you can
make sure that the automatically created array with a non-reifiable component
type (or any copy thereof) never becomes accessible for modification.
Here is an example of another unsafe varargs method:
Example (of another incorrect and harmful varargs method):
public class SomeClass<E> {
private Pair<E,E>[]
pairs;
public SomeClass(Pair<E,E>...
args) {
// varargs warning
pairs = args;
}
public Pair<E,E>[]
getPairs() {
List<Pair<E,E>>
tmp = new ArrayList<Pair<E,E>>();
for (Pair<E,E>
p : pairs)
tmp.add(p.clone());
return tmp.toArray(pairs);
}
... more methods ...
}
public static void main(String... args) {
SomeClass<String> test = new SomeClass<String>(new
Pair<String,String>("bad", "karma"));
//
unchecked warning
Pair<?,?>[] tmp = test.getPairs();
tmp[0] = Pair.makePair(0L,0L);
String s = test.pairs[0].getFirst();
//
ClassCastException
}
warning: [unchecked] Possible heap pollution from parameterized
vararg type Pair<E,E>
public SomeClass(Pair<E,E>...
args) {
^
warning: [unchecked] unchecked generic array creation for varargs
parameter of type Pair<String,String>[]
SomeClass<String> test = new SomeClass<String>(new Pair<String,String>("bad",
"karma"));
^
The varargs method in question is the constructor of class
SomeClass
;
it stores the
automatically created array with a non-reifiable component
type
Pair<E,E>
in a private field. Any modification
of this array can create heap pollution. Even if the class itself does
not modify the array in any of its methods, matters can go wrong.
In the example, the
getPairs()
method creates a deep copy of the
array and passes the copy to its caller. As soon as someone gets
hold of an array with a non-reifiable component type (like the copy of
the
pairs
field in the example), illegal elements can added to
the array without any error or warning from the compiler. The heap
pollution and the resulting unexpected
ClassCastException
is shown
in the
main()
method. Only if the automatically created
array were confined to the class and the class were guaranteed to use the
array sensibly in all situations, then the varargs warning could safely
be ignored.
In essence,
there are very few situations in which you can safely
suppress a varargs warning; usually the warning is justified.
|
LINK TO THIS
|
Practicalities.FAQ300C
|
REFERENCES
|
What
is a "varargs" warning?
How
can I suppress a "varargs" warning?
What
is the SuppressWarnings annotation?
What
is heap pollution?
When
does heap pollution occur?
|
Which
role do wildcards play in method signatures?
They broaden the set of argument or return
types that a method accepts or returns.
|
Consider the problem of writing a routine that prints out
all the elements in a collection. In non-generic Java it might look like
this:
Example (of non-generic
print
method):
void printCollection(
Collection
c) {
Iterator i = c.iterator();
for (k = 0; k < c.size(); k++) {
System.out.println(i.next());
}
}
In generic Java the same method might be implemented like this.
Example (of generic
print
method):
void printCollection(
Collection<Object>
c) {
for (Object e : c) {
System.out.println(e);
}
}
The problem is that this new version is much less useful than the old one.
Whereas the old code could be called with any kind of collection as a parameter,
the new code only takes
Collection<Object>
, which is not a
supertype of all kinds of collections. For instance, it is possible to
invoke the old version supplying a
List<String>
as an argument,
while the new version rejects the
List<String>
argument because
it has an incompatible type.
So what we need here is the supertype of all kinds of collections and
that's exactly what the unbounded wildcard parameterized type
Collection<?>
is.
Example (final version of
print
method):
void printCollection(
Collection<?>
c) {
for (Object e : c) {
System.out.println(e);
}
}
Now, we can call the
print
method with any type of collection.
Bounded wildcards are used for similar purposes. The sole difference
is that the set of types that they allow is smaller (because it's restricted
by the respective bound). The key idea for use of wildcards in method
signatures is to allow a broader set of argument or return types than would
be possible with a concrete instantiation. |
LINK TO THIS
|
Practicalities.FAQ301
|
REFERENCES
|
|
Which
one is better: a generic method with type parameters or a non-generic method
with wildcards?
Under
which circumstances are the generic version and the wildcard version of
a method equivalent?
If there is a transformation between
the generic and the wildcard version that maintains the semantics.
|
In many situations we can replace wildcards by type parameters
and vice versa. For example, the following two signatures are semantically
equivalent:
void reverse(
List
<?>
list) { ... }
<T>
void reverse(
List
<?>
list) { ... }
In this and the subsequent entries we aim to explore not only which of
two versions is better than the other, but also how we can transform between
a generic and a wildcard version of a method signature.
The Transformation
Wildcard => Generic:
The key idea for turning a method signature
with a wildcard into a generic method signature is simple: replace
each wildcard by a type variable. These type variables are basically the
captures of the respective wildcards, that is, the generic method signature
makes the captures visible as type parameters. For example, we can
transform the following method signature
<T>
void fill(
List
<?
super T>
list,
T
obj) {
... }
into this signature
<S, T extends S>
void fill(
List
<S>
list,
T
obj)
by replacing the wildcard "
? super T
" by an additional type parameter
S
.
The type relationship, namely that the former wildcard is a supertype of
T
,
is expressed by saying that "
T extends S
".
Generic => Wildcard:
Conversely, if we prefer method signatures
with fewer type parameters, then we can reduce the number of type parameters
by means of wildcards: replace each type parameter that appears in a parameterized
argument or return type by a wildcard. In the previous example, we
would transform the method signature
<S, T extends S>
void fill(
List
<S>
list,
T
obj)
into the signature
<T>
void fill(
List
<?
super T>
list,
T
obj) {
... }
by replacing the type variable
S
by a wildcard. The type
relationship, namely that
T
is a subtype of
S
, is expressed
by giving the wildcard a lower bound, that is, by saying "
? super T
".
The transformations sketched out above do not always work. Especially
the transformation from a generic version to a wildcard version is not
always possible. Problems pop up, for instance, when the generic method
signature has more than one type parameter and the type parameters have
certain type relationships, such as super-subtype or same-type relationships.
In such a situation it might be impossible to translate the type relationship
among the type parameters into a corresponding relationship among the wildcards.
In the example above, a semantically equivalent wildcard version could
be found, because the type relationship could be expressed correctly by
means of the wildcard bound. But this is not always possible, as
is demonstrated in subsequent entries.
In this entry, we discuss only situations in which a transformation
exists that allows for two semantically equivalent signature and the questions
is: which one is better? For illustration let us study a couple of
examples.
Case Study #1
Let us consider the following
reverse
method. It can
be declared as a generic method.
Example (of a method with type parameters):
public static
<T>
void
reverse(
List
<T>
list) {
ListIterator
<T>
fwd
= list.listIterator();
ListIterator
<T>
rev
= list.listIterator(list.size());
for (int i = 0, mid = list.size() >> 1; i < mid; i++)
{
T
tmp =
fwd.next();
fwd.set(rev.previous());
rev.set(tmp);
}
}
Alternatively, it can be declared as a non-generic method using a wildcard
argument type instead. The transformation simply replaces the unbounded
type parameter
T
by the unbounded wildcard "
?
".
Example (of the same method with wildcards; does not compile):
public static void reverse(
List
<?>
list) {
ListIterator
<?>
fwd
= list.listIterator();
ListIterator
<?>
rev
= list.listIterator(list.size());
for (int i = 0, mid = list.size() >> 1; i < mid; i++)
{
Object
tmp
= fwd.next();
fwd.set(rev.previous());
//
error
rev.set(tmp);
// error
}
}
The wildcard version has the problem that it does not compile. The iterators
of a
List<?>
are of type
ListIterator<?>
, a side
effect of which is that their
next
and
previous
methods
return an
Object
, while their
set
method requires a more
specific type, namely the "
capture of ?
".
We can find a workaround for this problem by using raw types, as shown
below.
Example (of the same method with wildcards; not recommended):
public
static
void reverse(
List
<?>
list) {
ListIterator fwd = list.listIterator();
ListIterator rev = list.listIterator(list.size());
for (int i = 0, mid =
list.size() >> 1; i < mid; i++) {
Object tmp
= fwd.next();
fwd.set(rev.previous());
//
unchecked warning
rev.set(tmp);
//
unchecked
warning
}
}
But even that workaround is not satisfying because the compiler
gives us unchecked warnings, and rightly so. After all we are calling
the
set
method on the
raw type
ListIterator
,
without knowing which type of object the iterator refers to.
The best implementation of the wildcard version of
reverse
would use a generic helper method, as shown below.
Example (of the same method with wildcards; uses helper method):
private
static
<T>
void reverseHelper(
List
<T>
list) {
ListIterator<T>
fwd = list.listIterator();
ListIterator<T>
rev = list.listIterator(list.size());
for
(int i = 0, mid = list.size() >> 1; i < mid; i++) {
T tmp = fwd.next();
fwd.set(rev.previous());
rev.set(tmp);
}
}
public
static
void reverse(
List
<?>
list) {
reverseHelper(list);
}
This solution compiles without warnings and works perfectly well,
thanks to wildcard capture. However, it raises the question: why
not use the generic version in the first place. The helper method
is exactly the generic version of our
reverse
method. The wildcard version only adds overhead and does not buy the user
anything.
Case
Study #2
Let us start with the wildcard version this time. We discuss
the example of a
copy
method.
Example (of the a method with wildcards):
public
static
<T>
void copy(
List
<?
super T>
dest,
List
<?
extends T>
src) {
int srcSize = src.size();
if (srcSize > dest.size())
throw new IndexOutOfBoundsException("Source does
not fit in dest");
ListIterator
<? super
T>
di=dest.listIterator();
ListIterator
<? extends
T>
si=src.listIterator();
for (int i = 0; i < srcSize; i++) {
di.next();
di.set(si.next());
}
}
It is a method that has one type parameter
T
and uses two different wildcard types as argument types. We can transform
it into a generic method without wildcards by replacing the two wildcards
by two type parameters. Here is the corresponding generic version
without wildcards.
Example (of the same method without wildcards):
public
static
<U,T
extends U,L extends T>
void copy(
List
<U>
dest,
List
<L>
src) {
int srcSize = src.size();
if (srcSize > dest.size())
throw
new IndexOutOfBoundsException("Source does not fit in dest");
ListIterator
<U>
di = dest.listIterator();
ListIterator
<L>
si
= src.listIterator();
for (int i = 0; i <
srcSize; i++) {
di.next();
di.set(si.next());
}
}
The version without wildcards uses two additional type parameters
U
and
L
.
U
stands for a supertype of
T
and
L
stands for a subtype
of
T
. Basically,
U
and
L
are the captures
of the wildcards "
? extends T
"
and "
? super T
" from the wildcard
version of the
copy
method.
Semantically the two version are equivalent. The main difference is
the number of type parameters. The version without wildcards expresses
clearly that 3 unknown types are involved:
T
,
a supertype of
T
, and
a subtype of
T
. In the
wildcard version this is less obvious. Which version is preferable
is to the eye of the beholder.
Case Study #3
Let's study the example of a
fill
method, which has been mentioned earlier, greater detail. Let's start with
the generic version without wildcards and let's try to figure out whether
we can get rid of the type parameters by means of wildcards.
Example (of the a method with type parameters):
public
static
<S,
T extends S>
void fill(
List
<S>
list,
T
obj) {
int size = list.size();
ListIterator
<S>
itr = list.listIterator();
for (int i = 0; i < size; i++) {
itr.next();
itr.set(obj);
}
}
The method takes two type parameters
S
and
T
and two method
parameters: an unknown instantiation of the generic type
List
,
namely
List<S>
, and
an object of unknown type
T
.
There is a relationship between
S
and
T
:
S
is a supertype of
T
.
When we try to eliminate the type parameters we find that we can easily
replace the type parameter
S
by a wildcard, but we cannot get rid of the type parameter
T
.
This is because there is no way to express by means of wildcards that the
fill
method takes an argument of unknown type. We could try something
like this:
Example (of the same method with wildcards; does not work):
public
static
void
fill(
List
<?>
list,
Object
obj) {
int size = list.size();
ListIterator
<?>
itr = list.listIterator();
for (int i = 0; i < size; i++) {
itr.next();
itr.set(obj);
//
error
}
}
The first problem is that this version does not compile; the
problem can be reduced to an unchecked warning by using a raw type
ListIterator
instead of the unbounded wildcard
ListIterator<?>
.
But the the real issues is that this signature gives up the relationship
between the element type of the list and the type of the object used for
filling the list. A semantically equivalent version of the
fill
method would look like this:
Example (of the same method with wildcards):
public
static
<T>
void
fill(
List
<?
super T>
list,
T
obj) {
int size = list.size();
ListIterator
<?
super T>
itr = list.listIterator();
for (int i = 0; i < size; i++) {
itr.next();
itr.set(obj);
}
}
Now, we have successfully eliminated the need for the type parameter
S
,
which stands for the list's element type, by using the "
?
super T
" wildcard, but we still need the type parameter
T
.
To this regard the example is similar to the copy method discussed earlier,
because we can reduce the number of type parameters by means of wildcards,
but we cannot entirely eliminate the type parameters. Which version
is better is a matter of style and taste.
Conclusion: In all these
examples it is mostly a matter of taste and style whether you prefer the
generic or the wildcard version. There is usually trade-off between
ease of implementation (the generic version is often easier to implement)
and complexity of signature (the wildcard version has fewer type
parameters or none at all).
|
LINK TO THIS
|
Practicalities.FAQ302A
|
REFERENCES
|
Should
I use wildcards in the return type of a method?
Which
methods and fields are accessible/inaccessible through a reference variable
of a wildcard type?
What
is the capture of a wildcard?
Under
which circumstances do the generic version and the wildcard version of
a method mean different things?
Under
which circumstances is there no transformation to the wildcard version
of a method possible?
|
Under
which circumstances do the generic version and the wildcard version of
a method mean different things?
When a type parameter appears repeatedly
in a generic method signature and in case of multi-level wildcards.
|
In many situations we can replace wildcards by type parameters
and vice versa. For example, the following two signatures are semantically
equivalent:
void reverse(
List
<?>
list) {
...
}
<T>
void reverse(
List
<T>
list) { ...
}
In the previous entry we saw several examples of equivalent method
signature, but there are also situations in which the generic version and
the wildcard version of a method signature mean different things. These
situations include generic method signature in which a type parameter appears
repeated and method signatures in which multi-level wildcards, such
as
List<Pair<?,?>>
,
appear. In the following we study a couple of examples.
Case Study #1
Let us consider the implementation of a
reverse
method. It is slightly different from the
reverse
method we discussed in the previous entry. The key difference is that the
List
type, and with it the type parameter
T
,
appears twice in the method's signature: in the argument type and the return
type. Let's start again with the generic version of the
reverse
method.
Example (of a method with type parameters):
public
static
<T>
List
<T>
reverse(
List
<T>
list) {
List<T> tmp = new ArrayList<T>(list);
for (int i = 0; i < list.size(); i++) {
tmp.set(i, list.get(list.size() - i - 1));
}
return tmp;
}
If we tried to declare this method as a non-generic method using wildcards,
a conceivable signature could look like this.
Example (of the same method with wildcards; does not compile):
public
static
List
<?>
reverse(
List
<?>
list) {
List<?> tmp
= new ArrayList<?>(list);
//
error
for (int i = 0; i <
list.size(); i++) {
tmp.set(i, list.get(list.size() - i - 1));
//
error
}
return tmp;
}
The first problem is that this version does not compile; the problem can
be reduced to an unchecked warning by using the raw types
List
and
ArrayList
instead
of the unbounded wildcards
List<?>
and
ArrayList<?>
.
Even the warnings can be eliminated by relying on wildcard capture and
using a generic helper method. But one fundamental issue remains:
the wildcard version has an entirely different semantic meaning compared
to the generic version.
The generic version is saying: the
reverse
method accepts a list with a certain, unknown element type and returns
a list of that same type. The wildcard version is saying: the
reverse
method accepts a list with a certain, unknown element type and returns
a list of a potentially different type. Remember, each occurrence
of a wildcard stands for a potentially different type. In principle,
the
reverse
method could
take a
List<Apple>
and return a
List<Orange>
.
There is nothing in the signature or the implementation of the
reverse
method that indicates that "what goes in does come out". In other
words, the wildcard signature does not reflect our intent correctly.
Conclusion: In this example it the generic version and the wildcard
version have different meaning.
Case Study #2
Another example where more than one wildcard occurs in the signature
of the method.
Example (of a method with type parameters):
class
Pair<S,T> {
private S first;
private T second;
...
public Pair(S s,T t) { first = s; second = t; }
public static
<U>
void flip(
Pair
<U,U>
pair) {
U tmp = pair.first;
pair.first = pair.second;
pair.second = tmp;
}
}
When we try to declare a wildcard version of the generic
flip
method we find that there is no way of doing so. We could try the
following:
Example (of the same method with wildcards; does not compile):
class
Pair<S,T> {
private S first;
private T second;
...
public Pair(S s,T t) { first = s; second = t; }
public static void flip(
Pair
<?,?>
pair) {
Object tmp = pair.first;
pair.first = pair.second;
//
error: imcompatible types
pair.second = tmp;
//
error: imcompatible types
}
}
But this wildcard version does not compile, and rightly so. It does
not make sense to flip the two parts of a
Pair<?,?>
.
Remember, each occurrance of a wildcard stands for a potentially different
type. We do not want to flip the two parts of a pair, if the part
are of different types. This additional requirement, that the parts
of the pair must be of the same type, cannot be expressed by means of wildcards.
The wildcard version above would be equivalent to the following generic
version:
Example (of the generic equivalent of the wildcard version; does not
compile):
class
Pair<S,T> {
private S first;
private T second;
...
public Pair(S s,T t) { first = s; second = t; }
public static
<U,V>
void
flip(
Pair
<U,V>
pair) {
U tmp = pair.first;
pair.first = pair.second;
//
error: imcompatible types
pair.second
= tmp;
//
error: imcompatible types
}
}
Now it should be obvious that the wildcard version simply does not express
our intent.
Conclusion: In this example only the generic version allows to
express the intent correctly.
Case
Study #3
If a method signature uses multi-level wildcard types then there
is always a difference between the generic method signature and the wildcard
version of it. Here is an example. Assume there is a generic type
Box
and we need to declare a method that takes a list of boxes.
Example (of a method with a type parameter):
public
static
<T>
void print1(
List
<Box<T>>
list) {
for (Box<T> box : list) {
System.out.println(box);
}
}
Example (of method with wildcards):
public
static void print2(
List
<Box<?>>
list) {
for (Box<?> box : list)
{
System.out.println(box);
}
}
Both methods are perfectly well behaved methods, but they are
not equivalent. The generic version requires a homogenous list of
boxes of the same type. The wildcard version accepts a heterogenous
list of boxes of different type. This becomes visible when the two
print
methods are invoked.
Example (calling the 2 versions):
List
<Box<?>>
list1 = new ArrayList<Box<?>>();
list1.add(new Box<String>("abc"));
list1.add(new Box<Integer>(100));
print1(list1);
//
error
print2(list1);
//
fine
List
<Box<Object>>
list2 = new ArrayList<Box<Object>>();
list2.add(new Box<Object>("abc"));
list2.add(new Box<Object>(100));
print1(list2);
//
fine
print2(list2);
//
error
error:
<T>print1(
Box<T>>) cannot
be applied to (
Box<?>>)
print1(list1);
^
error: print2(
Box<?>>)
cannot be applied to (
Box<Object>>)
print2(list2);
^
First, we create a list of boxes of different types and stuff a
Box<String>
and a
Box<Integer>
into the list. This heterogenous list of type
List<Box<?>>
cannot be passed to the generic method, because the generic method expects
a list of boxes of the same type.
Then, we create a list of boxes of the same type, namely of type
Box<Object>
,
and we stuff two
Box<Object>
objects into the list. This homogenous list of type
List<Box<Object>>
cannot be passed to the wildcard method, because the wildcard method expects
a list of boxes, where there is no restriction regarding the type of the
boxes.
Let us consider a third version of the
print
method, again with wildcards, but more relaxed so that it accepts either
type of list, the homogenous and the heterogenous list of boxes.
Example (of another wildcard version):
public
static void print3(
List
<?
extends Box<?>>
list) {
for (Box<?> box : list)
{
System.out.println(box);
}
}
Example (calling all 3 versions):
List
<Box<?>>
list1 = new ArrayList<Box<?>>();
list1.add(new Box<String>("abc"));
list1.add(new Box<Integer>(100));
print1(list1);
//
error
print2(list1);
//
fine
print3(list1);
//
fine
List
<Box<Object>>
list2 = new ArrayList<Box<Object>>();
list2.add(new Box<Object>("abc"));
list2.add(new Box<Object>(100));
print1(list2);
//
fine
print2(list2);
//
error
print3(list2);
//
fine
No matter how we put it, the generic version and the wildcard versions
are not equivalent.
Conclusion: In this example it the generic version and the wildcard
version have different meaning.
|
uiLINK TO THIS
|
Practicalities.FAQ302B
|
REFERENCES
|
What
do multi-level wildcards mean?
If
a wildcard appears repeatedly in a type argument section, does it stand
for the same type?
Which
methods and fields are accessible/inaccessible through a reference variable
of a wildcard type?
What
is the capture of a wildcard?
Under
which circumstances are the generic version and the wildcard version of
a method equivalent?
Under
which circumstances is there no transformation to the wildcard version
of a method possible?
|
Under
which circumstances is there no transformation to the wildcard version
of a method possible?
I
f a type parameter has
more than one bound.
|
Wildcards can
have at most one upper bound, while type parameters can have several upper
bounds. For this reason, there is not wildcard equivalent for generic
method signatures with type parameter with several bounds. Here is
an example.
Example (of a method with a type parameter with more than one bound):
public
interface State {
boolean isIdle();
}
public
static
<T
extends Enum<T> & State>
boolean hasIdleState(
EnumSet
<T>
set) {
for (T state : set)
if (state.isIdle())
return true;
return false;
}
This
hasIdleState
method
has a type parameter that must be a enum type that implements the
State
interface. The requirement of both being an enum type and implementing
an interface cannot be expressed by means of wildcards. If we tried
it it would look like this:
Example (of the same method without type parameters; does not compile):
public
static boolean hasIdleState(
EnumSet
<?
extends Enum<?> & State>
set) {
//
error
...
}
This attempt fails because a wildcard cannot have two bounds and for this
reason the expression "
? extends
Enum<?> & State
" is illegal syntax.
Conclusion: In this example there is no way to find an equivalent version
with wildcards and the generic version is the only viable solution.
|
uiLINK TO THIS
|
Practicalities.FAQ302C
|
REFERENCES
|
What
is the difference between a wildcard bound and a type parameter bound?
Under
which circumstances are the generic version and the wildcard version of
a method equivalent?
Under
which circumstances do the generic version and the wildcard version of
a method mean different things?
|
Should
I use wildcards in the return type of a method?
Avoid it, if you can.
|
Methods that
return their result through a reference of a wildcard type are rarely a
good idea. The key problem is that access to the result is restricted
and there is often not much the caller can do with the result he receives.
Remember, access to an object through a reference of a wildcard type is
restricted; the restrictions depend on the sort of wildcard being used.
For this reason wildcard return types are best avoided.
Example (of a method with a wildcard return type; not recommended):
List<?>
modifyList(List<?> list) {
...
return list;
}
List<String> names = ...
List<?>
result = modifyList(names);
result.add("Bobby Anderson");
//
error
Since the result is returned through a wildcard reference, a whole bunch
of methods cannot be invoked on the result. A generic method would
in this example be way more useful.
Example (alternative generic method; recommended):
<T>
List<T>
modifyList(List<T> list) {
...
return list;
}
List<String> names = ...
List<String>
result = modifyList(names);
result.add("Bobby Anderson");
//
fine
It is hard to imagine that a method such as
modifyList
would be sensible in the first place. Most likely it is bad, if not buggy
design. After all it is weird that a method received one unknown
type of list as input and returns another unknown type of list as output.
Does it turn a
List<Apples>
into a
List<Oranges>
?
The generic version is more likely to be the more sensible signature to
begin with. But there are examples, even in the JDK, where methods
return wildcard types and the design looks reasonable at first sight, and
yet suffers from the restrictions outlined above. The remainder of this
FAQ entry discusses such a more realistic example. If you are
not interested in further details, feel free to skip the rest of this entry.
It's quite instructive though, if you're interested in learning how to
design generic classes properly.
A More Comprehensive Example
The Problem
As promised, we are going to study an example from the JDK to illustrate
the problems with methods that return wildcard types. The example
is taken from the JDK package
java.lang.ref
.
This package provides reference classes, which support a limited degree
of interaction with the garbage collector. All these reference classes
are subtypes of a super class named
Reference<T>
.
Example (sketch of class
java.lang.ref.Reference
):
public abstract class
Reference<T>
{
public T get() { ... }
public void clear() { ... }
...
}
There are two reference classes of interest
SoftReference<T>
and
WeakReference<T>
.
Instances of the reference classes can be registered with a reference queue.
Example (sketch of class
java.lang.ref.WeakReference
):
public class
WeakReference<T>
extends Reference<T> {
public WeakReference(T referent) { ... }
public WeakReference(T referent,
ReferenceQueue<? super T>
q) { .. }
}
This reference queue is described by a type named
ReferenceQueue<T>
.
Its
poll
and
remove
methods return elements from the queue through a wildcard reference of
type
Reference<? extends
T>
.
Example (sketch of class
java.lang.ref.ReferenceQueue
):
public class
ReferenceQueue<T>
{
public
Reference<? extends T>
remove
() { ... }
public
Reference<? extends T>
remove
(long timeout) { ... }
public
Reference<? extends T>
poll() { ... }
}
The methods of the
ReferenceQueue<T>
type
are examples of methods that return their result through a wildcard type.
The purpose of the reference classes and the reference queue is of no relevance
for our discussion. What we intend to explore are the consequences
of the wildcard return type of the reference queue's methods.
Let's consider a use case for these reference classes. It is common
that the actual reference types are subtypes of the reference classes from
the JDK. This is because a reference type often must maintain additional
data. In our example this subtype is called
DateReference
and it is weak reference to a date object. It caches the representation
of the referenced date as a time value and has a couple of additional methods.
Example (of a user-defined reference class):
public
class WeakDateReference<T extends Date> extends WeakReference<T>
{
long time;
public
WeakDateReference
(T
t) {
super(t);
time = t.getTime();
}
public
WeakDateReference
(T
t,ReferenceQueue<? super T> q) {
super(t,q);
time = t.getTime();
}
public long
getCachedTime() { return time; }
public boolean
isEquivalentTo(DateReference<T> other) {
return this.time == other.getCachedTime();
}
public boolean
contains(T t) {
return this.get() == t;
}
}
Let's now create such a weak date reference and register it with
a reference queue.
Example (of using a user-defined reference class with a reference queue):
ReferenceQueue<Date>
queue = new ReferenceQueue<Date>();
Date date = new Date();
WeakDateReference<Date> dateRef
=
new WeakDateReference<Date>(date, queue);
The reference queue will later contain weak date references that have been
cleared by the garbage collector. When we retrieve entries from the reference
queue, they are passed to us through a reference of a the wildcard
type
Reference<? extends
Date>
, because this is the way the reference queue's methods are
declared.
Example (of using a user-defined reference class with a reference queue):
WeakDateReference<Date>
deadRef = queue.poll(); // error
Reference<?
extends Date>
deadRef = queue.poll(); //
fine
error:
incompatible types
found : Reference<capture of ? extends Date>
required: WeakDateReference<.Date>
WeakDateReference<Date> deadRef = queue.poll();
^
What is returned is a reference of type
Reference<?
extends Date>
pointing to
an
object of type
WeakDateReference<Date>
.
If we now try to use the returned object we find that we cannot access
the object as would like to. In particular, some of the methods of
my weak date reference type cannot be called.
Example (of using the returned reference object):
Reference<?
extends Date>
deadRef = queue.poll();
long time = deadRef.getCachedTime();
// error
long time = ((
WeakDateReference<Date>
)deadRef).getCachedTime();
// unchecked warning
long time = ((
WeakDateReference<?
extends Date>
)deadRef).getCachedTime(); // fine
error:
cannot find symbol
symbol : method getCachedTime()
location: class Reference<capture
of ? extends Date>
time = deadRef.getCachedTime();
^
warning: [unchecked] unchecked
cast
found : Reference<capture
of ? extends Date>
required: WeakDateReference<Date>
time = ((WeakDateReference<Date>)deadRef).getCachedTime();
^
Before we can access any of the weak date reference type's methods we must
cast down from its super-type
Reference
to its own type
WeakDateReference
.
This explains why the first invocation of the
getCachedTime
method fails; the super-type
Reference
does not have any such method.
So, we must cast down. We would like to cast the returned reference
variable of type
Reference<?
extends Date>
to the object's actual type
WeakDateReference<Date>
,
but the compiler issues an unchecked warning. This warning is justified
because the reference queue can potentially hold a mix of weak and soft
references of all sorts as long as they refer to a
Date
object or a subtype thereof. We know that the reference queue only
holds objects of our weak date reference type, because we know the context
of our little sample program. But the compiler can impossibly know
this and rejects the cast to
WeakDateReference<Date>
based on the static type information as an unchecked cast.
We can safely cast down from the type
Reference<?
extends Date>
to the wildcard type
WeakDateReference<?
extends Date>
though. This is safe because the two types have
the same type argument "
? extends
Date
". The compiler can ensure that type
WeakDateReference<?
extends Date>
is a subtype of
Reference<?
extends Date>
and the JVM can check at runtime based on the raw
types that the referenced object really is a
WeakDateReference
.
So, we invoke the weak date reference methods through a reference of
the wildcard type
WeakDateReference<?
extends Date>
. This fine for the
getCachedTime
method,
but fails when we try to invoke methods in whose argument type the type
parameter
T
of our type
WeakDateReference<T>
appears.
Example (of using the returned reference object):
Reference<?
extends Date>
deadRef = queue.poll();
long
time = ((
WeakDateReference<? extends
Date>
)deadRef).getCachedTime();
// fine
boolean equv = ((WeakDateReference<? extends Date>)deadRef).isEquivalentTo(dateRef);
// error
boolean cont = ((WeakDateReference<? extends Date>)deadRef).contains(date);
// error
error:
isEquivalentTo(WeakD
ateReference<capture
of ? extends Date>)
in Wea
kDateReference<capture
of ? extends Date>
cannot be applied to (
WeakDateReference<Date>)
boolean
equv
= ((WeakDateReference<?
extends Date>)deadRef).isEquivalentTo(dateRef)
;
^
error: contains(capture of ?
extends Dat
e)
in WeakDateReference<capture of ? extends Da
te>
cannot be applied to (Date)
boolean cont = ((WeakDateReference<? extends Date>)deadRef).contains
(date);
^
This illustrates the problems that wildcard return types introduce: certain
methods cannot be invoked through the returned wildcard reference.
In other word, there is not much you can do with the result. How
severe the restrictions are, depends on te nature of the wildcard type,
the type of the returned object and the signatures of the methods that
shall be invoked on the returned object. In our example we are forced
to access the result through a reference of type
WeakDateReference<?
extends Date>
. As a consequence, we cannot invoke the methods
boolean
isEquivalentTo(DateReference<T> other)
and
boolean
contains(T t)
, because the type parameter
T
appears in their argument types.
Conclusion
Can or should we conlcude that methods
with wildcard return types are always wrong? Not quite. There
are other examples in the JDK, where the wildcard return type does not
impose any problems. The most prominent example is the generic class
java.lang.Class<T>
.
It has a number of methods that return wildcard such as
Class<?>
,
Class<?
super T>
, and
Class<?
extends U>
, but at the same time class
Class<T>
does not have a single method in whose argument type the type parameter
T
would appear. The restriction illustrated above exists in
principle, but in practice it is irrelevant, because the type in question
does not have any methods whose inaccessibility would hurt.
This is different for the generic
ReferenceQueue<T>
type discussed above. The super type
Reference<T>
does not have any methods in whose argument type the type parameter
T
would appear, pretty much like class
Class<T>
.
But, it is common that subtypes of type
Reference<T>
are defined and used, and there is no reason why those subtypes shouldn't
be generic and have method in whose argument type the type parameter
T
would appear. And there we are ... and hit the limits.
A Conceivable Solution
The recommendation is: avoid wildcard return types if you can.
The question is: can we avoid the wildcard return type in the reference
queues's methods? The answer is: yes, but it comes at a cost.
In order to understand what the trade-off is we need to find out why the
reference queue returns a wildcard type instead of a concrete parameterized
type. After all, no other queue type returns a wildcard from any
of its methods; consider for instance
java.util.Queue
or
java.util.concurrent.BlockingQueue
.
The crux in case of the
ReferenceQueue
is its interaction with the
Reference
type
. Class
Reference
and all its subclasses have constructors that permit attachment of a reference
queue to a reference object. In class
Reference
this constructor is package visible, in the subclasses it is
public
.
Example (excerpt from class
java.lang.ref.Reference
):
public abstract class
Reference<T>
{
ReferenceQueue<? super T> queue;
Reference(T referent) {
... }
Reference(T referent,
ReferenceQueue<? super T>
queue) {
... }
public T get() { ... }
public void clear() { ... }
...
}
The package visible constructor takes the wildcard instantiation
ReferenceQueue<?
super T>
as the argument type and thereby allows to attach
a reference queue for references of a supertype, say
Date
,
to a reference for a subtype, say
NamedDate
.
Example (of using a reference with a reference queue):
ReferenceQueue<
Date
>
queue = new ReferenceQueue<Date>();
NamedDate date = new NamedDate("today");
WeakReference<
NamedDate
>
dateRef
=
new WeakReference<
NamedDate
>(date,
queue);
Thanks to the wildcard argument type in the reference's constructor
we can place references of type
Reference<NamedDate>
into
a reference queue of type
ReferenceQueue<Date>
.
Inside class
Reference
,
at some point in time, the reference puts itself into its attached reference
queue. For this purpose the type
ReferenceQueue
has a package visible
enqueue
method.
Example (excerpt from class
java.lang.ref.ReferenceQueue
):
public class
ReferenceQueue<T>
{
boolean enqueue(
Reference<? extends T>
ref) { ... }
public
Reference<? extends T>
remove
() { ... }
public
Reference<? extends T>
remove
(long timeout) { ... }
public
Reference<? extends T>
poll() { ... }
}
This
enqueue
method must
accept a
Reference<? extends T>
as an argument, because it is permitted that a reference of a subtype can
be put into the reference queue. Like in the example above, where
we registered a
Reference<NamedDate>
with a
ReferenceQueue<Date>
.
If the
enqueue
method
required an argument of the concrete type
Reference<T>
then
we could never store a
Reference<NamedDate>
in a
ReferenceQueue<Date>
.
A consequence of accepting references of type
Reference<?
extends T>
in the constructor is that the exact type of the references
in the queue is unknown. All retrieval methods, such as
poll
and
remove
, have no choice
and must return the same wildcard type that was accepted in the constructor
.
This is the reason why the reference queue's poll and remove methods return
wildcard types instead of concrete type.
If we want to get rid of the wildcard return type we must give up the
ability to attach a reference queue for references of a supertype, say
Date
,
to a reference for a subtype, say
NamedDate
.
An alternative design would look like this:
Example (sketch of a revised
Reference
class; different from JDK version
):
public abstract class
Reference<T>
{
ReferenceQueue<T> queue;
Reference(T referent) {
... }
Reference(T referent,
ReferenceQueue<T>
queue) {
... }
public T get() { ... }
public void clear() { ... }
...
}
Example
(sketch of a revised
ReferenceQueue
class
; different from JDK version
):
public class
ReferenceQueue<T>
{
boolean enqueue(
Reference<T>
ref) { ... }
public
Reference<T>
remove
() { ... }
public
Reference<T>
remove
(long timeout) { ... }
public
Reference<T>
poll() { ... }
}
After such a redesign we can not longer place references to
NamedDate
into a reference queue for reference to
Date
.
Example (of using a reference with a reference queue
;
different from JDK version
):
ReferenceQueue<
Date
>
queue = new ReferenceQueue<Date>();
NamedDate date = new NamedDate("today");
WeakReference<
NamedDate
>
dateRef
=
new WeakReference<
NamedDate
>(date,
queue);
// error
In return we now receive a concrete parameterized type when
we take references out of the queue and the concrete type gives us full
access to the reference type. The restrictions resulting from wildcard
return types are eliminated.
Example (of using a user-defined reference type
;
different from JDK version
):
Reference
<Date>
deadRef = queue.poll();
long
time = ((
WeakDateReference
<Date>
)deadRef).getCachedTime();
// fine
boolean equv = ((WeakDateReference<Date>)deadRef).isEquivalentTo(dateRef);
// fine
boolean cont = ((WeakDateReference<Date>)deadRef).contains(date);
// fine
As you can see, there is a trade-off: the flexibility to put refererences
to a subtype into a reference queue of references to a supertype costs
us limited access to the references retrieved from the queue, and vice
versa. The design decisions made for the reference queue are certainly
reasonable, because user-defined reference types with sophisticated functionality
are probably rare and hence the restrictions from the wildcard return type
will not hit too many programmers.
Nonetheless, the case study illustrates that design decisions made in
one place have consequences in other places. As a general rule, be
aware of the restrictions that come with wildcard return types and avoid
then if you can, unless you have a compelling reason to use them anyway. |
LINK TO THIS
|
Practicalities.FAQ303
|
REFERENCES
|
Under
which circumstances are the generic version and the wildcard version of
a method equivalent?
Under
which circumstances do the generic version and the wildcard version of
a method mean different things?
Under
which circumstances is there no transformation to the wildcard version
of a method possible?
Which
methods and fields are accessible/inaccessible through a reference variable
of a wildcard type?
Which
methods that use the type parameter in the argument or return type are
accessible in an unbounded wildcard instantiation?
Which
methods that use the type parameter in the argument or return type are
accessible in an upper bound wildcard instantiation?
Which
methods that use the type parameter in the argument or return type are
accessible in a lower bound wildcard instantiation?
Which
methods that use the type parameter as type argument of a parameterized
argument or return type are accessible in a wildcard instantiation?
Which
methods that use the type parameter as upper wildcard bound in a parameterized
argument or return type are accessible in a wildcard instantiation?
Which
methods that use the type parameter as lower wildcard bound in a parameterized
argument or return type are accessible in a wildcard instantiation?
|
How
do I implement a method that takes a wildcard argument?
Using a generic helper method and wildcard
capture.
|
Consider the situation where you decided that a certain
method should take arguments whose type is a wildcard parameterized type.
When you start implementing such a method you will find that you do not
have full access to the argument. This is because wildcards do not
permit certain operations on the wildcard parameterized type.
Example (implementation of a
reverse
method with wildcards;
does not work):
public static void reverse(List
<?>
list) {
List<?> tmp =
new ArrayList<?>
(list);
//
error
for (int i=0;i<list.size();i++){
tmp.
set
(i,list.get(list.size()-i-1));
//
error
}
list = tmp;
}
Using the wildcard type
List<?>
we can neither create a temporary
copy of the argument nor can we invoke the
set
method. A
workaround, that works in this particular case, is use of wildcard capture
and a generic helper method.
Example (corrected implementation of a
reverse
method with
wildcards):
public static void reverse(List
<?>
list) {
rev(list);
}
private static
<T>
void
rev(List
<T>
list) {
List<T> tmp = new ArrayList<T>(list);
for (int i=0;i<list.size();i++){
tmp.set(i,list.get(list.size()-i-1));
}
list = tmp;
}
Wildcard capture makes it possible to invoke a generic helper method.
The helper method does not use any wildcards; it is generic and has a type
parameter instead. It has unrestricted access to its arguments' methods
and can provide the necessary implementation.
Since the helper method has the exact same functionality as the original
method and permits the same set of argument types, one might consider using
it instead of the method with the wildcard argument in the first place.
Example (generic version of the
reverse
method):
public static
<T>
void reverse(List
<T>
list) {
List<T> tmp = new ArrayList<T>(list);
for (int i=0;i<list.size();i++){
tmp.set(i,list.get(list.size()-i-1));
}
list = tmp;
}
|
LINK TO THIS
|
Practicalities.FAQ304
|
REFERENCES
|
What
is the capture of a wildcard?
What
is a parameterized (or generic) method?
Can
I use a wildcard parameterized type like any other type?
Can
I create an object whose type is a wildcard parameterized type?
|
How
do I implement a method that takes a multi-level wildcard argument?
Using several generic helper methods
and wildcard capture.
|
Here is a an example of a method whose argument and return
type is a multi-level wildcard. It is a method that takes a list whose
element type is an arbitrary pair type and return such a list.
The
swapAndReverse
method reverses the order all the list elements
and swaps the members of each pair. It is a contrived example for
the purpose of illustrating the implementation technique.
Example:
class Pair<E> {
private E fst, snd;
public E getFirst() { return fst; }
public void setFirst(S s) { fst = s; }
...
}
class Test {
public static ArrayList<? extends Pair<?>> swapAndReverse(ArrayList
<?
extends Pair<?>>
l) {
...
}
public static void main(String[] args) {
ArrayList<Pair<Integer>>
list = new ArrayList<Pair<Integer>>();
list.add(new Pair<Integer>(-1,1,0));
list.add(new Pair<Integer>(1,0,0));
...
List<?> result =
swapAndReverse(list);
ArrayList<Pair<?>>
list
= new ArrayList<Pair<?>>();
list.add(new Pair<String>("a","b","c"));
list.add(new Pair<Integer>(1,0,-1));
list.add(new Pair<Object>(new
Date(),Thread.State.NEW,5));
...
List<?> result =
swapAndReverse(list);
}
}
The
swapAndReverse
method can be invoked on homogenous lists of
pairs of the same type, such as a
ArrayList<Pair<Integer>>
,
but also on a heterogenous list of pairs of different types, such as
ArrayList<Pair<?>>
.
When we try to implement the method we find that the wildcard argument
type does not permit invocation of the operations that we need.
Example (implementation of a
swapAndReverse
method with wildcards;
does not work):
public static ArrayList<? extends Pair<?>> swapAndReverse(ArrayList
<?
extends Pair<?>>
l) {
ArrayList<? extends Pair<?>> list
=
new ArrayList<? extends Pair<?>>
(l);
//
error
for (int i=0;i<l.size();i++){
list.
set
(i,l.get(l.size()-i-1));
//
error
}
for (Pair<?> pair : list) {
Object e = pair.getFirst();
pair.
setFirst
(pair.getSecond());
//
error
pair.
setSecond
(e);
//
error
}
return list;
}
We cannot create a temporary copy of the list and cannot access the individual
pairs in the list. Hence we apply the capture-helper technique from
above.
Example (implementation of a
swapAndReverse
method with helper
method; does not work):
public static ArrayList<? extends Pair<?>> swapAndReverse(ArrayList
<?
extends Pair<?>>
l) {
return capturePairType(l);
}
private static
<T extends Pair<?>>
ArrayList<T> capturePairType(ArrayList
<T>
l) {
ArrayList<T> list = new ArrayList<T>(l);
for (int i=0;i<l.size();i++){
list.set(i,l.get(l.size()-i-1));
}
for (T pair : list) {
Object e = pair.getFirst();
pair.
setFirst
(pair.getSecond());
//
error
pair.
setSecond
(e);
//
error
}
return list;
}
The compiler will capture the type of the pairs contained in the list,
but we still do not know what type of members the pairs have. We can use
the capture-helper technique again to capture the pairs' type argument.
Example (corrected implementation of a
swapAndReverse
method
with wildcards):
public static ArrayList<? extends Pair<?>> swapAndReverse(ArrayList
<?
extends Pair<?>>
l) {
return capturePairType(l);
}
private static
<T extends Pair<?>>
ArrayList<T> capturePairType(ArrayList
<T>
l) {
ArrayList<T> list = new ArrayList<T>(l);
for (int i=0;i<l.size();i++){
list.set(i,l.get(l.size()-i-1));
}
for (T pair : list) {
captureMemberType(pair);
}
return list;
}
private static
<E>
void
captureMemberType(Pair
<E>
pair)
{
E e = pair.getFirst();
pair.setFirst(pair.getSecond());
pair.setSecond(e);
}
In this case there is no alternative to the stepwise application of the
capture-helper technique. A generic version of the
swapAndReverse
method would have slightly different semantics.
Example (parameterized version of the
swapAndReverse
method):
public static
<
E
,T
extends Pair
<E>
>
ArrayList<T> swapAndReverse(ArrayList
<T>
l) {
ArrayList<T> list = new ArrayList<T>(l);
for (int i=0;i<l.size();i++){
list.set(i,l.get(l.size()-i-1));
}
for (T pair : list) {
E e = pair.getFirst();
pair.setFirst(pair.getSecond());
pair.setSecond(e);
}
return list;
}
This version of the
swapAndReverse
method has one disadvantage:
it does
not
accept a mixed list of pairs of arbitrary types, such
as
ArrayList<Pair<?>>
.
Example:
class Test {
public static void main(String[] args) {
ArrayList<Pair<Integer>>
list = new ArrayList<Pair<Integer>>();
list.add(new Pair<Integer>(-1,1,0));
list.add(new Pair<Integer>(1,0,0));
...
List<?> result =
swapAndReverse(list);
ArrayList<Pair<?>>
list = new ArrayList<Pair<?>>();
list.add(new Pair<String>("a","b","c"));
list.add(new Pair<Integer>(1,0,0));
list.add(new Pair<Object>(new
Date(),Thread.State.NEW,5));
...
List<?> result =
swapAndReverse(list);
// error
}
}
error: <E,T>swapAndReverse(java.util.ArrayList<T>) in Test
cannot be applied to (java.util.ArrayList<Pair<?>>)
List<?> result = swapAndReverse(list);
^
On the other hand, the generic
swapAndReverse
method has the advantage
that it returns a concrete instantiation of
ArrayList
, that does
not suffer from the limitations that come with the wildcard instantiation
that is returned from the wildcard version of the
swapAndReverse
method. |
LINK TO THIS
|
Practicalities.FAQ305
|
REFERENCES
|
How
do I implement a method that takes a wildcard argument?
What
do multi-level wildcards mean?
What
is the capture of a wildcard?
What
is a parameterized or generic method?
What
is a bounded type parameter?
Which
types are permitted as type parameter bounds?
Can
I use a type parameter as part of its own bounds or in the declaration
of other type parameters?
Can
I use a wildcard parameterized type like any other type?
Can
I create an object whose type is a wildcard parameterized type?
|
I
want to pass a U and a X<U> to a method. How do I correctly declare
that method?
Using an upper bound wildcard parameterized
type instead of a concrete parameterized type as the argument type.
|
Example (has a bug):
interface Acceptor<V> {
void accept(
Task<V>
task,
V
v);
}
interface Task<U> {
void go(Acceptor<? super U> acceptor);
}
class AcceptingTask<U> implements Task<U> {
public void go(Acceptor<? super U> acceptor) {
U result = null;
... produce result ...
acceptor.accept(this, result);
//
error
}
}
error: accept(Task<capture of ? super U>,capture of ? super
U)
in Acceptor<capture of ? super U> cannot be applied to (AcceptingTask<U>,U)
acceptor.accept(this,
result);
^
This is the example of a callback interface
Acceptor
and its
accept
method which takes result-producing task and the result. Note that
the
accept
method takes a result of type
V
and a corresponding
task of type
Task<V>
.
The task is described by an interface
Task
. It has a
method
go
that is supposed to produce a result and takes an
Acceptor
,
to which it passes the result.
The class
AcceptingTask
is an implementation of the
Task
interface and in its implementation of the
go
method we see an
invocation of the
accept
method. This invocation fails.
The problem with this invocation is that the
accept
method
is invoked on a wildcard instantiation of the
Acceptor
, namely
Acceptor<?
super U>
. Access to methods through wildcard parameterized types
is restricted. The error message clearly indicates the problem.
Method
accept
in
Acceptor<? super U>
expects
a
Task<capture of ? super U>
and a
capture of ? super U
.
What we pass as arguments are a
AcceptingTask<U>
and a
U
.
The argument of type
U
is fine because the declared argument type
is an unknown supertype of
U
. But the argument of type
AcceptingTask<U>
is
a problem. The declared argument type is an instantiation of
Task
for an unknown supertype of
U
. The compiler does not know which
supertype and therefor rejects all argument types.
The crux is that the signature of the accept method is too restrictive.
If we would permit instantiations of
Task
for subtypes of
U
,
then it would work.
Example (corrected):
interface Acceptor<V> {
void accept(
Task<
? extends
V>
task,
V
v);
}
interface Task<U> {
void go(Acceptor<? super U> acceptor);
}
class AcceptingTask<U> implements Task<U> {
public void go(Acceptor<? super U> acceptor) {
U result = null;
... produce result ...
acceptor.accept(this, result);
//
fine
}
}
With this relaxed signature the
accept
method in
Acceptor<?
super U>
expects a
Task<? extends capture of ? super U>
,
that is, an instantiation of
Task
for a subtype of a supertype
of
U
and
Task<U>
meets this requirement.
The common misunderstanding here is that the signature
accept(Task<V>
task, V v) looks
that I can pass a
Task<U>
whenever I
can pass a
U
. This is true for concrete instantiations of
the enclosing type, but not when wildcard instantiations are used.
The accessibility rules for methods that take the type parameter such as
V
as an argument and methods that take a parameterized type instantiated
on the type parameter such as
Task<V>
are very different.
The solution to the problem is relaxing the signature by using a wildcard
parameterized type as an argument type instead of a concrete parameterized
type. |
LINK TO THIS
|
Practicalities.FAQ306
|
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 parmeterized 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 parameteriezed type?
Which
methods that use the type parameter as upper wildcard bound in a parameterized
argument or return type are accessible in a wildcard instantiation?
Which
methods that use the type parameter as lower wildcard bound in a parameterized
argument or return type are accessible in a wildcard instantiation?
In
a wildcard instantiation, can I read and write fields whose type is the
type parameter?
|
Working
With Generic Interfaces
Can
a class implement different instantiations of the same generic interface?
Can
a subclass implement a different instantiation of a generic interface than
any of its superclasses does?
No, the superclass determines which instantiation
of a generic interface the entire class hierarchy must implement.
|
Example:
class Person implements Comparable<Person> {
public int compareTo(Person arg) { ... }
}
class Student extends Person implements Comparable<Student>
{ // error
public int compareTo(Student arg) { ... }
}
error: java.lang.Comparable cannot be inherited with different
arguments: <Student> and <Person>
class Student extends Person implements Comparable<Student>
{
^
The
Student
subclass would be implementing two different instantiations
of the generic
Comparable
interface, which is illegal. The
consequence is that a superclass that implement a certain instantiation
of a generic interface determines for all its subclasses which instantiation
of the interface they must implement. No subclass can ever implement
another instantiation of the generic interface.
This consequence makes proper use of generic interfaces fairly challenging.
Here is another example of the effect, using the
Delayed
interface
from the
java.util.concurrent
package.
Example (interface
java.util.concurrcent.Delayed
):
public interface Delayed extends Comparable<Delayed>
{
long getDelay(TimeUnit unit);
}
The
Delayed
interface is a sub-interface of an instantiation of
the
Comparable
interface and thereby takes away the chance that
any implementing class can ever be comparable to anything else but a
Delayed
object.
Example:
class SomeClass implements Delayed, Comparable<SomeClass>
{ // error
public long getDelay(TimeUnit unit) { ... }
public int compareTo(Delayed other) { ... }
public int compareTo(SomeClass other) { ... }
}
error: java.lang.Comparable cannot be inherited with
different arguments: <java.util.concurrent.Delayed> and <SomeClass>
class SomeClass implements Delayed, Comparable<SomeClass>
{
^
|
LINK TO THIS
|
Practicalities.FAQ402
|
REFERENCES
|
Can
a class implement different instantiations of the same generic interface?
|
What
happens if a class implements two parameterized interfaces that both define
a method with the same name?
If the two method have the same erasure
then the class is illegal and rejected with a compile-time error message.
|
If, after type erasure, two inherited methods happen to
have the same erasure, then the compiler issues an error message.
Example (of illegal class definition; before type erasure):
interface Equivalent
<T>
{
boolean equalTo(T other);
}
interface EqualityComparable
<T>
{
boolean equalTo(T other);
}
class SomeClass implements Equivalent
<Double>,
EqualityComparable
<SomeClass>
{
//
error
public boolean equalTo(Double other) { ... }
public boolean equalTo(SomeClass other) { ... }
}
error: name clash: equalTo(T) in EqualityComparable<SomeClass>
and equalTo(T) in Equivalent<java.lang.String> have the same erasure,
yet neither overrides the other
class SomeClass implements EqualityComparable<SomeClass>,
Equivalent<Double> {
^
During type erasure the compiler does not only create the type erased versions
of the two colliding interfaces, it would also try to create the necessary
bridge methods. Bridge methods are synthetic methods generated by
the compiler when a class has a parameterized supertype.
Example (after a conceivable translation by type erasure):
interface Equivalent {
boolean equalTo(
Object
other);
}
int
erface EqualityComparable {
boolean equalTo(
Object
other);
}
class SomeClass implements Equivalent, EqualityComparable
{
public boolean equalTo(Double other)
{ ... }
public boolean equalTo(Object other)
{ return equalTo((Double)other); }
public boolean equalTo(SomeClass other) { ... }
public boolean equalTo(Object other)
{ return equalTo((SomeClass)other); }
}
The bridge methods would have the same signature. Instead of resolving
the conflict the compiler reports an error.
By the way, the problem is
not
that the class has several overloaded
versions of the
equalTo
method. The problem stems from the
fact that the interfaces are generic and the methods have the same type
erasure. No problem occurs when the two interfaces have no type parameter.
Example (of legal class definition):
interface Equivalent {
boolean equalTo(
Double
other);
}
interf
ace EqualityComparable {
boolean equalTo(
SomeClass
other);
}
class SomeClass implements Equivalent, EqualityComparable {
public boolean equalTo(Double other) { ... }
public boolean equalTo(SomeClass other) { ... }
}
In the example above the compiler need not generate any bridge methods
because the interfaces are not generic.
Note, that there is no problem if the two interfaces are generic and
the conflicting methods have
different type erasures
.
Example (of legal class definition):
interface Equivalent
<T extends Number>
{
boolean equalTo(T other);
}
interface EqualityComparable
<T>
{
boolean equalTo(T other);
}
class SomeClass implements Equivalent
<Double>,
EqualityComparable
<SomeClass>
{
public boolean equalTo(Double other) { ... }
public boolean equalTo(SomeClass other) { ... }
}
Example (after a conceivable translation by type erasure):
interface Equivalent {
boolean equalTo(
Number
other);
}
int
erface EqualityComparable {
boolean equalTo(
Object
other);
}
class SomeClass implements Equivalent, EqualityComparable
{
public boolean equalTo(Double other)
{ ... }
public
boolean equalTo(Number other) { return equalTo((Double)other);
}
public boolean equalTo(SomeClass other) { ... }
public
boolean equalTo(Object other) { return equalTo((SomeClass)other);
}
}
The two
equalTo
methods have different erasures and then the bridge
method generation mechanism create two bridge methods with different signatures
and no problem occurs.
Effects similar to ones illustrated above can be observed with a parameterized
superclass and a parameterized interface if they have a method with the
same type erasure.
Last but not least, a legal way of implementing two interfaces with
methods that have the same type erasure: as long as the colliding methods
are instantiated for the same type argument there is no problem at all.
Example (of legal class definition):
class SomeClass implements Equivalent
<SomeClass>,
EqualityComparable
<SomeClass>
{
public boolean equalTo(SomeClass other)
{
... }
}
The class provide exactly one method, namely the matching one from both
interfaces and the compiler generates one synthetic bridge method.
No problem.
Example (after type erasure):
class SomeClass implements Equivalent
,
EqualityComparable
{
public boolean equalTo(SomeClass other)
{
... }
public
boolean equalTo(Object other) { return equalTo((SomeClass)other);
}
}
|
LINK TO THIS
|
Practicalities.FAQ403
|
REFERENCES
|
What
is type erasure?
What
is a bridge method?
|
Can
an interface type nested into a generic type use the enclosing type's type
parameters?
No, but as workaround you can generify
the nested interface itself.
|
Nested interfaces are implicitly static. This is sometimes
overlooked because the interface looks like it were a non-static member
of its enclosing class, while in fact it is static. Since type parameters
must not be used in any static context of a generic type, a nested interface
cannot use its enclosing type's type parameters.
Example (of a nested interface):
interface Action {
void run();
}
final class SomeAction implements Action {
public void run() { … }
}
final class Controller
<A extends Action>
{
public interface Command {
void doIt(
A
action);
// error
void undoIt(
A
action);
// error
}
…
}
error: non-static class A cannot be referenced from
a static context
void doIt(A action);
^
error: non-static class A cannot be referenced from a static context
void undoIt(A action);
^
The
Command
interface is nested into the generic
Controller
class. Inside the nested interface we cannot refer to the type
parameter
A
of the enclosing class, because the nested interface
is implicitly static and type parameters must not appear in any static
context.
So, how do we express that the
Command
interface mandates do/undo
methods for different types of actions? The solution is to generify
the interface itself independently of the generic enclosing class.
Example (same as above, but corrected):
interface Action {
void run();
}
final class SomeAction implements Action {
public void run() { … }
}
final class Controller
<A extends Action>
{
public interface Command
<B extends
Action>
{
void doIt(B action);
void undoIt(B action);
}
…
}
|
LINK TO THIS
|
Practicalities.FAQ404
|
REFERENCES
|
Why
can't I use a type parameter in any static context of the generic class?
How
do I refer to an interface type nested into a generic type?
|
Implementing
Infrastructure Methods
How
do I best implement the equals method of a generic type?
Override
Object.equals(Object)
as
usual and perform the type check using the unbounded wildcard instantiation.
|
The recommended implementation of the
equals
method
of a generic type looks like the one shown in the example below.
Conceivable alternatives are discussed and evaluated later.
Example (recommended implementation of
equals
):
class Triple<T> {
private T fst,
snd, trd;
public Triple(T t1, T t2, T t3) {fst
= t1; snd = t2; trd = t3;}
...
public boolean equals(
Object
other) {
if (this == other) return
true;
if (other == null) return
false;
if (this.getClass() !=
other.getClass()) return false;
Triple<?>
otherTriple =
(Triple<?>)
other;
return (this.fst.equals(otherTri
ple.fst)
&& this.snd.equals(otherTriple.snd)
&& this.trd.equals(otherTriple.trd));
}
}
Perhaps the greatest difficulty is the downcast to the triple type, after
the check for type match has been passed successfully. The most natural
approach would be a cast to
Triple<T>
, because only objects
of the same type are comparable to each other.
Example (not recommended):
class Triple<T> {
private T fst,
snd, trd;
public Triple(T t1, T t2, T t3) {fst
= t1; snd = t2; trd = t3;}
...
public boolean equals
(Object other)
{
if (this == other) return
true;
if (other == null) return
false;
if (this.getClass() !=
other.getClass()) return false;
Triple<T>
otherTriple
=
(Triple<T>)
other;
//
unchecked warning
return (this.fst.equals(otherTri
ple.fst)
&& this.snd.equals(otherTriple.snd)
&& this.trd.equals(otherTriple.trd));
}
}
The cast to
Triple<T>
results in an "unchecked cast" warning,
because the target type of the cast is a parameterized type. Only
the cast to
Triple<?>
is accepted without a warning. Let us
try out a cast to
Triple<?>
instead of
Triple<T>
.
Example (better, but does not compile):
class Triple<T> {
private T fst,
snd, trd;
public Triple(T t1, T t2, T t3) {fst
= t1; snd = t2; trd = t3;}
...
public boolean equals
(Object other)
{
if (this == other) return
true;
if (other == null) return
false;
if (this.getClass() !=
other.getClass()) return false;
Triple<T>
otherTriple =
(Triple<?>)
other;
//
error
return (this.fst.equals(otherTri
ple.fst)
&& this.snd.equals(otherTriple.snd)
&& this.trd.equals(otherTriple.trd));
}
}
error: incompatible types
found : Triple<capture of ?>
required: Triple<T>
Triple<T> otherTriple = (Triple<?>)other;
^
This implementation avoids the"unchecked" cast, but does not compile because
the compiler refuses to assign a
Triple<?>
to a
Triple<T>
.
This is because the compiler cannot ensure that the unbounded wildcard
parameterized type
Triple<?>
matches the concrete parameterized
type
Triple<T>
. To make it compile we have to change
the type of the local variable
otherTriple
from
Triple<T>
to
Triple<?>
. This change leads us to the first implementation
shown in this FAQ entry, which is the recommended way of implementing the
equals
method of a generic type.
Evaluation of the alternative implementations.
How do the two alternative implementations, the recommended one casting
to
Triple<?>
and the not recommended one casting to
Triple<T>
,
compare? The recommended implementation compiles without warnings,
which is clearly preferable when we strive for warning-free compilation
of our programs. Otherwise there is no difference in functionality
or behavior, despite of the different cast expressions in the source code.
At runtime both casts boils down to a cast to the raw type
Triple
.
If there is no difference in functionality and behavior and one of the
implementations raises a warning, isn't there a type-safety problem? After
all, "unchecked" warnings are issued to alert the programmer to potentially
unsafe code. It turns out that in this particular cases all is fine.
Let us see why.
With both implementations of
equals
it might happen that triples
of different member types, like a
Triple<String>
and a
Triple<Number>
,
pass the check for type match via
getClass()
and the cast to
Triple<?>
(or
Triple<T>
).
We would then compare members of different type with each other.
For instance, if a
Triple<String>
and a
Triple<Number>
are
compared, they would pass the type check, because they are both triples
and we would eventually compare the
Number
members with the
String
members.
Fortunately, the comparison of a
String
and a
Number
always yields
false
, because both
String.equals
and
Number.equals
return
false
in case of comparison with an object of an imcompatible
type.
In general, every implementation of an
equals
method is responsible
for performing a check for type match and to return
false
in case
of mismach. This rule is still valid, even in the presence of Java
generics, because the signature of
equals
is still the same as
in pre-generic Java: the
equals
method takes an
Object
as an argument. Hence, the argument can be of any reference type and the
implementation of
equals
must check whether the argument is of
an acceptable type so that the actual comparison for equality makes sense
and can be performed.
Yet another alternative.
It might seem natural to provide an
equals
method that has
a more specific signature, such as a version of
equals
in class
Triple
that takes a
Triple<T>
as an argument. This way we would not
need a type check in the first place. The crux is that a version
of
equals
that takes a
Triple<T>
as an argument would
not be an overriding version of
Object.equals(Object)
, because
the
equals
method in
Object
is not generic and the compiler
would not generate the necessary bridge methods. We would have to
provide the bridge method ourselves, which again would result in an "unchecked"
warning.
Example (not recommended):
class Triple<T> {
private T fst,
snd, trd;
public Triple(T t1, T t2, T t3) {fst
= t1; snd = t2; trd = t3;}
...
public boolean equals
(
Triple<T>
other) {
if (this == other) return
true;
if (other == null) return
false;
return (this.fst.equals(other
.fst)
&& this.snd.equals(other.snd)
&& this.trd.equals(other.trd));
}
public boolean equals(Object other)
{
return equals((Triple<T>)
other);
// unchecked
warning
}
}
This implementation has the flaw of raising an "unchecked" warning and
offers no advantage of the recommended implementation to make up for this
flaw. |
LINK TO THIS
|
Practicalities.FAQ501
|
REFERENCES
|
What
is a bridge method?
What
is an "unchecked" warning?
What
is the capture of a wildcard?
What
is a wildcard capture assignment-compatible to?
|
How
do I best implement the clone method of a generic type?
Override
Object.clone()
as usual
and ignore the inevitable unchecked warnings.
|
The recommended implementation of the
clone
method
of a generic type looks like the one shown in the example below.
Example (implementation of
clone
):
class Triple<T> implements Cloneable {
private T fst,
snd, trd;
public Triple(T t1, T t2, T
t3) {fst = t1; snd = t2; trd = t3;}
...
public
Triple<T>
clone()
{
Triple<T> clon = null;
try {
clon =
(Triple<T>)
super.clone();
//
unchecked warning
} catch (CloneNotSupportedException
e) {
throw new InternalError();
}
try {
Class<?> clzz = this.fst.getClass();
Method meth
= clzz.getMethod("clone", new Class[0]);
Object dupl
= meth.invoke(this.fst, new Object[0]);
clon.fst =
(T)
dupl;
//
unchecked warning
} catch (Exception e) {
...
}
try {
Class<?> clzz = this.snd.getClass();
Method meth
= clzz.getMethod("clone", new Class[0]);
Object dupl
= meth.invoke(this.snd, new Object[0]);
clon.snd =
(T)
dupl;
//
unchecked warning
} catch (Exception e) {
...
}
try {
Class<?> clzz = this.trd.getClass();
Method meth
= clzz.getMethod("clone", new Class[0]);
Object dupl
= meth.invoke(this.trd, new Object[0]);
clon.trd =
(T)
dupl;
//
unchecked warning
} catch (Exception e) {
...
}
return clon;
}
}
Return type.
In our implementation we declared the return type of the
clone
method not as type
Object
, but of the more specific generic type.
This is possible, since the overriding rules have been relaxed and an overriding
method in a subclass need no longer have the exact same signature as the
superclass's method that it overrides. Since Java 5.0 it is permitted
that the subclass version of a method returns a type that is a subtype
of the return type of the superclass's method. In our example, the method
clone
in class
Triple<T>
returns a
Triple<T>
and overrides
the
clone
method in class
Object
, which returns an
Object
.
The more specific return type is largely a matter of taste. One
might equally well stick to the traditional technique of declaring the
return type of all
clone
methods as type
Object
.
The more specific return type is beneficial for the users of our triple
class, because it saves them a cast from
Object
down to
Triple<T>
after a call to
Triple<T>.clone
.
"unchecked cast" warnings.
The most annoying aspect of implementing
clone
for a generic
type are the inevitable "unchecked" warnings. The warning stem from two
categories of casts that are needed.
-
Casting the result of
super.clone
to the generic type.
-
Casting the result of cloning any fields to the type that the type parameter
stands for.
Casting the result of super.clone to the generic type.
Part of every implementation of
clone
is the invocation of
the superclass's
clone
method. The result of
super.clone
is either of the supertype itself or of type
Object
. In our example
super.clone
is
Object.clone
, whose return type is
Object
. In
order to access the fields of the clone returned from
super.clone
a cast to own type is needed. In our example this is a cast to the type
Triple<T>
.
The target type of this cast is the generic type itself and the compiler
issues the usual "unchecked cast" warning.
In some cases the cast is not needed at all, namely when the clone produced
by
super.clone
is already deep enough so that the fields of the
clone need not be accessed. This would be the case if all fields
are either of primitive type or of an immutable reference type.
In all other cases, there is no way to avoid the unchecked warning.
A cast to
Triple<?>
instead of
Triple<T>
would
eliminate the unchecked warning, but does not give the required access
to the fields. The two fields in our example would be of type "capture
of ?" to which we cannot assign the result of cloning the individual
fields. Alternatively we might consider a cast to the raw type
Triple
instead
of
Triple<T>
, but that would give us "unchecked assignment"
warnings instead of "unchecked cast" warnings. The compiler would
issue the warnings when we access the fields of our raw triple class.
No matter how we put it, we cannot avoid the unchecked warnings the cast
after
super.clone
. The warnings are harmless and hence best
suppressed by means of the standard annotation
@annotation.SuppressWarnings
.
Cloning the individual fields.
We must invoke the fields' clone method via reflection because we do
not know whether the respective field has an accessible
clone
method. Two factor play a role:
-
Every class inherits a
clone
method from class
Object
,
but
Object.clone
is a
protected
method and for this reason
not part of the
public
interface of a class. In essence, all classes
have a
clone
method, but only a private one, unless they explicitly
provide a public
clone
method.
-
Most classes that have a
clone
method also implement the
Cloneable
interface. The
Cloneable
interface is an empty marker interface
and does not mandate that a
Cloneable
class must have a public
clone
method. Even if we could sucessfully cast down to
Cloneable
we would not have access to a
clone
method. Hence, for purposes
of invoking a
clone
method the
Cloneable
interface is
totally irrelevant.
In the example we use reflection to find out whether the field has a public
clone
method. If it has a
clone
method, we invoke it.
Casting the result of cloning any fields to the type that the type
parameter stands for.
If individual fields must be cloned, the
clone
method of the
respective fields' type must be invoked. The result of this invocation
of the
clone
method is often type
Object
, so that another
cast is necessary. If the field in question has the type that the
enclosing class's type parameter stands for then the target of this cast
is the type variable and the compiler issues the usual "unchecked cast"
warning. In our example we must clone the two fields of the unknown
type
T
, which requires that we invoke the field's
clone
method via reflection. The result of the reflective call is of type
Object
and we must cast from
Object
to the type parameter
T
.
Again, there is no way to avoid the unchecked casts after cloning the fields
and the warnings are best suppressed by means of the standard annotation
@annotation.SuppressWarnings
.
More "unchecked" warnings.
If a class has fields that are of a parameterized type and these fields
must be cloned then a cast from
Object
to the parameterized type
might be necessary and the compiler issues the usual "unchecked cast" warning.
Example:
class Store {
private ArrayList<String> store = new ArrayList<String>();
...
public Store clone() {
Store clon = (Store)super.clone();
clon.store =
(ArrayList<String>)
this.store.clone();
//
unchecked warning
}
}
Again there is no chance to avoid the "unchecked cast" warnings and they
are best suppressed by means of the standard annotation
@annotation.SuppressWarnings
.
The reason for the undesired unchecked warnings in conjunction with
the
clone
method stem from the fact that the
clone
method
is a non-generic legacy method. In situations where generic and non-generic
code is mixed, unchecked warnings cannot be avoided.
Exception Handling.
In the example, we left open how the exceptions from reflective invocation
of the members'
clone
methods should be handled. Should
we suppress the exceptions, or should we map them to a
CloneNotSupportedException
,
or perhaps simply propagate the exceptions to the caller?
Example (excerpt from implementation of
clone
):
public
Triple<T> clone(
)
{
...
try {
Class<?> clzz = this.fst.getClass();
Method meth = clzz.
getMethod
(
"clone"
, new Class[0]);
Object dupl = meth.
invoke
(this.fst, new Object[0]);
clon.fst = (T)dupl;
} catch (Exception
e) {
...
??? what should be done here ??? ...
}
...
}
Usually, a
clone
method does not throw any exceptions; at least
is does not through a
CloneNotSupportedException
. The point
in implementing a
clone
method is to support cloning. Why
should a
clone
method throw a
CloneNotSupportedException
then?
It is equally unusual that a
clone
method would throw any other
exception, because a class knows its fields and their types well enough
to successfully produce a clone of each field.
For a generic class the situation is more complex. We do not know
anything about those fields of the class whose type is a type parameter.
In particular, we do not know whether those fields are
Cloneable
and/or have a
clone
method, as was explained above. The
attempted invocation of the members'
clone
method via reflection
bears the risk of failure, indicated by a number of exceptions raised by
Class.getMethod
and
Method.invoke
such as
NoSuchMethodException
,
IllegalArgumentException
,
etc. In this situation the
clone
method might in fact fail
to produce a clone and it might make sense to indicate this failure by
mapping all (or some) exceptions to a
CloneNotSupportedException
.
Example (throwing a
CloneNotSupportedException
):
public
Triple<T> clone(
)
throws
CloneNotSupportedException
{
...
try {
Class<?> clzz = this.fst.getClass();
Method meth = clzz.
getMethod
(
"clone"
, new Class[0]);
Object dupl = meth.
invoke
(this.fst, new Object[0]);
clon.fst = (T)dupl;
} catch (Exception
e) {
throw
new
CloneNotSupportedException
(e.toString());
}
...
}
On the other hand, one might argue that a type that does not have a clone
method probably needs no cloning because objects of the type can safely
be referenced from many other objects at the same time. Class
String
is an example. Class
String
is neither
Cloneable
nor has it a
clone
method. Class
String
does not
support the cloning feature, because
String
objects are immutable,
that is, they cannot be modified. An immutable object is never copied,
but simply shared among all objects that hold a reference to it.
With our exception handling above the
clone
method of a
Triple<String>
would
throw a
CloneNotSupportedException
, which is not quite appropriate.
It would be preferable to let the original triple and its clone hold references
to the shared string members.
Example (suppressing the
NoSuchMethodException
):
public
Triple<T> clone(
)
{
...
try {
Class<?> clzz = this.fst.getClass();
Method
meth = clzz.
getMethod
(
"clone"
, new Class[0]);
Object dupl = meth.
invoke
(this.fst, new Object[0]);
clon.fst = (T)dupl;
} catch (
NoSuchMethodException
e) {
// exception
suppressed
} catch (
Exception
e) {
throw new
InternalError
(e.toString());
}
...
}
In the exception handling suggested above we suppress the
NoSuchMethodException
under
the assumption that an object without a
clone
method need not
be cloned, but can be shared.
Note, that we cannot ascertain statically by means of type argument
bounds, that the members of a triple have a
clone
method.
We could define the type parameter with
Cloneable
as a bound,
that is, as
class Triple<T extends Cloneable>
, but that would
not avoid any of the issues discussed above. The
Cloneable
interface is an empty tagging interface and does not demand that a cloneable
type has a
clone
method. We would still have to invoke the
clone
method via reflection and face the exception handling issues as before. |
LINK TO THIS
|
Practicalities.FAQ502
|
REFERENCES
|
What
is an "unchecked" warning?
What
is the SuppressWarnings annotation?
|
Using
Runtime Type Information
What
does the type parameter of class java.lang.Class mean?
The type parameter is the type that the
Class
object represents, e.g.
Class<String>
represents
String
.
|
An object of type
java.lang.Class
represents the
runtime type of an object. Such a
Class
object is usually
obtained via the
getClass
method defined in class
Object
.
Alternative ways of obtaining a
Class
object representing a certain
type are use of a class literal or the static method
forName
defined
in class
Class
.
Since Java 5.0 class
java.lang.Class
is a generic class with
one unbounded type parameter. The type parameter is the type that the
Class
object represents. For instance, type
Number
is represented
by a
Class
object of type
Class<Number>
, type
String
by a
Class
object of type
Class<String>
, and so forth.
Parameterized types share the same runtime type and as a result they
are represented by the same
Class
object, namely the
Class
object that represents the raw type. For instance, all instantiations
of
List
, such as
List<Long>
,
List<String>
,
List<?>
,
and the raw type
List
itself are represented by the same
Class
object; this
Class
object is of type
Class<List>
.
In general, the type argument of a
Class
object's type is the
erasure of the type that the
Class
object represents.
Note that the methods
Object.getClass
and
Class.forName
return references of a wildcard type. A side effect is that they cannot
be assigned to a
Class
object of the actual type.
Example (using
Class
objects):
Number n = new Long(0L);
Class<Number> c1 = Number.class;
Class<Number> c2 = Class.forName("java.lang.Number");
//
error
Class<Number> c3 = n.getClass();
//
error
The
forName
method returns a reference of type
Class<?>
,
not of type
Class<Number>
. Returning an object of any
Class
type makes sense because the method can return a
Class
object
representing any type.
The
getClass
method returns a reference of type
Class<?
extends X>
, where
X
is the erasure of the static type of
the expression on which
getClass
is called. Returning
Class<?
extends X>
makes sense because the type
X
might be
a supertype referring to a subtype object. The
getClass
method would then return the runtime type representation of the subclass
and not the representation of the supertype. In the example above the reference
of type
Number
refers to an object of type
Long
, so that
the
getClass
method returns a
Class
object of type
Class<Long>
instead of
Class<Number>
.
Example (corrected):
Number n = new Long(0L);
Class<Number>
c1 = Number.class;
Class<?>
c2 = Class.forName("java.lang.Number");
Class<? extends Number> c3 = n.getClass();
The easiest way of passing around type representations is via a reference
of type
Class<?>
. |
LINK TO THIS
|
Practicalities.FAQ601
|
REFERENCES
|
What
is type erasure?
How
do I pass type information to a method so that it can be used at runtime?
|
How
do I pass type information to a method so that it can be used at runtime?
By means of a
Class
object.
|
The type information that is provided by a type parameter
is static type information that is no longer available at runtime.
When we need type information that is available at runtime we must explicitly
supply the runtime time information to the method. Below are a couple
of situations where the static type information provided by a type parameter
does not suffice.
Example (of illegal or pointless use of type parameter):
public static <T> void someMethod() {
...
new T()
...
//
error
...
new T[
SIZE
]
...
//
error
... ref
instanceof
T
...
//
error
... (T)
ref
..
.
// unchecked warning
}
}
Utilities.
<String>
someMethod();
The type parameter
T
of the method does not provide any type information
that would still be accessible at runtime. At runtime the type parameter
is represented by the raw type of it leftmost bound or type
Object
,
if no bound was specified. For this reason, the compiler refuses
the accept type parameters in
new
expressions, and type checks
based on the type parameter are either illegal or nonsensical.
If we really need runtime type information we must pass it to the method
explicitly. There are 3 techniques for supplying runtime type information
to a method:
-
supply an object
-
supply an array
-
supply a
Class
object
The 3 alternative implementations of the method above would look like this:
Example (of passing runtime type information):
public static <T> void someMethod(
T
dummy) {
Class<?> type
= dummy.getClass();
... use type reflectively ...
}
public static <T> void someMethod(
T[]
dummy) {
... use type reflectively ...
Class<?> type
= dummy.getClass().getComponentType();
}
public static <T> void someMethod(
Class<T>
type) {
... use type reflectively ...
... (T)type.newInstance() ...
... (T[])Array.newInstance(type,SIZE) ...
... type.isInstance(ref) ...
... type.cast(tmp) ...
}
Utilities.someMethod(
new String()
);
Utilities.someMethod(
new String[0]
);
Utilities.someMethod(
String.class
);
The first two alternatives are wasteful, because dummy objects must be
created for the sole purpose of supplying their type information. In addition,
the first approach does not work when an abstract class or an interface
must be represented, because no objects of these types can be created.
The second technique is the classic approach; it is the one taken by
the
toArray
methods of the collection classes in package
java.util
(see
java.util.Collection.toArray(T[])
).
The third alternative is the recommended technique. It provides
runtime type information by means of a
Class
object.
Here are the corresponding operations based on the runtime type information
from the example above, this time performed using reflection.
Example (of reflective use of runtime type information):
public static <T> void som
eMethod(Class<T>
ty
pe) {
...
(T)type.newInstance()
...
...
(T[])Array.newInstance(type,
SIZE
)
...
...
type.isInstance(
ref
)
...
...
type.cast(
tmp
)
...
}
Examples using class
Class
to provide type information
can be found in the subsequent two FAQ entries (see
REFERENCES
or click
here
and
here
). |
LINK TO THIS
|
Practicalities.FAQ602
|
REFERENCES
|
What
does the type parameter of class java.lang.Class mean?
How
do I generically create objects and arrays?
How
do I perform a runtime type check whose target type is a type parameter?
|
How
do I generically create objects and arrays?
Using reflection.
|
The type information that is provided by a type parameter
is static type information that is no longer available at runtime.
It does not permit generic creation of objects or arrays.
Example (of failed generic array creation based on static type information):
class Utilities {
private static final int SIZE = 1024;
public static
<T>
T[] createBuffer() {
return
new T[SIZE]
;
//
error
}
}
public static void main(String[] args) {
String[] buffer = Utilities.
<String>
createBuffer();
}
The type parameter
T
of method
createBuffer
does not
provide any type information that would still be accessible at runtime.
At runtime the type parameter is represented by the raw type of it leftmost
bound or type
Object
, if no bound was specified. For this
reason, the compiler refuses the accept type parameters in
new
expressions.
If we need to generically create an object or array, then we must pass
type information to the
createBuffer
method that persists until
runtime. This runtime type information can then be used to perform the
generic object of array creation via reflection. The type information is
best supplied by means of a
Class
object. (A
Class
object used this way is occasionally called a
type
token
.)
Example (of generic array creation based on runtime type information):
public static
<T>
T[] createBuffer(
Class<T>
type)
{
return (T[])Array.newInstance(type,SIZE);
}
public static void main(String[] args) {
String[] buffer = Utilities.createBuffer(
String.class
);
}
Note that the parameterization of class
Class
allows to ensure
at compile time that no arbitrary types of
Class
objects are passed
to the
createBuffer
method. Only a
Class
object that
represents a runtime type that matches the desired component type of the
created array is permitted.
Example:
String[]
buffer = Utilities.createBuffer(
String.class
);
String[]
buffer = Utilities.createBuffer(
Long.class
);
//
error
Number[]
buffer = Utilities.createBuffer(
Long.class
);
Note also, that arrays of primitive type elements cannot be created
using the aforementioned technique.
Example (of a failed attempt to create an array of primitive type):
class Utilities {
@SuppressWarnings("unchecked")
public static
<T>
T[] slice(T[] src,
Class<T>
type,
int start, int length) {
T[] result = (T[])Array.newInstance(type,length);
System.arraycopy(src, start, result, 0, length);
return result;
}
}
class Test {
public static void main(String[] args) {
double[] avg = new double[]{1.0, 2.0, 3.0};
double[]
res = Utilities.slice(avg,
double.class
,
0, 2);
// error
}
}
error: <T>slice(T[],java.lang.Class<T>,int,int) cannot be
applied to (double[],java.lang.Class<java.lang.Double>,int,int)
double[] res = Utilities.slice(avg,
double.class, 0, 2);
^
Since primitive types are not permitted as type arguments, we cannot invoke
the
slice
method using
double.class
as the type token.
The compiler would have to infer
T:=double
,
which is not permitted because
double
is a primitive type and
cannot be used as the type argument of a generic method. The
slice
method can only create arrays of reference type elements, which means that
we have to convert back and forth between
double[]
and
Double[]
in
the example.
Example (of a successful attempt to create an array of reference type):
class Test {
public static void main(String[] args) {
double[] avg = new double[]{1.0,
2.0, 3.0};
Double[]
avgdup = new Double[avg.length];
for (int i=0; i<avg.length;i++) avgdup[i] = avg[i]; // auto-boxing
Double[]
tmp =
Utilities.
slice(avgdup,
Double.class
,
0, 2);
// fine
avg = new double[tmp.length];
for (int i=0; i<tmp.length;i++) avg[i] = tmp[i];
// auto-unboxing
}
}
|
LINK TO THIS
|
Practicalities.FAQ603
|
REFERENCES
|
What
does the type parameter of class java.lang.Class mean?
How
do I pass type information to a method so that it can be used at runtime?
Are
primitive types permitted as type arguments?
|
How
do I perform a runtime type check whose target type is a type parameter?
Using reflection.
|
The type information that is provided by a type parameter
is static type information that is no longer available at runtime.
It does not permit any generic type checks.
Consider a method that is supposed to extract from a sequence of objects
of arbitrary types all elements of a particular type. Such a method must
at runtime check for a match between the type of each element in the sequence
and the specific type that it is looking for. This type check cannot
be performed by means on the type parameter.
Example (of failed generic type check based on static type information):
class Utilities {
public static
<T>
Collection<T> extract(Collection<?> src) {
HashSet<T> dest = new HashSet<T>();
for (Object o : src)
if (o
instanceof
T
)
// error
dest.add(
(T)
o);
// unchecked warning
return dest;
}
}
public static void test(Collection<?> coll) {
Collection<Integer> coll = Utilities.
<Integer>
extract(coll);
}
Type parameters are not permitted in
instanceof
expressions and
the cast to the type parameter is nonsensical, because it is a cast to
type
Object
after type erasure.
For a type check at runtime we must explicitly provide runtime type
information so that we can perform the type check and cast by means of
reflection. The type information is best supplied by means of a
Class
object.
Example (of generic type check based on runtime type information):
class Utilities {
public static
<T>
Collection<T> extract(Collection<?> src,
Class<T>
type) {
HashSet<T> dest = new HashSet<T>();
for (Object o : src)
if (
type.isInstance(o)
)
dest.add(
type.cast(o)
);
return dest;
}
}
public static void test(Collection<?> coll) {
Collection<Integer> coll = Utilities.extract(coll,
Integer.class
);
}
|
LINK TO THIS
|
Practicalities.FAQ604
|
REFERENCES
|
What
does the type parameter of class java.lang.Class mean?
How
do I pass type information to a method so that it can be used at runtime?
|
Reflection
Which
information related to generics can I access reflectively?
The exact static type information, but
only inexact dynamic type information.
|
Using the reflection API of package
java.lang.reflect
you can access the exact declared type of fields, method parameters and
method return values. However, you have no access to the exact dynamic
type of an object that a reference variable refers to.
Below are a couple of examples that illustrate which information is
available by means of reflection. Subsequent FAQ entries discuss
in greater detail the ways and means of extracting the information.
Here is the short version of how the static and dynamic type information
is retrieved reflectively.
For illustration, we consider the field of a class:
Example (of a class with a field):
class SomeClass {
static Object field = new ArrayList<String>();
...
}
The information regarding the declared type of a field (
static type
information
) can be found like this:
Example (find declared type of a field):
class Test {
public static void main(String[] args) {
Field f = SomeClass.class.
getDeclaredField
("field");
Type t = f.
getGenericType
();
}
}
In order to retrieve the declared type of a field you need a representation
of the field in question as an object of type
java.lang.reflect.Field
.
Such a representation can be found by invoking either the method
getField()
or
getDeclaredField()
of
class
java.lang.Class
. Class
java.lang.reflect.Field
has a method named
getGenericType()
; it returns an object of type
java.lang.reflect.Type
,
which represents the declared type of the field.
The information regarding the type of the object that a reference refers
to (
dynamic type information
) can be found like this:
Example (find actual type of a field):
class Test {
public static void main(String[] args) {
Class<?> c = SomeClass.field.
getClass
();
}
}
In order to retrieve the actual type of an object you need a representation
of its type as an object of type
java.lang.Class
. This type representation
can be found by invoking the method
getClass()
of class
java.lang.Object
.
In the example above, the field
SomeClass.field
is declared
as a field of type
Object
; for this reason
Field.getGenericType()
yields the type information
Object
. This is the static type
information of the field as declared in the class definition.
At runtime the field variable
SomeClass.field
refers to an
object of any subtype of
Object
. The actual type of the
referenced object is retrieved using the object's
getClass()
method,
which is defined in class
Object
. If the field refers to an object
of type
ArrayList<String>
then
getClass()
yields the
raw type information
ArrayList
, but not the exact type information
ArrayList<String>
.
The table below shows further examples of the type information that
is available for the field of a class using
Field.getGenericType()
and
Object.getClass()
.
Declaration of Field
(retrieved via
Class.getField()
)
|
Static Type Information
(retrieved via
Field.getGenericType()
)
|
Dynamic Type Information
(retrieved via
Object.getClass()
)
|
class SomeClass {
Object
field
= new
ArrayList<String>
();
...
}
|
Object
|
regular type
|
ArrayList
|
generic type
|
class SomeClass {
List<String>
field
= new
ArrayList<String>
();
...
}
|
List<String>
|
parameterized type
|
ArrayList
|
generic type
|
class SomeClass {
Set<? extends Number>
field
= new
TreeSet<Long>
();
...
}
|
Set<? extends Number>
|
parameterized type
|
TreeSet
|
generic type
|
class SomeClass<T> {
T
field;
SomeClass(T t) { field = t; }
...
}
SomeClass<CharSequence> object
= new SomeClass<CharSequence>(
"a"
);
|
T
|
type variable
|
String
|
non-generic type
|
class SomeClass {
Iterable<?>[]
field
= new
Collection<?>[0]
;
...
}
|
Iterable<?>[]
|
generic array type
|
[LCollection
|
non-generic type
|
class SomeClass<T> {
T[]
array;
SomeClass(T... arg) { array = arg; }
...
}
SomeClass<String> object
= new SomeClass<String>(
"a"
);
|
T[]
|
generic array type
|
[LString
|
non-generic type
|
|
LINK TO THIS
|
Practicalities.FAQ701
|
REFERENCES
|
java.lang.reflect.Field.getGenericType()
java.lang.Object.getClass()
|
How
do I retrieve an object's actual (dynamic) type?
By calling its
getClass()
method.
|
When you want to retrieve an object's actual type (as opposed
to its declared type) you use a reference to the object in question and
invoke its
getClass()
method.
Example (of retrieving an object's actual type):
class Test {
public static void main(String[] args) {
Object tmp = java.util.EnumSet.allOf(java.util.concurrent.TimeUnit.class);
Class<?> clazz = tmp.
getClass()
;
System.out.println("actual type of Object tmp
is: "+clazz);
}
}
actual type of Object tmp is: class java.util.RegularEnumSet
The
actual
type of the object that the local
tmp
variable
refers to is unknown at compile time. It is some class type that extends
the abstract
EnumSet
class; we do not know which type exactly.
It turns out that in our example the actual type is
java.util.RegularEnumSet
,
which is an implementation specific class type defined by the JDK implementor.
The class is a private implementation detail of the JDK and is not even
mentioned in the API description of the
java.util
package.
Nonetheless the virtual machine can retrieve the actual type of the object
via reflection by means of the
getClass()
method.
In contrast, the
declared
type of the object in question is type
Object
,
because the reference variable
tmp
is of type
Object
.
In this example the declared type is not available through reflection,
because
tmp
is a local variable. The declared type is available
reflectively solely for fields of types, and for return types or parameter
types or exception types of methods. The actual type of an object,
however, can be retrieved for all objects regardless of their declaration:
for local variables, fields of classes, return types of methods, arguments
passed to method, etc.
The
getClass()
method of class
Object
returns an object
of type
java.lang.Class
, which means that the actual type of each
object is represented by a
Class
object. You can extract
various information about the type represented by the
Class
object,
such as "is it a primitive type?", "is it an array type?", "is it an interface,
or a class, or an enum type?", "which fields does the type have?", "which
methods does the type have?", etc. You can additionally find out whether
the
Class
object represents a generic type by asking it: "does
it have type parameters?".
|
LINK TO THIS
|
Practicalities.FAQ702
|
REFERENCES
|
How
do I figure out whether a type is a generic type?
What
is a parameterized or generic type?
How
do I retrieve an object's declared type?
java.lang.Class
|
How
do I retrieve an object's declared (static) type?
By finding the declaration's reflective
representation and calling the appropriate
getGeneric...()
method.
|
When you want to retrieve an object's declared type (as
opposed to its actual type) you first need a representation of the declaration.
-
Field.
For a field of a type you need a representation of that field
in terms of an object of type
java.lang.reflect.Field
. This
can be obtained by one of the methods
getField()
,
getFields()
,
getDeclaredField()
,
or
getDeclaredFields()
of class
Class
.
-
Return Value.
For the return value of a method you need a
representation of the method in terms of an object of type
java.lang.reflect.Method
.
This can be obtained by one of the methods
getMethod()
,
getMethods()
,
getDeclaredMethod()
,
or
getDeclaredMethods()
of class
Class
. Then you invoke
the
getGenericReturnType()
method of class
Method
.
-
Method Parameter.
Same as for the return value.
Once you have a representation of the method, you invoke the
getGenericParameterTypes()
method of class
Method
.
-
Method Exceptions.
Same as for the return value.
Once you have a representation of the method, you invoke the
getGenericExceptionTypes()
method of class
Method
.
Note, that there is no representation of the declaration of a local variable
on the stack of a method. Only the declarations of fields declared in classes,
interfaces or enumeration types, and return types, parameter types, and
exception types of methods have a reflective representation.
Example (of retrieving a field's declared type):
class Test {
private static EnumSet<TimeUnit> set = EnumSet.allOf(TimeUnit.class);
public static void main(String[] args) {
Field field = Test.class.
getDeclaredField
("set");
Type type = field.
getGenericType
();
System.out.println("declared type of field set
is: "+type);
}
}
declared type of field set is: java.util.EnumSet<java.util.concurrent.TimeUnit>
The declared return type, argument type, or exception type of a method
is retrieved similarly by invoking the corresponding
getGeneric...Type()
method.
All these methods return an object of type
java.reflect.Type
,
which means that the declared type of an object is represented by a
Type
object.
Type
is an interface and represents all type-like
constructs in Java reflection. It has five subtypes, as shown in
the subsequent diagram.
Figure: Subtypes of Interface
java.lang.reflect.Type
As you can tell from the diagram, class
Class
is a subtype
of interface
Type
, but it is not the only subtype. A
Type
can represent one of the following type-like things:
-
A regular type
. In this case the
Type
variable refers
to a
Class
object. Examples of regular types are non-generic
types such
String
or
CharSequence
, enumeration types
such as
TimeUnit
, array types with regular component types such
as
String[]
(but
not
Class<?>[]
, because
the component type is a parameterized type), and raw types such as
List
or
Set
. In other words, a regular type is a type that has nothing
to do with generics.
-
A parameterized type.
In this case the
Type
variable
refers to an object of the subtype
ParameterizedType
. Examples
of parameterized types are
List<String>
or
Set<? extends
Number>
or
Iterator<E>
, that is, all types that are instantiations
of generic types and have type arguments.
-
A type variable.
In this case the
Type
variable refers
to an object of the subtype
TypeVariable
. Examples of type
variabes are
T
,
E
,
K
,
V
, that is, the
type parameters of generic types and generic methods.
-
A generic array type.
In this case the
Type
variable
refers to an object of the subtype
GenericArrayType
. Examples
of generic array types are
Class<?>[]
or
T[]
or
Future<Object>[]
or
Iterator<?>[][]
,
that is, all array types with a non-regular component type.
-
A wildcard type.
In this case the
Type
variable refers
to an object of the subtype
WildcardType
. Examples of wildcard
types are
?
or
? extends Number
or
? super T
,
that is, all wildcard expressions. If you retrieved the declared
type of a field or the return type, argument type or exception type of
a method, the resulting
Type
variable can never refer to a
WildcardType
,
because wildcards are not types; they can only be used as type arguments.
Hence, the subtype's name "wildcard type" is slightly misleading.
Only when you retrieve the type argument of a parameterized type you might
come across a
Type
variable that refers to a
WildcardType
.
This would, for instance, happen if you ask for the type argument of the
type
Class<?>
.
Extracting information from the
Type
object returned by
Field.getGenericType()
or a similar method is not as easy as it is to retrieve information from
a
Class
object. When you have a
Class
variable
you simply invoke methods of class
Class
. When you have
a
Type
variable you cannot invoke any methods, because the
Type
interface is an empty interface. Before you can extract any information
you must figure out to which of the 5 subtypes discussed above the
Type
variable refers. This is usually done by a cascade of
instanceof
tests.
Example (of analyzing
java.lang.reflect.Type
):
void analyzeType(
Type
type) {
if (
type instanceof
Class
)
{
// regular type, e.g. String or Date[]
} else if (
type instanceof
ParameterizedType
)
{
// parameteriezd type, e.g. List<String>
or Set<? extends Number>
} else if (
type instanceof
TypeVariable
)
{
// type variable, e.g. T
} else if (
type instanceof
GenericArrayType
)
{
// generic array, e.g. List<?>[] or
T[]
} else if (
type instanceof
WildcardType
)
{
// wildcard, e.g. ? extends Number or
? super Long
} else {
// we should never get here
throw new InternalError("unknown type
representation "+type);
}
}
Once you know what subtype of type
Type
the variable refers to,
you simply cast down to the respective subtype and start retrieving information
by invocation of the subtype's methods. Just browse the respective
type's JavaDoc; most methods are self-explanatory. Here are some
examples:
-
If it is a
Class
then you can pose the usual questions such as
"are you a primitive type?", "are you an array type?", "are you an interface,
or a class, or an enum type?", "which fields do you have?", "which methods
do you have?", "do you have type parameters?", etc.
-
If it is a
ParameterizedType
you can ask "what type arguments
do you have?", "what is your raw type?", etc.
-
If it is a
TypeVariable
you can ask "which bounds do you have?",
"which generic type do you belong to?", etc.
-
If it is a
GenericArrayType
you can ask "what is your component
type?".
-
If it is a
WildcardType
you can as "what is your upper and lower
bound?".
|
LINK TO THIS
|
Practicalities.FAQ703
|
REFERENCES
|
How
do I retrieve an object's actual type?
java.lang.reflect.Type
|
What
is the difference between a generic type and a parameterized type in reflection?
A generic type is represented by a
Class
object; a parameterized type is represented by a
ParameterizedType
object.
|
Generic and parameterized types are easily confused when
you access them via reflection.
-
We say that a type is a
generic
type (as opposed to
non-generic
type) when it declares formal type parameters, that is, placeholders that
can be replaced by type arguments. For instance,
java.util.List
is a generic type because it is declared as
interface List<E> {
... }
and has one type parameter
E
. In contrast, class
java.util.Date
is a non-generic type, because it is a plain, regular class type that does
have formal type parameters.
-
We talk of a
parameterized
type (as opposed to a
raw
type)
when we mean an instantiation of a generic type where the formal type parameters
are replaced by actual type arguments. For instance,
List<String>
is a parameterized type where the type parameter
E
is replaced
by
String
. In constrast,
List
is a raw type. The
same is true for
Date
.
In order to illustrate the difference between generic and parameterized
type, let us consider an example. Say, we want to retrieve the declared
and actual type of the private field
header
of class
java.util.LinkedList
.
The field is declared as follows:
public class LinkedList<E> {
private transient
Entry<E>
header = new Entry<E>(null,
null, null);
...
private static class Entry<T> { ... }
}
where
Entry
is a nested generic type defined in class
LinkedList
and
E
is the
LinkedList
's type parameter.
The
header
field's
declared
type is
Entry<E>
and
its
actual
type is
Entry
. This might be confusing at first
sight, because the header field is declared as field of type
Entry<E>
and it actually refers to an object of type
Entry<E>
.
However, due to type erasure, actual types are always raw types, because
type erasure drops all information regarding type arguments. This
mismatch between declared type and actual type adds to the confusion regarding
the distinction between parameterized and generic types.
In our example, the
header
field's
declared
type is
Entry<E>
and
Entry<E>
is a parameterized type (as opposed to a raw type). This is because
Entry<E>
is an instantiation of the generic type
Entry
rather than the
raw type
Entry
.
The
header
field's
actual
type is the raw type
Entry
(as a side effect of type erasure) and
Entry
is a generic type
(as opposed to a non-generic type). This is because class
Entry
has a formal type parameter
T
.
Declaration of Field
(retrieved via
Class.getField()
)
|
Static Type Information
(retrieved via
Field.getGenericType()
)
|
Dynamic Type Information
(retrieved via
Object.getClass()
)
|
public class LinkedList<E> {
private transient
Entry<E>
header
= new
Entry<E>
(null, null, null);
...
private static class Entry<T> { ... }
}
|
Entry<E>
|
parameterized type
|
Entry
|
generic type
|
Let us consider another example. Say, we want to retrieve
the declared and actual type of the public field
EMPTY_LIST
of class
Collections
. The field is declared as follows:
public class Collections {
public static final
List
EMPTY_LIST = new EmptyList();
...
private static class EmptyList extends AbstractList<Object>
{ ... }
}
where
EmptyList
is a nested type defined in class
Collections
.
The
EMPTY_LIST
field's
declared
type is
List
and its
actual
type is
LinkedList.EmptyList
.
The
EMPTY_LIST
field's
declared
type
List
is
not a parameterized type, because it does not have any type arguments;
it is a raw type. In turn, the raw type
List
is a generic
type, because interface
List
has a formal type parameter
E
.
The
EMPTY_LIST
field's
actual
type
LinkedList.EmptyList
is a non-generic type (as opposed to a generic type), because it does not
have any formal type parameters; it is just a plain, regular class type.
Declaration of Field
(retrieved via
Class.getField()
)
|
Static Type Information
(retrieved via
Field.getGenericType()
)
|
Dynamic Type Information
(retrieved via
Object.getClass()
)
|
public class Collections {
public static final
List
EMPTY_LIST
= new
EmptyList
();
...
private static class EmptyList
extends AbstractList<Object> { ... }
}
|
List
|
regular (raw) type
|
EmptyList
|
non-generic type
|
The starting point for retrieving information regarding parameterized
and generic types is different. Being generic or non-generic is a property
of a type that is represented by a
Class
object. In contrast,
whether a type is parameterized or raw is a property of a type represented
by a
Type
object. As a result, we need a
Class
object to distinguish between
generic
or
non-generic
and we need a
Type
object to distinguish between
parameterized
and
raw
.
The method below distinguishes between a parameterized and a raw type.
It needs a
Type
object for this distinction.
Example (of distinction between parameterized and raw type):
static boolean isParameterizedType(
Type
type) {
if (type instanceof ParameterizedType)
return true;
else
return false;
}
The methods below distinguish between a generic and a non-generic type.
The distinction regarding generic and non-generic requires a
Class
object.
Example (of distinction between generic and non-generic type):
static boolean isGenericType(
Class<?>
clazz)
{
TypeVariable<?>[] params = clazz.getTypeParameters();
if (params != null && params.length > 0) {
return true;
}
else {
return false;
}
}
static boolean isGenericType(
Type
type) {
if (type instanceof Class && isGenericType((Class<?>)type))
return true;
else
return false;
}
The overloaded version of the method that takes a
Type
object
delegates to the other version of the method that takes a
Class
object, because only
Class
objects provide the information whether
the type in question has type parameters (i.e. is generic), or not. |
LINK TO THIS
|
Practicalities.FAQ704
|
REFERENCES
|
What
is a parameterized or generic type?
Which
information related to generics can I access reflectively?
What
is type erasure?
How
do I figure out whether a type is a generic type?
Which
information is available about a generic type?
How
do I figure out whether a type is a parameterized type?
Which
information is available about a parameterized type?
|
How
do I figure out whether a type is a generic type?
By asking it whether it has type parameters.
|
When you have the type representation of a type in form
of a
Class
object then you can find out whether the type represents
a generic type by retrieving its type parameters. If it does not
have any type parameters then the type is a non-generic type, otherwise
it is a generic type. Here is an example:
Example (of distinction between generic and non-generic type):
Object object = new LinkedHashMap<String,Number>();
Class<?> clazz =
object.getClass()
;
TypeVariable<?>[] params =
clazz.getTypeParameters()
;
if (params != null && params.length > 0) {
System.out.println(clazz + " is a GENERIC TYPE");
// generic type, e.g. HashSet
}
else {
System.out.println(clazz + " is a NON-GENERIC TYPE");
// non-generic type, e.g. String
}
class java.util.LinkedHashMap is a GENERIC TYPE
We obtain the
Class
object by calling the
getClass()
of an object. The
Class
object represents the type
LinkedHashMap
in our example. Note that
getClass()
returns the actual
dynamic type of an object and the actual dynamic type is always a raw type
because of type erasure.
Then we retrieve the type parameters by callling
getTypeParameters()
.
If type parameters are returned then the type is a generic type, otherwise
it is non-generic. |
LINK TO THIS
|
Practicalities.FAQ705
|
REFERENCES
|
What
is a parameterized or generic type?
What
is the difference between a generic type and a parameterized type in reflection?
|
Which
information is available about a generic type?
All the information that is available
for regular types plus information about the generic type's type parameters.
|
A generic type is represented by a
Class
object.
For this reason we can retrieve all the information about a generic type
that is also available for regular non-generic types, such as fields, methods,
supertypes, modifiers, annotations, etc. Different from a non-generic type
a generic type has type parameters. They can be retrieved by means of the
getTypeParameters()
method.
Let us take a look at an example, namely the generic class
EnumSet<E
extends Enum<E>>
.
Example (of retrieving information about a generic type):
Object object = new EnumMap<TimeUnit,Number>(TimeUnit.class);
Class<?>
clazz = object.getClass();
TypeVariable<?>[]
params = clazz.
getTypeParameters()
;
if (params != null && params.length > 0) {
System.out.println(clazz + " is a GENERIC TYPE with "+params.length+"
type parameters");
System.out.println();
for (TypeVariable<?> typeparam : params) {
System.out.println("\t"+typeparam);
}
}
else {
System.out.println(clazz + " is a NON-GENERIC TYPE");
}
class java.util.EnumMap is a GENERIC TYPE with 2 type parameters
TYPE PARAMETERS:
K
V
|
LINK TO THIS
|
Practicalities.FAQ706
|
REFERENCES
|
How
do I figure out whether a type is a generic type?
Which
information is available about a type parameter?
java.lang.Class
java.lang.reflect.GenericDeclaration
java.lang.reflect.Type
java.lang.reflect.TypeVariable
|
How
do I figure out whether a type is a parameterized type?
By asking whether the type representation
is a
ParameterizedType
.
|
When you have the type representation of a type in form
of a
Type
object then you can find out whether the type represents
a parameterized type (as opposed to a raw type) by checking whether the
type representation refers to an object of a type that implements the
ParameterizedType
interface. Here is an example:
Example (of distinction between parameterized and regular (raw) type):
Method method = EnumSet.class.getMethod("clone");
System.out.println("METHOD: "+method.toGenericString());
Type returnType = method.getGenericReturnType();
if (returnType
instanceof ParameterizedType
) {
System.out.println(returnType + " is a PARAMETERIZED
TYPE");
} else if (returnType instanceof Class) {
System.out.println(returnType + " is a RAW TYPE");
} else {
System.out.println(returnType + " is something else");
}
METHOD: public java.util.EnumSet<E> java.util.EnumSet.clone()
java.util.EnumSet<E> is a PARAMETERIZED TYPE
First we retrieve the declared return type of the
clone()
method
of class
EnumSet
. by calling the
getGenericReturnType()
method
of class
java.lang.reflect.Method
. The resulting
Type
object represents the
clone()
method's return type, which in our
example is
EnumSet<E>
. Then we verify that the return
type is a parameterized type by means of an
instanceof
test. |
LINK TO THIS
|
Practicalities.FAQ707
|
REFERENCES
|
Which
information is available about a parameterized type?
|
Which
information is available about a parameterized type?
Information about the parameterized type's
type arguments, its corresponding raw type, and its enclosing type if it
is a nested type or inner class.
|
A parameterized type is represented by a
ParameterizedType
object. A parameterized type has actual type arguments, a corresponding
raw type, and you can find out which enclosing type the parameterized type
belongs to if it a nested type or inner class.
Let us take a look at an example, namely the parameterized type
EnumMap<K,V>
, which we retrieve as the return type of the
clone()
method of
class
EnumMap
..
Example (of retrieving information about a parameterized type):
Method method = EnumMap.class.getMethod("clone");
System.out.println("METHOD: "+method.toGenericString());
Type returnType = method.getGenericReturnType();
if (returnType instanceof ParameterizedType) {
System.out.println(returnType + " is a PARAMETERIZED
TYPE");
ParameterizedType type = (ParameterizedType) returnType;
Type rawType = type.
getRawType()
;
System.out.println("raw type : " + rawType);
Type ownerType = type.
getOwnerType()
;
System.out.println("owner type: " + ownerType
+ ((ownerType != null) ? "" : ", i.e. is a top-level type"));
Type[] typeArguments = type.
getActualTypeArguments()
;
System.out.println("actual type arguments: ");
for (Type t : typeArguments)
System.out.println("\t" + t);
}
METHOD: public java.util.EnumMap<K, V> java.util.EnumMap.clone()
java.util.EnumMap<K, V> is a PARAMETERIZED TYPE
raw type : class java.util.EnumMap
owner type: null, i.e. is a top-level type
actual type arguments:
K
V
|
LINK TO THIS
|
Practicalities.FAQ708
|
REFERENCES
|
How
do I figure out whether a type is a parameterized type?
java.lang.reflect.ParameteriezedType
|
How
do I retrieve the representation of a generic method?
By retrieving the type erasure of the
generic method.
|
Generic methods are retrieved like non-generic methods:
the
getMethod()
method of class
Class
is invoked providing
a description of the method's type signature, that is, the name of the
method and the raw types of the parameter types. What we supply is
a description of the method's type erasure; we need not specify in any
way, that the method is a generic method.
As an example let us retrieve the representation of the generic
toArray()
method of interface
Collection
. It is declared as:
interface Collection<E> {
...
<T> T[] toArray(T[] a) { ... }
}
Example (of retrieving the representation of a generic method):
Method method = Collection.class.getMethod("toArray",
Object[].class
);
System.out.println("METHOD: "+method.toGenericString());
METHOD: public abstract <T> T[] java.util.Collection.toArray(T[])
Note, that we did not mention whether we are looking for a generic or a
non-generic method. We just supplied the method name "
toArray
"
and specified its parameter type as
Object[]
, which is the type
erasure of the declared parameter type
T[]
.
Note, that there is some minor potential for confusion regarding the
method description that is delivered by the resulting
Method
object.
In the example above, we retrieved the method description using the
toGenericString()
method
of class
Method
.
System.out.println("METHOD: "+method.
toGenericString()
);
METHOD: public abstract <T> T[] java.util.Collection.toArray(T[])
It describes the generic method's signature including information regarding
its type parameter
T
. Had we used the
toString()
method instead, the resulting method description had described the type
erasure of the method.
System.out.println("METHOD: "+method.
toString()
);
METHOD: public abstract java.lang.Object[] java.util.Collection.toArray(java.lang.Object[])
The confusing element here is the fact that
toString()
does not
deliver a description of the method as it is declared, but of its type
erasure. |
LINK TO THIS
|
Practicalities.FAQ709
|
REFERENCES
|
How
do I figure out whether a method is a generic method?
What
is a generic declaration?
|
How
do I figure out whether a method is a generic method?
By asking it whether it has type parameters.
|
Starting with the reflective representation of a method
in form of a
Method
object you can find out whether the method
is generic or non-generic by retrieving its type parameters. (Note,
we are looking for
type
parameters, not
method
parameters.)
If the method does not have any type parameters then it is a non-generic
method, otherwise it is a generic method. Here is an example:
Example (of distinction between generic and non-generic method):
Method method = Collection.class.
getMethod
("toArray",Object[].class);
TypeVariable[] typeParams =
method.getTypeParameters()
;
if (typeParams!=null && typeParams.length>0) {
System.out.println(method.getName()+" is a GENERIC
METHOD");
} else {
System.out.println(method.getName() +" is a NON-GENERIC
METHOD");
}
toArray is a GENERIC METHOD
We obtain the
Method
object by calling the
getMethod()
method of the
Class
object that represents the type whose method
we are looking for. In our example the
Method
object represents
the generic
toArray()
of interface
Collection
.
Then we retrieve the type parameters by callling
getTypeParameters()
.
If type parameters are returned then the method is a generic method, otherwise
it is non-generic. |
LINK TO THIS
|
Practicalities.FAQ710
|
REFERENCES
|
Which
information is available about a generic method?
How
do I figure out whether a type is a generic type?
What
is a generic declaration?
|
Which
information is available about a generic method?
All the information that is available
for regular methods plus information about the generic method's type parameters.
|
A generic method is represented by a
Method
object.
For this reason we can retrieve all the information about a generic method
that is also available for regular non-generic methods, such as return
type,
method parameter types, exception types, declaring class, modifiers, annotations,
etc. Different from a non-generic method a generic method has type parameters.
They can be retrieved by means of the
getTypeParameters()
method.
Type parameters are represented by
TypeVariable
objects.
Let us take a look at an example, namely the generic method
<T extends Object & Comparable<? super T>> T Collections.max(Collection<?
extends T>)
.
Example (of retrieving information about a generic method):
Method theMethod = Collections.class.
getMethod
("max",Collection.class);
System.out.println("analyzing method: ");
System.out.println(theMethod.toGenericString()+"\n");
TypeVariable[] typeParams = theMethod.
getTypeParameters
();
if (typeParams!=null && typeParams.length>0) {
System.out.println("GENERIC METHOD");
System.out.println("type parameters: ");
for (TypeVariable v : typeParams) {
System.out.println("\t"+v);
}
} else {
System.out.println("NON-GENERIC METHOD");
}
System.out.println();
Type type = theMethod.
getGenericReturnType
();
System.out.println("generic return type of method "+theMethod.getName()+":
" + type);
System.out.println();
Type[] genParamTypes = theMethod.
getGenericParameterTypes
();
if (genParamTypes == null || genParamTypes.length == 0) {
System.out.println("no parameters");
} else {
System.out.println("generic parameter types: ");
for (Type t : genParamTypes) {
System.out.println("\t"+t);
}
}
System.out.println();
Type[] genExcTypes = theMethod.
getGenericExceptionTypes
();
if (genExcTypes == null || genExcTypes.length == 0) {
System.out.println("no exceptions");
} else {
System.out.println("generic exception types: ");
for (Type t : genExcTypes) {
System.out.println("\t"+t);
}
}
analyzing method:
public static <T> T java.util.Collections.max(java.util.Collection<?
extends T>)
GENERIC METHOD
type parameters:
T
generic return type of method max: T
generic parameter types:
java.util.Collection<? extends T>
no exceptions
Do not confuse
getParameterTypes()
with
getTypeParameters()
.
The methods
getParameterTypes()
and
getGenericParameterTypes()
return the types of the method parameters; in our example the type
Collection<?
extends T>
. The method
getTypedParameters()
returns
a generic method's type parameters; in our example the type parameter
T
. |
LINK TO THIS
|
Practicalities.FAQ711
|
REFERENCES
|
How
do I figure out whether a method is a generic method?
What
is a generic declaration?
java.lang.reflect.Method
Which
information is available about a type parameter?
|
Which
information is available about a type parameter?
The type parameter's name, its bounds,
and the generic type or method that the type parameter belongs to.
|
Type parameters of generic types and methods are represented
by
TypeVariable
objects. A type parameter has a name, bounds,
and you can find out which generic type or method the type parameter belongs
to.
Let us take a look at an example, namely the type parameter of the generic
class
EnumSet<E extends Enum<E>>
.
Example (of retrieving information about a generic type):
Object object = new EnumMap<TimeUnit,Number>(TimeUnit.class);
Class<?>
clazz = object.getClass();
TypeVariable<?>[]
params = clazz.
getTypeParameters()
;
if (params != null && params.length > 0) {
System.out.println(clazz + " is a GENERIC TYPE with "+params.length+"
type parameters");
System.out.println();
for (TypeVariable<?> typeparam : params) {
System.out.println(typeparam + " is a TYPE VARIABLE");
System.out.println("name : " + typeparam.
getName()
);
GenericDeclaration genDecl = typeparam.
getGenericDeclaration()
;
System.out.println("is type parameter of generic
declaration: " + genDecl);
Type[] bounds = typeparam.
getBounds()
;
System.out.println("bounds: ");
for (Type bound : bounds)
System.out.println("\t" + bound
+ "\n");
System.out.println();
}
}
else {
System.out.println(clazz + " is a NON-GENERIC
TYPE");
}
class java.util.EnumMap is a GENERIC TYPE with 2 type parameters
K is a TYPE VARIABLE
name : K
is type parameter of generic declaration: class java.util.EnumMap
bounds:
java.lang.Enum<K>
V is a TYPE VARIABLE
name : V
is type parameter of generic declaration: class java.util.EnumMap
bounds:
class java.lang.Object
|
LINK TO THIS
|
Practicalities.FAQ712
|
REFERENCES
|
How
do I figure out whether a method is a generic method?
What
is a generic declaration?
java.lang.reflect.Method
|
What
is a generic declaration?
What
is a wildcard type?
A wildcard expression; it appears as
the type argument of a parameterized type.
|
In Java reflection a
wildcard type
is a wildcard
expression such as "
? extends Number
". It is represented
by an object of type
java.lang.reflect.WildcardType
and can appear
solely as a type argument of a parameterized type. The term "wildcard type"
is slightly misleading, because a wildcard is not a type like the return
type of a method or the type of a field. More correctly it is a type
argument of a parameterized type.
Let us take a look at an example, namely the wildcards that appear in
the signature of the generic method
<T extends Object & Comparable<
?
super T
>> T Collections.max(Collection<
? extends T
>)
.
The first wildcard appears in the bounds of the method's type parameter
T
;
its second bound is
Comparable<? super T>
, which is a parameterized
type, and its type argument is the wildcard "
? super T
".
The second wildcard appears in the method's declared argument type
Collection<?
extends T>
, which is a parameterized type, and its type argument is
the wildcard "
? extends T
".
Here is how the wildcard in the bound is retrieved:
Example (of a wildcard in Java reflection):
Method
method = Collections.class.
getMethod
("max",Collection.class);
System.out.println("METHOD: "+method.toGenericString());
TypeVariable<Method>
typeParameter
= method.
getTypeParameters
()[0];
System.out.println("TYPE PARAMETER: "+typeParameter);
ParameterizedType
bound = (ParameterizedType)typeParameter.
getBounds
()[1];
System.out.println("TYPE PARAMETER BOUND: "+bound);
WildcardType
wildcard = (
WildcardType
)bound.
getActualTypeArguments
()[0];
System.out.println("WILDCARD: "+wildcard);
METHOD: public static <T> T java.util.Collections.max(java.util.Collection<?
extends T>)
TYPE PARAMETER: T
TYPE PARAMETER BOUND: java.lang.Comparable<? super T>
WILDCARD: ? super T
We retrieve the method
Collections.max
via
Class.getMethod()
and its type parameter
T
via
GenericDeclaration.getTypeParameters()
.
The result is the representation of the generic method`s type parameter
T
as
an object of type
java.lang.reflect.TypeVariable. W
e retrieve
the type variable's two bounds via
TypeVariable.getBounds()
.
The second bound is
Comparable<? super T>
and it is represented
by an object of type
java.lang.reflect.ParameterizedType
.
W
e
retrieve its type argument
? super T
via
ParameterizedType.getActualTypeArguments()
and
check whether the type argument is a wildcard expression by checking whether
it is represented by an object of type
java.lang.reflect.WildcardType
.
Here is how the wildcard in the declared method parameter type is retrieved:
Example (of a wildcard in Java reflection):
Method
method = Collections.class.
getMethod
("max",Collection.class);
System.out.println("METHOD: "+method.toGenericString());
ParameterizedType
methodParameterType
= (ParameterizedType)method.
getGenericParameterTypes
()[0];
System.out.println("METHOD PARAMETER TYPE: "+methodParameterType);
WildcardType
wildcard = (WildcardType)methodParameterType.
getActualTypeArguments
()[0];
System.out.println("WILDCARD: "+wildcard);
METHOD: public static <T> T java.util.Collections.max(java.util.Collection<?
extends T>)
METHOD PARAMETER TYPE: java.util.Collection<? extends T>
WILDCARD: ? extends T
We obtain a representation of the method as before and this time retrieve
the type of its method parameter
Collection<? extends T>
via
Method.getGenericParameterTypes()
.
The result is the representation of the parameterized type
Collection<?
extends T>
as an object of type
java.lang.reflect.ParameterizedType.
W
e retrieve its type argument
? extends T
via
ParameterizedType.getActualTypeArguments()
and check whether the type argument is a wildcard expression by checking
whether it is represented by an object of type
java.lang.reflect.WildcardType
. |
LINK TO THIS
|
#FAQ714
|
REFERENCES
|
Which
information is available about a wildcard?
|
Which
information is available about a wildcard?
The upper and the lower bound.
|
Wildcards can have an upper or a lower bound. Consequently,
a wildcard represented reflectively by an object of type
java.lang.reflect.Wildcard
supports retrieval of the bound.
For illustration, let us revisit the wildcards from the previous FAQ
entry
Practicalities.FAQ714
,
namely the wildcards that appear in the method signature
<T extends Object & Comparable<
?
super T
>> T Collections.max(Collection<
? extends T
>)
.
Say, we retrieved a presentation of the wild "
? super T
" as described
in the previous FAQ entry
Practicalities.FAQ714
.
Then we can obtain it upper bound by calling the methods
Wildcard.getLowerBounds()
and
Wildcard.getUpperBounds()
.
Example (of retrieving a wildcard's bound):
Method method = Collections.class.getMethod("max",Collection.class);
TypeVariable<Method> typeParameter = method.getTypeParameters()[0];
ParameterizedType bound = (ParameterizedType)typeParameter.getBounds()[1];
WildcardType wildcard = (WildcardType)bound.getActualTypeArguments()[0];
System.out.println("WILDCARD: "+wildcard);
Type[] lowerBounds = wildcard.
getLowerBounds
();
System.out.print("lower bound: ");
if (lowerBounds != null && lowerBounds.length > 0) {
for (Type t : lowerBounds)
System.out.println("\t" + t);
}
else {
System.out.println("\t" + "<none>");
}
Type[] upperBounds = wildcard.
getUpperBounds
();
System.out.print("upper bound: ");
if (upperBounds != null && upperBounds.length > 0) {
for (Type t : upperBounds)
System.out.println("\t" + t);
}
else {
System.out.println("\t" + "<none>");
}
WILDCARD: ? super T
lower bound: T
upper bound: class java.lang.Object
Interestingly, we can retrieve upper
and
lower bounds although a
wildcard can have at most one bound - either an upper bound or a lower
bound, but never both.
The wildcard "
? super T
" has a lower bound, but no upper bound.
Yet the
getUpperBounds()
method returns an upper bound, namely
Object
,
which makes sense because
Object
can be seen as the default upper
bound of every wildcard.
Conversely, the wildcard "
? extends T
" has an upper bound,
but no lower bound. The
getLowerBounds()
method returns
a zero-length array in that case.
This is illustrated by the wildcard in the method's parameter type
Collection<?
extends T>
. Say, we retrieved a presentation of the wild "
?
extends T
" as described in the previous FAQ entry
Practicalities.FAQ714
.
Then we can try out which bounds the methods
Wildcard.getLowerBounds()
and
Wildcard.getUpperBounds()
return.
Example (of retrieving a wildcard's bound):
Method method = Collections.class.getMethod("max",Collection.class);
ParameterizedType methodParameterType = (ParameterizedType)method.getGenericParameterTypes()[0];
WildcardType wildcard = (WildcardType)methodParameterType.getActualTypeArguments()[0];
System.out.println("WILDCARD: "+wildcard);
Type[] lowerBounds = wildcard.
getLowerBounds
();
System.out.print("lower bound: ");
if (lowerBounds != null && lowerBounds.length > 0) {
for (Type t : lowerBounds)
System.out.println("\t" + t);
}
else {
System.out.println("\t" + "<none>");
}
Type[] upperBounds = wildcard.
getUpperBounds
();
System.out.print("upper bound: ");
if (upperBounds != null && upperBounds.length > 0) {
for (Type t : upperBounds)
System.out.println("\t" + t);
}
else {
System.out.println("\t" + "<none>");
}
WILDCARD: ? extends T
lower bound: <none>
upper bound: T
|
LINK TO THIS
|
Practicalities.FAQ715
|
REFERENCES
|
What
is a wildcard type?
java.lang.reflect.Wildcard
|
CONTENT
PREVIOUS
NEXT
INDEX
|