Java Generics FAQs - Under The Hood Of The Compiler
Technicalities
- Under The Hood Of The Compiler
© Copyright 2003-2022 by Angelika Langer. All
Rights Reserved.
Under The Hood Of The Compiler
Compiler Messages
What
is an "unchecked" warning?
A warning by which the compiler indicates
that it cannot ensure type safety.
|
The term "unchecked" warning is misleading. It does
not mean that the warning is unchecked in any way. The term "unchecked"
refers to the fact that the compiler and the runtime system do not have
enough type information to perform all type checks that would be necessary
to ensure type safety. In this sense, certain operations are "unchecked".
The most common source of "unchecked" warnings is the use of raw types.
"unchecked" warnings are issued when an object is accessed through a raw
type variable, because the raw type does not provide enough type information
to perform all necessary type checks.
Example (of unchecked warning in conjunction with raw types):
TreeSet
se
t = new TreeSet();
set.add("abc");
//
unchecked warning
set.remove("abc");
warning: [unchecked] unchecked call to add(E) as a member of the
raw type java.util.TreeSet
set.add("abc");
^
When the
add
method is invoked the compiler does not know whether
it is safe to add a
String
object to the collection. If
the
TreeSet
is a collection that contains
String
s (or
a supertype thereof), then it would be safe. But from the type information
provided by the raw type
TreeSet
the compiler cannot tell.
Hence the call is potentially unsafe and an "unchecked" warning is issued.
"unchecked" warnings are also reported when the compiler finds a cast
whose target type is either a parameterized type or a type parameter.
Example (of an unchecked warning in conjunction with a cast to a parameterized
type or type variable):
class Wrapper<T> {
private T wrapped
;
public
Wrapper
(T
arg) {wrapped = arg;}
...
p
ublic
Wrapper
<T>
clone()
{
Wrapper<T> clon = null;
try {
clon =
(Wrapper<T>)
super.clone();
//
unchecked warning
} catch (CloneNotSupportedException
e) {
throw new InternalError();
}
try {
Class<?> clzz = this.wrapped.getClass();
Method meth
= clzz.getMethod("clone", new Class[0]);
Object dupl
= meth.invoke(this.wrapped, new Object[0]);
clon.wrapped =
(T)
dupl;
//
unchecked warning
} catch (Exception e) {}
return clon;
}
}
warning: [unchecked] unchecked cast
found : java.lang.Object
required:
Wrapper
<T>
clon = (
Wrapper
<T>)super.clone();
^
warning: [unchecked] unchecked cast
found : java.lang.Object
required: T
clon.
wrapped
= (T)dupl;
^
A cast whose target type is either a (concrete or bounded wildcard) parameterized
type or a type parameter is unsafe, if a dynamic type check at runtime
is involved. At runtime, only the type erasure is available, not
the exact static type that is visible in the source code. As a result,
the runtime part of the cast is performed based on the type erasure, not
on the exact static type.
In the example, the cast to
Wrapper<T>
would check whether
the object returned from
super.clone
is a
Wrapper
, not
whether it is a wrapper with a particular type of members. Similarly,
the casts to the type parameter
T
are cast to type
Object
at runtime, and probably optimized away altogether. Due to type erasure,
the runtime system is unable to perform more useful type checks at runtime.
In a way, the source code is misleading, because it suggests that a
cast to the respective target type is performed, while in fact the dynamic
part of the cast only checks against the type erasure of the target type.
The "unchecked" warning is issued to draw the programmer's attention to
this mismatch between the static and dynamic aspect of the cast. |
LINK TO THIS
|
Technicalities.FAQ001
|
REFERENCES
|
What
does type-safety mean?
How
can I disable or enable unchecked warnings?
What
is the raw type?
Can
I use a raw type like any other type?
Can
I cast to a parameterized type?
Can
I cast to the type that the type parameter stands for?
|
How
can I disable or enable "unchecked" warnings?
Via the compiler options
-Xlint:unchecked
and
-Xlint:-unchecked
and via the standard annotation
@SuppressWarnings("unchecked")
.
|
The compiler option
-Xlint:-unchecked
disables
all
unchecked warnings that would occur in a compilation.
The annotation
@SuppressWarnings("unchecked")
suppresses all
warnings for the annotated part of the program.
Note, in the first release of Java 5.0 the
SuppressWarnings
annotation is not yet supported. |
LINK TO THIS
|
Technicalities.FAQ002
|
REFERENCES
|
What
is the -Xlint:unchecked compiler option?
What
is the SuppressWarnings annotation?
|
What
is the -Xlint:unchecked compiler option?
The compiler option -Xlint:unchecked
enables "unchecked" warnings, the option -Xlint:-unchecked disables all
unchecked warnings.
|
"unchecked" warnings are by default disabled. If
you compile a program with no particular compiler options then the compiler
will not report any "unchecked" warnings. If the compiler finds source
code for which it would want to report an "unchecked" warning it only gives
a general hint. You will find the following note at the end of the
list of all other errors and warnings:
Note: util/Wrapper.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
If you want to see the "unchecked" warnings you must start the compiler
with the
-Xlint:unchecked
option.
Example (of globally enabling unchecked warnings):
javac
-Xlint:unchecked
util/Wrapper.java
The option
-Xlint:unchecked en
ables the "unchecked" warnings.
The "unchecked" warnings are also enabled when you use the
-Xlint:all
option.
The option
-Xlint:-unchecked
disables the "unchecked" warnings.
This is useful to suppress all "unchecked" warnings, while other
types of warnings remain enabled.
Example (of globally disabling unchecked warnings):
javac -g -source 1.5
-Xlint:all
-Xlint:-unchecked
util/Wrapper.java
In this example, using
-Xlint:all
all warnings (such as "unchecked",
"deprecated", "fallthrough", etc.) are enabled and subsequently the "unchecked"
warnings are disabled using
-Xlint:-unchecked
. As a result all
warnings except "unchecked" warnings will be reported. |
LINK TO THIS
|
Technicalities.FAQ003
|
REFERENCES
|
What
is an "unchecked" warning?
What
is the SuppressWarnings annotation?
|
What
is the SuppressWarnings annotation?
A standard annotation that suppresses
warnings for the annotated part of the program.
|
The compiler supports a number of standard annotations
(see package
java.lang.annotation
).
Among them is the
SuppressWarnings
annotation. It contains a list
of warning labels. If a definition in the source code is annotated
with the
SuppressWarnings
annotation, then all warnings, whose
labels appear in the annotation's list of warning labels, are suppressed
for the annotated definition or any of its parts.
The
SuppressWarnings
annotation can be used to suppress any
type of labelled warning. In particular we can use the annotation
to suppress "unchecked" warnings.
Example (of suppressing unchecked warnings):
@SuppressWarnings("unchecked")
class Wrapper<T> {
private T wrapped
;
public
Wrapper
(T
arg) {wrapped = arg;}
...
p
ublic
Wrapper
<T>
clone()
{
Wrapper<T> clon = null;
try {
clon =
(Wrapper<T>)
super.clone();
//
unchecked warning supressed
} catch (CloneNotSupportedException
e) {
throw new InternalError();
}
try {
Class<?> clzz = this.wrapped.getClass();
Method meth
= clzz.getMethod("clone", new Class[0]);
Object dupl
= meth.invoke(this.wrapped, new Object[0]);
clon.wrapped =
(T)
dupl;
//
unchecked warning supressed
} catch (Exception e) {}
return clon;
}
}
This example would usually raise 2 "unchecked" warnings. Since we
annotated the entire class, all unchecked warnings raised anywhere in the
class implementation are suppressed.
We can suppress several types of annotations at a time. In this
case we must specify a list of warning labels.
Example (of suppressing several types of warnings):
@SuppressWarnings(value={"unchecked","deprecation"})
public static void someMethod() {
...
TreeSet se
t = new TreeSet();
set.add(new Date(104,8,11));
//
unchecked and deprecation warning suppressed
...
}
This example would usually raise 2 warnings when the call to method
add
is compiled:
warning: [deprecation] Date(int,int,int) in java.util.Date
has been deprecated
set.add(new Date(104,8,11));
^
warning: [unchecked] unchecked call to add(E) as a member of the
raw type java.util.TreeSet
set.add(new Date(104,8,11));
The annotation preceding the enclosing method suppresses all unchecked
and deprecation warnings anywhere in the method implementation.
Annotations can
not
be attached to statements, expressions, or
blocks, only to program entities with a definition like types, variables,
etc.
Example (of illegal placement of annotation):
public static void someMethod() {
...
TreeSet se
t = new TreeSet();
@SuppressWarnings(value={"unchecked"})
//
error
set.add(new Date(104,8,11));
...
}
Annotations can be attached to the definition of packages, classes, interfaces,
fields, methods, parameters, constructors, local variables, enum types,
enum constants, and annotation types. An annotated package declaration
must go into a file named
package-info.java
in the directory that
represents the package.
Note, in release of Java 5.0 the
SuppressWarnings
annotation
is not yet supported by all compilers. Sun's compiler will support in in
release 6.0. |
LINK TO THIS
|
Technicalities.FAQ004
|
REFERENCES
|
What
is an "unchecked" warning?
How
can I disable or enable unchecked warnings?
|
How
can I avoid "unchecked cast" warnings?
By using an unbounded wildcard parmeterized
type as target type of a cast expression.
|
Occasionally, we would like to cast to a parameterized
type, just to discover that the compiler flags it with an "unchecked" warning.
As we are interested in warning-free compilation of our source code, we
would like to avoid this warning. Use of an unbounded wildcard parameterized
type instead of a concrete or a bounded wildcard parameterized type would
help avoid the warning.
A typical example 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 Wrapper<T> {
private T wrapped;
...
public boolean equals
(Object other)
{
...
Wrapper<T> otherWrapper
=
(Wrapper<T>)
other;
//
warning; unchecked cast
return (this.wrapped.equals(otherWrapper.wrapped));
}
}
When we replace the cast to
Wrapper<T>
by a cast to
Wrapper<?>
the warning disappears, because unbounded wildcard parameterized types
are permitted as target type of a cast without any warnings.
Example (implementation of
equals
):
class Wrapper<T> {
private T wrapped;
...
public boolean equals
(Object other)
{
...
Wrapper<?> otherWrapper
=
(Wrapper<?>)
other;
return (this.wrapped.equals(otherWrapper.wrapped));
}
}
Note, this technique works in this example only because we need no write
access to the fields of the object refered to through the wildcard parameterized
type 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. |
LINK TO THIS
|
Technicalities.FAQ005
|
REFERENCES
|
Can
I cast to a parameterized type?
What
is an "unchecked" warning?
How
can I disable or enable unchecked warnings?
How
do I best implement the equals method of a generic type?
|
Is
it possible to eliminate all "unchecked" warnings?
Almost.
|
"Unchecked" warnings stem either from using generic types
in their raw form or from casts whose target type is a type parameter or
a concrete or bounded wildcard parameterized type. If you refrain from
both using raw types and the critical casts you can theoretically eliminate
all "unchecked" warnings. Whether this is doable in practice depends
on the circumstances.
Raw types.
When source code is compiled for use in Java 5.0 that was developed
before Java 5.0 and uses classes that are generic in Java 5.0, then "unchecked"
warnings are inevitable. For instance, if "legacy" code uses types
such as
List
, which used to be a regular (non-generic) types before
Java 5.0, but are generic in Java 5.0, all these uses of
List
are
considered uses of a raw type in Java 5.0. Use of the raw types will
lead to "unchecked" warnings. If you want to eliminate the "unchecked"
warnings you must re-engineer the "legacy" code and replace all raw uses
of
List
with appropriate instantiations of
List
such
as
List<String>
,
List<Object>
,
List<?>
,
etc. All "unchecked" warnings can be eliminated this way.
In source code developed for Java 5.0 you can prevent "unchecked" warnings
in the first place by never using raw types. Always provide type
arguments when you use a generic type. There are no situations in
which you are forced to use a raw type. In case of doubt, when you
feel you have no idea which type argument would be appropriate, try the
unbounded wildcard "
?
".
In essence, "unchecked" warnings due to use of raw types can be eliminated
if you have access to legacy code and are willing to re-engineer it.
Casts.
"Unchecked" warnings as a result of cast expressions can be eliminated
by eliminating the offensive casts. Eliminating such casts is almost
always possible. There are, however, a few situations where a cast
to a type parameter or a concrete or bounded wildcard parameterized type
cannot be avoided.
These are typically situations where a method returns a supertype reference
to an object of a more specific type. The classic example is the
clone
method; it returns an
Object
reference to an object of the type
on which it was invoked. In order to recover the returned object's actual
type a cast in necessary. If the cloned object is of a parameterized
type, then the target type of the cast is an instantiation of that parameterized
type, and an "unchecked" warning is inevitable. The
clone
method is just one example that leads to unavoidable "unchecked" warnings.
Invocation of methods via reflection has similar effects because the return
value of a reflectively invoked method is returned via an
Object
reference. It is likely that you will find further examples of unavoidable
"unchecked" casts in practice. For a detailed discussion of an example
see
Technicalities.FAQ502
,
which explains the implementation of a
clone
method for a generic
class.
In sum, there are situations in which you cannot eliminate "unchecked"
warnings due to a cast expression.
|
LINK TO THIS
|
Technicalities.FAQ006
|
REFERENCES
|
What
is an "unchecked" warning?
How
do I best implement the clone method of a generic type?
|
Why
do I get an "unchecked" warning although there is no type information missing?
Because
the compiler performs all type checks based on the type erasure when you
use a raw type.
|
Usually the compiler issues an "unchecked" warning in order
to alert you to some type-safety hazard that the compiler cannot prevent
because there is not enough type information available. One of these situations
is the invocation of methods of a raw type.
Example (of unchecked warning in conjunction with raw types):
class TreeSet<E> {
boolean
add(E o)
{
...
}
}
TreeSet
se
t = new TreeSet();
set.add("abc");
//
unchecked warning
warning: [unchecked] unchecked call to add(E) as a
member of the raw type TreeSet
set.add("abc");
^
When the
add
method is invoked the compiler does not know whether
it is safe to add a
String
object to the collection because the
raw type
TreeSet
does
not provide any information regarding the type of the contained elements.
Curiously, an unchecked warning is also issued in situations where there
is enough type information available.
Example (of a spurious unchecked warning in conjunction with raw types):
class
SomeType<T> {
public List<String>
getList() { ...
}
}
SomeType
raw
= new
SomeType
();
List<String> listString =
raw.getList();
// unchecked warning
warning:
[unchecked] unchecked conversion
found : List
required: List<String>
List<String> listString = raw.getList();
^
In this example, there is no type information missing. The
getList
method is declared to return a
List<String>
and this is so even in the raw type because the method does not depend
on the enclosing class's type parameter. Yet the compiler complains.
The reason is that the compiler computes the type erasure of a generic
type when it finds an occurrence of the raw type in the source code.
Type erasure does not only elide all occurances of the type parameter
T
,
but also elides the type argument of the
getList
method's return type. After type erasure, the
getList
method returns just a
List
and no longer a
List<String>
.
All subsequent type checks are performed based on the type erasure; hence
the "unchecked" warning.
The "unchecked" warning can easily be avoided by refraining from use
of the raw type.
SomeType
is a generic type and should always be used with a type argument. In general,
the use of raw types will inevitably result in "unchecked" warnings; some
of the warnings may be spurious, but most of them are justified.
Note, that no spurious warning is
issued when the method in question is a static method.
Example (of invoking a static method of a raw type):
class
SomeType<T> {
public
static
List<String> getList() { ...
}
}
SomeType
raw
= new
SomeType
();
List<String> listString =
raw.getList();
// fine
|
LINK TO THIS
|
Technicalities.FAQ007
|
REFERENCES
|
What
is an "unchecked" warning?
Should
I prefer parameterized types over raw types?
Why
shouldn't I mix parameterized and raw types, if I feel like it?
|
Heap Pollution
What
is heap pollution?
A situation where a variable of a parameterized
type refers to an object that is not of that parameterized type.
|
It can happen that a variable of a parameterized type such
as
List<String>
refers
to an object that is not of that parameterized type.
Example (of heap pollution):
List
ln = new ArrayList<Number>();
List<String> ls =ln;
//
unchecked warning
String
s = ls.get(0);
//
ClassCastException
After the assignment of the reference variable
ln
to the reference variable
ls
,
the
List<String>
variable
will point to a
List<Number>
object. Such a situation is called
heap
pollution
and is usually indicated by an unchecked warning.
A polluted heap is likely to lead to an unexpected
ClassCastException
at runtime. In the example above, it will lead to a
ClassCastException
,
when a object is retrieved from the
List<String>
and assigned to a
String
variable, because the object is a
Number
,
not a
String
. |
LINK TO THIS
|
Technicalities.FAQ050
|
REFERENCES
|
What
is an "unchecked" warning?
When
does heap pollution occur?
|
When
does heap pollution occur?
As a result of mixing raw and parameterized
type, unwise casting, and separate compilation.
|
Heap pollution occurs in three situations:
-
mixing raw types and parameterized types
-
performing unchecked casts
-
separate compilation of translation units
With the exception of separate compilation, the compiler will always issue
an unchecked warning to draw your attention to the potential heap pollution.
If you co-compile your code without warnings then no heap pollution can
ever occur.
Raw Types
Heap pollution can occur when raw types and parameterized types are
mixed and a raw type variable is assigned to a parameterized type variable.
Note, that heap pollution does not necessarily occur, even if the compiler
issues an unchecked warning.
Example (of mixing raw and parameterized types):
List ln = new ArrayList<Number>();
List ls = new LinkedList<String>();
List<String> list;
list = ln;
// unchecked warning
+ heap pollution
list = ls;
// unchecked warning
+ NO heap pollution
The first assignment leads to heap pollution, because the
List<String>
variable would then point to a
List<Number>
. The second
assignment does not result in heap pollution, because the raw type variable
on the right-hand side of the assignment refers to a
List<String>
,
which matches the parameterized type on the left-hand side of the assignment.
Mixing raw and parameterized types should be avoided, if possible.
It cannot be avoided when non-generic legacy code is combined with modern
generic code. But otherwise, the mix is bad programming style.
Unchecked Casts
Unwise casting can lead to all kinds of unhealthy situations.
In particular, in can lead to heap pollution.
Example (of cast to parameterized type polluting the heap):
List<? extends Number> ln = new ArrayList<Long>();
List<Short> ls = (List<Short>) ln;
//
unchecked warning
+ heap pollution
List<Long> ll = (List<Long>) ln;
//
unchecked warning
+ NO heap pollution
The compiler permits the two casts in the example above, because
List<?
extends Number>
is a supertype of the types
List<Short>
and
List<Long>
. The casts are similar to casts from supertype
Object
to subtype
Short
or
Long
. The key difference is
that the correctness of a cast to a non-parameterized type can be ensured
at runtime and will promptly lead to
ClassCastException
, while
a cast to a parameterized type cannot be ensured at runtime because of
type erasure and might results in heap pollution.
Casts with a parameterized target type can lead to heap pollution, and
so do casts to type variables.
Example (of cast to type variable polluting the heap):
<S,T> S convert(T arg) {
return (S)arg;
// unchecked
warning
}
Number n = convert(new Long(5L));
// fine
String s = convert(new Long(5L));
// ClassCastException
In this example we do not cast to a parameterized type, but a type
variable
S
. The compiler permits the cast because the cast
could succeed, but there is no way to ensure success of the cast at runtime.
Casts, whose target type is a parameterized type or a type variable,
should be avoided, if possible.
Separate Compilation
Another situation, in which heap pollution can occur is separate compilation
of translation units.
Example (initial implementation):
file
FileCrawler.java
:
final class FileCrawler {
...
public
List<String>
getFileNames() {
List<String>
list
= new LinkedList<String>();
...
return list;
}
}
file
Test
.
java
:
final class Test {
public static void main(String[] args) {
FileCrawler crawler
= new FileCrawler("http:\\www.goofy.com");
List<String>
files = crawler.getFileNames();
System.out.println(files.get(0));
}
}
The program compiles and runs fine. Now, let's assume that we modify
the
FileCrawler
implementation. Instead of returning a
List<String>
we return a
List<StringBuilder>
. Note, the other class
is not changed at all.
Example (after modification and co-compilation):
file
FileCrawler.java
:
final class FileCrawler {
...
public
List<StringBuilder>
getFileNames() {
List<StringBuilder>
list = new LinkedList<StringBuilder>();
...
return list;
}
}
file
Test
.
java
:
final class Test {
public static void main(String[] args) {
FileCrawler crawler
= new FileCrawler("http:\\www.goofy.com");
List<String>
files = crawler.getFileNames();
//
error
System.out.println(files.get(0));
}
}
When we co-compile both translation units, the compiler would report an
error in the unmodified file
Test.java
because the return type of the
getNames()
method does no longer
match the expected type
List<String>
.
If we compiler separately, that is, only compile the modified file
Test.java
,
then no error would be reported. This is because the class,
in which the error occurs, has not been re-compiled. When the program
is executed, a
ClassCastException
will occur.
Example (after modification and separate compilation):
file
FileCrawler.java
:
final class FileCrawler {
...
public
List<StringBuilder>
getFileNames() {
List<StringBuilder>
list = new LinkedList<StringBuilder>();
...
return list;
}
}
file
Test
.
java
:
final class Test {
public static void main(String[] args) {
FileCrawler crawler
= new FileCrawler("http:\\www.goofy.com");
List<String>
files = crawler.getFileNames();
//
fine
System.out.println(files.get(0));
// ClassCastException
}
}
This is another example of heap pollution. The compiler, since it
does not see the entire program, but only a part of it, cannot detect the
error. Co-compilation avoids this problem and enables the compiler
to detect and report the error.
Separate compilation in general is hazardous, independently of generics.
If you provide a method that first returns a
String
and later
you change it to return a
StringBuilder
, without re-compiling
all parts of the program that use the method, you end up in a similarly
disastrous situation. The crux is the incompatible change of the
modified method. Either you can make sure that the modified part
is co-compiled with all parts that use it or you must not introduce any
incompatible changes such as changes in semantics of types or signatures
of methods. |
LINK TO THIS
|
Technicalities.FAQ051
|
REFERENCES
|
What
is heap pollution?
What
is an "unchecked" warning?
How
can I avoid "unchecked cast" warnings?
Is
it possible to eliminate all "unchecked" warnings?
|
Type Erasure
How
does the compiler translate Java generics?
By
creating one unique byte code representation of each generic type (or method)
and mapping all instantiations of the generic type (or method) to this
unique representation.
|
The Java
compiler is responsible for translating Java source code that contains
definitions and usages of generic types and methods into Java byte code
that the virtual machine can interpret. How does that translation work?
A compiler that must translate a generic type or method (in any language,
not just Java) has in principle two choices:
Code specialization.
The compiler generates a new representation
for every instantiation of a generic type or method. For instance, the
compiler would generate code for a list of integers and additional, different
code for a list of strings, a list of dates, a list of buffers, and so
on.
Code sharing.
The compiler generates code for only one representation
of a generic type or method and maps all the instantiations of the generic
type or method to the unique representation, performing type checks and
type conversions where needed.
Code specialization is the approach that C++ takes for its templates:
The C++ compiler generates executable code for every instantiation
of a template. The downside of code specialization of generic types is
its potential for code bloat. A list of integers and a list of strings
would be represented in the executable code as two different types. Note
that code bloat is not inevitable in C++ and can generally be avoided by
an experienced programmer.
Code specialization is particularly wasteful in cases where the elements
in a collection are references (or pointers), because all references (or
pointers) are of the same size and internally have the same representation.
There is no need for generation of mostly identical code for a list of
references to integers and a list of references to strings. Both
lists could internally be represented by a list of references to any type
of object. The compiler just has to add a couple of casts whenever these
references are passed in and out of the generic type or method. Since in
Java most types are reference types, it deems natural that Java chooses
code sharing as its technique for translation of generic types and methods.
The Java compiler applies the code sharing technique and creates one
unique byte code representation of each generic type (or method).
The various instantiations of the generic type (or method) are mapped onto
this unique representation by a technique that is called
type erasure
. |
LINK
TO THIS
|
Technicalities.FAQ100
|
REFERENCES
|
What
is type erasure?
|
What
is type erasure?
A process that maps a parameterized
type (or method) to its unique byte code representation by eliding type
parameters and arguments.
|
The compiler generates only one byte code representation
of a generic type or method and maps all the instantiations of the generic
type or method to the unique representation. This mapping is performed
by type erasure. The essence of type erasure is the removal of all
information that is related to type parameters and type arguments. In addition,
the compiler adds type checks and type conversions where needed and inserts
synthetic bridge methods if necessary. It is important to understand type
erasure because certain effects related to Java generics are difficult
to understand without a proper understanding of the translation process.
The type erasure process can be imagined as a translation from generic
Java source code back into regular Java code. In reality the compiler
is more efficient and translates directly to Java byte code. But
the byte code created is equivalent to the non-generic Java code you will
be seeing in the subsequent examples.
The steps performed during type erasure include:
Eliding type parameters.
When the compiler finds the definition of a generic type or method,
it removes all occurrences of the type parameters and replaces them by
their leftmost bound, or type
Object
if no bound had been specified.
Eliding type arguments.
When the compiler finds a paramterized type, i.e. an instantiation
of a generic type, then it removes the type arguments. For instance, the
types
List<String>
,
Set<Long>
, and
Map<String,?>
are translated to
List
,
Set
and
Map
respectively.
Example (before type erasure):
interface Comparable
<A>
{
public int compareTo(
A
that);
}
final class NumericValue implements Comparable
<NumericValue>
{
priva
te byte value;
public
NumericValue
(byte
value) { this.value = value; }
public byte getValue() { return
value; }
public int compareTo(
NumericValue
t
hat) { return this.value - that.value; }
}
class Collections {
public static
<A extends Comparable<A>>A
max(Collection
<A>
xs) {
Iterator
<A>
xi = xs.iterator();
A
w = xi.next();
while (xi.hasNext()) {
A
x
= xi.next();
if (w.compareTo(x) < 0) w = x;
}
return w;
}
}
final class Test {
public static void main (String[ ] args) {
LinkedList
<NumericValue>
numberList
= new LinkedList
<NumericValue>
();
numberList
.add(new
NumericValue((byte)0));
numberList
.add(new
NumericValue((byte)1));
NumericValue y = Collections.max(
numberList
);
}
}
Type parameters are
green
and type arguments
are
blue
. During type erasure the type
arguments are discarded and the type paramters are replaced by their leftmost
bound.
Example (after type erasure):
interface Comparable {
public int compareTo(
Object
that);
}
final class NumericValue implements Comparable {
priva
te byte value;
public
NumericValue
(byte
value) { this.value = value; }
public byte getValue() { return
value; }
public int compareTo(
NumericValue
t
hat) { return this.value - that.value; }
public int compareTo(Object
that) { return this.compareTo((NumericValue)that); }
}
class Collections {
public static
Comparable
max(Collection xs) {
Iterator xi = xs.iterator();
Comparable
w =
(Comparable)
xi.next();
while (xi.hasNext()) {
Comparable
x =
(Comparable)
xi.next();
if (w.compareTo(x) < 0) w = x;
}
return w;
}
}
final class Test {
public static void main (String[ ] args) {
LinkedList
numberList
= new LinkedList();
numberList
.add(new
NumericValue((byte)0));
numberList
.add(new
NumericValue((byte)1));
NumericValue y =
(NumericValue)
Collections.max(
numberList
);
}
}
The generic
Comparable
interface is translated to a non-generic
interface and the unbounded type parameter
A
is replaced by type
Object
.
The
NumericValue
class implements the non-generic
Comparable
interface after type erasure, and the compiler adds a so-called
bridge
method
. The bridge method is needed so that class
NumericValue
remains a class that implements the
Comparable
interface after
type erasure.
The generic method
max
is translated to a non-generic method
and the bounded type parameter
A
is replaced by its leftmost bound,
namely
Comparable
. The parameterized interface
Iterator<A>
is
translated to the raw type
Iterator
and the compiler adds a cast
whenever an element is retrieved from the raw type
Iterator
.
The uses of the parameterized type
LinkedList<NumericValue>
and the generic
max
method in the
main
method are translated
to uses of the non-generic type and method and, again, the compiler must
add a cast.
|
LINK TO THIS
|
Technicalities.FAQ101
|
REFERENCES
|
What
is a bridge method?
Why
does the compiler add casts when it translates generics?
How
does type erasure work when a type parameter has several bounds?
|
What
is reification?
Representing type parameters and arguments of generic
types and methods at runtime. Reification is the opposite of
type
erasure
.
|
In Java, type parameters and type arguments are elided
when the compiler performs type erasure. A side effect of type erasure
is that the virtual machine has no information regarding type parameters
and type arguments. The JVM cannot tell the difference between a
List<String>
and a
List<Date>
.
In other languages, like for instance C#, type parameters and type arguments
of generics types and methods do have a runtime representation. This representation
allows the runtime system to perform certain checks and operations based
on type arguments. In such a language the runtime system can tell
the difference between a
List<String>
and a
List<Date>
. |
LINK TO THIS
|
Technicalities.FAQ101A
|
REFERENCES
|
What
is type erasure?
What
is a reifiable type?
|
What
is a bridge method?
A synthetic method that the compiler
generates in the course of type erasure. It is sometimes needed when
a type extends or implements a parameterized class or interface.
|
The compiler insert bridge methods in subtypes of parameterized
supertypes to ensure that subtyping works as expected.
Example (before type erasure):
interface Comparable
<A>
{
public int compareTo(
A
that);
}
final class NumericValue implements Comparable
<NumericValue>
{
priva
te byte value;
public
NumericValue
(byte
value) { this.value = value; }
public byte getValue() { return
value; }
public int compareTo(
NumericValue
t
hat) { return this.value - that.value; }
}
In the example, class
NumericValue
implements interface
Comparable<NumericValue>
and must therefore override the superinterface's
compareTo
method.
The method takes a
NumericValue
as an argument. In the process
of type erasure, the compiler translates the parameterized
Comparable<A>
interface to its type erased counterpart
Comparable
. The
type erasure changes the signature of the interface's
compareTo
method. After type erasure the method takes an
Object
as
an argument.
Example (after type erasure):
interface Comparable {
public int compareTo(
Object
that);
}
final class NumericValue implements Comparable {
priva
te byte value;
public
NumericValue
(byte
value) { this.value = value; }
public byte getValue() { return
value; }
public int compareTo(
NumericValue
t
hat) { return this.value - that.value; }
public int compareTo(Object
that) { return this.compareTo((NumericValue)that); }
}
After this translation, method
NumericValue.compareTo(NumericValue)
is no longer an implementation of the interface's
compareTo
method.
The type erased
Comparable
interface requires a
compareTo
method
with argument type
Object
, not
NumericValue
. This is
a side effect of type erasure: the two methods (in the interface and the
implementing class) have identical signatures before type erasure and different
signatures after type erasure.
In order to achieve that class
NumericValue
remains a class
that correctly implements the
Comparable
interface, the compiler
adds a bridge method to the class. The bridge method has the same
signature as the interface's method after type erasure, because that's
the method that must be implemented. The bridge method delegates to the
orignal methods in the implementing class.
The existence of the bridge method does not mean that objects of arbitrary
types can be passed as arguments to the
compareTo
method in
NumericValue
.
The bridge method is an implementation detail and the compiler makes sure
that it normally cannot be invoked.
Example (illegal attempt to invoke bridge method):
NumericValue value = new NumericValue((byte)0);
value.compareTo(value);
// fine
value.compareTo("abc");
// error
The compiler does not invoke the bridge method when an object of a type
other than
NumericValue
is passed to the
compareTo
method.
Instead it rejects the call with an error message, saying that the
compareTo
method expects a
NumericValue
as an argument and other types of
arguments are not permitted.
You can, however, invoke the synthetic bridge message using reflection.
But, if you provide an argument of a type other than
NumericValue
,
the method will fail with a
ClassCastException
thanks of the cast
in the implementation of the bridge method.
Example (failed attempt to invoke bridge method via reflection):
int reflectiveCompareTo(NumericValue value, Object other)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
{
Method meth = NumericValue.class.getMethod("compareTo",
new Class[]{Object.class});
return (Integer)meth.invoke(value, new Object[]{other});
}
NumericValue value = new NumericValue((byte)0);
reflectiveCompareTo(value, value);
//
fine
reflectiveCompareTo(value,"abc");
//
ClassCastException
The cast to type
NumericValue
in the bridge
method fails with a
ClassCastException
when
an argument
of a type other than
NumericValue
is passed to the bridge method.
This was it is guaranteed that a bridge method, even when it is called,
will fail for unexpected argument types. |
LINK TO THIS
|
Technicalities.FAQ102
|
REFERENCES
|
What
is type erasure?
Under
which circumstances is a bridge method generated?
|
Under
which circumstances is a bridge method generated?
When a type extends or implements a parameterized
class or interface and type erasure changes the signature of any inherited
method.
|
Bridge methods are necessary when a class implements a
parameterized interface or extends a parameterized superclass and type
ersure changes the argument type of any of the inherited non-static methods.
Below is an example of a class that extends a parameterized superclass.
Example (before type erasure):
class Superclass
<T extends
Bound>
{
public void m1(
T
arg)
{ ... }
public
T
m2() { ...
}
}
class Subclass extends Superclass
<SubTypeOfBound>
{
public void
m1(
SubTypeOfBound
arg) { ... }
public
SubTypeOfBound
m
2() { ... }
}
Example (after type erasure):
class Superclass {
void m1(
Bound
arg) {
... }
Bound
m2() { ... }
}
class Subclass extends Superclass {
public void m1(SubTypeOfBound arg) { ... }
public void m1(Bound arg) { m1((SubTypeOfBound)arg);
}
public SubTypeOfBound m2() { ... }
public Bound
m2() { return m2(); }
}
Type erasure changes the signature of the superclass's methods. The
subclass's methods are no longer overriding versions of the superclass's
method after type erasure. In order to make overriding work the compiler
adds bridge methods.
The compiler must add bridge methods even if the subclass does not override
the inherited methods.
Example (before type erasure):
class Superclass
<T extends
Bound>
{
public void m1(
T
arg)
{ ... }
public
T
m2() { ...
}
}
class AnotherSubclass extends Superclass
<SubTypeOfBound>
{
}
Example (after type erasure):
class Superclass {
void m1(
Bound
arg) {
... }
Bound
m2() { ... }
}
class AnotherSubclass extends Superclass {
public void m1(Bound arg)
{ super.m1((SubTypeOfBound)arg); }
public Bound m2() { return super.m2();
}
}
The subclass is derived from a particular instantiation of the superclass
and therefore inherits the methods with a particular signature. After
type erasure the signature of the superclass's methods are different from
the signatures that the subclass is supposed to have inherited. The
compiler adds bridge methods, so that the subclass has the expected inherited
methods.
No bridge method is needed when type erasure does not change the signature
of any of the methods of the parameterized supertype. Also, no bridge
method is needed if the signatures of methods in the sub- and supertype
change in the same way. This can occur when the subtype is generic
itself.
Example (before type erasure):
interface Callable
<V>
{
public
V
call();
}
class Task
<T>
implements
Callable
<T>
{
public
T
call() { ...
}
}
Example (after type erasure):
interface Callable {
public
Object
call();
}
class Task implements Callable {
public
Object
call()
{ ... }
}
The return type of the
call
method changes during type erasure
in the interface and the implementing class. After type erasure the
two methods have the same signature so that the subclass's method implements
the interface's method without a brdige method.
However, it does not suffice that the subclass is generic. The
key is that the method signatures must not match after type erasure.
Otherwise, we again need a bridge method.
Example (before type erasure):
interface Copyable
<V>
extends Cloneable {
public
V
copy();
}
class Triple
<T extends Copyable<T>>
implements Copyable
<Triple<T>>
{
public
Triple<T>
copy() { ... }
}
Example (after type erasure):
interface Copyable extends Cloneable {
public
Object
copy();
}
class Triple implements Copyable {
public
Triple
copy() { ... }
public Object copy() { return copy();
}
}
The method signatures change to
Object copy()
in the interface
and
Triple copy()
in the subclass. As a result, the compiler adds
a bridge method. |
LINK TO THIS
|
Technicalities.FAQ103
|
REFERENCES
|
What
is type erasure?
|
Why
does the compiler add casts when it translates generics?
Because the return type of methods of
a parameterized type might change as a side effect of type erasure.
|
During type erasure the compiler replaces type parameters
by the leftmost bound, or type
Object
if no bound was specified.
This means that methods whose return type is the type parameter would return
a reference that is either the leftmost bound or
Object
, instead
of the more specific type that was specified in the parameterized type
and that the caller expects. A cast is need from the leftmost bound
or
Object
down to the more specific type..
Example (before type erasure):
public class Pair<X,Y> {
private X first;
private Y second;
public Pair(X x, Y y) {
first = x;
second = y;
}
public X getFirst() { return first; }
public Y getSecond() { return second; }
public void setFirst(X x) { first = x; }
public void setSecond(Y y) { second = y; }
}
final class Test {
public static void main(String[] args) {
Pair<String,Long> pair = new Pair<String,Long>("limit",
10000L);
String s = pair.getFirst();
Long l = pair.getSecond();
Object o = pair.getSecond();
}
}
Example (after type erasure):
public class Pair {
private
Object
first;
private
Object
second;
public Pair(
Object
x,
Object
y) {
first = x;
second = y;
}
public
Object
getFirst()
{ return first; }
public
Object
getSecond()
{ return second; }
public void setFirst(
Object
x) { first = x; }
public void setSecond(
Object
y) { second = y; }
}
final class Test {
public static void main(String[] args) {
Pair pair = new Pair("limit", 10000L);
String s =
(String)
pair.getFirst();
Long l =
(Long)
pair.getSeond();
Object o =
pair.getSecond();
}
}
After type erasure the methods
getFirst
and
getSecond
of
type
Pair
both have the return type
Object
. Since
the declared static type of the pair in our test case is
Pair<String,Long>
the caller of
getFirst
and
getSecond
expects a
String
and a
Long
as the return value. Without a cast this would
not work and in order to make it work the compiler adds the necessary casts
from
Object
to
String
and
Long
respectively.
The inserted casts cannot fail at runtime with a
ClassCastException
because the compiler already made sure at compile-time that both fields
are references to objects of the expected type. The compiler would
issue an error method if arguments of types other than
String
or
Long
had been passed to the constructor or the
set
methods. Hence it is guarantees that these casts cannot fail.
In general, casts silently added by the compiler are guaranteed not
to raise a
ClassCastException
if the program was compiled without
warnings. This is the type-safety guarantee.
Implicit casts are inserted when methods are invoked whose
return
type
changed during type erasure. Invocation of methods whose
argument
type
changed during type erasure do not require insertion of any casts.
For instance, after type erasure the
setFirst
and
setSecond
methods of class
Pair
take
Object
arguments. Invoking
them with arguments of a more specific type such as
String
and
Long
is possible without the need for any casts. |
LINK TO THIS
|
Technicalities.FAQ104
|
REFERENCES
|
What
is type erasure?
What
does type-safety mean?
|
How
does type erasure work when a type parameter has several bounds?
The compiler adds casts as needed.
|
In the process of type erasure the compiler replaces type
parameters by their leftmost bound, or type
Object
if no bound
was specified. How does that work if a type parameter has several bounds?
Example (before type erasure):
interface Runnable {
void run();
}
interface Callable<V> {
V call();
}
class X<T extends Callable<Long> & Runnable> {
private T task1, task2;
...
public void do() {
task1.run();
Long result = task2.call();
}
}
Example (after type erasure):
interface Runnable {
void run();
}
interface Callable {
Object
call();
}
class X {
private
Callable
task1,
task2;
...
public void do() {
(
(Runnable)
task1).run();
Long result =
(Long)
task2.call();
}
}
The type parameter
T
is replaced by the bound
Callable
,
which means that both fields are held as references of type
Callable
.
Methods of the leftmost bound (which is
Callable
in our example)
can be called directly. For invocation of methods of the other bounds
(
Runnable
in our example) the compiler adds a cast to the respective
bound type, so that the methods are accessible. The inserted cast cannot
fail at runtime with a
ClassCastException
because the compiler
already made sure at compile-time that both fields are references to objects
of a type that is within both bounds.
In general, casts silently added by the compiler are guaranteed
not to raise a
ClassCastException
if the program was compiled
without warnings. This is the type-safety guarantee. |
LINK TO THIS
|
Technicalities.FAQ105
|
REFERENCES
|
What
does type-safety mean?
|
What
is a reifiable type?
A type whose type information is fully
available at runtime, that is, a type that does not lose information in
the course of type erasure.
|
As a side effect of type erasure, some type information
that is present in the source code is no longer available at runtime.
For instance, parameterized types are translated to their corresponding
raw type in a process called
type erasure
and lose the information
regarding their type arguments.
For example, types such as
List<String>
or
Pair<?
extends Number, ? extends Number>
are available to and used by the
compiler in their exact form, including the type argument information.
After type erasure, the virtual machine has only the raw types
List
and
Pair
available, which means that part of the type information
is lost.
In contrast, non-parameterized types such as
java.util.Date
or
java.lang.Thread.State
are not affected by type erasure.
Their type information remains exact, because they do not have type arguments.
Among the instantiations of a generic type only the unbounded wildcard
instantiations, such as
Map<?,?>
or
Pair<?,?>
,
are unaffected by type erasure. They do lose their type arguments,
but since all type arguments are unbounded wildcards, no information is
lost.
Types that do NOT lose any information during type erasure are called
reifiable
types
. The term reifiable stems from
reification
.
Reification means that type parameters and type arguments of generic types
and methods are available at runtime. Java does not have such a runtime
representation for type arguments because of type erasure. Consequently,
the reifiable types in Java are only those types for which reification
does not make a difference, that is, the types that do not need any runtime
representation of type arguments.
The following types are reifiable:
-
primitive types
-
non-generic (or non-parameterized) reference types
-
unbounded wildcard instantiations
-
raw types
-
arrays of any of the above
The non-reifiable types, which lose type information as a side effect of
type erasure, are:
-
instantiations of a generic type with at least one concrete type argument
-
instantiations of a generic type with at least one bounded wildcard as
type argument
Reifiable types are permitted in some places where non-reifiable types
are disallowed. Reifiable types are permitted (and non-reifiable
types are prohibited):
-
as type in an
instanceof
expression
-
as component type of an array
|
LINK TO THIS
|
Technicalities.FAQ106
|
REFERENCES
|
What
is type erasure?
What
is reification?
What
is an unbounded wildcard parameterized type?
What
is the raw type?
Which
types can or must not appear as target type in an instanceof expression?
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
parameterized type?
|
What
is the type erasure of a parameterized type?
The type without any type arguments.
|
The erasure of a parameterized type is the type without
any type arguments (i.e. the raw type). This definition extends to arrays
and nested types.
Examples:
parameterized type
|
type erasure
|
List<String>
|
List
|
Map.Entry<String,Long>
|
Map.Entry
|
Pair<Long,Long>[]
|
Pair[]
|
Comparable<? super Number>
|
Comparable
|
The type erasure of a non-parameterized type is the type itself. |
LINK TO THIS
|
Technicalities.FAQ107
|
REFERENCES
|
What
is the raw type?
|
What
is the type erasure of a type parameter?
The type erasure of its leftmost bound,
or type
Object
if no bound was specified.
|
The type erasure of a type parameter is the erasure of
its leftmost bound. The type erasure of an unbounded type parameter is
type
Object
.
Examples:
type parameters
|
type erasure
|
<T>
|
Object
|
<T extends Number>
|
Number
|
<T extends Comparable<T>>
|
Comparable
|
<T extends Cloneable & Comparable<T>>
|
Cloneable
|
<T extends Object & Comparable<T>>
|
Object
|
<S, T extends S>
|
Object,Object
|
|
LINK TO THIS
|
Technicalities.FAQ108
|
REFERENCES
|
What
is a bounded type parameter?
|
What
is the type erasure of a generic method?
A method with the same name and the types
of all method parameters replaced by their respective type erasures.
|
The erasure of a method signature is a signature consisting
of the same name and the erasures of all the formal method parameter types.
Examples:
parameterized method
|
type erasure
|
Iterator<E> iterator()
|
Iterator iterator()
|
<T> T[] toArray(T[] a)
|
Object[] toArray(Object[] a)
|
<U> AtomicLongFieldUpdater<U>
newUpdater(Class<U> tclass, String fieldName)
|
AtomicLongFieldUpdater
newUpdater(Class tclass,String fieldName)
|
|
LINK TO THIS
|
Technicalities.FAQ109
|
REFERENCES
|
What
is type erasure?
What
is the type erasure of a parameterized type?
What
is the type erasure of a type parameter?
|
Is
generic code faster or slower than non-generic code?
There is no perceivable difference.
|
Some programmers, especially those with a C++ background,
expect that generic code should perform much faster than non-generic code,
because this is one observable benefit of using templates in C++.
Other programmers assume that the synthetic bridge methods and implicit
casts inserted by the compiler in the process of type erasure would degrade
the runtime performance of generic code. Which one is true?
The short answer is: it is likely that one will find neither a substantial
difference in runtime performance nor any consistent trend. However,
this has not yet been verified by any benchmarks I know of. Nevertheless,
let us take a look at the various overlapping effects that might explain
such a statement.
Implicit casts.
The casts added by the compiler are exactly the casts that would appear
in non-generic code. Hence the implicit casts do not add any overhead.
Example (generic code):
List
<String>
list = new List
<String>
();
list.add("abc");
String s = list.get(0);
Example (after type erasure):
List list = new LinkedList();
list.add("abc");
String s =
(String)
list.get(0);
|
Example (non-generic code):
List list = new LinkedList();
list.add("abc");
String s =
(String)
list.get(0);
|
The non-generic code is exactly what the compiler generates in the process
of type erasure, hence there is no difference in performance.
Bridge methods.
The compiler adds bridge methods. These synthetic methods cause an additional
method invocation at runtime, they are represented in the byte code and
increase its volume, and they add to the memory footprint of the program.
Example (generic code):
final class Byte implements Comparable
<Byte>
{
priva
te byte value;
public Byte(byte value) {
this.value = value;
}
public byte byteValue() { return
value; }
public int compareTo(
Byte
t
hat) {
return this.value - that.value;
}
}
Example (after type erasure):
final class Byte implements Comparable {
priva
te byte value;
public Byte(byte value) {
this.value = value;
}
public byte byteValue() { return
value; }
public int compareTo(Byte t
hat)
{
return this.value - that.value;
}
public int compareTo(Object
that) {
return this.compareTo((Byte)that);
}
}
|
Example (non-generic code):
final class Byte implements Comparable {
priva
te byte value;
public Byte(byte value) {
this.value = value;
}
public byte byteValue() { return
value; }
public int compareTo(
Object
that) {
return this.value - (
(Byte)
that).value;
}
}
|
It is likely that there is a slight performance penalty for the bridge
method that affects runtime execution and class loading. However,
only new (i.e. 5.0) source code is affected. If we compile legacy
(i.e. 1.4-compatible) source code, there are no additional bridge methods
and the byte code should be identical, more or less, to the way it was
before. Most likely the slight performance penalty is compensated for by
improvements in Hotspot.
Runtime type information.
Static information about type parameters and their bounds is made available
via reflection. This runtime type information adds to the size of
the byte code and the memory footprint, because the information must be
loaded into memory at runtime. Again, this only affects new (i.e.
5.0) source code. On the other hand, there are some enhancements
to reflection that apply even to existing language features, and those
do require slightly larger class files, too. At the same time, the representation
of runtime type information has been improved. For example, there is now
an access bit for "synthetic" rather than a class file attribute, and class
literals now generate only a single instruction. These things often balance
out. For any particular program you might notice a very slight degradation
in startup time due to slightly larger class files, or you might find improved
running time because of shorter code sequences. Yet it is unlikely
that one will find any large or consistent trends.
Compilation time.
Compiler performance might decrease because translating generic source
code is more work than translating non-generic source code. Just
think of all the static type checks the compiler must perform for generic
types and methods. On the other hand, the performance of a compiler
is often more dominated by its implementation techniques rather than the
features of the language being compiled. Again, it is unlikely
that one will find any perceivable or measurable trends. |
LINK TO THIS
|
Technicalities.FAQ110
|
REFERENCES
|
How
does the compiler translate Java generics?
What
is type erasure?
What
is a bridge method?
Why
does the compiler add casts when it translates generics?
|
How
do I compile generics for use with JDK <= 1.4?
Type System
How
do parameterized types fit into the Java type system?
Instantiations of generic types have
certain super-subtype relationship among each other and have a type relationship
to their respective raw type. These type relationships are relevant
for method invocation, assignment and casts.
|
Relevance of type relationships and type converstion
rules in practice.
The type system of a programming language determines which types are
convertible to which other types. These conversion rules have an
impact on various areas of a programming language. One area where
conversion rules and type relationships play role is casts and
instanceof
expressions. Other area is assignment compatibility and method invocation,
where argument and return value passing relies on convertibility of the
involved types.
The type conversion rules determine which casts are accepted and which
ones are rejected. For example, the types
String
and
Integer
have no relationship and for this reason the compiler rejects the attempt
of a cast from
String
to
Integer
, or vice versa.
In contrast, the types
Number
and
Integer
have a super-subtype
relationship;
Integer
is a subtype of
Number
and
Number
is a supertype of
Integer
. Thanks to this relationship, the compiler
accepts the cast from
Number
to
Integer
, or vice versa.
The cast from
Integer
to
Number
is not even necessary,
because the conversion from a subtype to a supertype is considered an implicit
type conversion, which need not be expressed explicitly in terms of a cast;
this conversion is automatically performed by the compiler whenever necessary.
The same rules apply to
instanceof
expressions.
The conversion rules define which types are assignment compatible.
Using the examples from above, we see that a
String
cannot be
assigned to an
Integer
variable, or vice versa, due to the lack
of a type relationship. In contrast, an
Integer
can be assigned
to a
Number
variable, but not vice versa. A side effect
of the super-subtype relationship is that we can assign a subtype object
to a supertype variable, without an explicit cast anywhere. This
is the so-called
widening reference conversion
; it is an implicit
conversion that the compiler performs automatically whenever it is needed.
The converse, namely assignment of a supertype object to a subtype variable,
is not permitted. This is because the so-called
narrowing reference
conversion
is not an implicit conversion. It can only be triggered
by an explicit cast.
The rules for assignment compatibility also define which objects can
be passed to which method. An argument can be passed to a method
if its type is assignment compatible to the declared type of the method
parameter. For instance, we cannot pass an
Integer
to a
method that asks for
String
, but we can pass an
Integer
to a method that asks for a
Number
. The same rules apply
to the return value of a method.
Super-subtype relationships of parameterized types.
In order to understand how objects of parameterized types can be used
in assignments, method invocations and casts, we need an understanding
of the relationship that parameterized types have among each other and
with non-parameterized types. And we need to know the related conversion
rules.
We already mentioned
super-subtype relationships
and the related
narrowing
and
widening reference conversions
. They exist since
Java was invented, that is, among non-generic types. The super-subtype
relationship has been extended to include parameterized types. In
the Java 5.0 type system super-subtype relationships and the related narrowing/widening
reference conversions exist among parameterized types, too. We will
explain the details in separate FAQ entries. Here are some initial
examples to get a first impression of the impact that type relationships
and conversion rules have on method invocation.
Consider a method whose declared parameter type is a wildcard parameterized
type. A wildcard parameterized type acts as supertype of all members
of the type family that the wildcard type denotes.
Example (of widening reference conversion from concrete instantiation
to wildcard instantiation):
void printAll(
LinkedList<?
extends Number>
c) { ... }
LinkedList<Long>
l = new
LinkedList<Long>();
...
printAll(l);
//
widening reference conversion
We can pass a
List<Long>
as an argument to the
printAll
method that asks for a
LinkedList<? extends Number>
.
This is permitted thanks to the super-subtype relationship between a wildcard
instantiation and a concrete instantiation.
LinkedList<Long>
is a member of the type family denoted by
LinkedList<? extends Number>
,
and as such a
LinkedList<Long>
is a subtype of
LinkedList<?
extends Number>
. The compiler automatically performs a widening
conversion from subtype to supertype and thus allows that a
LinkedList<Long>
can be supplied as argument to the
printAll
method that asks for
a
LinkedList<? extends Number>
.
Note that this super-subtype relationship between a wildcard instantiation
and a member of the type family that the wildcard denotes is different
from inheritance. Inheritance implies a super-subtype relationship as well,
but it is a special case of the more general super-subtype relationship
that involves wildcard instantiations.
We know inheritance relationships from non-generic Java. It is
the relationship between a superclass and its derived subclasses, or a
super-interface and its sub-interfaces, or the relationship between an
interface and its implementing classes. Equivalent inheritance relationships
exists among instantiations of different generic types. The prerequisite
is that the instantiations must have the same type arguments. Note
that this situation differs from the super-subtype relationship mentioned
above, where we discussed the relationship between wildcard instantiations
and concrete instantiations of the
same
generic type, whereas we
now talk of the relationship between instantiations of
different
generic types with identical type arguments.
Example (of widening reference conversion from one concrete parameterized
type to another concrete parameterized type):
void printAll(
Collection<Long>
c) { ... }
LinkedList<Long>
l = new
LinkedList<Long>();
...
printAll(l);
//
widening reference conversion
The raw types
Collection
and
LinkedList
have a super-subtype
relationship;
Collection
is a supertype of
LinkedList
.
This super-subtype relationship among the raw types is extended to the
parameterized types, provided the type arguments are identical:
Collection<Long>
is a supertype of
LinkedList<Long>
,
Collection<String>
is a supertype of
LinkedList<String>
, and so on.
It is common that programmers believe that the super-subtype relationship
among type arguments would extend into the respective parameterized
type. This is not true. Concrete instantiations of the same generic
type for different type arguments have no type relationship. For instance,
Number
is a supertype of
Integer
, but
List<Number>
is not
a supertype of
List<Integer>
. A type relationship among
different instantiations of the same generic type exists only among wildcard
instantiations and concrete instantiations, but never among concrete instantiations.
Example (of illegal attempt to convert between different concrete instantiations
of the same generic type):
void printAll(
LinkedList<Number>
c) { ... }
LinkedList<Long>
l = new
LinkedList<Long>();
...
printAll(l);
//
error; no conversion
Due to the lack of a type relationship between
LinkedList<Number>
and
LinkedList<Long>
the compiler cannot convert the
LinkedList<Long>
to a
LinkedList<Number>
and the method call is rejected with
an error message.
Unchecked conversion of parameterized types.
With the advent of parameterized types a novel category of type relationship
was added to the Java type system: the relationship between a parameterized
type and the corresponding raw type. The conversion from a parameterized
type to the corresponding raw type is a widening reference conversion like
the conversion from a subtype to the supertype. It is an implicit conversion.
An example of such a conversion is the conversion from a parameterized
type such as
List<String>
or
List<? extends Number>
to the raw type
List
. The counterpart, namely the conversion
from the raw type to an instantiation of the respective generic type, is
the so-called
unchecked conversion
. It is an automatic conversion,
too, but the compiler reports an "unchecked conversion" warning.
Details are explained in separate FAQ entries. Here are some initial
examples to get a first impression of usefulness of unchecked conversions.
They are mainly permitted for compatibility between generic and non-generic
source code.
Below is an example of a method whose declared parameter type is a raw
type. The method might be a pre-Java-5.0 method that was defined before
generic and parameterized types had been available in Java. For this
reason it declares
List
as the argument type. Now, in Java
5.0,
List
is a raw type.
Example (of a widening reference conversion from parameterized type
to raw type):
void printAll(
List
c) { ... }
List<String>
l = new LinkedList<String>();
...
printAll(l);
//
widening reference conversion
Source code such as the one above is an example of a fairly common situation,
where non-generic legacy code meets generic Java 5.0 code. The
printAll
method is an example of legacy code that was developed before Java 5.0
and uses raw types. If more recently developed parts of the program
use instantiations of the generic type
List
, then we end up passing
an instantiation such as
List<String>
to the
printAll
method
that declared the raw type
List
as its parameter type. Thanks
to the type relationship between the raw type and the parameterized type,
the method call is permitted. It involves an automatic widening reference
conversion from the parameterized type to the raw type.
Below is an example of the conversion in the opposite direction.
We consider a method has a declared parameter type that is a parameterized
type. We pass a raw type argument to the method and rely on an unchecked
conversion to make it work.
Example (of an unchecked conversion from raw type to parameterized type):
void printAll(
List<Long>
c) { ... }
List
l = new LinkedList();
...
printAll(l);
//
unchecked conversion
Like the previous example, this kind of code is common in situation where
generic and non-generic code are mixed.
The subsequent FAQ entries discuss details of the various type relationships
and conversions among raw types, concrete parameterized types, bounded
and unbounded wildcard parameterized types.
|
LINK TO THIS
|
Technicalities.FAQ201
|
REFERENCES
|
What
is the raw type?
What
is a wildcard parameterized type?
Which
super-subtype relationships exist among instantiations of generic types?
How
does the raw type relate to instantiations of the corresponding generic
type?
How
do instantiations of a generic type relate to instantiations of other
generic types that have the same type argument?
How
do unbounded wildcard instantiations of a generic type relate to other
instantiations of the same generic type?
How
do wildcard instantiations with an upper bound relate to other instantiations
of the same generic type?
How
do wildcard instantiations with a lower bound relate to other instantiations
of the same generic type?
|
How
does the raw type relate to instantiations of the corresponding generic
type?
The raw type is the supertype of all
instantiations of the corresponding generic type.
|
The raw types have the regular supertype-subtype relationship
with other raw types. For illustration we use the collection classes and
interfaces from the JDK (see package
java.util
).
In addition, the raw types are supertypes of all concrete and all wildcard
instantiations of the generic type. For instance, the raw type
Collection
is a supertype of all instantiations of the generic type
Collection
.
With both properties combined a raw type is the supertype of all instantiations
of all its generic and non-generic subtypes. For instance,
the raw type
Collection
is a supertype of all instantiations of
all generic collection classes.
Regarding conversions, the usual reference widening conversion from
subtype to supertype is allowed. That is, every instantiation of a generic
type can be converted to the corresponding raw type. The reverse
is permitted, too, for reasons of compatibility between generic and non-generic
types. It is the so-called unchecked conversion and is accompanied
by an "unchecked" warning. In detail, the conversion from a raw type to
a concrete or bounded wildcard instantiation of the corresponding generic
type leads to a warning. The conversion from the raw type to the
unbounded wildcard instantiation is warning-free. |
LINK TO THIS
|
Technicalities.FAQ202
|
REFERENCES
|
How
do parameterized types fit into the Java type system?
What
is the raw type?
What
is a concrete paramterized type?
What
is a wildcard parameterized type?
What
is the unbounded wildcard parameterized type?
Which
super-subtype relationships exist among instantiations of generic types?
|
How
do instantiations of a generic type relate to instantiations of other
generic types that have the same type argument?
An instantiation of a generic
type is the supertype of all instantiations of generic subtypes that have
the same type argument.
|
Instantiations of the generic types have supertype-subtype
relationships with concrete instantiations of generic subtypes provided
they all have the exact same type arguments. The example below uses the
JDK collection types (see package
java.util
)
for illustration.
The diagram illustrates the super-subtype relationship among instantiations
that have the same type argument. The type argument can be a concrete
type, but also a bounded or unbounded wildcard. For instance,
Collection<Number>
is the supertype of
List<Number>
and
LinkedList<Number>
,
Collection<?
extends Number>
is the supertype of
List<? extends Number>
and
LinkedList<? extends Number>
,
Collection<?>
is the supertype of
List<?>
and
LinkedList<?>
,
and so on.
Type relationships to other concrete instantiations do not exist.
In particular, the supertype-subtype relationship among the type arguments
does not extend to the instantiations. For example,
Collection<Number>
is NOT a supertype of
Collection<Long>
.
Regarding conversions, the usual reference widening conversion from
subtype to supertype is allowed. The reverse is not permitted. |
LINK TO THIS
|
Technicalities.FAQ203
|
REFERENCES
|
How
do parameterized types fit into the Java type system?
What
is the raw type?
What
is a concrete parameterized type?
What
is a wildcard parameterized type?
What
is the unbounded wildcard parmeterized type?
Which
super-subtype relationships exist among instantiations of generic types?
|
How
do unbounded wildcard instantiations of a generic type relate to other
instantiations of the same generic type?
An unbounded wildcard instantiation is
the supertype of all instantiations of the generic type.
|
The unbounded wildcard instantiation of a generic type
is supertype of all concrete and all wildcard instantiations of the same
generic type.
For instance, the unbounded wildcard instantiation
Collection<?>
is supertype of all instantiations of the generic type
Collection
.
The example below uses the JDK collection types (see package
java.util
)
for illustration.
At the same, an unbounded wildcard instantiation is supertype of all
unbounded instantiations of any generic subtypes. For instance, the
unbounded wildcard instantiation
Collection<?>
is supertype
of
List<?>
,
LinkedList<?>
, and so on. Both type
relationships combined, an unbounded wildcard instantiation is supertype
of all instantiations of the same generic type and of all instantiations
of all its generic subtypes.
Regarding conversions, the usual reference widening conversion from
subtype to supertype is allowed. The reverse is not permitted.
There is one special rules for unbounded wildcard instantiations: the
conversion from a raw type to the unbounded wildcard instantiation is not
an "unchecked" conversion, that is, the conversion from
Collection
to
Collection<?>
does not lead to an "unchecked" warning.
This is different for concrete and bounded instantiations, where the conversion
from the raw type to the concrete and bounded wildcard instantiation leads
to an "unchecked" warning. |
LINK TO THIS
|
Technicalities.FAQ204
|
REFERENCES
|
How
do parameterized types fit into the Java type system?
Which
super-subtype relationships exist among instantiations of parameterized
types?
What
is the raw type?
What
is a concrete parmeterizd type?
What
is a wildcard parameterized type?
What
is the unbounded wildcard parameterized type?
What
is a wildcard?
What
is an unbounded wildcard?
What
is a bounded wildcard?
|
How
do wildcard instantiations with an upper bound relate to other instantiations
of the same generic type?
A wildcard instantiation wild an upper
bound is supertype of all instantiations of the same generic type where
the type argument is a subtype of the upper bound.
|
An upper bound wildcard instantiation is supertype of all
concrete instantiations with type arguments that are subtypes of the upper
bound, the upper bound itself being included. For instance,
Collection<?
extends Number>
is supertype of
Collection<Number>
,
Collection<Long>
,
Collection<Short>
,
etc., because
Number
,
Long
, and
Short
are subtypes
(or same type) of
Number
. The underlying idea is: a subtype of
the upper bound (e.g.
Long
) belongs to the family of types that
the wildcard (e.g.
? extends Number
) stands for and in this case
the wildcard instantiation (e.g.
Collection<? extends Number>
)
is a supertype of the concrete instantiation on the subtype of the upper
bound (e.g.
Collection<Long>
).
At the same time, a wildcard instantiation with an upper bound is supertype
of all generic subtypes that are instantiated on the same upper bound wildcard.
For instance,
Collection<? extends Number>
is supertype of
Set<?
extends Number>
and
ArrayList<? extends Number>
.
The upper bound wildcard instantiation is also supertype of other upper
bound wildcard instantiation with an upper bound that is a subtype of the
own upper bound. For instance,
Collection<? extends Number>
is supertype of
Collection<? extends Long>
and
Collection<?
extend Short>
, because
Long
and
Short
are subtypes
of
Number
.
Similarly,
Collection<? extends Comparable<?>>
is supertype
of
Collection<? extends Number>
and
Collection<? extends
Delayed>
, because
Number
is a subtype of
Comparable<Number>
,
which is a subtype of
Comparable<?>
, and
Delayed
(see
java.util.concurrent.Delayed
)
is a subtype of
Comparable<Delayed>
, which is a subtype of
Comparable<?>
.
The idea is that if the upper bound of one wildcard is a supertype of the
upper bound of another wildcard then the type family with the supertype
bound includes the type family with the subtype bound. If one family
of types (e.g.
? extends Comparable<?>)
includes the other
(e.g.
? extends Number>
and
? extends Delayed
) then the
wildcard instantiation on the larger family (e.g.
Collection<? extends
Comparable<?>>
) is supertype of the wildcard instantiation of the
included family (e.g.
Collection<? extends Number>
).
All these type relationships combined make a bounded wildcard instantiation
supertype of a quite a number of instantiations of the same generic type
and subtypes thereof.
Regarding conversions, the usual reference widening conversion from
subtype to supertype is allowed. The reverse is not permitted. |
LINK TO THIS
|
Technicalities.FAQ205
|
REFERENCES
|
How
do parameterized types fit into the Java type system?
Which
super-subtype relationships exist among instantiations of parameterized
types?
What
is the raw type?
What
is a concrete parameterized type?
What
is a wildcard parameterized type?
What
is the unbounded wildcard parameterized type?
What
is a wildcard?
What
is an unbounded wildcard?
What
is a bounded wildcard?
|
How
do wildcard instantiations with a lower bound relate to other instantiations
of the same generic type?
A wildcard instantiation wild a lower
bound is supertype of all instantiations of the generic type where the
type argument is a supertype of the lower bound.
|
A wildcard instantiation with a lower bound is supertype
of all concrete instantiation with type arguments that are supertypes of
the lower bound, the lower bound itself included. For instance,
Collection<?
super Number>
is supertype of
Collection<Number>
,
Collection<Serializable>
,
and
Collection<Object>
, because
Number
,
Serializable
and
Object
are supertypes (or same type) of
Number
. The
underlying idea is: a supertype of the lower bound (e.g.
Object
)
belongs to the family of types that the wildcard (e.g.
? super Number
)
stands for and in this case the wildcard instantiation (e.g.
Collection<?
super Number>
) is a supertype of the concrete instantiation on the
supertype of the lower bound (e.g.
Collection<Object>
).
At the same time, a wildcard instantiation with a lower bound is supertype
of parameterized subtypes that are instantiated on the same lower bound
wildcard. For instance,
Collection<? super Number>
is
supertype of
Set<? super Number>
and
ArrayList<? super
Number>
.
The lower bound wildcard instantiation is also supertype of other lower
bound wildcard instantiation with a lower bound bound that is a supertype
of the own lower bound. For instance,
Collection<? super Number>
is supertype of
Collection<? super Serializable>
, because
Serializable
is a supertype of
Number
. The idea is that if the
lower bound of one wildcard is a subtype of the lower bound of another
wildcard then the type family with the subtype bound includes the type
family with the supertype bound. If one family of types (e.g.
?
super Number)
includes the other (e.g.
? super Serializable
)
then the wildcard instantiation on the larger family (e.g.
Collection<?
super Number>
) is supertype of the wildcard instantiation of the included
family (e.g.
Collection<? super Serializable>
).
Regarding conversions, the usual reference widening conversion from
subtype to supertype is allowed. The reverse is not permitted. |
LINK TO THIS
|
Technicalities.FAQ206
|
REFERENCES
|
How
do parameterized types fit into the Java type system?
Which
super-subtype relationships exist among instantiations of parameterized
types?
What
is the raw type?
What
is a concrete parameterized type?
What
is a wildcard parameterized type?
What
is the unbounded wildcard parameterized type?
What
is a wildcard?
What
is an unbounded wildcard?
What
is a bounded wildcard?
|
Which
super-subtype relationships exist among instantiations of generic types?
This is fairly complicated and
the type relationships are best determined setting up tables as explained
in this FAQ entry.
|
Super-subtype relationships among instantiations of generic
types are determined by two orthogonal aspects.
On the one hand, there is the inheritance relationship between a supertype
and a subtype. This is the usual notion of inheritance as we know it from
non-generic Java. For instance, the interface
Collection
is a supertype of the interface
List
. This inheritance relationship
is extended in analogy to instantiations of generic types, i.e. to parameterized
types. The prerequisite is that the instantiations must have identical
type arguments. An example is the supertype
Collection<Long>
and its subtype
List<Long>
. The rule is: as long as the type
arguments are identical, the inheritance relationship among generic types
leads to a super-subtype relationship among corresponding parameterized
types.
On the other hand, there is a relationship based on the type arguments.
The prerequisite is that at least one of the involved type arguments is
a wildcard. For example,
Collection<? extends Number>
is a
supertype of
Collection<Long>
, because the type
Long
is a member of the type family that the wildcard "
? extends Number
"
denotes.
This kind of type relationship also exists between two wildcard instantiations
of the same generic type with different wildcards as type arguments. The
prerequisite is that the type family denoted by one wildcard is a superset
of the type family denoted by the other wildcard. For example,
Collection<?>
is a supertype of
Collection<? extends Number>
, because the
family of types denoted by the wildcard "
?
" is a superset of the
family of types denoted by the wildcard "
? extends Number
".
The super-sub
set
relationship among the type arguments leads to
a super-sub
type
relationship among corresponding instantiations
of the same parameterized type. The type relationship mentioned above,
between a wildcard instantiation and a concrete instantiation of the same
generic type, is a special case of this rule; you just interpret the concrete
type argument as a type family with only one member, namely the concrete
type itself.
Both effects - the super-subtype relationship due to inheritance and
the super-subtype relationship due to type arguments - are combined and
lead to a two-dimensional super-subtype relationship table. The tables
below use examples for illustration.
The vertical axis of the table lists parameterized types according to
their inheritance relationship, starting with the supertype on the top
to a subtype on the bottom. The horizontal axis lists type arguments according
to their super-subset relationship of the type families they denote, starting
with the largest type set on the lefthand side to the smallest type set
on the righthand side.
|
|
|
|
|
|
|
?
|
? extends Serializable
|
? extends Number
|
Long
|
Collection
|
Collection<?>
|
Collection<? extends Serializable>
|
Collection<? extends Number>
|
Collection<Long>
|
List
|
List<?>
|
List<? extends Serializable>
|
List<? extends Number>
|
List<Long>
|
ArrayList
|
ArrayList<?>
|
ArrayList<? extends
Serializable>
|
ArrayList<? extends
Number>
|
ArrayList<Long>
|
If you pick a certain entry in the table, say
List<? extends
Serializable>
, then the subtable to the bottom and to the right contains
all subtypes of the entry.
Below is another example that involves lower bound wildcards. Again,
the horizontal axis lists type arguments according to their super-subset
relationship of the type families they denote, starting with largest set
of types denoted by the unbounded wildcard "
?
" over type families
of decreasing size to a type set consisting of one concrete type. The difficulty
with lower bound wildcards is that the super-subset relationship of the
type families denoted by lower bound wildcards is slightly counter-intuitive
to determine. Details are discussed in a separate FAQ entry.
|
|
|
|
|
|
|
?
|
? super Long
|
? super Number
|
Object
|
Collection
|
Collection<?>
|
Collection<? super Long>
|
Collection<? super Number>
|
Collection<Object>
|
List
|
List<?>
|
List<? super Long>
|
List<? super Number>
|
List<Object>
|
ArrayList
|
ArrayList<?>
|
ArrayList<? super Long>
|
ArrayList<? super Number>
|
ArrayList<Object>
|
If you pick a certain entry in the table, say
List<? super Long>
,
then the subtable to the bottom and to the right contains all subtypes
of the entry and you would find information such as:
ArrayList<?
super Number>
is
a subtype of
List<?
super Long>
.
Generic Types With More Than One Type Parameter
We have been setting up tables to determine the super-subtype relationships
among different instantiations of different, yet related parameterized
types. These tables were two-dimensional because we took into account
inheritance on the one hand and various values for a type argument on the
other hand. A similar technique can be applied to parameterized types
with more than one type parameter.
The example below uses a generic class
Pair
with two type parameters.
The vertical axis lists the first type arguments order by their super-subset
relationship from the largest type set to the smallest type set.
The horizontal axis does the same for the second type argument.
|
|
|
|
|
|
?
|
? super Long
|
? super Number
|
Object
|
?
|
Pair<?,?>
|
Pair<?,? super Long>
|
Pair<?,? super Number>
|
Pair<?,Object>
|
? extends Serializable
|
Pair<? extends Serializable,?>
|
Pair<? extends Serializable,?
super Long>
|
Pair<? extends
Serializable
,? super Number>
|
Pair<? extends Serializable,Object>
|
? extends Number
|
Pair<? extends Number,?>
|
Pair<? extends Number,?
super Long>
|
Pair<? extends Number,?
super Number>
|
Pair<? extends Number,Object>
|
Long
|
Pair<Long,?>
|
Pair<Long,? super Long>
|
Pair<Long,? super Number>
|
Pair<Long,Object>
|
Tables with more than two dimensions can be set up in analogy.
The key point is that you list generic types from supertype to subtype
and type arguments from superset to subset. For this purpose you need to
interpret type arguments as sets of types and determine their super-subtype
relationships. Note that the latter can be rather counter-intuitive
in case of multi-level wildcards involving lower bounds. Details
are discussed in a separate FAQ entry. |
LINK TO THIS
|
Technicalities.FAQ207
|
REFERENCES
|
How
do parameterized types fit into the Java type system?
Which
super-subset relationships exist among wildcards?
What
is the raw type?
What
is a concrete parameterized type?
What
is a wildcard parameterized type?
What
is the unbounded wildcard parameterized type?
|
Which
super-subset relationships exist among wildcards?
A super-subtype relationship between
upper bounds leads to a super-subset relationship between the resulting
upper bound wildcards, and vice versa for a lower bounds.
|
A wildcard denotes a family (or set)
of types. These type sets can have super-subset relationships, namely when
one type set includes the other type set. The super-subset relationship
among wildcards determines the super-subtype relationships of instantiations
of parameterized types using these wildcards as type arguments. The
super-subtype relationships among instantiations of parameterized types
play an important role in method invocation, assignments, type conversions,
casts and type checks (as was discussed in a previous FAQ entry). For this
reason, we need to know how to determine the super-subset relationships
among the type sets denoted by wildcards.
Here are the rules:
-
The unbounded wildcard "?" denotes the set of all
types and is the superset of all sets denoted by bounded wildcards.
-
A wildcard with an upper bound A denotes a superset
of another wildcard with an upper bound B, if A is a
super
type of
B.
-
A wildcard with a lower bound A denotes a superset
of another wildcard with a lower bound B, if A is a
sub
type of B.
-
A concrete type denotes a set with only one element,
namely the type itself.
|
upper bound wildcards
|
lower bound wildcards
|
|
?
|
?
|
? extends
SuperType
|
? super
SubType
|
? extends
SubType
|
? super
SuperType
|
concrete type*
*either
SubType
or a subtype thereof
|
concrete type*
*either
SuperType
or a supertype thereof
|
Here are some examples illustrating the rules:
|
upper bound wildcards
|
lower bound wildcards
|
|
?
|
?
|
? extends Serializable
|
? super Long
|
? extends Number
|
? super Number
|
Long
|
Serializable
|
The wildcard "
?
" denotes the largest possible type set, namely
the set of all types. It is the superset of all type sets.
Upper bound wildcards are fairly easy to understand, compared to lower
bound wildcards. The wildcard "
? extends Serializable
"
denotes the family of types that are subtypes of
Serializable
,
the type
Serializable
itself being included. Naturally, this type
family is a subset of the type set denoted by the wildcard "
?
".
The wildcard "
? extends Number
" denotes the family of
types that are subtypes of
Number
, the type
Number
itself
being included. Since
Number
is a subtype of
Serializable
,
the type family "
? extends Number
" is a subset of the type family
"
? extends Serializable
". In other words, the super-sub
type
relationship between the upper bounds leads to a super-sub
set
relationship between the resulting type sets.
The concrete type
Long
denotes a single-member type set, which
is a subset of the type family "
? extends Number
" because
Long
is a subtype of
Number
.
Among the lower bound wildcards, the wildcard "
? super Long
"
denotes the family of types that are supertypes of
Long
, the type
Long
itself being included. The wildcard "
? super Number
"
denotes the family of types that are supertypes of
Number
, the
type
Number
itself being included.
Number
is a
supertype of
Long
, and for this reason the type family "
?
super Number
" is smaller than the type family "
? super Long
";
the latter includes type
Long
as member, while the former excludes
it. In other words, the super-sub
type
relationship between
the lower bounds leads to the opposite relationship between the resulting
type sets, namely a sub-super
set
relationship.
Multi-Level Wildcards
Matters are more complicated when it comes to multi-level wildcards.
In principle, the rules outlined above are extended to multi-level wildcards
in analogy. However, the resulting super-subset relationships tend
to be everything but intuitive to understand, so that in practice you will
probably want to refrain from overly complex multi-level wildcards.
Nonetheless, we discuss in the following the rules for super-subset relationships
among two-level wildcards.
For multi-level wildcards we apply the same rules as before. The only
difference is that the bounds are wildcards instead of concrete types.
As a result we do not consider type relationships among the bounds, but
set relationships.
-
A wildcard with an upper bound A denotes a superset
of another wildcard with an upper bound B, if A denotes a
super
set
of B.
-
A wildcard with a lower bound A denotes a superset
of another wildcard with a lower bound B, if A denotes a
sub
set
of B.
The bounds A and B are wildcards and we can look up their super-subset
relationships in the tables above, which leads us to the following tables
for multi-level wildcards:
|
upper-upper bound wildcards
|
upper-lower bound wildcards
|
|
?
|
?
|
? extends ParType<
?
>
|
? extends ParType<
?
>
|
? extends
ParType
<
? extends
SuperType
>
|
? extends ParType<
? super
SubType
>
|
? extends
ParType
<
? extends
SubType
>
|
? extends ParType<
? super
SuperType
>
|
? extends ParType<
concrete type
*
>
*either
SubType
or a subtype thereof
|
? extends
ParType
<
concrete
type
*
>
*either
SuperType
or a supertype thereof
|
concrete type*
<
concrete type**
>
*either
ParType
or a subtype thereof
**either
SubType
or a subtype thereof
|
concrete type*
<
concrete type**
>
*either
ParType
or a subtype thereof
**either
SuperType
or a supertype thereof
|
|
lower-upper bound wildcards
|
lower-lower bound wildcards
|
|
?
|
?
|
? super
ParType
<
concrete type
*
>
*either
SubType
or a subtype thereof
|
? super
ParType
<
concrete type
*
>
*either
SuperType
or a supertype thereof
|
? super ParType<
? extends
SubType
>
|
? super ParType<
? super
SuperType
>
|
? super
ParType
<
? extends
SuperType
>
|
? super ParType<
? super
SubType
>
|
? super ParType<
?
>
|
? super ParType<
?
>
|
concrete type*
<?>
*either
ParType
or a supertype thereof
|
concrete type*
<?>
*either
ParType
or a supertype thereof
|
The rules look fairly complex, but basically it is a recursive process.
For instance: the type set denoted by "
? extends
ParType
<?
extends SuperType>
"
is
a superset of the type set denoted by "
? extends
ParTyp
e
<?
extends
SubType
>
"
because
the upper bound "
? extends SuperType
"
is a superset of the upper bound "
? extends
SubType
",
and this is because the inner upper bound
SuperType
is
a supertype of the inner upper bound
SubType
.
In this recursive way, the super-subset relationships of multi-level wildcards
can be made plausible. Here are some concrete examples:
Examples:
|
upper-upper bound wildcards
|
upper-lower bound wildcards
|
|
?
|
?
|
? extends List<?>
|
? extends List<?>
|
? extends List<? extends Serializable>
|
? extends List<? super Long>
|
? extends List<? extends Number>
|
? extends List<? super Number>
|
? extends List<Long>
|
? extends List<Serializable>
|
ArrayList<Long>
|
LinkedList<Serializable>
|
|
lower-upper bound wildcards
|
lower-lower bound wildcards
|
|
?
|
?
|
? super List<Long>
|
? super List<Object>
|
? super List<? extends Number>
|
? super List<? super Serializable>
|
? super List<? extends Serializable>
|
? super List<? super Number>
|
? super List<?>
|
? super List<?>
|
Collection<?>
|
Collection<?>
|
|
LINK TO THIS
|
Technicalities.FAQ208
|
REFERENCES
|
How
do parameterized types fit into the Java type system?
Which
super-subtype relationships exist among instantiations of parameterized
types?
What
is the raw type?
What
is a concrete instantiation?
What
is a wildcard instantiation?
What
is the unbounded wildcard instantiation?
|
Does
"extends" always mean "inheritance"?
No.
|
extends
is an overloaded keyword in the Java programming
language. It has different meanings, depending on the context in
which it appears. The
extends
keyword can appear in four different
locations:
-
in the definition of a class
-
in the definition of an interface
-
in the definition of type parameter bounds
-
in the definition of wildcard bounds
Below we discuss each of these cases in detail. Basically it boils
down to the observation that
extends
refers to more general super-subtype
relationship, of which inheritance is a special case. In conjunction
with class and interface definitions
extends
means inheritance.
In conjunction with type parameter bounds and wildcard bounds
extends
it
means either inheritance, or identity, or member of a type family denoted
by a wildcard instantiation.
Definition of classes and interfaces
Example (
extends
in the definition of a class):
public class Quadruple<T>
extends
Triple<T> {
private T fth;
public Triple(T t1, T t2, T t3, T t4) {
super(T1, t2, t3);
fth = t4;
}
...
}
This is an example where
extends
means inheritance. We define
a subclass
Quadruple
that is derived from a superclass
Triple
.
This kind of inheritance exists between non-generic and generic classes.
It leads to a super-subtype relationship between the two types. In
case of generic types it leads to a super-subtype relationship between
instantiations of the two types that have identical type arguments.
For instance,
Triple<Long>
is a subtype of
Triple<Long>
,
and
Quadruple<? extends Number>
is a subtype of
Triple<?
extends Number>
.
Example (
extends
in the definition of an interface):
public interface SortedSet<E>
extends
Set<E> {
...
}
This is another example where
extends
means inheritance. This
time the inheritance relationship exists between two interfaces instead
of two classes. We have the super-subtype relationships as for classes.
Definition of type parameter bounds
Example (
extends
in the definition of type parameter bounds):
public class Caller<V, T
extends
Callable<V>> {
public Caller(T task) {
FutureTask<V> future = new FutureTask<V>(task);
Thread thread = new Thread(future);
thread.setDaemon(false);
thread.start();
try { System.out.println ("result:
" + future.get()); }
catch (Exception e) { e.printStackTrace();
}
}
}
In this example
extends
does not mean inheritance. The
extends
keyword is here used to define the bounds of a type parameter. A
common misunderstanding is that the type argument that later replaces the
type parameter in an instantiation must inherit from the bounds.
This is often the case, but it is not the only option.
In our example, we can supply a sub-interface of
Callable<V>
as a type argument, that is, an interface that
extends
the bound.
But we can also supply a class type that
implements
the bound.
In other words,
extends
in conjunction with type parameter bounds
does not strictly mean inheritance, but it also includes the
implements
-relationship
that exists between classes and interfaces.
In conjunction with type parameter bounds the
extends
keyword
refers to an even broader notion of subtyping. It includes relationships
that cannot be expressed in terms of
extends
or
implements
as
we know them from non-generic Java. Consider the following example.
Example (
extends
in the definition of a type parameter bound
that is a final class):
public class SomeClass<T
extends
String> {
...
}
In this example the bound is a final class. Final classes cannot
be inherited from. The only permitted type argument is type
String
,
which is not a type that inherits from the bound; it is the bound itself.
When applied to a bound that is a final class,
extends
does not
mean inheritance; it means
identity
.
Let us consider another example that demonstrates that
extends
in conjunction with type parameter bounds means more than inheritance.
Example (
extends
in the definition of a type parameter bound
that is a wildcard parameterized type):
public class SomeClass<T
extends
Collection<?>> {
...
}
In this example the bound is a wildcard instantiation of the
Collection
interface. Wildcard parameterized types cannot be inherited from. In this
case
extends
does not mean inheritance either. It refers to the
super-subtype relationship that exists among wildcard parameterized types
and concrete parameterized type. Conceivable type arguments would
be concrete parameterized type, such as
Collection<String>
or
List<Long>
, but also other wildcard parameterized
types, such as
Collection<? extends Number>
. These type
arguments do not inherit from the bound, but they are members of the type
family that the bound denotes. When applied to a bound that is a wildcard
parameterized type,
extends
does not mean inheritance; it means
member
of the type family
denoted by the wildcard parameterized type.
Definition of wildcard bounds
Example (
extends
in the definition of a wildcard bound):
List<?
extends
Number>
ref = new ArrayList<Number>();
The meaning of
extends
in conjunction with wildcard bounds is
similar to the meaning of
extends
in conjuction with type parameter
bounds. It does NOT mean that the unknown type that the wildcard
stands for (the so-called
wildcard capture
) must inherit from the
wildcard bound. It can, but it does not have to. The capture can equally
well be the bound itself and
extends
would mean identity instead
of inheritance.
If the bound is a wildcard parameterized type, then
extends
refers to the subtype relationship that exists among wildcard parameterized
types and concrete parameterized types.
Example (
extends
in the definition of a wildcard bound):
Triple<?
extends
Collection<?>> q = new Triple<List<? extends Number>>();
In this example the wildcard capture cannot be a type that inherits from
the bound, because wildcard parameterized types such as
Collection<?>
cannot be inherited from. Instead the wildcard capture must be a
member of the type family denoted by
Collection<?>
, such
as
Collection<String>
,
List<Number>
,
List<?>
or
List<? extends Number>
. Again,
extends
does
not mean inheritance; it means
member of the type family
denoted
by the wildcard parameterized type. |
LINK TO THIS
|
Technicalities.FAQ209
|
REFERENCES
|
How
do parameterized types fit into the Java type system?
Which
super-subset relationships exist among wildcards?
What
is the capture of a wildcard?
|
Exception Handling
Can
I use generic or parameterized types in exception handling?
Why
are generic exception and error types illegal?
Because the virtual machine cannot distinguish
between different instantiations of a generic exception type.
|
A generic class must not directly or indirectly be derived
from class
Throwable
, which means that generic exception or error
types are not allowed. Imagine if they were allowed ...
Example (of illegal generic exception type):
class IllegalArgumentException<T> extends Exception
{
// illegal
private T info;
public IllegalArgumentException(T arg) { info = arg; }
public T getInfo() { return info; }
}
We might then want to catch instantiations of this (illegal) generic exception
type.
Example (of illegal use of illegal parameterized exception type):
void method_1() {
try { method_2(); }
catch (IllegalArgumentException<String> e) { ... }
//
illegal
catch (IllegalArgumentException<Long> e) { ... }
//
illegal
catch (Throwable e) { ... }
}
Taking into account that generic Java source code is translated to Java
byte code by type erasure, it should be clear that the method's
catch
clauses do not make any sense. Both parameterized exception types
have the same runtime type and the mechanism for catching exceptions is
a runtime mechanism performed by the virtual machine based on the non-exact
runtime types. The JVM has no chance to distinguish between different
instantiations of the same generic (exception) type. For this reason,
generic exception and error types are pointless in Java and banned from
the language. (Note that generic exception and error types are not pointless
per se, but in the context of Java generics with type erasure their are
nonsensical.)
Other problems occur when we define methods that throw instantiations
of an (illegal) generic exception type.
Example (of illegal use of illegal parameterized exception type):
void method_1()
throws IllegalArgumentException<String>, IllegalArgumentException<Long>
{
// illegal
... do something ...
throw new IllegalArgumentException<String>("argument
missing");
... do something else ...
throw new IllegalArgumentException<Long>(timeout);
}
Again, after type erasure, both parameterized exception types have the
same runtime type and the method's
throws
clause is nonsensical.
The method could at best throw the raw type, which means that it must not
create and throw any instantiations of the (illegal) generic type either.
Another reason to disallow generic exception and error types. |
LINK TO THIS
|
Technicalities.FAQ302
|
REFERENCES
|
What
is type erasure?
|
Can
I use a type parameter in exception handling?
Can
I use a type parameter in a catch clause?
No.
|
Using a type parameter in a
catch
clause is nonsensical
because of the translation by type erasure.
Example (before type erasure):
<
E extends Exception
>
void someMethod() {
try { … do something that might raise an exception
…
} catch (
E
e) { …
do something special for this particular exception type …
} catch (IllegalStateException e) { … handle
illegal state …
} catch (Exception e) { … handle all remaining
exceptions …
}
}
Example (after type erasure):
void someMethod() {
try { … do something that might raise an exception
…
} catch (
Exception
e)
{ … do something special for this particular exception type
…
} catch (IllegalStateException e) { … handle
illegal state …
} catch (Exception e) { … handle all remaining
exceptions …
}
}
After type erasure the
catch
clause would boil down to a
catch
clause using the type parameter's bound. This is because type parameters
do not have a runtime type representation of themselves. Type parameters
are replaced by their leftmost bound in the course of translation by type
erasure. In our example the
catch
clause for the unknown exception
type
E
is translated by type erasure to a
catch
clause
for type
Exception
, which is the bound of the type parameter
E
.
This
catch
clause for type
Exception
precedes further
catch
clauses for other exception types, and renders them pointless.
In other words, there never is a
catch
clause for the particular
unknown exception type that the type argument stands for. Instead of catching
a particular exception type we end up catching the bound of the unknown
exception type, which changes the meaning of the sequence of
catch
clauses substantially and is almost always undesired. For this reason,
the use of type parameters in
catch
clauses is illegal. |
LINK TO THIS
|
Technicalities.FAQ304
|
REFERENCES
|
Can
I use a type parameter in in a throws clause?
Can
I throw an object whose type is a type parameter?
|
Can
I use a type parameter in in a throws clause?
Yes.
|
Using a type paramter in a
throws
clause is permitted.
Example (before type erasure):
public interface Action<E extends Exception> {
void run()
throws E
;
}
public final class Executor {
public static <E extends Exception>
void execute(Action<E> action)
throws
E
{
…
action.run();
…
}
}
public final class Test {
private static class TestAction implements
Action<java.io.FileNotFoundException>
{
public void run()
throws
java.io.FileNotFoundException
{
…
throw
new java.io.FileNotFoundException()
;
…
}
public static void main(String[] args) {
try {
Executor.execute(new TestAction())
;
}
catch
(java.io.FileNotFoundException f)
{
… }
}
}
In this example we see a generic interface
Action
whose type parameter
E
is the exception type that its
run
method throws. This is
perfectly reasonable, because
throws
clauses are a compile-time
feature and the lack of a runtime type representation of the type parameter
is not needed. At runtime, a particular exception type will have replaced
the type parameter
E
, so that we would throw and catch an object
of a concrete exception type. In our example we instantiate the
Action
interface using the
FileNotFound
exception type as a type argument,
so that a
FileNotFound
exception is raised and caught.
Even after type erasure the code snippet above still captures the intent.
Example (after type erasure):
public interface Action {
void run()
throws
Exception
;
}
public final class Executor {
public static void execute(Action action)
throws
Exception
{
…
action.run();
…
}
}
public final class Test {
private static class TestAction implements Action {
public void run()
throws
java.io.FileNotFoundException
{
…
throw
new java.io.FileNotFoundException()
;
…
}
public static void main(String[] args) {
try {
Executor.execute(new TestAction())
;
}
catch
(java.io.FileNotFoundException f)
{
… }
}
}
|
LINK TO THIS
|
Technicalities.FAQ305
|
REFERENCES
|
Can
I use a type parameter in a catch clause?
Can
I throw an object whose type is a type parameter?
|
Can
I throw an object whose type is a type parameter?
In principle, yes, but in practice, not
really.
|
We can declare methods that throw an exception (or error)
of unknown type.
Example (of method with type parameter in
throws
clause):
interface Task<
E extends Exception
>
{
void run()
throws E
;
}
Can such a method throw an object of the unknown exception (or error) type?
The method has in principle 3 ways of raising such an exception (or error):
-
create a new exception (or error) and throw it
-
catch the exception (or error) of an invoked operation and re-throw it
-
propagate the exception (or error) of an invoked operation
As we must not create objects of an unknown type the first possibility
is not an option. Since type parameters must not appear in catch
clauses, we cannot catch and therefore not re-throw an exception of an
unknown type. At best, we can propagate an exception of unknown type
that is raised by any of the invoked operations.
Example (throwing an object of unknown type):
final class CleanUp<
E extends
Exception
, T extends Task<E>> {
public void cleanup(T task)
throws
E
{
task.run();
}
}
final class DisconnectTask implements Task<IllegalAccessException>
{
public void run() throws IllegalAccessException {
...
throw new IllegalAccessException();
...
}
}
class Test {
public static void main(String[] args) {
CleanUp<IllegalAccessException,DisconnectTask>
cleaner
= new CleanUp<IllegalAccessException,DisconnectTask>();
try { cleaner.cleanup(new DisconnectTask());
}
catch (IllegalAccessException e) { e.printStackTrace();
}
}
}
|
LINK TO THIS
|
Technicalities.FAQ306
|
REFERENCES
|
Can
I create an object whose type is a type parameter?
Can
I use a type parameter in a catch clause?
Can
I use a type parameter in in a throws clause?
|
Static Context
How
do I refer to static members of a parameterized type?
Using the raw type as the scope qualifier,
instead of the any instantiation of the generic type.
|
If you refer to a static member of a generic type, the
static member name must be preceded by the name of the enclosing scope,
such as
EnclosingType.StaticMember
. In case of a generic
enclosing type the question is: which instantiation of the generic type
can or must be used as the scope qualifier?
The rule is that no instantiation can be used. The scope is qualified
using the raw type. This is because there is only one instance of
a static member per type.
Example (of a generic type with static members):
public final class Counted<T> {
public
static
final int MAX = 1024;
public
static
class BeyondThresholdException
extends Exception {}
private
static
int count;
public
static
int getCount() { return count;
}
private final T value;
public Counted(T arg) throws BeyondThresholdException {
value = arg;
count++;
if (count >= 1024) throw new BeyondThresholdException();
}
public void finalize() { count--; }
public T getValue() { return value; }
}
int m =
Counted
.MAX;
//
ok
int k =
Counted
<Long>
.MAX;
//
error
int n =
Counted
<?>
.MAX;
//
error
try {
Counted<?>[] array = null;
array[0] = new Counted<Long>(10L);
array[1] = new Counted<String>("abc");
}
catch (
Counted
.BeyondThresholdException e) {
e.printStackTrace();
}
System.out.println(
Counted
.getCount());
//
ok
System.out.println(
Counted
<Long>
.getCount());
//
error
System.out.println(
Counted
<?>
.getCount());
//
error
In the example above, the generic class
Counted
has a static member
MAX
and there is only one unique
Counted.MAX
, regardless of the number
of objects of type
Counted
and regardless of the number of instantiations
of the generic type
Counted
that may be used somewhere in the
program. Referring to
MAX
as
Counted<String>.MAX
,
Counted<Long>.MAX
,
Counted<?>.MAX
,
etc. would be misleading, because it suggests that there were several manifestations
of the
MAX
member, which is not true. There is only one
Counted.MAX
,
and it must be referred to using the raw type
Counted
as the scope
qualifier. The same is true for other static members such as static methods
and static nested types. The example illustrates that we must refer
to the static method as
Counted.getCount
and to the static nested
exception class as
Counted.BeyondThresholdException
.
In sum, it is illegal to refer to a static member using an instantiation
of the generic enclosing type. This is true for all categories of
static members, including static fields, static methods, and static nested
types, because each of these members exists only once per type. |
LINK TO THIS
|
Technicalities.FAQ351
|
REFERENCES
|
Is
there one instances of a static field per instantiation of a generic type?
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?
How
do I refer to an enum type nested into a generic type?
How
do I refer to a (non-static) inner class of a generic type?
Can
I import a particular instantiation of generic type?
|
How
do I refer to a (non-static) inner class of a generic type?
Using an instantiation of the enclosing
generic type as the scope qualifier, instead of the raw type.
|
Static nested types are referred to using the raw form
of the enclosing generic type as the scope qualifier. This is different
for inner classes.
Like a static nested type, an inner class exists only once, in the sense
that there is only one
.class
file that represents the inner class.
Different from a static nested type, an inner class depends on the type
argument of its outer class type. This is because each object of
an inner class type has a hidden reference to an object of the outer class
type. The type of this hidden reference is an instantiation of the
generic enclosing type. As a result, the inner class type is not
independent of the enclosing class's type arguments.
Example (of an inner class nested into a generic outer class):
class Sequence
<E>
{
private E[] theSequence;
private int idx;
// static classes
public static class
NoMoreElementsException extends Exception {}
public static class
NoElementsException extends Exception {}
// (non-static) inner
class
public class Iterator
{
boolean hasNext() {
return (theSequence != null && idx < theSequence.length);
}
E
getNext() throws NoElementsException,
NoMoreElementsException,
java.lang.IllegalStateException {
if (theSequence == null)
throw new NoElementsException();
if (idx < 0)
throw new java.lang.IllegalStateException();
if (idx >= theSequence.length)
throw new NoMoreElementsException();
else
return theSequence[idx++];
}
}
public Iterator getIterator()
{
return this.new Iterator();
}
}
class Test {
private static <T> void print(Sequence<T>
seq)
throws
Sequence.NoElementsException
,
Sequence.NoMoreElementsException {
Sequence
<T>
.Iterator
iter = seq.new Iterator();
while (iter.hasNext())
System.out.println(iter.getNext());
}
public static void main(String[] args) {
try {
Sequence<String> seq1 = new Sequence<String>();
... fill the sequence ...
print(seq1);
Sequence<Long> seq2 = new Sequence<Long>();
... fill the sequence ...
print(seq2);
} catch (Exception e)
{ e.printStackTrace();
}
}
}
In the example above, the inner class
Iterator
depends on the
outer class's type parameter
E
: the type parameter
E
is the return type of the iterator's
getNext
method and the inner
class access the outer class's array of elements of type
E
.
For this reason, the iterator type is referred to as
Sequence<T>.Iterator
(using an instantiation), instead of just
Sequence.Iterator
(using
the raw type). This does not imply, that a scope qualification using
the raw type is illegal; it is permitted and it means that the outer object
being referred to by the inner object is of the raw type instead of a more
specific type.
In contrast, the nested exception types are
static
classes and
do
not
depend on the outer class's type parameter. (Static nested
types never depend on their enclosing type's type parameters, because the
type parameters must not appear in any static context of a generic class.
Details are explained in a separate FAQ entry.) Static nested classes must
be referred to using the raw type as the scope qualifier, that is, as
Sequence.NoElementsException
;
use of an instantiation as the scope, such as
Sequence<T>.NoElementsException
,
is illegal. |
LINK TO THIS
|
Technicalities.FAQ352
|
REFERENCES
|
How
do I refer to static members of a generic type?
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?
Using the raw type as the scope qualifier,
instead of the any instantiation of the generic type.
|
Nested interfaces are implicitly static. This is sometimes
confusing because the interface looks like it were a non-static member
of its enclosing class, while in fact it is static. As a static member
of a generic type it must be referred to using the raw form of the enclosing
type as the scope qualifier. Using an instantiation as the scope
qualifier is illegal and rejected by the compiler.
Example (of a nested interface):
class
Controller
<E extends
Executor
>
{
private E executor;
public Controller(E e) { executor = e; }
...
public
interface
Command
{ void doIt(Runnable task); }
public Command command() {
return new Command() {
public void doIt(Runnable
task) { executor.execute(task); }
};
}
}
class Test
public static void test(Controller<ThreadPoolExecutor>
c) {
Controller<ExecutorService>
controller
= new Controller<ExecutorService>(Executors.newCachedThreadPool());
Controller.Command
command = controller.command();
...
}
}
The
Command
interface is nested into the generic
Controller
class. The compiler does not allow that we refer to the nested interface
using any instantiation of the enclosing generic class as the scope qualifier.
Instead of saying
Controller<ExecutorService>.Command
we must
say
Controller.Command
.
Below is another example of a nested interface taken from the
java.util
package.
The generic
Map
interface has a nested
Entry
interface.
Example (of a nested interface taken from package
java.util
):
public interface
Map
<K,V>
{
public
interface
Entry<K,V>
{
public K getKey();
public V getValue();
...
}
public Set<
Map.Entry<K, V>
> entrySet();
...
}
The source code above is an excerpt from the JDK source code. Note
that the nested interface
Entry
is generic itself and has its
own type parameters, which are independent of the outer interface's type
parameters. The fact that the type parameters of inner and outer
interface have the same names, namely
K
and
V
, is perhaps
confusing, but perfectly legal. The inner interface's type parameters
K
and
V
are visible only inside the inner interface and have nothing
to do with the outer interface's type parameters
K
and
V
.
When the inner interface
Entry
is used it must be referred
to using the raw type
Map
as the scope qualifier, that is, as
Map.Entry<String,Long>
for
instance. A qualification such as
Map<String,Long>.Entry<String,Long>
is
illegal. |
LINK TO THIS
|
Technicalities.FAQ353
|
REFERENCES
|
Why
can't I use a type parameter in any static context of the generic class?
How
do I refer to static members of a parameterized type?
Can
I import a particular instantiation of parameterized type?
|
How
do I refer to an enum type nested into a generic type?
Using the raw type as the scope qualifier,
instead
of the any instantiation of the generic type.
|
Nested enum types are implicitly static. This is sometimes
confusing because the enum type looks like it were a non-static member
of its enclosing class, while in fact it is static. As a static member
of a generic type it must be referred to using the raw form of the enclosing
type as the scope qualifier. Using an instantiation as the scope
qualifier is illegal and rejected by the compiler.
Example (of a nested enum type):
class
Controller
<E extends
Executor
>
{
private State state;
...
public
enum
State
{
VALID, INVALID; }
public State getState() { return state; }
}
class Test
public static void test(Controller<ThreadPoolExecutor>
c) {
Controller<ExecutorService>
controller
= new Controller<ExecutorService>(Executors.newCachedThreadPool());
Controller.State
state = controller.getState();
switch (state)) {
case INVALID: ... ;
case VALID: ... ;
}
...
}
}
The enum type
State
is nested into the generic
Controller
class. The compiler does not allow that we refer to the nested interface
using any instantiation of the enclosing generic class as the scope qualifier.
Instead of saying
Controller<ExecutorService>.State
we must
say
Controller.State
. The same applies to the enum constants;
they are referred to as
Controller.State.VALID
and
Controller.State.INVALID
. |
LINK TO THIS
|
Technicalities.FAQ354
|
REFERENCES
|
How
do I refer to static members of a paramterized type?
Can
I import a particular parameterized type?
|
Can
I import a particular parameterized type?
No.
|
In an
import
statement we must not use parameterized
types; only raw types are permitted. This applies to regular and
static
import
statements.
Example (of a generic type with static members):
package com.sap.util;
public final class
Counted
<T>
{
public
static
final int MAX = 1024;
public
static
class BeyondThresholdException
extends Exception {}
private
static
int count;
public
static
int getCount() { return count;
}
private final T value;
public Counted(T arg) throws BeyondThresholdException {
value = arg;
count++;
if (count >= 1024) throw new BeyondThresholdException();
}
public void finalize() { count--; }
public T getValue() { return value; }
}
import com.sap.util.
*
;
//
ok
import com.sap.util.
Counted
;
//
ok
import com.sap.util.
Counted
<String>
;
//
error
import static com.sap.
Counted
.
*
;
//
ok
import static com.sap.
Counted
<String>
.
*
;
//
error
import static cam.sap.
Counted
.BeyondThresholdException;
//
ok
import static com.sap.
Counted
<String>
.BeyondThresholdException;
//
error
|
LINK TO THIS
|
Technicalities.FAQ355
|
REFERENCES
|
How
do I refer to static members of a parameterized type?
|
Why
are generic enum types illegal?
Because they do not make sense in Java.
|
An enum type is similar to a class type of which only a
limited number of instances, namely the enum values, exist. The enum values
are static fields of the enum type. The key question is: of which type
would the static enum values be if the enum type were allowed to be parameterized?
Example (of an illegal generic enum type):
public enum Tag<T> { // illegal, but assume we
could do this
good, bad;
private T attribute;
public void setAttribute(T arg) { attribute = arg; }
public T getAttribute() { return attribute; }
}
This enum type would be translated to a class that roughly looks like this:
public class Tag<T> extends Enum<Tag<T>> {
public static final Tag<
???
>
good;
public static final Tag<
???
>
bad;
private static final Tag $VALUES[];
private T attribute;
private Tag(String s, int i) { super(s, i); }
static {
good = new Tag("good", 0);
bad = new Tag("bad" , 1);
$VALUES = (new Tag[] { good, bad });
}
public void setAttribute(T arg) { attribute = arg; }
public T getAttribute() { return attribute; }
}
The static enum values cannot be of type
Tag<T>
because type
parameters such a
T
must not appear in any static context. Should
they be of the raw type
Tag
then? In this case the private
attribute
field would be of type
Object,
the invocation of the
setAttribute
method would be flagged an "unchecked call" and the
getAttribute
method would only return
Object
. The entire parameterization would
be pointless then.
On the other hand, if we wanted that the type of the enum values is
a particular instantiation of the generic enum type, how would we tell
the compiler? There is no syntax for specifying the type of an enum
value.
Also, when we refer to the enum values we must qualify their name by
the name of their defining class, that is,
Tag.good
and
Tag.bad
.
Although
Tag
is a parameterized type, we cannot say
Tag<String>.good
or
Tag<Long>.bad
. This is because static members of a
generic type must be referred to via the raw type name. In other
words, we would not even be capable of expressing that we intend to refer
to an enum value belonging to a particular instantiation of the generic
enum type.
No matter how we put it: generic enum types do not make sense. |
LINK TO THIS
|
Technicalities.FAQ356
|
REFERENCES
|
Why
can't I use a type parameter in any static context of the generic class?
How
do I refer to static members of a parameterized type?
|
Type Argument
Inference
What
is type argument inference?
The automatic deduction of the type arguments
at compile time.
|
Type inference happens when the compiler can deduce the
type arguments of a generic type or method from context information.
In this case the type arguments need not be explicitly specified.
There are two situations in which type inference is attempted:
-
when an object of a generic type is created
, and
-
when a generic method is invoked.
Example (of automatic type inference on instance creation):
class ArrayList<E> {
...
}
List<String> list1 = new ArrayList
<String>
();
// type parameter specified => E:=String
List<String> list2 = new ArrayList
<>
();
// type parameter inferred => E:=String
List<String> list3 = new ArrayList();
// type parameter omitted => using
raw type
Class
ArrayList<E>
is a generic class. Usually you must
specify the type parameter whenever you use the generic type (unless you
want to use the raw type). In an instance creation expression you can omit
the type parameter and replace it by empty angle brackets. When the compiler
sees the empty angle brackets in the
new
-expression it takes a
look at the lefthand side of the assignment in which the
new
-expression
appears. From the static type of the lefthand side variable the compiler
infers the type parameter of the generic type of the newly created object.
In the example above the compiler concludes that the type variable
E
must be replaced by the type
String
. If you neither specify the
type parameter no use the empty angle brackets you refer to the raw type.
[Note: Type inference for
new
-expressions was introduced in
Java 7 and did not exist in Java 5 and 6.]
Example (of automatic type inference on method invocation):
class Collections {
...
public static <T> void copy(List<? super T>
dest, List<? extends T> src) { ... }
...
}
List<String> src = new ArrayList<>();
List<Object> dst = new ArrayList<>();
Collections.
<CharSequence>
copy(dst,src); // type
parameter specified => T:=CharSequence
Collections.
<>
copy(dst,src);
// error: illegal syntax
Collections.copy(dst,src);
// type parameter inferred => T:=String
The
copy()
method is a generic method. When the generic
method is invoked without explicit specification of the type argument then
the compiler takes a look at the arguments that are provided for the method
call. From their static types the compiler infers the type parameter
of the generic
copy()
method. In the example above the compiler
concludes that the type variable
T
must be replaced by the type
String
.
Different from type inference in conjunction with new-expressions empty
angle bracket are not permitted; the brackets must be omitted entirely. |
LINK TO THIS
|
Technicalities.FAQ400
|
REFERENCES
|
What
is type argument inference for generic methods?
What
is type argument inference for instance creation expressions?
What
is the diamond operator?
Is
there a correspondence between type inference for method invocation and
type inference for instance creation?
|
Is
there a correspondence between type inference for method invocation and
type inference for instance creation?
Yes, the instance creation expression
for a generic type is treated like invocation of a generic creator method.
|
The rules for type inference
for method
invocation and type inference for instance creation
are the same.
Consider a generic class with a constructor:
class SomeClass<T> {
SomeClass(T arg) { ... }
}
The creation of an object of this type can involve type inference, e.g.
in this example:
SomeClass<Long> ref = new SomeClass<>(0L);
The type inference for the instance creation is performed in the same way
as for a static creator method. If the class had the following
creator method:
class SomeClass<T> {
SomeClass(T arg) { ... }
static <E> SomeClass<E>
make(E arg)
{ return new SomeClass<E>(arg);
}
}
then the type inference for the invocation of the creator method would
yield the same result as type inference for the instance creation.
That is, the following leads to equivalent type inference:
SomeClass<Long> ref = new SomeClass<>(0L);
SomeClass<Long> ref = SomeClass.make(0L);
|
LINK TO THIS
|
Technicalities.FAQ400A
|
REFERENCES
|
What
is type argument inference for instance creation expressions?
What
is type argument inference for generic methods?
|
What
is the "diamond" operator?
It denotes the empty angle brackets that
are used for type inference in
new
-expressions.
|
Since Java 7 the compiler can infer the type parameters
of a parameterized type in a
new
-expression. In order to
trigger the type inference the so-called
diamond operator
is used.
Below is an example.
Example (of diamond operator):
List<String> list1 = new ArrayList
<String>
();
List<String> list2 = new ArrayList
<>
();
The empty angle brackets
<>
are
called the
diamond operator
. The diamond operator is not really
an operator in the sense of the syntax specification of the Java programming
language. Rather it is just an empty type parameter specification.
The empty brackets are needed in order to distinguish between the raw type
ArrayList
and the incomplete type
ArrayList<>
.
Until Java 9 the diamond operator was not permitted in anonymous inner
class definitions.
Here is an example:
Callable<Long> task = new Callable <> () {
// error in Java 7/8;
fine
since Java 9
public Long call() { return 0L; }
};
|
LINK TO THIS
|
Technicalities.FAQ400B
|
REFERENCES
|
What
is type argument inference for instance creation expressions?
Why
does the type inference for an instance creation expression fail?
|
What
is type argument inference for instance creation expressions?
The automatic deduction of the type arguments
in a
new
-expression.
|
When an object of a parameterized type is created using
a
new
-expression (also called an
instance creation expression
)
then the compiler can infer part of the type information of the object
to be created. More specifically, the compiler can deduce the type parameters
of the parameterized type if it is incomplete. In order to trigger
the automatic type inference the so-called
diamond operator
is used
(see
Technicalities.FAQ400A
).
[Note: Type inference for new-expressions is available since Java 7.]
Type inference takes into account the context in which the
new
-expression
appears and what the
new
-expression looks like. The type
inference process works in two separate steps:
-
First, the compiler takes a look at the static types of the constructor
arguments in the
new
-expression.
-
Second, if any of the missing type parameters cannot be resolved from the
constructor arguments, then the compiler uses information from the context
in which the
new
-expression appears.
In Java 7, the only permitted inference context is an
assignment context
.
If the
new
-expression is the right-hand side of an assignment,
then the compiler deduces the missing type parameter information from the
lefthand side of the assignment, if possible.
Since Java 8, an additional inference context is permitted, namely the
method
invocation context
. If the
new
-expression is the argument
to a method invocation, then the compiler deduces the missing type parameter
information from the method's declared argument type, if possible.
Examples (of type inference for an instance creation expression
without constructor arguments):
List<String> list1 = new ArrayList
<String>
();
List<String> list2 = new ArrayList
<>
();
// type inference
Map<Callable<String>, List<Future<String>>> tasks1
= new HashMap
<Callable<String>, List<Future<String>>>
();
Map<Callable<String>, List<Future<String>>> tasks2
= new HashMap
<>
();
// type inference
In the examples above no constructor arguments are specified. As
a result, the compiler cannot deduce any type information from the constructor
arguments. Instead it takes a look at the static type of the expression
on the lefthand side of the assignment and infers the missing type parameters
from there.
Examples (of improved type inference in Java 8):
List<String> list1 =
new ArrayList<>
();
// fine since Java 7
List<String> list2 = Collections.synchronizedList(
new
ArrayList<>
()
); //
error in
Java 7
;
fine in Java 8
error: incompatible types
List<String> list3
= Collections.synchronizedList(new ArrayList<>());
^
required: List<String>
found: List<Object>
In both examples type inference is needed, because no constructor arguments
are specified. The first
new
-expression appears in an assignment
context and the compiler infers the missing type parameter
String
from the left-hand side of the assignment. The second
new
-expression
appears in a method invocation context. Before Java 8, this yields
a compile-time error. Since Java 8, the compiler infers the missing type
parameter
String
from the declared type of the argument of the
synchronizedList
method.
(Actually, it is a little more complicated. The
synchronizedList
method
is a generic method and the compiler must first infer the generic method's
type parameter before it knows the method's declared argument type.
The generic
synchronizedList
method
appears in an assignement context, from which the compiler deduces that
the type parameter for
synchronizedList
must
be
String
. Its declared argument type then is
List<String>
and from this information the compiler infers that the new
ArrayList
must be an
ArrayList<String>
.)
The inference is different if constructor arguments are provided.
In such a context the compiler takes a look at the static types of the
constructor arguments and ignores the lefthand side of the assignment.
Examples (of type inference for an instance creation expression
with constructor arguments):
Set<Long> s1 = new HashSet<>();
Set<
Long
> s2 = new HashSet<>(Arrays.asList(0L,0L));
Set<
Number
> s3 = new HashSet<>(Arrays.asList(0L,0L));
//
error in Java 7
;
fine
since Java 8
Set<Number> s4 = new HashSet<Number>(Arrays.asList(0L,0L));
error: incompatible types
Set<Number> s3 =
new HashSet<>(Arrays.asList(0L,0L));
^
required: Set<Number>
found: HashSet<Long>
- The first
new
-expression does not have constructor arguments;
the missing type parameter is inferred from the lefthand side of the assignment.
The lefthand side is of type
Set<Long>
and compiler concludes
that the missing type parameter must be
Long
.
- The second
new
-expression has constructor arguments; the
missing type parameter is inferred from the constructor argument. The constructor
argument is of type
List<Long>
and the compiler again concludes
that the missing type parameter must be
Long
. Note, the
lefthand side of the assignment is ignored because the constructor argument
provides enough information for the type inference to complete successfully.
- The third
new
-expression demonstrates that the lefthand side
of the assignment is indeed ignored (in Java 7). The compiler again infers
from the constructors argument, i.e., the result of the
asList
method, that the missing type parameter for the new
HashSet
must
be
Long
. This leads to a type mismatch and an according
error message. The compiler does not conclude that the missing type
parameter should be
Number
because it ignores the lefthand side
of the assignment. In Java 8, the type inference was modified and
improved. Since then, compiler infers
Number
as the
type parameter form the new
HashSet
on the right-hand side of
the compiler and from that deduces
Number
as the type parameter
for the
asList
method. In Java 8, this compiles just fine.
- The fourth
new
-expression does not rely on type inference
and simply specifies the intended type parameter explicitly.
There is yet a different result of type inference if the context of
the
new
-expression has neither constructor arguments nor a lefthand
side of an assignment. Here is an example.
Examples (of type inference for an instance creation expression
in a method invocation context):
void method(Set<Long> arg) { ... }
method(new HashSet<>());
//
error in Java 7
;
fine
in Java 8
method(new HashSet<>(Arrays.asList(0L,1L,2L)));
error: method method in class TypeInference cannot be applied to
given types
method(new HashSet<>());
^
required: Set<Long>
found: HashSet<Object>
The
new
-expressions appears as the argument of a method invocation.
In the first invocation there is neither a constructor argument nor a lefthand
side of an assignment. For lack of more specific information the
compiler in Java 7 concludes that the missing type parameter must be
Object
.
This leads to a type mismatch and an according error message. This
changed with Java 8. The method invocation context is now a permitted
type inference context. The compiler concludes from the declared argument
type of
method
that the new
HashSet
must be a
HashSet<Long>
.
The second invocation is fine because the compiler can infer the missing
type parameter from the constructor argument. |
LINK TO THIS
|
Technicalities.FAQ400C
|
REFERENCES
|
What
is the diamond operator?
Why
does the type inference for an instance creation expression fail?
|
Why
does the type inference for an instance creation expression fail?
What
is type argument inference for generic methods?
The automatic deduction of the type arguments
of a generic method at compile time.
|
A generic method can be invoked in two ways:
-
Explicit type argument specification
. The type arguments are
explicitly specified in a type argument list.
-
Automatic type argument inference.
The method is invoked like regular
non-generic methods, that is, without specification of the type arguments.
In this case the compiler automatically infers the type arguments from
the invocation context.
Example (of automatic type inference):
class
Collections {
public static <A extends Comparable<?
super A>> A max (Collection<A> xs) {
Iterator<A> xi = xs.iterator();
A w = xi.
next();
while (xi.hasNext()) {
A x = xi.next();
if (w.compareTo(x) < 0) w = x;
}
return w;
}
}
final class Test {
public static void main (String[ ] args) {
LinkedList<Long> list = new LinkedList<Long>();
list.add(0L);
list.add(1L);
Long y =
Collections.max(list)
;
}
}
In this example, the
max
method is invoked like a regular method
and the compiler automatically infers the type argument from the type of
the method argument. In our example the compiler finds that the formal
method parameter is
Collection<A>
and that the actual method
argument is of type
LinkedList<Long>
. From this information
the compiler concludes that
A
must be replaced by
Long
,
which yields an applicable
max
method with the signature
Long
max(Collection<Long>)
. |
LINK TO THIS
|
Technicalities.FAQ401
|
REFERENCES
|
What
is a parameterized or generic method?
What is
explicit type argument specification?
What
happens if a type parameter does not appear in the method parameter list?
Why
doesn't type argument inference fail when I provide inconsistent method
arguments?
|
What
is explicit type argument specification?
Providing a type argument list when the
method is invoked.
|
A generic method can be invoked with or without an explicit
type argument specification. If you do not want to rely on the compiler's
automatic type argument inference process you can specify the type arguments
explicitly.
Example (of a generic method and its invocation):
public class Utilities {
public static <T extends Comparable> T max(T arg1, T
arg2) {
return (arg1.compareTo(arg2)>0)?arg1:arg2;
}
}
public class Test {
public static void main(String[] args) {
System.out.println(Utilities.
<String>max
("abc","xyz"));
}
}
The
max
method can be invoked as
Utilities.<String>max("abc","xyz")
,
where the type argument is explicitly provided, or as
Utilities.max("abc","xyz")
,
in which case the compiler automatically infers the type argument.
The syntax for explicit type argument specification requires that the
type argument list is precedes the method name, like in
Utilities.<String>max
.
Note the difference to parameterized types, where the type argument list
follows the type name, like in
List<String>
.
There is a little syntax quirk in the explicit type argument specification:
it is required that the type argument(s) is preceded by either a type (for
a static method like the one in the example), or the object on which the
method is invoked (for a non-static method), or
super
(in case
that a superclass's method is invoked). This is even required when
methods are invoked in the scope of the same class. In the scope
of the class we usually omit the type or object on which the method is
invoked; we simply say
method()
rather than
this.method()
.
This shorthand is not permitted when a type argument list precedes the
method name. We must not say
<String>method()
, instead we must
say
this.<String>method()
.
Example:
class SomeClass {
private static <T> void parameterizedClassMethod()
{ ... }
private static void regularClassMethod()
{ ... }
private <T>
void parameterizedInstanceMethod() { ... }
private
void regularInstanceMethod() { ...
}
public void anotherMethod() {
...
regularClassMethod();
// fine
SomeClass.regularClassMethod();
// fine
regularInstanceMethod();
// fine
this.regularInstanceMethod();
// fine
<String>
parameterizedClassMethod();
//
error
SomeClass.
<String>
parameterizedClassMethod
();
// fine
<String>
parameterizedMethod();
//
error
this.
<String>
parameterizedMethod
();
// fine
...
}
}
|
LINK TO THIS
|
Technicalities.FAQ402
|
REFERENCES
|
What
is a parameterized or generic method?
How
do I invoke a generic method?
What
is type argument inference?
Can
I use a wildcard as the explicit type argument of a generic method?
|
Can
I use a wildcard as the explicit type argument of a generic method?
No, wildcards are not permitted as explicit
type arguments of a generic method.
|
When a generic method is invoked,
then we usually rely on the compiler to infer the type argument of the
generic method. In rare cases, we specify the type argument explicitly.
Here is an example where explicit type argument specification would make
sense.
Example (of generic methods):
class Factory {
private static Class<
? > type;
public static <T> void
setComponentType(Class< ? extends T> token) {
type = token;
}
public static <T> List<T>
make(int size) {
Object array = Array.newInstance(type, size);
return Arrays.asList((T[])array); // warning: unchecked
cast
}
}
This class hat two factory methods that create a
list which is backed by an array. Such a list has a fixed size and
is type-safe in the sense that it rejects elements of an undesired type,
just like an array would reject elements of a type that is not compatible
to the array's component type.
Example (of using the factory methods):
public static void main(String[]
args) {
Factory.setComponentType(String.class);
List<String> stringList;
stringList = Factory.make(10);
stringList.set(0,"Hello");
stringList.set(1,new Date());
// expected error: illegal argument type
List<Date> objectList;
dateList = Factory.make(10);
// compiles although it should not !!!
dateList.set(1,new Date());
// run-time error: ArrayStoreException
}
The factory is configured (using the
setComponentType()
method) to create lists that are backed by an array of strings. When
we invoke a factory method then the compiler performs type inference and
tries to figure out what the best type argument for invocation of the generic
make()
method would be. Since the type parameter does not appear in the
method's arguments list the compiler infers the type argument from the
context in which the return value is used.
As long as we assign the result of invoking the
make()
method to a reference of type
List<String>
, all is fine.
The compiler infers
T:=String
when the
make()
method is invoked. We can place strings into the
list, but no other type of object, because the reference of type
List<String>
will
not permit it.
If we try assigning the result of invoking the
make()
method to a reference of type
List<Date>
, it will compile as
well. The compiler will again infer the type argument from the usage of
the return value; this time it infers
T:=Date
.
However, when we attempt placing a date into the list, which is backed
by a string array internally, an
ArrayStoreException
will be raised.
Basically, what we created is a list that will always raise
ArrayStoreException
s.
This undesired situation is a side effect of the "unchecked cast" warning
in the implementation of the
make()
method.
In order to prevent the undesired situation, the
make()
method is best invoked with an explicitly specified type argument.
Example (of explicit type argument specification):
public static void main(String[]
args) {
Factory.setComponentType(String.class);
List<String> stringList;
stringList = Factory.
<String>
make(10);
stringList.set(0,"Hello");
List<Date> objectList;
dateList = Factory.
<String>
make(10);
// does no longer compile
dateList.set(1,new Date());
// run-time error: ArrayStoreException
}
Basically, the example shows a situation in which explicit type argument
specification serves a purpose and is helpful.
In the example we have been using a concrete type
when we specified the type argument explicitly, which raises the question:
can we also use wildcards as explicit type arguments? The answer is: No,
wildcards are not permitted as explicit type arguments of parameterized
method.
Example (of a wildcard as explicitly specified
type arguments of generic methods):
List<? super String> list1 = Factory.make(10);
// fine
List<? super String> list2 = Factory.<String>make(10);
// fine
List<? super String> list3 = Factory.<
?
super String
>make(10); // error
The Java syntax simply does not allow wildcards in
the location where explicit type arguments appear and you might see funny
compiler message ranging from "wildcard is not allowed in this location"
to less helpful statements such as "illegal start of type", "illegal start
of expression", "( expected", "; expected" or the like.
|
LINK TO THIS
|
Technicalities.FAQ402A
|
REFERENCES
|
What
is type argument inference?
What
is explicit type argument specification?
What happens
if a type parameter does not appear in the method parameter list?
|
What
happens if a type parameter does not appear in the method parameter list?
The compiler tries to infer the type
argument from the calling context.
|
If the type parameter does not appear in the types of the
method arguments, then the compiler cannot infer the type arguments by
examining the types of the actual method arguments. If the type parameter
appears in the method's return type, then the compiler takes a look at
the context in which the return value is used. If the method call
appears as the righthand side operand of an assignment, then the compiler
tries to infer the method's type arguments from the static type of the
lefthand side operand of the assignment.
Example (for inference from assignment context):
public final class Utilities {
...
public static <T>
HashSet<T>
create(int size) {
return new HashSet<T>(size);
}
}
public final class Test
public static void main(String[] args) {
HashSet<Integer>
hi = Utilities.create(10);
}
}
The
create
method is generic and the type parameter
T
does
not appear in the method parameter list; it appears in the method's return
type
HashSet<T>
though. The result of the method is assigned
to a variable of type
HashSet<Integer>
so that the compiler
infers that the type argument of the
create
method must be
T:=Integer
.
The compiler is even smart enough to infer the type argument of the
create
method if the method result is assigned to a variable of a supertype of
HashSet<Integer>
,
such as
Collection<Integer>
.
The invocation of a generic method might appear as the argument of another
method invocation. Such an invocation context was not considered
for type inference before Java 8. This changed with the improved
type inference in Java 8.
In Java 5, 6, and 7, the compiler does not try to perform any special
type inference when the invocation of a generic method appears in a method
invocation context. Instead the compiler handles the method call
as though it would appear in no context. No context means that the compiler
performs the type inference algorithm as though the method result was assigned
to a variable of type
Object
.
Example (for inference from a method invocation context):
public final class Utilities {
...
public static <T>
HashSet<T>
create(int size) {
return new HashSet<T>(size);
}
public static void print(
HashSet<String>
h) {
for (String s : h) System.out.println(s);
}
}
public final class Test
public static void main(String[] args) {
Utilities.print(Utilities.create(10));
//
error in Java 5,6,7
;
fine
in Java 8
}
}
error: print(java.util.HashSet<java.lang.String>) cannot be
applied to (java.util.HashSet<java.lang.Object>)
Utilities.print(Utilities.create(10));
^
In Java 5, 6, and 7, the compiler treats the call
Utilities.print(Utilities.create(10))
like it were an assignment
Object o = Utilities.create(10))
.
A lefthand side of type
Object
does not provide any particular
type information so that the compiler cannot really infer anything.
If no specific type can be inferred then the compiler chooses
Object
as the type argument. With type
Object
as the type argument
the
create
method returns a
HashSet<Object>
,
which is incompatible to a
HashSet<String>
and leads to the
error message displayed above.
In Java 8, the compiler considers that the
print
method needs
an argument of type
HashSet<String>
and figures out that the
type parameter for the
create
method must be
String
then.
The key difference is that previously the method invocation context
was treated like no context for type inference and in Java 8 it a valid
type inference context, from which the compiler retrieves information for
the type deduction process.
If the type argument inference does not lead to the desired result or
if we want to disable the automatic inference, we can explicitly specify
the type arguments.
Example (for explicit type argument specification):
public final class Utilities {
...
public static <
T> HashSet<T>
create(int size) {
return new HashSet<T>(size);
}
public static void print(HashSet<String>
h) {
for (String s : h) System.out.println(s);
}
}
public final class Test
public static void main(String[] args) {
Utilities.print(Utilities.
<String>
create(10));
}
}
|
LINK TO THIS
|
Technicalities.FAQ403
|
REFERENCES
|
What
is a parameterized or generic method?
What
is type argument inference?
What
is explicit type argument specification?
Why
doesn't type argument inference fail when I provide inconsistent method
arguments?
Why
do temporary variables matter in case of invocation of generic methods?
|
Why
doesn't type argument inference fail when I provide inconsistent method
arguments?
Because the "inconsistent" arguments
might make sense to the compiler.
|
Occasionally the compiler infers a type where we might
expect that no type can be inferred.
Example (of surprising type argument inference):
public final class Utilities {
...
public static
<T>
void fill(
T
[] array,
T
elem) {
for (int i=0; i<array.length; ++i)
{ array[i] = elem; }
}
}
public final class Test {
public static void main(String[] args) {
Utilities.fill(new
String
[5],
new
String
("XYZ")); // T:=String
Utilities.fill(new
String
[5],
new
Integer
(100)); // T:=Object&Serializable&Comparable
}
}
This is the example of a method whose type argument appears in several
method arguments. Quite obviously the intent is that the component
type of the array should match the type of the second argument. For
this reason we might expect that a method invocation such as
Utilities.fill
(new
String[5], new Integer(1
00))
would fail, because the argument
types
String[]
and
Integer
are inconsistent.
However, the compiler does not reject this method call. Instead
it performs type inference and infers the common supertypes of
String
and
Integer
as type argument. To be precise the compiler
infers
T := Object & Serializable & Comparable<?
extends Object&Serializable&Comparable<?>>
which is a synthetic type construct used internally by the compiler.
It denotes the set of supertypes of
String
and
Integer
.
Whether the result of this successful type inference is desired or not
depends on the circumstances. In this example the source code compiles,
but the method invocation in question will fail at runtime with an
ArrayStoreException
,
because the method would try to store integers in an array of strings.
In Java 5, 6, and 7 it was possible to prevent the perhaps undesired
type argument inference by a minor modification of the
fill
method.
Example (modified, in Java 5, 6, and 7):
public final class Utilities {
...
public static <
T
,
S
extends T
> void fill(
T
[] array,
S
elem) {
for (int i=0; i<array.length; ++i)
{ array[i] = elem; }
}
}
public final class Test {
public static void main(String[] args) {
Utilities.fill(new
String
[5],
new
String
("XYZ")); // T:=String
and
S:=String
Utilities.fill(new
String
[5],
new
Integer
(100)); // T:=String
and
S:=Integer =>
error (in Java 5,6,7)
}
}
In Java 5, 6, and 7 the compiler inferred both type arguments separately
as
String
and
Integer
. When it checked the
bounds it found that
S
is
not within bounds because
Integer
is not a subtype of
String
and the call was rejected.
Since Java 8 the compiler does no longer issue an error message and
you will observe the same behavior as in the initial example without the
additional type variable "
S extends T
". The fact that the
type inference process in Java 5, 6, and 7 could not find a common
super type and issued an error message was just an accident. The
glitch was fixed in Java 8 and nowadays the compiler exploits the covariance
of arrays in this example, too.
Example (same as above, but in Java 8):
public final class Utilities {
...
public static <
T
,
S
extends T
> void fill(
T
[] array,
S
elem) {
for (int i=0; i<array.length; ++i)
{ array[i] = elem; }
}
}
public final class Test {
public static void main(String[] args) {
Utilities.fill(new
String
[5],
new
String
("XYZ")); // T:=Object&Serializable&Comparable
and
S:=String => fine
Utilities.fill(new
String
[5],
new
Integer
(100)); // T:=Object&Serializable&Comparable
and
S:=Integer =>
fine (in Java 8)
}
}
|
LINK TO THIS
|
Technicalities.FAQ404
|
REFERENCES
|
What
is a parameterized or generic method?
What
is type argument inference?
What
is explicit type argument specification?
What
happens if a type parameter does not appear in the method parameter list?
Why
do temporary variables matter in case of invocation of generic methods?
|
Why
do temporary variables matter in case of invocation of generic methods?
Because of the automatic type argument
inference.
|
Usually, it does not make a difference whether
we use the result of a method call for invocation of the next method, like
in
f(x).g();
or whether we store the result first in a temporary variable
and then pass the temporary variable to the next method, like in
tmp=f(x);
tmp.g();
The effect is usually the same. However, when the methods
are generic methods, it may well make a difference.
Example:
public final class Utilities {
...
public static <T> HashSet<T> create(int size)
{
return new HashSet<T>(size);
}
}
Let us consider a chained method call where the result of
the
create
method is used for calling the
iterator
method.
We can perform the chained method call in two steps using a temporary variable:
HashSet<Integer>
tmp
= Utilities.create(5);
Iterator<Integer> iter =
tmp
.iterator();
Or we can perform it one step:
Iterator<Integer> iter = Utilities.create(5).iterator();
If the methods were non-generic methods the result of both
invocation techniques would be the same. Not so in our example, where the
first method is generic and the compiler must infer the type parameter
from the context. Obviously, the context is different in a chained
call compared to the use of temporaries.
In the two-step invocation the result of the
create
method appears in an assignment, and assignment is a context that the compiler
considers for type inference. In the one-step invocation the result of
the
create
method is used to invoke another method, and method
chains are not considered for type inference.
Here is what the compiler infers for the two-step call:
HashSet<Integer>
tmp
=
Utilities.create(5);
// T:=Integer
Iterator<Integer> iter =
tmp
.iterator();
// T:=Integer
For inference of the type argument of the
create
method the compiler takes a look at the type of the left-hand side of the
assignement, namely the type of the temporary variable. It is of
type
HashSet<Integer>
and the compiler infers
T:=Integer
.
The one-step call, in contrast, does not compile because
the type argument inference works differently in this case:
Iterator<Integer> iter = Utilities.create(5).iterator();
error: incompatible types
Iterator<Integer>
iter = Utilities.create(5).iterator();
^
required: Iterator<Integer>
found: Iterator<Object>
For inference of the type argument of the
create
method the compiler does not consider any context information, because
it neither appears in an assignment nor a method invocation context, but
in a method chain instead. It treats the invocation of
create
as though the result of
create
were assigned to a variable of
type
Object
, which does not provide any type information to deduce
anything from. For this reason the compiler infers
T:=Object
,
which yields an instantiation of the
create
method with the signature
HashSet<Object>
create()
.
Equipped with this information the compiler finds that
the
iterator
method return an
Iterator<Object>
, which
leads to the error message.
The example demonstrates, that in rare cases there can
be a difference between a one-step nested method call such as
f(x).g();
and a two-step call using a temporary variable such as
tmp=f(x); tmp.g();
.
The difference stems from the fact that an assignment context is considered
for the type argument inference, while other situations are not.
|
LINK TO THIS
|
Technicalities.FAQ405
|
REFERENCES
|
What
is a parameterized or generic)method?
What
is type argument inference?
What
is explicit type argument specification?
What
happens if a type parameter does not appear in the method parameter list?
Why
doesn't type argument inference fail when I provide inconsistent method
arguments?
|
Wildcard Capture
What
is the capture of a wildcard?
An anonymous type variable that represents
the particular unknown type that the wildcard stands for. The compiler
uses the capture internally for evaluation of expressions and the term
"capture of ?" occasionally shows up in error message.
|
A wildcard is compatible with all types from a set of types.
For instance, all instantiations of the generic type
List
, such
as
List<String>
,
List<Number>
,
List<Long>
,
etc. can be assigned to a reference variable of type
List<?>
.
Wildcards are typically used as argument or return types in method signatures.
The goal and effect is that the method accepts arguments of a larger set
of types, namely all types that belong to the type family that the wildcard
denotes.
Example (of a wildcard in a method signature):
public static void reverse(List<?> list) {
// ... the implementation ...
}
The
reverse
method accepts arguments that are of a type that is
an instantiation of the generic type
List
. In order to use the
type argument
list
of type
List<?>
the compiler converts
the wildcard instantiation to the so-called
capture
of the wildcard.
The capture represents the particular albeit unknown type of the argument
that is actually passed the method. This particular unknown type
is, of course, a member of the type family that the wildcard denotes.
The wildcard capture can be imagined as an anonymous type variable that
the compiler generates internally and uses as the static type of the method
parameter. A type variable is like a type parameter of a generic
type or method; it stands for a particular unknown type. Changing the method
parameter's type from the instantiation using a wildcard to an instantiation
using the capture is known as
capture conversion
. The translation
of our method can be imagined as though the compiler had re-implemented
the method to use a synthetic generic method that has the wildcard capture
as it type parameter.
Example (pseudo code showing the wildcard capture):
private static <T_?001> void
reverse_?001(List<T_?001> list) {
// ... the implementation
...
}
public static void reverse(List<?> list) {
reverse_?001(list);
}
For the analysis and translation of the implementation of the
reverse
method the compiler will use
List<T_?001>
as the type of the
method parameter. The synthetic type variable
T_?001
is used for
all purposes where the method parameter's static type information is needed,
like for instance, in type checks and for type inference.
For illustration, let us consider a conceivable implementation of the
reverse
method
using a generic helper method
rev
for which the compiler must
infer the type arguments from the wildcard.
Example (implementation of the
reverse
method):
private static <T> void rev(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) {
rev(list);
}
The compiler applies capture conversion and generates an anonymous type
variable. In principle, the compiler translates the
reverse
method
to something like this.
Example (pseudo code showing
the wildcard capture):
private static <T> void rev(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);
}
}
private static <T_?001> void reverse_?001(List<T_?001>
list) {
rev(list);
}
public static void reverse(List<?> list) {
reverse_?001(list);
}
Among other things, the compiler uses the wildcard capture
T_?001
for inference of the type argument
T
of the generic helper
method
rev
. It infers
T:=T_?001
and therefore invokes
the instantiation
<T_?001>rev
of method
rev
. |
LINK TO THIS
|
Technicalities.FAQ501
|
REFERENCES
|
What
is a wildcard capture assignment-compatible to?
|
What
is the capture of an unbounded wildcard compatible to?
Nothing, except the unbounded wildcard
itself.
|
The capture of a wildcard is compatible to a corresponding
wildcard, never to a concrete type.
Example (implementation of assignment of wildcard instantiation):
private static void method(
List<?>
list) {
List
<String>
ll1 = list; // error
List
<? extends String>
ll2 = list; // error
List
<? super String>
ll3 = list; // error
List
<?>
ll4 = list; // fine
}
error:
incompatible types
found : java.util.List
<capture
of ?
>
required: java.util.List<java.lang.String>
List<String> ll1 = list;
^
error
:
incompatible types
found : java.util.List
<capture
of ?
required: java.util.List<?
extends java.lang.String>
List<? extends String>
ll2 = list;
^
error
:
incompatible types
found : java.util.List
<capture
of ?
required: java.util.List<?
super java.lang.String>
List<? super String>
ll3 = list;
^
The method takes an argument of type
List<?>
,
which the compiler translates to type
List<capture of ?>
. The
wildcard capture denotes a particular unknown type. From the static
type information "capture of ?" the compiler cannot tell, whether capture
stands for
String
; the capture could stand for any type.
On this ground the compiler rejects the assignment of the method result
of type
List<capture of ?>
to the variable of type
List<String>
in the example. |
LINK TO THIS
|
Technicalities.FAQ502
|
REFERENCES
|
What
is the capture of a wildcard?
What
does type-safety mean?
|
Is
the capture of a bounded wildcard compatible to the bound?
No, not even if the bound is a final
class.
|
The capture of a wildcard is compatible to a corresponding
wildcard, never to a concrete type. Correspondingly, the capture of a bounded
wildcard is compatible solely to other wildcards, but never to the bound.
For illustration we use the
getClass
method, which is defined
in class
Object
(see
java.lang.Object.getClass
).
The result of the
getClass
method is of type
Class<? extends
X>
, where
X
is the erasure of the static type of the expression
on which
getClass
is called.
Example (with bounded wildcard):
Number n = new Integer(5);
Class<Number> c = n.getClass(); // error
error: incompatible types
found : java.lang.Class<capture of ? extends java.lang.Number>
required: java.lang.Class<java.lang.Number>
Class<Number> c =
n.getClass();
^
In our example the static type of the expression on which
getClass
is called is
Number
. Hence the return value is of type
Class<capture
of ? extends Number>
. A variable of type
Class<capture of ?
extends Number>
can refer to a
Class<Number>
, a
Class<Long>
,
a
Class<Integer>
, etc. There's no guarantee that
is actually refers to a
Class<Number>
, and indeed, in our example
it refers to a
Class<Integer>
. The compiler rightly complains.
How do we fix it?
Example (corrected):
Number n = new Integer(5);
Class<?>
c1 = n.getClass();
Class<? extends Number> c2 = n.getClass();
Since
getClass
returns a wildcard instantiation we must assign
the result to a wildcard instantiation.
Class<?>
would
be correct, but also the more specific type
Class<? extends Number>
would
work.
Interestingly, the capture of a bounded wildcard, whose upper bound
is a final class, is still incompatible to the bounds type, although the
set of types that the bounded wildcard denotes contains only one type,
namely the bounds type itself.
Example (using final class as bound):
String s = new String("abc");
Class<String>
c0 = s.getClass(); // error
Class<?>
c1 = s.getClass(); // fine
Class<? extends String> c2 = s.getClass(); // fine
error: incompatible types
found : java.lang.Class<capture of ? extends java.lang.String>
required: java.lang.Class<java.lang.String>
Class<String>
c0 = s.getClass();
^
|
LINK TO THIS
|
Technicalities.FAQ503
|
REFERENCES
|
What
is the capture of a wildcard?
What
is a wildcard capture assignment-compatible to?
|
Wildcard Instantiations
Which
methods and fields are accessible/inaccessible through a reference variable
of a wildcard type?
It depends on the kind
of wildcard.
|
Using an object through a reference variable of a wildcard
parameterized type is restricted. Consider the following class:
Example ( of a parameterized class):
class Box<T> {
private T t;
public Box(T t) { this.t = t; }
public void put(T t) { this.t = t;}
public T take() { return t; }
public boolean equalTo(Box<T> other) { return this.t.equals(other.t);
}
public Box<T> copy() { return new Box<T>(t); }
}
In a wildcard parameterized type such as
Box<?>
the type of
the field and the argument and the return types of the methods would be
unknown. It is like the field t would be of type "
?
" and
the
put
method would take an argument of type "
?
" and
so on. In this situation the compiler does not let us assign anything
to the field or pass anything to the
put
method. The reason is
that the compiler cannot make sure that the object that we are trying to
assign to the field or pass as an argument to a method is of the expected
type, since the expected type is unknown. Similar effects can be
observed for methods such as like
equalTo
and
clone
,
which have a parameterized argument or return type and the type parameter
T
appears as type argument of the parameterized argument or return type.
Below is a table that lists which uses of fields and methods are legal
or illegal. It assumes a class like this:
class X<
T
> {
private
T
t;
public
T
m()
{ ... }
public void m(
T
arg)
{ ... }
public Y<
T
> f()
{ ... }
public void f(Y<
T
> arg)
{ ... }
public Y<? extends
T
> f()
{ ... }
public void f(Y<? extends
T
> arg) { ...
}
public Y<? super
T
> f()
{ ... }
public void f(Y<? super
T
> arg)
{ ... }
}
Examples and further explanations can be found in subsequent FAQ entries.
X<?>
|
unbounded wildcard
|
|
legal
|
illegal
|
fields
|
x =
t
|
We can
read
a field whose type is the type parameter.
The field is accessible through a reference of type
Object
. |
t
= x
|
We cannot
assign
to a field whose type is the type parameter
except:
null
|
methods
|
T
m()
|
We can call methods that use the type parameter as the
return type
.
The returned value is accessible through a reference of type
Object
. |
void m(
T
)
|
We cannot call methods that use the type parameter as an
argument
type
.
except:
null
|
Y<
T
>
f()
Y<?
extends
T
> f()
Y<?
super
T
> f()
|
We can call methods that use the type parameter as
type argument
in the return type or as an
upper
or
lower wildcard bound
in the
return type
.
The returned value is accessible through the unbounded wildcard instantiation
of the return type (i.e.
Y<?>
). |
void f(
Y<
T
>
)
void f(
Y<?
extends
T
>
)
|
We cannot call methods that use the type parameter as
type argument
in an argument type or as an
upper wildcard bound
in an
argument
type
.
except:
null
|
void f(
Y<?
super
T
>
)
|
We can call methods that use the type parameter as a
lower wildcard
bound
in an
argument type
.
The method argument must be either
null
or of type
Y<Object>
(the argument type instantiated for type
Object
). |
|
|
X<? extends B>
|
wildcard with upper bound
|
|
legal
|
illegal
|
fields
|
x =
t
|
We can
read
a field whose type is the type parameter.
The field is accessible through a a reference whose type is the upper
bound. |
t
=
x
|
We cannot
assign
to a field whose type is the type parameter.
except:
null
|
methods
|
T
m()
|
We can call methods that use the type parameter as the
return type
.
The returned value is accessible through a reference whose type is
the upper bound. |
void m(
T
)
|
We cannot call methods that use the type parameter as an
argument
type
.
except:
null
|
Y<
T
>
f()
Y<?
extends
T
> f()
|
We can call methods that use the type parameter as
type argument
in the return type or as an
upper wildcard bound
in the
return
type
.
The returned value is accessible through the upper bound wildcard instantiation
of the return type (i.e.
Y<? extends B>
). |
void f(
Y<
T
>
)
void f(
Y<?
extends
T
>
)
|
We cannot call methods that use the type parameter as
type argument
in an argument type or as an
upper wildcard bound
in an
argument
type
.
except:
null
|
Y<?
super
T
> f()
|
We can call methods that use the type parameter as an
lower wildcard
bound
in the
return type
.
The returned value is accessible through the unbounded wildcard instantiation
of the return type (i.e.
Y<?>
). |
|
|
void f(
Y<?
super
T
>
)
|
We cannot call methods that use the type parameter as an
lower
wildcard bound
in an
argument type
.
The method argument must be either
null
or of a type that
belongs to the family denoted by
Y<? super B>
. |
|
|
|
X<? super B>
|
wildcard with lower bound
|
|
legal
|
illegal
|
fields
|
t
= x
|
We can
assign
to a field whose type is the type parameter.
The value to be assigend must be either
null
or of a type
that is the lower bound or a subtype thereof. |
|
|
x =
t
|
We can
read
a field whose type is the type parameter.
The field is accessible through a reference of type
Object
. |
|
|
methods
|
void m(
T
)
|
We can call methods that use the type parameter as an
argument type
.
The method argument must be either
null
or of a type
B
(the lower bound). |
|
|
T
m()
|
We can call methods that use the type parameter as the
return type
.
The returned value is accessible through a reference of type
Object
. |
|
|
Y<
T
>
f()
Y<?
super
T
> f()
|
We can call methods that use the type parameter as
type argument
in the return type or as a
lower wildcard bound
in the
return
type
.
The returned value is accessible through the lower bound wildcard instantiation
of the return type (i.e.
Y<? super B>
). |
void f(
Y<
T
>
)
|
We cannot call methods that use the type parameter as type argument
in an
argument type
.
except:
null
|
Y<?
extends
T
> f()
|
We can call methods that use the type parameter as an
upper wildcard
bound
in the
return type
.
The returned value is accessible through the unbounded wildcard instantiation
of the return type (i.e.
Y<?>
). |
|
|
|
void f(
Y<?
extends
T
>
)
|
We can call methods that use the type parameter as an
upper
wildcard bound
in an
argument type
.
The method argument must be either
null
or of a type that
belong to the family denoted by
Y<? extends B>
. |
|
|
void f(Y<?
super
T
>)
|
We can call methods that use the type parameter as a
lower wildcard
bound
in an
argument type
.
The method argument must be either
null
or of type
Y<Object>
(the argument type instantiated for type
Object
). |
|
|
|
LINK TO THIS
|
Technicalities.FAQ601
|
REFERENCES
|
What
is a wildcard instantiation?
What
is a wildcard?
What
is an unbounded wildcard?
What
is a bounded wildcard?
Which
methods that use the type parameter in the argument or return type are
accessible in an unbounded wildcard parameterized type?
Which
methods that use the type parameter in the argument or return type are
accessible in an upper bound wildcard parameterized type?
Which
methods that use the type parameter in the argument or return type are
accessible in a lower bound wildcard parameterized type?
Which
methods that use the type parameter as type argument of a parameterized
argument or return type are accessible in a wildcard parameterized type?
Which
methods that use the type parameter as upper wildcard bound in a parameterized
argument or return type are accessible in a wildcard parameterized type?
Which
methods that use the type parameter as lower wildcard bound in a parameterized
argument or return type are accessible in a wildcard parameterized type?
In
a wildcard parameterized type, can I read and write fields whose type is
the type parameter?
|
Which
methods that use the type parameter in the argument or return type are
accessible in an unbounded wildcard parameterized type?
We cannot call methods through an unbounded
wildcard parameterized type that take arguments of the "unknown" type.
But we can call methods that return objects of the "unknown" type.
|
Example:
class Box<
T
> {
private T t;
public Box(T t) { this.t = t; }
public void put(
T
t) { this.t = t;}
public
T
take() { return t; }
public boolean contains(
T
t) { return this.t == t;
}
public String toString() { return "Box["+t.toString()+"]";
}
}
class Test {
public static void main(String[] args) {
Box<?>
box = new Box<String>("abc");
box.put("xyz");
//
error
box.put(null);
//
ok
box.contains("abc");
//
error
box.toString();
//
ok
String s = box.take();
//
error
Object o = box.take();
//
ok
}
}
We cannot call the
put
method of the
Box
type through
a reference variable of type
Box<?>
, because the method takes
an argument of the unknown type that the wildcard stands for. From
the type information
Box<?>
the compiler does not know whether
the object we are passing to the method is compatible with the actual object
contained in the box. If the
Box<?>
would be refering
to a
Box<Long>
, then it would clearly violate the type guarantees
if we could put a string into the box that is supposed to contain a long
value. The only argument that is accepted it the
null
reference,
because it has no type.
The same reasoning applies to
all
methods that take an argument
of the "unknown" type, even if the method does not even modify the box,
like the
contains
method. It just takes an object of the
"unknown" type and compares it. If a string is passed to the
contains
method of a box that contains a long value, it simply returns
false
.
No harm is done. Yet the invocation is illegal if performed through
a reference variable of type
Box<?>
. [Defining the
contains
method as taking an argument of type
Object
, instead of
T
,
would avoid this effect. In this case the
contains
method
would not take an object of "unknown", but an object of "any" type, and
it would be permitted to invoke it through a reference variable of type
Box<?>
.]
We can freely invoke any methods that neither take nor return objects
of the "unknown" type.
The example demonstrates that methods returning an object of the unknown
type can be called and return an object of unknown type, which can be assigned
to a reference variable of type
Object
, but to not to a reference
variable of a more specific type. |
LINK TO THIS
|
Technicalities.FAQ602
|
REFERENCES
|
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 parameterized type?
Which
methods that use the type parameter in the argument or return type are
accessible in an upper bound wildcard parameterized type?
Which
methods that use the type parameter in the argument or return type are
accessible in a lower bound wildcard parameterized type?
Which
methods that use the type parameter as type argument of a parameterized
argument or return type are accessible in a wildcard parameterized type?
Which
methods that use the type parameter as upper wildcard bound in a parameterized
argument or return type are accessible in a wildcard parameterized type?
Which
methods that use the type parameter as lower wildcard bound in a parameterized
argument or return type are accessible in a wildcard parameterized type?
In
a wildcard instantiation, can I read and write fields whose type is the
type parameter?
What
is a wildcard instantiation?
What
is a wildcard?
What
is an unbounded wildcard?
What
is a bounded wildcard?
|
Which
methods that use the type parameter in the argument or return type are
accessible in an upper bound wildcard parameterized type?
Which
methods that use the type parameter in the argument or return type are
accessible in a lower bound wildcard parameterized type?
Which
methods that use the type parameter as type argument of a parameterized
argument or return type are accessible in a wildcard parameterized type?
We cannot call methods that use the type
parameter as type argument in an argument type. We can call methods that
use the type parameter as type argument in the return type; the returned
value is accessible through a wildcard instantiation of the return type.
|
In a wildcard parameterized type, we cannot call methods
that use the type parameter as type argument in an argument type
in an argument type. But, we can call methods that use the type parameter
as type argument in the return type; the returned value is accessible through
a wildcard instantiation of the return type. The wildcard instantiation
of the return type corresponds to the wildcards instantiation that was
used for the method invocation, e.g. a method of
Box<? extends Number>
would return a
ReturnType<? extends Number>
and a a method
of
Box<? super Number>
would return a
ReturnType<? super
Number>
.
Unbounded
wildcard parameterized type.
Example (access through unbounded wildcard):
class Box<
T
> {
private T t;
...
public boolean equalTo(
Box<
T
>
other) { return this.t.equals(other.t); }
public
Box<
T
>
copy()
{ return new Box<T>(t); }
}
class Test {
public static void main(String[] args) {
Box<String> stringBox = new Box<String>("abc");
Box<?>
unknownBox = stringBox;
boolean equal = true;
equal = unknownBox.equalTo(
unknownBox
);
//
error
equal = unknownBox.equalTo(stringBox);
//
error
Box<?> box1 = unknownBox.copy();
//
ok
Box<String> box2 = unknownBox.copy();
//
error
}
}
error: equalTo(Box<capture of ?>) in Box<capture of ?> cannot
be applied to (Box<capture of ?>)
equal = unknownBox.equalTo(
unknownBox
);
^
error: equalTo(Box<capture of ?>) in Box<capture of ?> cannot
be applied to (Box<java.lang.String>)
equal = unknownBox.equalTo(stringBox);
^
error: incompatible types
found : Box<capture of ?>
required: Box<java.lang.String>
Box<String> box2
= unknownBox.copy();
^
We cannot call the
equalTo
method of the
Box
type through
a reference variable of type
Box<?>
, because the compiler does
not know which type of object is expected as an argument of the
equalTo
method. In our example the reference variable
unknownBox
refers
to a
Box<String>
and hence only a
Box<String>
would
be acceptable as an argument of the
equalTo
method. But
from the type information
Box<?>
the compiler does not know
which type of box would be acceptable as an argument. For this reason
the compiler rejects all attempts to invoke the
equalTo
method.
Invocation of the
copy
method is permitted. It returns
an object of an unknown instantiation of the
Box
type to which
we can refer through a variable of type
Box<?>
. More
specific information about the instantiation of the
Box
type is
not available and for this reason the assigment to a reference variable
of an instantiation different from
Box<?>
fails.
In the example, the parameterized argument and return type happens to
be the enclosing type. This is just by chance. The rules explained
above apply in the same way to unrelated parameterized argument and return
types. For instance, if the
Box
class had methods taking
or returning a
Comparable<T>
the same rules would apply: methods
that take arguments of type
Comparable<T>
cannot be called
and methods that return a
Comparable<T>
can be called and the
result can be assigned to a
Comparable<?>
.
A note on the error messages and the term "capture of":
The compiler uses the term "capture of ?" when it refers to the unknown
type that the wildcard stands for. More on the wildcard capture can
be found in
Technicalities.FAQ501
.
The compiler uses the capture of a wildcard in order to denote the signatures
of methods in wildcard instantiations.
The signature of a method in a wildcard parameterized type is determined
by replacing all occurrences of the type parameter by the capture of the
parameterized type's wildcard. For instance, the method
equalTo
(Box<T>)
in
Box<?>
has the signature
equalTo(Box<capture of ?>)
.
The same method in
Box<? extends Number>
has the signature
equalTo(Box<capture
of ? extends Number>)
. The method
takeContentFrom
(Box<?
extends T>
)
in
Box<?>
has the signature
takeContentFrom(Box<?
extends capture of ?>)
and the same method in
Box<? super Number>
has
the signature
takeContentFrom(Box<? extends capture of ? super Number>)
.
What these "capture of capture" things mean is explained below in the
discussion of various examples. Just to give you an idea, the term "
?
extends capture of ?
" refers to a subtype ("? extends ...")
of an unknown type ("capture of ?") , and the term "
? extends capture
of ? super Number
" refers to subtype ("? extends ...") of an
unknown type ("capture of ? ...") that is a supertype of
Number
("?
super Number") .
Bounded
wildcard parameterized types.
The example above used a reference variable of the unbounded wildcard
type
Box<?>
. If we use a bounded wildcard type such as
Box<?
extends Number>
or
Box<? super Number>
the same rules
apply.
Example (access through bounded wilcdard):
class Box<
T
> {
private T t;
...
public boolean equalTo(
Box<
T
>
other) { return this.t.equals(other.t); }
public
Box<
T
>
copy()
{ return new Box<T>(t); }
}
class Test {
public static void main(String[] args) {
Box<Number>
numberBox = new Box<Number>(0L);
Box<?
extends Number>
unknownBox = numberBox;
boolean equal = true;
equal = unknownBox.equalTo(unknownBox);
//
error
equal = unknownBox.equalTo(numberBox);
//
error
Box<?> box1 = unknownBox.copy();
//
ok
Box<? extends Number>
box2 = unknownBox.copy();
// ok
Box<Number> box3 =
unknownBox.copy();
//
error
}
}
error: equalTo(Box<capture of ? extends java.lang.Number>)
in Box<capture of ? extends java.lang.Number>
cannot be applied to (Box<capture of ? extends java.lang.Number>)
equal = unknownBox.equalTo(unknownBox);
^
error: equalTo(Box<capture of ? extends java.lang.Number>)
in Box<capture of ? extends java.lang.Number>
cannot be applied to (Box<java.lang.
Number
>)
equal = unknownBox.equalTo(numberBox);
^
error: incompatible types
found : Box<capture of ? extends java.lang.Number>
required: Box<java.lang.Number>
Box<Number> box3
= unknownBox.copy();
^
The
equalTo
method cannot be called through a reference variable
of type
Box<? extends Number>
because the argument type is
still an unknown type. Thanks to the upper bound the compiler knows
that the expected argument type must be an instantiation of
Box
for a type argument that is a subtype of
Number
, but the compiler
still does not know which instantiation exactly. Hence, the compiler cannot
make sure that the right type of argument is provided for the method invocation
and rejects the method invocation. The effect is exactly the same as for
Box<?>
,
and likewise for
Box<? super Number>
.
The copy method can be called and the in this case the result can be
assigned to a reference variable of the more specific type
Box<?
extends Number>
, instead of just
Box<?>
. When invoked
on a
Box<? super Number>
the result would be assignable to
a
Box<? super Number>
. |
LINK TO THIS
|
Technicalities.FAQ605
|
REFERENCES
|
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 parameterized type?
Which
methods that use the type parameter in the argument or return type are
accessible in an upper bound wildcard parameterized type?
Which
methods that use the type parameter in the argument or return type are
accessible in a lower bound wildcard parameterized type?
Which
methods that use the type parameter as type argument of a parameterized
argument or return type are accessible in a wildcard parameterized type?
Which
methods that use the type parameter as upper wildcard bound in a parameterized
argument or return type are accessible in a wildcard parameterized type?
Which
methods that use the type parameter as lower wildcard bound in a parameterized
argument or return type are accessible in a wildcard parameterized type?
In
a wildcard instantiation, can I read and write fields whose type is the
type parameter?
What
is a wildcard instantiation?
What
is a wildcard?
What
is the capture of a wildcard?
|
Which
methods that use the type parameter as upper wildcard bound in a parameterized
argument or return type are accessible in a wildcard parameterized type?
We cannot call methods that use the type
parameter as an upper wildcard bound in an argument type, with an
exception for access through a lower bound wildcard parameterized type.
We can call methods that use the type parameter as the upper wildcard bound
in the return type; the returned value is accessible through the unbounded
wildcard instantiation of the return type.
|
In an upper bound wildcard parameterized type, we cannot
call methods that use the type parameter as an upper wildcard bound
in an argument type. This holds for access through unbounded and upper
bound wildcard parameterized types. Access through a lower bound
wildcard parameterized type is possible for certain argument types.
We can call methods that use the type parameter as the upper wildcard
bound in the return type; the returned value is accessible through the
unbounded wildcard instantiation of the return type. This holds for
access through unbounded and lower bound wildcard parameterized types.
Access through an upper bound wildcard parameterized type yields a more
specific return type, namely a return type that corresponds to the upper
bound wildcard instantiation that was used for the method invocation, e.g.
a method of
Box<? extends Number>
would return a
ReturnType<?
extends Number>
.
Unbounded
wildcard parameterized type.
Example (access through unbounded wildcard):
class Box<
T
> {
private T t;
...
public void takeContentFrom(
Box<? extends
T
>
box) { t = box.t; }
public
Class<? extends
T
>
getContentType() { ... }
}
class Test {
public static void main(String[] args) {
Box<Number> numberBox
= new Box<Number>(5L);
Box<?>
unknownBox = numberBox;
unknownBox.takeContentFrom(numberBox);
//
error
unknownBox.takeContentFrom(unknownBox);
//
error
Class<Number> type0
= unknownBox.getContentType();
//
error
Class<? extends Number>
type1 = unknownBox.getContentType();
//
error
Class<?> type2 = unknownBox.getContentType();
//
ok
}
}
error: takeContentFrom(Box<? extends capture of ?>) in Box<capture
of ?>
cannot be applied to (Box<java.lang.Number>)
unknownBox.takeContentFrom(numberBox);
^
error: takeContentFrom(Box<? extends capture of ?>) in Box<capture
of ?>
cannot be applied to (Box<capture of ?>)
unknownBox.takeContentFrom(unknownBox);
^
error: incompatible types
found : java.lang.Class<capture of ? extends capture
of ?>
required: java.lang.Class<java.lang.Number>
Class<Number> type0
= unknownBox.getContentType();
^
error: incompatible types
found : java.lang.Class<capture of ? extends capture
of ?>
required: java.lang.Class<? extends java.lang.Number>
Class<? extends Number>
type1 = unknownBox.getContentType();
^
We cannot call the
takeContentFrom
through a reference variable
of type
Box<?>
, because the compiler does not know which type
of object is expected as an argument of the
takeContentFrom
method.
From the error message you can tell what the compiler find, namely a method
with the signature
takeContentFrom(Box<? extends capture of ?>)
.
The term
Box<? extends capture of ?>
stands for an instantiation
of Box with a type argument of an unknown type that is a subtype of another
unknown type. In essence, the argument type is unknown and the compiler
has no chance to perform any type checks to make sure the correct type
of argument is passed to the method call. And hence the invocation is illegal.
Invocation of the
getContentType
method is permitted.
The return value is of a type that is an unknown instantiation of the
Class
type to which we can refer through a variable of type
Class<?>
.
More specific information about the instantiation of the
Class
type is not available and for this reason the assigment to instantiations
such as
Class<? extends Number>
as
Class<Number>
fails.
Upper
bound wildcard parameterized type.
Let us see what the situation is like when we use a bounded wildcard
parameterized type instead of the unbounded one.
Example (access through upper bound wildcard):
class Box<
T
> {
private T t;
...
public void takeContentFrom(
Box<? extends
T
>
box) { t = box.t; }
public
Class<? extends
T
>
getContentType() { ... }
}
class Test {
public static void main(String[] args) {
Box<Number> numberBox
= new Box<Number>(5L);
Box<?
extends Number>
unknownBox = numberBox;
unknownBox.takeContentFrom(numberBox);
//
error
unknownBox.takeContentFrom(unknownBox);
//
error
Class<Number> type0
= unknownBox.getContentType();
//
error
Class<? extends Number>
type1 = unknownBox.getContentType();
//
ok
}
}
error: takeContentFrom(Box<? extends capture of ? extends java.lang.Number>)
in Box<capture of ? extends java.lang.Number>
cannot be applied to (Box<java.lang.Number>)
unknownBox.takeContentFrom(numberBox);
^
error: takeContentFrom(Box<? extends capture of ? extends java.lang.Number>)
in Box<capture of ? extends java.lang.Number>
cannot be applied to (Box<capture of ? extends java.lang.Number>)
unknownBox.takeContentFrom(unknownBox);
^
error: incompatible types
found : java.lang.Class<capture of ? extends capture
of ? extends java.lang.Number>
required: java.lang.Class<java.lang.Number>
Class<Number> type1
= unknownBox.getContentType();
^
In an upper bound wildcard parameterized type such as
Box<? extends
Number>
the behavior is the same as in an unbounded wildcard parameterized
type. The invocation of
takeFromContent
is rejected because the
argument type in unknown. The argument type is
Box<? extends
capture of ? extends Number>
which is an instantiation of
Box
for an unknown subtype of an unknown subtype of
Number
.
Invocation of the
getContentType
method is permitted.
The return type is more specific than for the unbounded wildcard case;
we can refer to the result through a variable of type
Class<? extends
Number>
. This is because the return type is
Class<capture
of ? extends capture of ? extends Number>
, which is an unknown subtype
of an unknown subtype of
Number
, that is, a subtype of
Number
.
Lower
bound wildcard parameterized type.
Last but no least, the invocation through a lower bound wildcard.
Example (access through lower bound wildcard):
class Box<
T
> {
private T t;
...
public void takeContentFrom(
Box<? extends
T
>
box) { t = box.t; }
public
Class<? extends
T
>
getContentType() { ... }
}
class Test {
public static void main(String[] args) {
Box<Long>
longBox = new Box<Long>(0L);
Box<Number> numberBox
= new Box<Number>(0L);
Box<Object> objectBox
= new Box<Object>(5L);
Box<? extends Number>
unknownNumberBox = numberBox;
Box<?
super Number>
unknownBox
= numberBox;
unknownBox.takeContentFrom(longBox);
//
ok
unknownBox.takeContentFrom(numberBox);
//
ok
unknownBox.takeContentFrom(objectBox);
//
error
unknownBox.takeContentFrom(unknownNumberBox);
//
ok
unknownBox.takeContentFrom(unknownBox);
//
error
Class<Number> type1
= unknownBox.getContentType();
//
error
Class<? extends Number>
type2 = unknownBox.getContentType();
//
error
Class<? super Number>
type3 = unknownBox.getContentType();
//
error
Class<?> type4 = unknownBox.getContentType();
//
ok
}
}
error: takeContentFrom(Box<? extends capture of ? super java.lang.Number>)
in Box<capture of ? super java.lang.Number>
cannot be applied to (Box<java.lang.Object>)
unknownBox.takeContentFrom(objectBox);
^
error: takeContentFrom(Box<? extends capture of ? super java.lang.Number>)
in Box<capture of ? super java.lang.Number>
cannot be applied to (Box<capture of ? super java.lang.Number>)
unknownBox.takeContentFrom(unknownBox);
^
error: incompatible types
found : java.lang.Class<capture of ? extends capture
of ? super java.lang.Number>
required: java.lang.Class<java.lang.Number>
Class<Number> type1
= unknownBox.getContentType();
^
error: incompatible types
found : java.lang.Class<capture of ? extends capture
of ? super java.lang.Number>
required: java.lang.Class<? extends java.lang.Number>
Class<? extends Number>
type2 = unknownBox.getContentType();
^
error: incompatible types
found : java.lang.Class<capture of ? extends capture
of ? super java.lang.Number>
required: java.lang.Class<? super java.lang.Number>
Class<? super Number>
type3 = unknownBox.getContentType();
^
The key difference for lower bound wildcards is that the
takeContentFrom
method can be called for certain argument types, namely for those types
that are members of the type family denoted by
Box<? extends Number>
in our example. It is basically as though the
takeContentFrom
method in
Box<? super Number>
had the signature
takeContentFrom(Box<?
extends Number>)
. Why is this? The compiler determines the signature
of the
takeContentFrom
method
in
Box<? super Number>
as
takeContentFrom(Box<? extends
capture of ? super java.lang.Number>)
. Now, what does "
? extends
capture of ? super java.lang.Number
" mean? It is an unknown
subtype of an unknown supertype of
Number
. No matter what
this unknown supertype of
Number
may be, the subtypes of
Number
would conform to this description.
Invocation of the
getContentType
method is permitted, as expected.
Perhaps surprising is that fact that the return type is not
Class<?
super Number>
, but only
Class<?>
. This is because the
return type is
Class<capture of ? extends capture of ? super Number>
,
which is an unknown subtype of an unknown supertype of
Number
.
Just imagine the unknown supertype of
Number
were
Object
,
then it could by any type. Hence we know nothing about the return type
except that it is an instantiation of class
Class
and
Class<?>
correctly
describes it. |
LINK TO THIS
|
Technicalities.FAQ606
|
REFERENCES
|
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 parameterized type?
Which
methods that use the type parameter in the argument or return type are
accessible in an upper bound wildcard parameterized type?
Which
methods that use the type parameter in the argument or return type are
accessible in a lower bound wildcard parameterized type?
Which
methods that use the type parameter as type argument of a parameterized
argument or return type are accessible in a wildcard parameterized typ?
Which
methods that use the type parameter as upper wildcard bound in a parameterized
argument or return type are accessible in a wildcard parameterized type?
Which
methods that use the type parameter as lower wildcard bound in a parameterized
argument or return type are accessible in a wildcard parameterized type?
In
a wildcard instantiation, can I read and write fields whose type is the
type parameter?
What
is a wildcard instantiation?
What
is a wildcard?
What
is the capture of a wildcard?
|
Which
methods that use the type parameter as lower wildcard bound in a parameterized
argument or return type are accessible in a wildcard parameterized type?
We can call methods that use the type
parameter as a lower wildcard bound in the return type; the returned value
is
accessible through a wildcard instantiation of the return type (which one
depends on the wildcard parameterized type being used). We can call methods
that use the type parameter as a lower wildcard bound in an argument
type; the argument type is restricted depending on the wildcard being use.
|
In a lower bound wildcard parameterized type, we can call
methods that use the type parameter as a lower wildcard bound in the return
type. The returned value is accessible through a wildcard instantiation
of the return type. In an unbounded wildcard parameterized type and
an upper bound wildcard parameterized type the return type is the unbounded
wildcard instantiation of the return type. In a lower bound wildcard parameterized
type the return type is more specific, namely the lower bound wildcard
instantiation of the return type. That is, a method in class
Box
that return
Comparable<? super T>
would return
Comparable<?>
in
Box<?>
and
Box<? extends Number>
and would return
Comparable<?
super Number>
in
Box<? super Number>
.
This matches the return types of methods with an upper bound wildcard;
a method in class
Box
that return
Comparable<? extends
T>
would return
Comparable<?>
in
Box<?>
and
Box<?
super Number>
and would return
Comparable<? extends Number>
in
Box<? extends Number>
. The reasoning for lower bound
wildcard parameterized types is the exactly the same as for upper bound
wildcard parameterized types. The more specific return type in a
lower bound wildcard parameterized type stems from the fact that the return
type of a method that returns
Comparable<? super T>
in
the instantiation
Box<? super Number>
would be
Comparable<capture
of ? super capture of ? super Number>
, which boils down to
Comparable<?
super Number>
.
More interesting is the invocation of methods that take arguments with
lower wildcard bounds. We can call methods that use the type parameter
as a lower wildcard bound in an argument type. In an unbounded wildcard
parameterized type and a lower bound wildcard parameterized type the only
permitted argument type is the argument type instantiated for type
Object
.
That is, a method in class
Box
that takes an argument of type
Comparable<?
super Number>
would in the parameterized types
Box<?>
and
Box<? super Number>
accept arguments of
Comparable<Object>
.
Note, this is different from methods that use the type parameter as a
upper
wildcard bound in an argument type; they cannot be invokeds at all.
The reason for this permitted argument type is that such a method would
have the signature
method(Comparable<? super capture of ?>)
in an unbounded parameterized type such as
Box<?>
, and "? super
capture of ?" denotes an unknown supertype of an unknown type. The ultimate
supertype of all types is
Object
, hence
Comparable<Object>
is permitted as an argument type. And likewise in
Box<? super Number>
,
where the signature would involve "? super capture of ? super Number",
and again
Object
is the only type that would fit.
In an upper bound wildcard parameterized type with upper bound
Bound
the permitted arguments types are the types that belong to the family denoted
by
ArgumentType<? super Bound>
. That is, a method in
class
Box
that takes an argument of type
Comparable<? super
Number>
would in the instantiation
Box<? extends Number>
accept arguments from the type family
Comparable<? super Number>
.
Note, this is similar to a method with an upper bound argument type in
an lower bound parameterized type; e.g. a method in class
Box
that takes an argument of type
Comparable<? extends Number>
would in the parameterized type
Box<? super Number>
accept
arguments from the type family
Comparable<? extends Number>
.
The reason for the permitted argument types is that the compiler determines
the signature of such a method in
Box<? extends Number>
as
method
(Box<? super capture of ? extends Number>)
. "
super capture of ? extends Number
" means unknown supertype of an unknown
subtype of
Number
, in other words all supertypes of
Number
.
Unbounded
wildcard parameterized type.
Example (access through unbounded wildcard):
class Box<
T
> {
private T t;
...
public int compareTo(
Comparable<? super
T
>
other) { return other.compareTo(t); }
public
Box<? super
T
>
copy() { return new Box<T>(t); }
}
class Test {
public static void main(String[] args) {
Box<Number> numberBox
= new Box<Number>(5L);
Box<?>
unknownBox = numberBox;
Comparable<?> comparableToUnknown
= new Integer(1);
Comparable<Object>
comparableToObject = ...;
Comparable<? super
Number> comparableToNumber = comparableToObject;
int compared = 0;
compared = unknownBox.compareTo(comparableToUnknown);
//
error
compared = unknownBox.compareTo(comparableToObject);
//
ok
compared = unknownBox.compareTo(comparableToNumber);
//
error
Box<?>
box1 = unknownBox.copy();
// ok
Box<? extends Number>
box2 = unknownBox.copy();
// error
Box<? super Number>
box3 = unknownBox.copy);
// error
}
}
error: compareTo(java.lang.Comparable<? super capture of ?>)
in Box<capture of ?> cannot be applied to (java.lang.Comparable<capture
of ?>)
compared = unknownBox.compareTo(comparableToUnknown);
^
error: compareTo(java.lang.Comparable<? super capture of ?>)
in Box<capture of ?> cannot be applied to (java.lang.Comparable<capture
of ? super java.lang.Number>)
compared = unknownBox.compareTo(comparableToNumber);
^
error: incompatible types
found : Box<capture of ? super capture of ?>
required: Box<? extends java.lang.Number>
Box<? extends Number>
box2 = unknownBox.copy();
^
error: incompatible types
found : Box<capture of ? super capture of ?>
required: Box<? super java.lang.Number>
Box<? super Number>
box3 = unknownBox.copy();
^
The example shows that method
compareTo
can only be invoked for
arguments of type
Comparable<Object>
and that the return type
of method
copy
is
Comparable<?>
.
Upper
bound wildcard parameterized type.
Example (access through upper bound wildcard):
class Box<
T
> {
private T t;
...
public int compareTo(
Comparable<? super
T
>
other) { return other.compareTo(t); }
public
Box<? super
T
>
copy() { return new Box<T>(t); }
}
class Test {
public static void main(String[] args) {
Box<Number> numberBox
= new Box<Number>(5L);
Box<?
extends Number>
unknownBox = numberBox;
Comparable<?> comparableToUnknown
= new Integer(1);
Comparable<Object>
comparableToObject = ...;
Comparable<? super
Number> comparableToNumber = comparableToObject;
int compared = 0;
compared = unknownBox.compareTo(comparableToUnknown);
//
error
compared = unknownBox.compareTo(comparableToObject);
//
ok
compared = unknownBox.compareTo(comparableToNumber);
//
ok
Box<?>
box1 = unknownBox.copy();
// ok
Box<? extends Number>
box2 = unknownBox.copy();
// error
Box<? super Number>
box3 = unknownBox.copy);
// error
}
}
error: compareTo(java.lang.Comparable<? super capture of ? extends
java.lang.Number>) in Box<capture of ? extends java.lang.Number> cannot
be applied to (java.lang.Comparable<capture of ?>)
compared = unknownBox.compareTo(comparableToUnknown);
^
error: incompatible types
found : Box<capture of ? super capture of ? extends
java.lang.Number>
required: Box<? extends java.lang.Number>
Box<? extends Number>
box2 = unknownBox.copy();
^
error: incompatible types
found : Box<capture of ? super capture of ? extends
java.lang.Number>
required: Box<? super java.lang.Number>
Box<? super Number>
box3 = unknownBox.copy();
^
The example shows that method
compareTo
can only be invoked for
arguments from the type family denoted by
Comparable<? super
Number>
and that the return type of method
copy
is
Comparable<?>
.
Lower
bound wildcard parameterized type.
Example (access through lower bound wildcard):
class Box<
T
> {
private T t;
...
public int compareTo(
Comparable<? super
T
>
other) { return other.compareTo(t); }
public
Box<? super
T
>
copy() { return new Box<T>(t); }
}
class Test {
public static void main(String[] args) {
Box<Number> numberBox
= new Box<Number>(5L);
Box<?
super Number>
unknownBox = numberBox;
Comparable<?> comparableToUnknown
= new Integer(1);
Comparable<Object>
comparableToObject = ...;
Comparable<? super
Number> comparableToNumber = comparableToObject;
int compared = 0;
compared = unknownBox.compareTo(comparableToUnknown);
//
error
compared = unknownBox.compareTo(comparableToObject);
//
ok
compared = unknownBox.compareTo(comparableToNumber);
//
error
Box<?>
box1 = unknownBox.copy();
// ok
Box<? extends Number>
box2 = unknownBox.copy();
// error
Box<? super Number>
box3 = unknownBox.copy);
// ok
}
}
error: compareTo(java.lang.Comparable<? super capture of ? super
java.lang.Number>) in Box<capture of ? super java.lang.Number> cannot
be applied to (java.lang.Comparable<capture of ?>)
compared = unknownBox.compareTo(comparableToUnknown);
^
error: compareTo(java.lang.Comparable<? super capture of ? super
java.lang.Number>) in Box<capture of ? super java.lang.Number> cannot
be applied to (java.lang.Comparable<capture of ? super java.lang.Number>)
compared = unknownBox.compareTo(comparableToNumber);
^
error: incompatible types
found : Box<capture of ? super capture of ? super
java.lang.Number>
required: Box<? extends java.lang.Number>
Box<? extends Number>
box3 = unknownBox.copy();
^
The example shows that method
compareTo
can only be invoked for
arguments of type
Comparable<Object>
and that the return type
of method
copy
is
Comparable<? super Number>
. |
LINK TO THIS
|
Technicalities.FAQ607
|
REFERENCES
|
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 parameterized type?
Which
methods that use the type parameter in the argument or return type are
accessible in an upper bound wildcard parameterized type?
Which
methods that use the type parameter in the argument or return type are
accessible in a lower bound wildcard parameterized type?
Which
methods that use the type parameter as type argument of a parameterized
argument or return type are accessible in a wildcard parameterized type?
Which
methods that use the type parameter as upper wildcard bound in a parameterized
argument or return type are accessible in a wildcard parameterized type?
Which
methods that use the type parameter as lower wildcard bound in a parameterized
argument or return type are accessible in a wildcard parameterized type?
In
a wildcard parameterized type, can I read and write fields whose type is
the type parameter?
What
is a wildcard instantiation?
What
is a wildcard?
What
is the capture of a wildcard?
|
In
a wildcard parameterized type, can I read and write fields whose type is
the type parameter?
Is
it really impossible to create an object whose type is a wildcard parameterized
type?
It is actually illegal and the compiler
tries to prevent it, but there's a workaround.
|
The creation of objects of a wildcard
parameterized type is discouraged: it is illegal that a wildcard parameterized
type appears in a
new
expression.
Example (of illegal creation of
objects of a wildcard
parameterized type
):
class Sequence<E>
{
public Sequence() {...}
public Sequence(int size) {...}
public
<F extends E> Sequence(F arg) {...}
public
<F extends E> Sequence(F[] a) {...}
public Sequence(Sequence<? extends E> s) {...}
}
Sequence
<?>
seq1 =
new Sequence
<?>
();
//
error
Sequence
<?> seq2
=
new Sequence
<?>
(10);
//
error
Sequence<String> s = new Sequence<String>();
... populate the sequence ...
Sequence<? super String> seq3 =
new
Sequence
<? super String>
("xyz");
//
error
Sequence<
?
super String
> seq4 =
new
Sequence
<
?
super String
>
(new
String[] {"abc","ABC"});
//
error
Sequence<
?
super String
> seq5 =
new
Sequence
<
?
super String
>
(s);
//
error
The compiler rejects all attempts to create an object
of wildcard types such as
Sequence
<?>
or
Sequence
<?
super String>
.
The compiler's effort to prevent the creation
of objects of a wildcard parameterized type can easily be circumvented.
It is unlikely that you will ever want to create an object of a wildcard
parameterized type, but should you ever need one, here's the workaround.
Instead of directly creating the object of the
wildcard parameterized type via a
new
expression you define a generic factory method that creates the object.
Example (of creation of
objects of a wildcard
parameterized type; workaround
):
class Factory {
public interface Dummy<T> {}
public final static
Dummy<?>
unboundedDummy = null;
public final static
Dummy<?
super String>
superStringDummy = null;
public static <T>
Sequence
<T>
make(
Dummy<T>
arg) {
return new
Sequence
<T>();
}
public static <T>
Sequence
<T>
make(
Dummy<T>
arg, int size) {
return new
Sequence
<T>(size);
}
public static <T,S extends T> Sequence<T> make(
Dummy<T>
d, S arg) {
return new Sequence<T>(arg);
}
public static <T,S extends T> Sequence<T> make(
Dummy<T>
d, S[] array) {
return new Sequence<T>(array);
}
public static <T> Sequence<T> make(
Dummy<T>
d, Sequence<? extends T> seq) {
return new Sequence<T>(seq);
}
}
Sequence
<?> seq1
=
Factory.make
(Factory.
unboundedDummy
);
// fine
Sequence
<?> seq2
=
Factory.make
(Factory.
unboundedDummy
,10
);
//
fine
Sequence<String> s = new Sequence<String>();
... populate the sequence ...
Sequence<
?
super String
> seq3 =
Factory.make
(Factory.
superStringDummy
,
"xyz");
//
fine
Sequence<
?
super String
> seq4 =
Factory.make
(Factory.
superStringDummy
,
new
String[] {"abc","ABC"});
// fine
Sequence<
?
super String
> seq5 =
Factory.make
(Factory.
superStringDummy
,
s);
// fine
The trick is defining a generic factory method that takes a dummy argument
for each constructor that you had otherwise used. This dummy argument
must be a of a generic type and is needed so that the compiler can infer
the type argument when the factory method is invoked. When the method
is invoked with a dummy argument that is of a wildcard type, then the compiler
infers that the factory method's type argument is that particular wildcard
and consequently the factory method creates an object of the
corresponding
wildcard parameterized type.
In the example the
make
method is first invoked twice with a dummy argument of the unbounded wildcard
type
Dummy<?>
.
The type argument
T
is
infered as
T:=?
(or more precisely
T:=capture
of ?
) and hence the
make
method creates an object of type
Sequence
<?>
.
The subsequent three invocations use a dummy argument of the unbounded
wildcard type
Dummy<? super
String>
. Consequently, the compiler infers
T
as
T:=? super String
(or more precisely
T:=capture
of ? super String
)
.
In addition, the compiler infers
S
as
S:=String
and
checks that
S
is a subtype
of
T
.
As mentioned above, it is unlikely you will ever
need this workaround.
|
LINK TO THIS
|
Technicalities.FAQ609
|
REFERENCES
|
What
is a wildcard parameterized type?
Can
I create an object whose type is a wildcard parameterized type?
What
is type argument inference?
|
Cast and instanceof
Which
types can or must not appear as target type in an instanceof expression?
Only reifiable types are permitted.
|
It is a compile-time error if the reference type mentioned
after the
instanceof
operator does not denote a reifiable type.
In other words, concrete and bounded wildcard parameterized types are NOT
permitted in an
instanceof
expression.
Examples:
Object o = new LinkedList<Long>();
System.out.println (o
instanceof List);
System.out.println (o
instanceof List<?>);
System.out.println (o
instanceof List<Long>);
// error
System.out.println (o
instanceof List<? extends Number>); // error
System.out.println (o
instanceof List<? super Number>);
// error
The reason for disallowing non-reifiable types (i.e., instantiations
of a generic type with at least one type arguments that is a concrete type
or a bounded wildcard) in
instanceof
expression is that these
parameterized types do not have an exact runtime type representation.
Their dynamic type after type erasure is just the raw type.
The evaluation of an
instanceof
expression could at best check
whether the object in question is an instance of the raw type. In
the example, the expression
(o instanceof List<Long>)
would
check whether o is an instance of type
List
, which is a check
different from what the source code suggests. In order to avoid confusion,
the non-reifiable types are prohibited in
instanceof
expression.
Only the reifiable types (i.e., the raw type and the unbounded wildcard
parameterized type) are permitted in an
instanceof
expression.
The reifiable types do not lose any type information during translation
by type erasure. For this reason, the
instanceof
check makes sense
and is allowed. |
LINK TO THIS
|
Technicalities.FAQ701
|
REFERENCES
|
What
is a reifiable type?
What
is an unbounded wildcard instantiation?
|
Overloading
and Overriding
What
is method overriding?
What
is method overloading?
When a class has two methods with the
same name, but signatures that are not override-equivalent.
|
Method overloading happens among methods defined in the
same type, when the methods have the same name and differ in the number
or type of arguments.
Example (of method overloading):
class Container {
public void remove(
int
index) { ... }
public void remove(
Object
o) { ... }
//
overloads
}
The overloading methods need not necessarily be defined in the same type,
but can also be inherited from a supertype.
Example (of method overloading):
class Super {
public void method(
String
arg) { ... }
public void method(
StringBuffer
arg) { ... }
//
overloads
}
class Sub extends Super {
public void method(
StringBuilder
arg) { ... }
//
overloads
}
In this example, all three versions of the method overload each other because
they have the same name but different argument types. When the method is
invoked the compiler picks the best match from the three available candidates.
This process is called
overload resolution
.
Example (of overload resolution):
Sub ref = new Sub();
ref.method("ABC");
// calls Super.method(String)
ref.method(new StringBuilder("ABC")); // calls Sub.method(StringBuilder)
The return type is irrelevant for overloading, that is, overloaded methods
can have different return types. It is an error if the difference is only
in the return type.
Example (of method overloading):
class Container {
public boolean remove(
Object
o) { ... }
public Object remove(
int
index) { ... }
//
overloads
public void remove(
int
index) {
... }
// error: already defined
}
The first two methods overload each other, because they have the same name
and different arguments type. The last two methods are in conflict,
because the have the same name and the same argument type and differ only
in the return type. The general rule is that overloaded methods must
have the same name but different method
signatures
, and the return
type is not part of a method signature. A more precise definition
of method signature is given in FAQ entry
FAQ810
.
Generic methods can be overloaded as well.
Example (of overloaded generic methods):
class SomeClass {
public
<T>
void method(
T
arg) { ... }
public
<T extends Number>
void method(
T
arg) { ... }
// overloads
public
void method(
Long
arg) { ... }
//
overloads
}
These three methods have different signatures. Even the first and
the second method differ, because they have different argument types, namely
a type parameter with bound
Object
and a type parameter with bound
with bound
Number
. This is because the type parameters of
a generic method including the type parameter bounds are part of the method
signature.
Method overloading is sometimes confused with method
overriding
.
Overriding happens among a method in a subtype and a method with the same
name inherited from a supertype, if the signatures of those methods are
override-equivalent
.
This means that methods in the same class never override each other; they
can only overload each other. But methods in a subtype can either
override or overload a supertype's method with the same name. It
is sometimes difficult to tell the difference between overloading and overriding.
The rule is: the subtype method overloads the supertype method if it is
not override-equivalent. Override-equivalence is explained in more detail
in FAQ entry
FAQ812
.
Example (of method overloading vs. overriding):
class Super {
public void method(
String
arg) { ... }
}
class Sub extends Super {
public void method(
String
arg) { ... }
//
overrides
public void method(
StringBuilder
arg) { ... }
//
overloads
}
The first method in the subtype has an override-equivalent signature, basically
because it has the same signature as the supertype method.
The second method has a different argument type and therefore is not override-equivalent
and hence overloads the supertype method.
The rules for overriding are more complex than sketeched out above;
they are discussed in detail in the subsequent FAQ entries. In particular,
overriding does not only require override-equivalent signatures, but additionally
a compatible return type and a compatible
throws
clause.
No such rules apply to overloading.
Example (of overloaded methods with different return types and
throws
clauses):
class SomeClass {
public void method(
String
arg)
{ ... }
public void method(
StringBuffer
arg)
throws IllegalArgumentException
{ ... }
//
overloads
public
boolean
method(
StringBuilder
arg) {
... }
// overloads
}
There is one additional rule, that applies to methods in general: methods
declared in the same type (or inherited from a supertype) must not have
the same erasure. This is because generics are translated by type erasure.
Two methods with the same erasure would have identical signatures in the
byte code and the virtual machine could not distinguish between them.
This is prohibited and the compiler issues an error method if two method
have the same erasure.
Example (of conflicting erasures):
class SomeClass {
public
<T extends Number>
void method(
T
arg) { ... }
public
void method(
Number
arg) { ... }
// error:
already defined
}
Before type erasure the two methods have different signatures, namely
<$T1_extends_Number>method($T1_extends_Number)
and
method(Number)
. After type erasure they both have the signature
method(Number)
and consequently the compiler reports an error. |
LINK TO THIS
|
Technicalities.FAQ802
|
REFERENCES
|
What
is overload resolution?
Why
doesn't method overloading work as I expect it?
|
What
is the @Override annotation?
An annotation that you can attach to
a method so that the compiler issues an error if the annotated method does
not override any supertype method.
|
Overriding and overloading are often confused. If
you are in doubt, you can annotate a subtype's method with the standard
annotation
java.lang.@Override
.
The compiler then checks whether the annotated method overrides a supertype
method. If not, it reports an error.
Example (of using the
@Override
annotation):
class Super {
public <T> void method(T arg) { ... }
}
class Sub extends Super {
@Override
public <T extends Number> void method(T
arg) { ... }
// error: name clash
}
error: method does not override
a method from its superclass
@Override public <T
extends Number> void method(T arg) {
^
|
LINK TO THIS
|
Technicalities.FAQ803
|
REFERENCES
|
What
is method overriding?
What
is method overloading?
|
What
is a method signature?
An identification of a method that consist
of the method's name and its arguments types.
|
A method
signature
consists of the method's name
and its arguments types. This is different from a method
descriptor
,
which is the signature plus the return type. In case of generic methods,
the type parameters are part of the signature. Method signatures
play a role in overloading and overriding.
Examples:
Declaration |
Signature |
void method(String arg)
|
method(String)
|
int method(String arg)
|
method(String)
|
String method(String arg1, int
arg2) throws Exception
|
method(String,int)
|
void method(List<? extends Number>
list)
|
method(List<?_extends_Number>)
|
T method(T arg)
*)
where
T
is enclosing class's type parameter
|
method($T1_extends_Object)
|
void add(K key, V val)
*)
with type parameters
K
and
V
from
enclosing class
|
add($T1_extends_Object,$T2_extends_Object)
|
<T> T method(T arg)
|
<$T1_extends_Object>method
($T1_extends_Object)
|
<E> int method(E arg)
|
<$T1_extends_Object>method
($T1_extends_Object)
|
<T extends Number> T method(T
arg)
|
<$T1_extends_Number>method
($T1_extends_Number)
|
<A extends I&J, B extends
J&I> void method(A a, B b)
|
<$T1_extends_I&J,$T2_extends_I&J>method
($T1_extends_I&J,$T2_extends_I&J)
|
<T> void method(List<T> list)
|
<$T1_extends_Object>method
(List<$T1_extends_Object>)
|
<S extends Number> void method(List<S>
list)
|
<$T1_extends_Number>method
(List<$T1_extends_Number>)
|
<E> void sort(List<? extends
E> list)
|
<$T1_extends_Object>sort
(List<? extends $T1_extends_Object>)
|
<E extends Comparable<Number>>
void sort(List<E> list)
|
<$T1_extends_Comparable<Number>>sort
(List<$T1_extends_Comparable<Number>>)
|
<E extends Comparable<E>>
void sort(List<E> list)
|
<$T1_extends_Comparable<$T1_extends_Comparable>>sort
(List<$T1_extends_Comparable<$T1_extends_Comparable>>)
|
<E extends Comparable<? super K>> void sort(List<E> l)
*)
where
K
is enclosing class's type parameter
|
<$T1_extends_Comparable<?_super_$T2_extends_Object>>sort
(List<$T1_extends_Comparable<$T2_extends_Object>>)
|
The terms
$T
n
_extends_Bound
1
&...&Bound
n
stand for synthetic type variables. These synthetic names are used
in lieu of the original names because the name of a type variable is irrelevant
for the signature. For instance, the methods
<T> void method(T
arg)
and
<S> void method(S arg)
have the same signature;
they are a method named
method
with one unbounded type parameter
and one method parameter whose type is the unbounded type parameter.
Basically, the compiler renames all type variables of a generic method
and uses synthetic names instead of the actual names.
The terms
$Bound
1
&...&Bound
n
denote
the type variable's bounds. For the purpose of a signature, the order of
the bounds is irrelevant, that is, "
A extends I&J
" is equivalent
to "
B extends J&I
". Just imagine the compiler would
order the bounds alphabetically. |
LINK TO THIS
|
Technicalities.FAQ810
|
REFERENCES
|
What
is method overriding?
What
is a subsignature?
What
are override-equivalent signatures?
When
does a method override its supertype's method?
|
What
is a subsignature?
A method signature that is identical
to another method signature, or identical to the erasure of another method
signature.
|
Subsignatures are relevant in conjunction with overriding.
A prerequisite for overriding is that the subtype method's signature is
a subsignature of the supertype method's signature.
A signature is a
subsignature
of another signature if:
-
the two signatures are identical, or
-
the signature is identical to the erasure of the other signature.
Example (subsignatures):
class Super {
public void setName(
String
arg) { ... }
public void setNames(
Collection
<String>
c)
{ ... }
public void printCollection(
Collection
<?>
c)
{ ... }
}
class Sub extends Super {
public void setName(
String
arg) { ... }
// subsignature
public void setNames(
Collection
c) { ... }
// subsignature
public void printCollection(
Collection
c) { ... }
// subsignature
}
In the example, the subclass version of the
setName
method is
a subsignature of the superclass version of the method, because it has
the same signature. The subclass versions of the
setNames
and
printCollection
methods are subsignatures of the superclass versions of the methods, because
their signatures are identical to the erasures of the superclass's methods.
Note, that in the case of the
setNames
method an unchecked
warning is issued. The reasons for this warning are discussed in FAQ entry
FAQ812
.
The unchecked warning is issued whenever an argument type of the superclass
method is a parameterized type and the corresponding argument type in the
subclass method is a raw type, unless the parameterized type is the unbounded
wildcard instantiation of the generic type.
|
LINK TO THIS
|
Technicalities.FAQ811
|
REFERENCES
|
What
is method overriding?
What
is a method signature?
What
are override-equivalent signatures?
When
does a method override its supertype's method?
|
What
are override-equivalent signatures?
Two method signatures where one signature
is a subsignature of the other.
|
Override-equivalent signatures are relevant in conjunction
with overriding and overloading. We talk of
override-equivalent
signatures when a subtype method's signature is a subsignature of a supertype
method's signature.
The term
subsignature
was explained in FAQ entry
FAQ811
:
A signature is a
subsignature
of another signature if the signature
is identical to the other signature or identical to the erasure of the
other signature.
Override-equivalence is a prerequisite of method overriding.
-
We talk of
overriding
if the signature of a subtype's method
is override-equivalent to (synonym to: "is a subsignature of") a supertype's
method.
-
We talk of
overloading
if a class has two methods with the same
name, but signatures that are
not
override-equivalent, that is,
neither is a subsignature of the other.
Example (overloading and overriding):
class Super {
public void setName(
String
arg) { ... }
}
class Sub extends Super {
public void setName(
StringBuilder
arg) { ... }
//
overloads
public void setName(
String
arg) { ... }
//
overrides
}
In the example, the two
setName
methods in the subclass have the
same name, but different signatures: one method takes a
String
argument, the other takes a
StringBuilder
argument. As the
two signatures are different (and neither is the subsignature of the other),
these two methods are not override-equivalent and hence the
overload
.
In constrast, the two versions of the
setName
method taking
a
String
argument in the super- and subclass
override
.
This is because they have identical and therefore override-equivalent signatures.
In the example above, the erasure of the methods is irrelevant, because
none of the methods has a generic argument type. Let us consider
an examples that involves generic types.
Example (overloading and overriding):
class Super {
public void printCollection(
Collection
<?>
c) { ... }
}
class Sub extends Super {
public void printCollection(
List<?>
c) { ... }
//
overloads
public void printCollection(
Collection
c) { ... }
//
overrides
}
The two
printCollection
methods in the subclass have the same
name, but different signatures: one method takes a
List<?>
argument, the other takes a
Collection
argument. As the
two signatures are different (and neither is the subsignature of the other),
these two methods
overload
.
In constrast, the two versions of the
printCollection
method
taking a
Collection<?>
argument in the superclass and a
Collection
argument in the subclass
override
although the methods do not have
identical signatures. But the signatures are almost identical: the
subclass signature is the erasure of the superclass signature.
Note, that the converse is not permitted. If the superclass method
had a signature that is identical to the erasure of a subclass method,
the compiler would issue an error message. This is neither overloading
nor overriding, but just a name clash.
Example (neither overloading nor overriding):
class Super {
public void printCollection(
Collection
c) { ... }
}
class Sub extends Super {
public void printCollection(
Collection
<?>
c) { ... }
// error
}
error: name clash: printCollection(List<?>)
in Sub and printCollection(List) in Super
have the same erasure, yet neither overrides the other
class Sub extends Super {
^
The notion of override-equivalent signatures is slightly more complex among
generic methods. See
FAQ820
and subsequent entries for further details and examples.
|
LINK TO THIS
|
Technicalities.FAQ812
|
REFERENCES
|
What
is method overriding?
What
is a method signature?
What
is a subsignature?
When
does a method override its supertype's method?
Can
a method of a non-generic subtype override a method of a generic supertype?
Can
a method of a generic subtype override a method of a generic supertype?
Can
a generic method override a generic one?
Can
a non-generic method override a generic one?
Can
a generic method override a non-generic one?
|
When
does a method override its supertype's method?
When the subtype's method has a signature
that is a subsignature of the supertype's method and the two methods have
compatible return types and
throws
clauses.
|
A subtype's method overrides a supertype's method if the
subtype's method has a signature that is:
-
identical to the supertype's method's signature, or
-
identical to the erasure of the supertype's method's signature.
It is said that the subtype method has a
subsignature
or that the
two signatures are
override-equivalent
.
First an example where the subtype's method has the same signature as
the supertype's method.
Example (of overriding methods with identical signatures):
class Super {
public void method(
String
arg) { ... }
}
class Sub extends Super {
public void method(
String
arg) { ... }
//
overrides
}
Both methods have the same signature, namely
method(String)
, and
for this reason the subclass's method overrides the superclass's method.
Second, an example where the subtype's method has a signature whose
erasure is identical to the signature of the supertype's method.
Example (of overriding methods with generics involved):
class Super {
public void printCollection(
Collection
<?>
c) { ... }
}
class Sub extends Super {
public void printCollection(
Collection
c) { ... }
//
overrides
}
The erasures of the methods are the same, namely
printCollection(Collection)
.
In this situation, the erased subtype method is considered an overriding
version of the non-erased supertype method.
This kind of overriding is permitted in order to allow that legacy supertypes
can be re-engineered and generified without affecting any existing subtypes
of the legacy supertype. Imagine the supertype had originally not
used any generics. In that original situation, the signatures of supertype
and subtype method had been identical. After generification of the
supertype the signatures are different. Without the rule that the
subtype method can be the erasure of the supertype method all subtypes
would also need to be re-engineered and generified. Fortunately, the additional
rule renders this re-engineering effort unnecessary and the generification
of the supertype does not affect overriding methods in subtypes.
There are additional rules for overriding. When a subtype's method
overrides a supertype's method, then:
-
the subtype method's return type must be substitutable for the supertype
method's return type, and
-
the
throws
clauses of both methods must not be in conflict.
Substitutable Return Types
|
Let us first consider some examples of substitutable and incompatible
return types before we look into conflicting
throws
clauses.
Example (incompatible return types):
class Super {
public
void
method(String arg) { ... }
}
class Sub extends Super {
public
int
method(String arg) { ... }
//
error
}
error: method(String) in Sub
cannot override method(String) in Super;
attempting to use incompatible return type
found : int
required: void
public int method(String
arg) { ... }
^
Both methods have the same signature, but non-substitutable return types.
A subtype method's return type
R
Sub
is
substitutable
for
a supertype method's return type
R
Super
if:
-
both type are
void
-
both types are the same primitive type
-
R
Sub
is identical to
R
Super
-
R
Sub
is a subtype of
R
Super
-
R
Sub
is a raw type that is identical to
the erasure of
R
Super
-
R
Sub
is a raw type that is a subtype of
the erasure of
R
Super
The last two situations lead to an unchecked warning unless
R
Super
is an unbounded wildcard instantiation.
Example (substitutable return types):
class Super {
public
Super
copy() { ... }
public
List<String>
getList() { ... }
public
Map
getLinks() { ... }
}
class Sub extends Super {
public
Sub
copy() { ... }
// overrides
public
ArrayList<String>
getList() { ... }
//
overrides
public
Map<String,File>
getLinks() { ...
}
// overrides
}
In these examples, the subtype method's return type is a subtype of the
supertype method's return type. The technical term for this kind
of substitutability is
covariant return types
.
Note, that in addition to the regular super-/subtype relationship, an
instantiation of a generic type is also considered a subtype of the corresponding
raw type. The
getLinks
method in the code snippet above
illustrates this kind of substitutable return types: the supertype method
returns the raw type
Map
and the subtype method returns the instantiation
Map<String,File>
.
The converse is also permitted, namely a supertype version of a method
that returns an instantiation and a subtype version that return the corresponding
raw type. But this kind of overriding is not type-safe unless the
supertype method's return type is an unbounded wildcard instantiation,
as illustrated in the code sample below.
Example (substitutable return types):
class Super {
public
Class<?>
getType() { ... }
public
Map<String,File>
getLinks() { ... }
}
class Sub extends Super {
public
Class
getType() { ... }
//
overrides
public
Map
getLinks() { ... }
//
overrides with unchecked warning
}
warning: getLinks() in Sub overrides getLinks() in Super;
return type requires unchecked conversion
found : java.util.Map
required: java.util.Map<java.lang.String,java.io.File>
public Map getLinks()
{ ... }
^
Conflicting
throws
Clauses
|
The supertype and the overriding subtype method must not have conflicting
throws
clauses.
Example (conflicting
throws
clauses):
class Super {
public void method() { ... }
}
class Sub extends Super {
public void method()
throws UserException
{ ... }
//
error
}
error: method() in Sub cannot override
method() in Super;
overridden method does not throw UserException
public void method()
throws UserException { ... }
^
Both methods have the same signature, but conflicting
throws
clauses.
The overriding subtype method may not be declared to throw more checked
exceptions than the overridden supertype method. The
throws
clause of the supertype method must at least contain a supertype of each
exception type in the
throws
clause of the subtype method.
Example (compatible
throws
clauses):
class Super {
public void method()
throws IOException, InterruptedException
{ ... }
}
class Sub extends Super {
public void method()
throws FileNotFoundException
{ ... }
// overrides
}
|
LINK TO THIS
|
Technicalities.FAQ813
|
REFERENCES
|
What
is method overriding?
What
is a method signature?
What
is a subsignature?
What
are override-equivalent signatures?
What
are covariant-return types?
What
are substitutable return types?
Can
a method of a non-generic subtype override a method of a generic supertype?
Can
a method of a generic subtype override a method of a generic supertype?
Can
a generic method override a generic one?
Can
a non-generic method override a generic one?
Can
a generic method override a non-generic one?
Why
doesn't method overriding work as I expect it?
|
What
are covariant return types?
Return types of overriding methods where
the supertype method's return type is a supertype of the subtype method's
return type.
|
When a method defined in a subtype overrides a method defined
in a supertype then there are certain rules for the return types of those
methods. Either the return types are identical, or the subtype method's
return type is a raw type that is identical to the supertype method's return
type. Java allows one exception from this rule: the subtype
method's return type is allowed to be a subtype of the supertype method's
return type, or the subtype method's return type is a raw type that is
identical to a subtype of the supertype method's return type.
Example (substitutable return types):
class Super {
public
Super
copy() { ... }
public
List<String>
getList() { ... }
public
Map
getLinks() { ... }
}
class Sub extends Super {
public
Sub
copy() { ... }
// overrides
public
ArrayList<String>
getList() { ... }
//
overrides
public
Map<String,File>
getLinks() { ...
}
// overrides
}
In these examples, the subtype method's return type is a subtype of the
supertype method's return type. Note, that an instantiation of a
generic type is considered a subtype of the corresponding raw type.
The technical term for this kind of return type substitutability is
covariant
return types
. |
LINK TO THIS
|
Technicalities.FAQ814
|
REFERENCES
|
What
is method overriding?
What
is method overloading?
What
is the @Override annotation?
What
is a method signature?
What
is a subsignature?
What
are override-equivalent signatures?
When
does a method override its supertype's method?
What
are substitutable return types?
|
What
are substitutable return types?
The return type of an overriding subclass
method must be substitutable for the superclass method's return type.
|
Substitutable return types are required when a method overrides
another method. In that situation the subtype method's return type
must be substitutable for the supertype method's return type.
A subtype method's return type
R
Sub
is considere
substitutable
for a supertype method's return type
R
Super
if:
-
both type are
void
-
both types are the same primitive type
-
R
Sub
is identical to
R
Super
-
R
Sub
is a subtype of
R
Super
-
R
Sub
is a raw type that is identical to
the erasure of
R
Super
-
R
Sub
is a raw type that is a subtype of
the erasure of
R
Super
The last two situations lead to an unchecked warning unless
R
Super
is an unbounded wildcard instantiaion.
Let's take a look at a couple of examples. In the following code
snippet the overriding method has the same return type as its superclass
method.
Example (plain substitutable return types):
class Super {
public
void
setName(String s) { ... }
public
boolean
equals(Object
other) { ... }
public
String
toString() { ... }
}
class Sub extends Super {
public
void
setName(String s) { ... }
//
overrides
public
boolean
equals(Object
other) { ... }
// overrides
public
String
toString() { ... }
//
overrides
}
A little more exciting are the cases in which the subtype method's return
type is a subtype of the supertype method's return type. In this
case we talk of
covariant
return types.
Example (covariant substitutable return types):
class Super {
public
Super
copy() { ... }
public
List<String>
getNames() { ... }
public
Map
getLinks() { ... }
}
class Sub extends Super {
public
Sub
copy() { ... }
// overrides
public
ArrayList<String>
getNames() { ... }
//
overrides
public
Map<String,File>
getLinks() { ...
}
// overrides
}
Worth mentioning is that an instantiation of a generic type is considered
a subtype of the corresponding raw type in this context. The
getLinks
method illustrates this case. The supertype method returns the raw
type
Map
and the overriding subtype method returns the more specific
type
Map<String,File>
.
The converse is permitted, too, but is generally not type-safe. The
safe situation is when the subtype's method returns a raw type and the
supertype's method returns the wildcard instantiation of the corresponding
generic type or of a generic supertype.
Example (raw substitutable return types):
class Super {
public
Class<?>
getContentType() { ... }
public
List<?>
asList() { ... }
}
class Sub extends Super {
public
Class
getContentType() { ... }
// overrides
public
LinkedList
asList() { ... }
//
overrides
}
The
getContentType
method in the supertype returns the wildcard
instantiation
Class<?>
and the subtype method's return type
Class
,
the raw type, is considered a substitutable return type. Note, that
the subtype method's return type can additionally be covariant, that is,
a raw type that is a subtype of the erasure of the supertype method's return
type. This is illustrated by the
asList
method.
When the supertype method's return type is an instantiation different
from a wildcard instantation, then the substitution by the raw type in
the subclass method is not type-safe and the compiler issues a warning.
Example (raw substitutable return types):
class Super {
public
Class<Super>
getThisType() { ... }
public
List<String>
asStrings() { ... }
}
class Sub extends Super {
public
Class
getThisType()
{ ... }
// overrides with unchecked warning
public
LinkedList
asStrings() { ... }
//
overrides with unchecked warning
}
The type-safety problem is that the superclass method
getThisType
for instance promises that a type token of type
Class<Super>
is returned, while the subclass method only returns a type token of the
raw type
Class
, which may or may not be compatible with
Class<Super>
.
The compiler cannot prevent that the subtype method returns an inappropriate
type token that is actually of type
Class<Alien>
. |
LINK TO THIS
|
Technicalities.FAQ815
|
REFERENCES
|
What
is method overriding?
What
is method overloading?
What
is the @Override annotation?
What
is a method signature?
What
is a subsignature?
What
are override-equivalent signatures?
When
does a method override its supertype's method?
What
are covariant-return types?
Can
a method of a non-generic subtype override a method of a generic supertype?
Can
a method of a generic subtype override a method of a generic supertype?
Can
a generic method override a generic one?
Can
a non-generic method override a generic one?
Can
a generic method override a non-generic one?
What
is overload resolution?
How does overload resolution
work when generic methods are involved?
Why
doesn't method overloading work as I expect it?
Why
doesn't method overriding work as I expect it?
|
Can
a method of a non-generic subtype override a method of a generic supertype?
Yes.
|
This FAQ entry discusses whether and when methods of a
regular,
non-generic
subtype override methods inherited from a
generic
supertype. The question comes up because overriding requires that
the method signatures are override-equivalent. If the supertype is
generic then its method signatures might involve a type parameter.
At the same time, the subtype is non-generic and its method signatures
will
not
involve type parameters. Hence the signatures are
different, which raises the question whether overriding is possible when
the supertype is generic and the subtype is not.
The answer is: Yes, it is possible. A non-generic type can extend
or implement a concrete instantiation of a generic supertype and then its
methods can override methods from the generic supertype. Here is an example:
Example (of a non-generic subtype with overriding methods):
class Super
<T>
{
...
public void set(
T
arg) { ... }
public
T
get() { ... }
}
class Sub extends Super
<Number>
{
...
public void set(
Number
arg) { ... }
//
overrides
public
Number
get() { ... }
//
overrides
}
The subtype methods override the corresponding supertype methods.
The subtype derives from a certain instantiation of the supertype, namely
Super<Number>
in the example. For this reason, all signatures in the subtype, where
T
is replaced by
Number
, are override-equivalent signatures.
Let us consider some deviations from this straigth-forward case.
What if we defined the following methods in the subclass?
Example (of a non-generic subtype with redefined supertype methods):
class Super
<T>
{
...
public void set(
T
arg) { ... }
...
}
class Sub extends Super
<Number>
{
...
public void set(
Object
arg) { ... }
//
error: same erasure
public void set(
Long
arg) { ... }
//
overloads
}
The first
set
method in the subclass is rejected because it has
the same erasure as its superclass's version of the
set
method,
namely
void set(Object)
.
The second
set
method in the subclass is not override-equivalent,
because its signature is truly different from the supertype method's signature;
they have a different argument type. For this reason it overloads
the
set
method, instead of overriding it.
Let us consider the
get
methods. What if we defined the following
methods in the subclass?
Example (of a non-generic subtype with redefined supertype methods):
class Super
<T>
{
...
public
T
get() { ... }
...
}
class Sub extends Super
<Number>
{
...
public
Object
get() { ... }
//
error: incompatible return type
public
Long
get() { ... }
//
overrides
}
The
Sub.get()
method that returns
Object
is recognized
as a potentially overriding method, but with incompatible return type,
and is therefore rejected with an error method.
The
Sub.get()
method that returns
Long
has an override-equivalent
signature, because its return type is a subtype of the supertype method's
return type (covariant return type).
Naturally, the two
get
methods cannot coexist in the same
class anyway, because their signatures are the same and they only differ
in the return type. |
LINK TO THIS
|
Technicalities.FAQ820
|
REFERENCES
|
What
is method overriding?
What
is method overloading?
What
is the @Override annotation?
What
is a method signature?
What
is a subsignature?
What
are override-equivalent signatures?
When
does a method override its supertype's method?
What
are covariant-return types?
What
are substitutable return types?
Can
a method of a generic subtype override a method of a generic supertype?
Why
doesn't method overriding work as I expect it?
|
Can
a method of a generic subtype override a method of a generic supertype?
Yes, but make sure you do not inadvertently
overload instead of override.
|
This FAQ entry discusses whether and when methods of a
generic
subtype can override methods of a
generic
supertype. Method
signatures in the sub- and the supertype may involve type parameters of
the respective enclosing class. What is the required relationship
between type variables in sub- and supertype methods in order to allow
for method overriding?
The answer is: Yes, methods of a generic subtype can override methods
of a generic supertype, but it is not always trivial to get it right and
it is common that mistakes are made and that overriding is confused
with overloading.
Let us start with a simple example:
Example (of a generic subtype with override-equivalent methods):
class Super
<T>
{
...
public void set(
T
arg) { ... }
public
T
get() { ... }
}
class Sub
<S>
extends Super
<S>
{
...
public void set(
S
arg) { ... }
//
overrides
public
S
get() { ... }
//
overrides
}
The subtype methods override the corresponding supertype methods.
The generic subtype derives from a certain instantiation of the supertype,
namely
Super<S>
in the example, where
S
is the subtype's
type parameter. Since the names of type parameters are irrelevant
for the method signatures, the corresponding methods in super- and subtype
have identical signatures, namely
set($T1_extends_Object)
and
get()
and identical return types.
The remainder of this FAQ entry discusses slightly more complex overriding
situation for further illustration of the principle.
Here is another example of successful overriding.
Example (of a generic subtype with override-equivalent methods):
class Super
<T>
{
...
public void set(
T
arg) { ... }
public
T
get() { ... }
}
class Sub<
A
,
B
> extends
Super<
A
> {
...
public void set(
A
arg)
{ ... }
// overrides
public
A
get() { ...
}
//
overrides
}
The signatures and the return types are indentical - a classical example
of overriding. The second type parameter
B
simply does not
appear in the overriding methods' signatures and is therefore irrelevant
for overriding.
However, if we slightly change the subtype, our attempt to override
the inherited methods goes wrong. We declare the subtype methods
using the type parameter
B
although we derive from the supertype
instantiation on type parameter
A
.
Example (of a generic subtype without override-equivalent methods):
class Super
<T>
{
...
public void set(
T
arg) { ... }
public
T
get() { ... }
}
class Sub<
A
,
B
> extends
Super<
A
> {
...
public void set(
B
arg)
{ ... }
// error: same erasure
public
B
get() { ...
}
//
error: incompatible return type
}
error: name clash: set(B) in
Sub<A,B> and set(T) in Super<A> have the same erasure,
yet neither overrides the other
class Sub<A,B> extends Super<A> {
^
error: get() in Sub cannot override get() in Super;
attempting to use incompatible return type
found : B
required: A
public B get() { ...
}
^
The
set
methods have signatures that are no longer override-equivalent,
namely
set($T1_extends_Object)
in the supertype and
set($T2_extends_Object)
in the subtype. This is because the compiler distinguishes between different
type parameters. At the same time, both signatures have the identical
erasures, namely
set(Object)
, and the compiler rejects the
subtype method
set
with an error message because no two methods
in the same type may have identical signatures.
The
get
methods have identical signatures, but incompatible
return types. Again, because the compiler distinguishes between different
type parameters. For this reason the subtpye method
get
is
rejected with an error message.
Let us modify the subtype a second type and see what happens now.
The modification is that the second type parameter is bounded by the first
type parameter.
Example (of a generic subtype without override-equivalent methods):
class Super
<T>
{
...
public void set(
T
arg) { ... }
public
T
get() { ... }
}
class Sub<
A
,
B
extends A
>
extends Super
<A>
{
...
public void set(
B
arg) { ... }
//
error: same erasure
public
B
get() { ... }
//
overrides
}
error: name clash: set(B) in
Sub<A,B> and set(T) in Super<A> have the same erasure,
yet neither overrides the other
class Sub<A,B extends A> extends Super<A>
{
^
The
set
methods still have signatures that are not override-equivalent,
namely
set($T1_extends_Object)
in the supertype and
set($T2_extends_$T1)
in
the subtype. And both signatures still have the same erasure, namely
set(Object)
.
Again, the compiler rejects the subtype method
set
with an error
message.
The
get
methods have identical signatures and this time compatible
return types, because the type parameter
B
is a subtype of the
type parameter
A
. For this reason the subtpye method
get
overrides
the supertype method.
Let us consider a situation where the subtype's type parameter has
different bounds than the supertype's type parameter.
Example (of a generic subtype with override-equivalent methods):
class Super
<T>
{
...
public void set(
T
arg) { ... }
public
T
get() { ... }
}
class Sub<
N
extends Number
>
extends Super<
N
> {
...
public void set(
N
arg) { ... }
//
overrides
public
N
get() { ... }
//
overrides
}
The signatures and the return types of the methods are identical in super
and subclass, namely
set($T1_extends_Number)
with return type
void
and
get()
with return type
$T1_extends_Number
.
Let us change the subtype declaration slightly; we declare subclass
methods so that they use the bound in the method signatures instead of
the type parameter .
Example (of a generic subtype with different yet override-equivalent
methods):
class Super
<T>
{
...
public void set(
T
arg) { ... }
public
T
get() { ... }
}
class Sub
<N extends Number
> extends Super<
N
>
{
...
public void set(
Number
arg) { ... }
// overrides
public
Number
get()
{ ... }
//
error: incompatible return type
}
The
set
methods now have different signatures, namely
set($T1_extends_Number)
in the supertype and
set(Number)
in the subtype. Normally,
this would be overloading, but in this case with type parameters involved
the signatures are considered override-equivalent. This is because the
supertype's
set
method takes an argument of type
$T1_extends_Number
,
which is a placeholder for a subtype of
Number
. The subtype's
set
method takes an argument of type
Number
, which means that any
argument passed to the
set
method via a supertype reference is
acceptable to the subtype version of the method.
Consider the example of a concrete parameterization of our super- and
subtype in order to understand why it makes sense that the subtype version
of the
set
method is an overriding rather than an overloading
version of the supertype method, despite of the different signature.
For illustration we use
Sub<Integer>
and its supertype
Super<Integer>
.
The two methods involved in overriding are
Super<Integer>.set(Integer)
and
Sub<Integer>.set(Number)
.
When a supertype reference refers to a subtype object and we invoke the
set method through the supertype reference, then we see
Super
<Integer>.set(
Integer
)
,
but actually invoke the overriding
Sub
<Integer>.set(
Number
)
.
Is it type-safe?
Example (of calling the overridden
set
method):
Super<Integer> ref = new Sub<Integer>();
ref.set(10); // calls Sub<Integer>.set(Number)
All we can pass to
Super<Integer>.set(Integer)
are objects
of type
Integer
; they are handed over to
Sub<Integer>.set(Number)
,
which happily accepts the
Integer
objects because they are subtypes
of
Number
. And the same is true for
all
parameterizations
of type
Sub
. because the declared argument type of the supertype
method is always a subtype of the declared argument type of the subtype
method. Hence it is type-safe that the subtype version of the
set
method is considered an overriding version of the supertype's
set
method.
The opposite is true for the
get
methods. They have identical
signatures, but incompatible return types, namely
$T1_extends_Number
in the supertype and
Number
in the subtype. If we invoked the
subtype's
set
method via a supertype reference then we would expect
a return value of type
$T1_extends_Number
, which is a placeholder
for a subtype of
Number
, while in fact the subtype method would
return a
Number
reference, which can refer to any arbitrary subtype
of
Number
, not necessarily the one we are prepared to receive.
Hence considering the subtype version of the
get
method an overriding
version of the supertype's
get
method would not be type-safe and
is therefore rejected as an error.
In constrast, the following fairly similar example leads to an overloading
situation.
Example (of a generic subtype without override-equivalent methods):
class Super
<T>
{
...
public void set(
T
arg) { ... }
public
T
get() { ... }
}
class Sub<
N extends Number
> extends Super<
Number
>
{
...
public void set(
N
arg)
{ ... }
// overloads
public
N
get() { ...
}
//
overrides
}
The
set
methods again have different yet similar signatures. This
time it is the other way round: we have
set(Number)
in the supertype
and
set($T1_extends_Number)
in the subtype. This is overlaoding
rather than overriding, because the supertype method has a declared argument
type that is a supertype of the subtype method's declared argument type.
Overriding would not be type-safe in this situation.
To understand it let us use the same concrete parameterization as above,
namely
Sub<Integer>
and its supertype
Super<Number>
.
The two methods in question are
Super<Number>.set(Number)
and
Sub<Integer>.set(Integer)
.
When a supertype reference refers to a subtype object and we invoke the
set
method through the supertype reference, then we see
Super
<Number>.set(
Number
)
,
but would actually invoke
Sub
<Integer>.set(
Integer
)
,
if this were overrding. This is not type-safe because we could pass an
object of type
Long
to
Super<Number>.set(Number)
,
but the subtype method
Sub<Integer>.set(Integer)
cannot take
it.
The
get
methods are not problematic in this example.
They have the identical signatures and covariant return type, namely
Number
in the supertype and
$T1_extends_Number
in the subtype. Hence,
we have overriding for the
get
methods.
Overloading leads to confusing and/or ambiguous method invocations.
Consider for example the instantiation
Sub<Number>
. In this
instantiation we have
Super<Number>.set(Number)
in the supertype
and overloaded version
Sub<Number>.set(Number)
in the subtype,
both methods have the same signature.
Example (of calling the overloaded
set
method):
Integer integer = 10;
Number number = integer;
Sub<Number> ref = new Sub<Number>();
ref.set(integer);
// error: ambiguous
ref.set(number);
// error: ambiguous
error: reference to set
is ambiguous,
both method set(T) in Super<Number> and method set(N) in Sub<Number>
match
ref.set(integer);
^
eror: reference to set is ambiguous,
both method set(T) in Super<Number> and method set(N) in Sub<Number>
match
ref.set(number);
^
The point to take notice of is that the set method in
Sub<Number>
does not override the
set
method inherited from
Super<Number>
although in this particular instantiation both methods have the same argument
type.
This is because the decision whether a subtype method overrides or overloads
a supertype method is made by the compiler when it compiles the generic
subtype. At that point in time there is no knowledge regarding the
concrete type by which the type parameter
N
might later be replaced.
Based on the declaration of the generic subtype the two
set
methods
have different signatures, namely
set(Number)
in the supertype
and
set($T1_extends_Number)
in the generic subtype. In a certain
instantiation of the subtype, namely in
Sub<Number>
, the type
parameter
N
(or
$T1_extends_Number
) might be replaced
by the concrete type
Number
. As a result both
set
methods of
Sub<Number>
suddenly have the same arguments type.
But that does not change the fact that the two methods still have different
signatures and therefore overload rather than override each other.
|
LINK TO THIS
|
Technicalities.FAQ821
|
REFERENCES
|
What
is method overriding?
What
is method overloading?
What
is the @Override annotation?
What
is a method signature?
What
is a subsignature?
What
are override-equivalent signatures?
When
does a method override its supertype's method?
What
are covariant-return types?
What
are substitutable return types?
Can
a method of a non-generic subtype override a method of a generic supertype?
Why
doesn't method overriding work as I expect it?
|
Can
a generic method override a generic one?
Yes.
|
This FAQ
entry discusses the relationship between generic methods in super- and
subtypes with respect to overriding and overloading.
Say, we have a supertype with generic methods and we intend to override
the generic methods in a subtype. Which subtype methods are considered
overriding versions of the the generic supertype methods? FAQ entry
FAQ823
discusses which non-generic methods in a subtype override a generic method
in a supertype. In this FAQ entry we explore which
generic
subtype methods override generic methods in a supertype.
Example (of generic subtype methods overriding generic supertype methods):
class Super {
public
<T>
void set(
T
arg) { ... }
public
<T>
T
get() { ... }
}
class Sub extends Super {
public
<S>
void set(
S
arg) { ... }
//
overrides
public
<S> S
get() { ... }
//
overrides
}
In this example the subtype methods are generic methods and have the same
signatures as the supertype methods, namely
<$T1_extends_Object>set($T1_extends_Object)
and
<$T1_extends_Object>get()
. Note, that the names of
the type parameters of the generic methods differ in the super- and the
subtype. This, however, does not affect the signatures because the
names of type parameters are irrelevant for the signature.
If the methods have type parameters
with different bounds
, then
they do not override, because the methods have signatures that are not
override-equivalent. Remember, the type parameter bounds are part
of a generic method's signature.
Example (of generic subtype methods overloading generic supertype methods;
not recommended):
class Super {
public
<T>
void set(
T
arg) { ... }
public
<T> T
get() { ... }
}
class Sub extends Super {
public
<S
extends Number
>
void set(
S
arg) { ... }
// overloads
public
<S
extends Number
>
S
get() { ... }
//
overloads
}
In this example the subtype methods have the signatures
<$T1_extends_Number>set($T1_extends_Number)
and
<$T1_extends_Number>get()
, while the supertype methods
have the signatures
<$T1_extends_Object>set($T1_extends_Object)
and
<$T1_extends_Object>get()
. The signatures are clearly
different and there is no chance that the subtype methods can ever override
the supertype methods. The resulting overload situation makes for "interesting"
effects when the methods are invoked; most of the time you will see the
compiler complaining about ambiguous method calls. For this reason
complex overloading situations like the one above are generally best avoided.
Remember that the order of the type parameter bounds does not affect
the signature of a generic method. For this reason, the methods in
the following example do override, although they have different type parameter
bounds.
Example (of generic subtype methods overriding generic supertype methods):
class Super {
public <T extends
Comparable<T>
&
Serializable
> void set(T arg)
{ ... }
public <T extends
Comparable<T>
&
Serializable
>
T get() { ... }
}
class Sub extends Super {
public <S extends
Serializable
&
Comparable<S>
> void set(S
arg) { ... }
// overrides
public <S extends
Serializable
&
Comparable<S>
> S get() { ... }
//
overrides
}
The respective methods have identical signatures because the order of the
type parameter bounds is irrelevant. |
LINK TO THIS
|
Technicalities.FAQ822
|
REFERENCES
|
What
is method overriding?
What
is method overloading?
What
is the @Override annotation?
What
is a method signature?
What
is a subsignature?
What
is overload resolution?
Can
a generic method override a non-generic one?
What
is overload resolution?
Why
doesn't method overloading work as I expect it?
|
Can
a non-generic method override a generic one?
Yes.
|
This FAQ entry discusses the relationship between generic
and non-generic methods with respect to overloading.
Say, we have a supertype with generic methods and we intend to override
the generic methods in a subtype. Which subtype methods are considered
overriding versions of the the generic supertype methods? Before
we discuss non-generic subtype methods that override generic supertype
methods, let us consider the more obvious case of a subtype with generic
methods that override generic methods from the supertype.
Example (of generic subtype methods overriding generic supertype methods):
class Super {
public
<T>
void set(
T
arg) { ... }
public
<T>
T
get() { ... }
}
class Sub extends Super {
public
<S>
void set(
S
arg) { ... }
//
overrides
public
<S> S
get() { ... }
//
overrides
}
In this example the subtype methods are generic methods and have the same
signatures as the supertype methods, namely
<$T1_extends_Object>set($T1_extends_Object)
and
<$T1_extends_Object>get()
. Note, that the names of
the type parameters of the generic methods differ in the super- and the
subtype. This, however, does not affect the signatures because the
names of type parameters are irrelevant for the signature.
Now, let us explore an example where non-generic subtype methods override
generic supertype methods. Non-generic subtype methods are considered
overriding versions of the generic supertype methods if the signatures'
erasures are identical.
Example (of non-generic subtype methods overriding generic supertype
methods):
class Super {
public
<T>
void set(
T
arg) { ... }
public
<T>
T
get() { ... }
}
class Sub extends Super {
public void set(
Object
arg) { ... }
//
overrides
public
Object
get() { ... }
//
overrides with unchecked warning
}
warning: get() in Sub overrides
<T>get() in Super;
return type requires unchecked conversion
found : Object
required: T
public Object get()
{
^
Here the subtype methods have signatures, namely
set(Object)
and
get()
,
that are identical to the erasures of the supertype methods. These
type-erased signatures are considered override-equivalent.
There is one blemish in the case of the
get
method: we receive
an unchecked warning because the return types are not really compatible.
The return type of the subtype method
get
is
Object
,
the return type of the supertype method
get
is an unbounded type
parameter. The subtype method's return type and the supertype method's
return type are not
compatible
, because the subtype method's return
type is neither identical to the supertype method's return type nor is
it a subtype thereof. However, the subtype method's return type
Object
is
convertible
to the supertype method's return type by means of
an unchecked conversion. An unchecked warning indicates that a type check
is necessary that neither the compiler nor the virtual machine can perform.
In other words, the unchecked operation is not type-safe. In case
of the convertible return types someone would have to make sure that the
subtype method's return value is type-compatible to the supertype method's
return type, but nobody except the programmer can ensure this.
The main purpose of allowing that methods with erased signatures override
methods with generic signatures is backward compatibility. Imagine
both classes had initially been non-generic legacy classes. In this
initial situation both classes had had methods that take and return
Object
reference.
Example (of legacy classes):
class Super {
public void set(
Object
arg) { ... }
public
Object
get() { ... }
}
class Sub extends Super {
public void set(
Object
arg) { ... }
//
overrides
public
Object
get() { ... }
//
overrides
}
Later we decide to re-engineer the superclass and turn it into a class
with generic methods.
Example (same as before, but after generification of the supertype):
class Super {
public
<T>
void set(
T
arg) { ... }
public
<T>
T
get() { ... }
}
class Sub extends Super {
publicvoid set(
Object
arg) { ... }
// overrides
???
public
Object
get() { ... }
//
overrides ???
}
Now the question is: do the subtype methods still override the generified
supertype methods? The answer is: yes. This is because subtype
methods whose signatures are identical to the erasures of the supertype
methods overrides the supertype methods. That is, we can generify
the supertype without affecting the legacy subtypes.
At last, let us discuss a counter-example where the non-generic subtype
methods do
not
override the generic supertype methods.
Example (of non-generic subtype method overloading, instead of overriding,
generic supertype methods; not recommended):
class Super {
public
<T>
void set(
T
arg) { ... }
public
<T> T
get() { ... }
}
class Sub extends Super {
public void set(
String
arg) { ... }
// overloads
public
String
get()
{ ... }
//
overrides with unchecked warning
}
warning: get() in Sub overrides
<T>get() in Super;
return type requires unchecked conversion
found : String
required: T
public String get()
{
^
Here the subtype method
set
has the signature
set(String)
and
this signature is not override-equivalent to the supertype method's signature
<$T1_extends_Object>set($T1_extends_Object)
.
This is because the subtype method's signature is different from both the
supertype method's signature and the erasure thereof. As the subtype
method
set
does not override, it overloads.
The subtype method
get
on the other hand overrides the supertype
method
get
. This is because the subtype method's signature
is identical to the erasure of the supertype method's signature.
The return types are not compatible, but only convertible by unchecked
conversion; the subtype method's return type
String
is a
subtype of the erasure of the supertype method's return type. That
is, the subtype method's return type is acceptable as a result of the combination
of unchecked conversion and covariance.
The entire subclass is an example of poor design. The combination
of overloading and overriding of corresponding methods is confusing at
best. |
LINK TO THIS
|
Technicalities.FAQ823
|
REFERENCES
|
What
is an unchecked warning?
What
is method overriding?
What
is method overloading?
What
is the @Override annotation?
What
is a method signature?
What
is overload resolution?
Can
a generic method override a generic one?
Can
a generic method override a non-generic one?
Why
doesn't method overloading work as I expect it?
|
Can
a generic method override a non-generic one?
No.
|
This FAQ entry discusses whether a generic method in a
subtype can override an non-generic method inherited from a supertype.
We have seen in
FAQ823
that the converse is possible, namely that a regular, non-generic method
in a subtype can override a generic method inherited from a supertype.
It raises the question whether the same is true if the roles are flipped
and a generic method in a subtype attempts to override a non-generic method
from a supertype.
The answer is: No, generic methods in a subtype can
not
override non-generic methods in a supertype. Generic subtype methods can
only overload non-generic supertype methods.
Let us consider a superclass with non-generic methods and let us explore
why generic methods in subtypes do not override the non-generic supertype
methods.
Example (of a supertype with non-generic methods and a subtype with
generic methods):
class Super {
public void set(
Object
arg) { ... }
public
Object
get() { ... }
}
class Sub extends Super {
public
<S>
void set(
S
arg) { ... }
//
error
public
<S> S
get() { ... }
//
error
}
error: name clash: <S>set(S) in Sub and set(Object) in Super
have the same erasure,
yet neither overrides the other
class Sub extends Super {
^
error: name clash: <S>get() in Sub and get() in Super have the
same erasure,
yet neither overrides the other
class Sub extends Super {
^
Although the erasures of the subtype methods have signatures that are identical
to the supertype methods' signatures the respective signatures are not
override-equivalent. The override-equivalence only holds when the
subtype method's signature is identical to the erasure of the supertype
method's signature, but not vice versa. Consequently, the subtype
methods overload, instead of override, the supertype methods. Moreover,
the fact that the erasures of corresponding methods in super- and subtype
are identical is in conflict with the rule that a class (the subclass in
this case) must not have methods with identical erasures. As a result
the compiler issues error messages.
Let us consider a slightly different subtype, where again overloading
is mixed up with overriding.
Example (of a supertype with non-generic methods and a subtype with
generic methods; not recommended):
class Super {
public void set(
Object
arg) { ... }
public
Object
get() { ... }
}
class Sub extends Super {
public
<S extends Number>
void set(
S
arg)
{ ... }
// overloads
public
<S extends Number> S
get() { ... }
//
overloads
}
This subclass compiles, but it does not show an example of overriding,
but instead of overloading. This is because the supertype methods have
the signatures
set(Object)
and
get()
, while the subtype
methods have the signatures
<$T1_extends_Number>set($T1_extends_Number)
and
<$T1_extends_Number>get()
. In this example, not even
the erasures of the signatures are identical, so that there is not the
slightest chance that the subtype methods could override the supertype
methods. Instead, the subtype methods overload the supertype methods.
What happens when we invoke the overloaded methods?
Example (of invoking the overloaded methods):
Sub sub = new Sub();
Super sup = sub;
sup.set("abc");
// calls
Super
.set(Object)
sup.set(0);
// calls
Super
.set(Object)
Object o = sup.get(); // calls
Super
.get()
sub.set("abc");
// calls
Super
.set(Object)
sub.set(0);
// calls
Sub
.<Integer>set(Integer)
Integer i = sub.get();
// error:
ambiguous
error: reference to get is ambiguous,
both method get() in Super and method <S>get() in Sub match
Integer i = sub.get();
^
The invocation through a supertype reference leads to calls to the
supertype versions of the overloaded method, regardless of the dynamic
type of the referenced object. This is the behavior that is expected
of overloaded methods.
The invocation through a subtype reference leads to a call to the subtype
versions of the overloaded
set
method, provided the argument is
of a subtype of Number, and to invocation of the supertype version of the
method otherwise. Invocation of the overloaded
get
method through
a subtype reference leads to an error message. Let us see how that happens.
When the
set
method is invoked with a
String
argument,
then the compiler finds only one viable method, namely the non-generic
set(Object)
method
from the supertype. The generic subtype method is not a candidate for this
invocation because
String
is no subtype of
Number
and
hence the possible type argument
String
is not within bounds.
Consequently, the supertype method is called.
When the
set
method is invoked with a
Integer
argument,
then the compiler finds two candidates for the invocation of the
set
method: the non-generic
set(Object)
method from the supertype
and the parametrization
<Integer>set(Integer)
inferred from
the generic subtype. Note, the compiler performs type argument inference
before is looks for the best match among the candidate methods. Since
the parametrization is the better match, the subtype method is invoked.
For the invocation of the
get
method, the compiler finds two
candidates:
get()
from the supertype and
<$T1_extends_Number>get()
from the subtype. Neither of the two signatures is more specific
and thus the compiler reports an error. Note, the compiler does not
consider the return type when it resolves the overloading and in particular
does not perform type inference based on a generic method's return type.
In our example it means that the compiler does
not
infer from the
calling context that the instantiation
<Integer>get()
would
be a viable candidate method and then picks it as the better match.
Only the signature, and never the return type, of a method are relevant
for overload resolution.
The overload resolution is slightly different, when explicit instantiations
are invoked.
Example (of invoking the overloaded methods with explicitly specified
type arguments):
Sub sub = new Sub();
Super sup = sub;
Integer n = sub.<Integer>get(); // calls
Sub
.<Integer>set(Integer)
sub.<Integer>set(0);
// calls
Sub
.<Integer>get()
In this situation the candidate set contains only the respective generic
subtype method, because the supertype methods are not generic and for this
reason cannot be invoked with explicitly specified type arguments.
As the examples demonstrate, there is no way that a generic subtype
method can override a non-generic supertype method. Instead, generic
subtype methods overload non-generic supertype methods. Such overloading
should generally be avoided because it is confusing and hard to understand. |
LINK TO THIS
|
Technicalities.FAQ824
|
REFERENCES
|
What
is type argument inference?
What
is method overriding?
What
is method overloading?
What
is the @Override annotation?
What
is a method signature?
What
is overload resolution?
Can
a generic method override a generic one?
Can
a non-generic method override a generic one?
Can
a generic method override a non-generic one?
What
is overload resolution?
Why
doesn't method overloading work as I expect it?
|
What
is overload resolution?
The process of determining the best match
from a set of overloaded methods.
|
When a type has several overloaded methods, then the compiler
must decide which method to call when it finds a method invocation expression.
The process of picking the best match from the set of candidate methods
is called
overload-resolution
.
Example (of method overloading):
class Super {
public void method(
String
arg) { ... }
public void method(
StringBuffer
arg) { ... }
//
overloads
}
class Sub extends Super {
public void method(
StringBuilder
arg) { ... }
//
overloads
}
In this example, all three versions of the method overload each other because
they have the same name but different argument types. When the method is
invoked the compiler picks the best match from the three available candidates.
The compiler takes a look at the type of the argument provided to the method
call and tries to find a method in the candidate set that accepts this
particular type of argument.
Example (of overload resolution):
Sub ref = new Sub();
ref.method("ABC");
// calls
Super
.method(String)
ref.method(new StringBuilder("ABC")); // calls
Sub
.method(StringBuilder)
In the first method call a
String
is provided and consequently
the compiler invokes the method that takes a
String
argument.
When a
StringBuilder
is provided the method with the
StringBuilder
argument type is the best match. In both cases there is an exact
match. This is not always so.
When there is no exact match the compiler considers various conversions,
among them the conversion from a subtype to a supertype (called reference-widening),
conversions among primitive types (e.g.
int
to
long
),
autoboxing and unboxing (e.g.
int
to
Integer
), and the
conversion from a parameterized type to the raw type (called unchecked
conversion). By and large, overload resolution is a complex process and
can lead to surprising results, in the sense that the compiler picks and
invokes a method that nobody had expected would be called.
This sounds harmless, but can be a serious problem. For instance,
when an overloading method is added to an existing class, then this additional
candidate can change the result of overload resolution and thereby inadvertantly
change the effect of method invocations in an unrelated part of the program.
This is usually undesired and the resulting bugs are difficult to track
down, because symptom and source of the problem are mostly unrelated.
As a general rule, overloading should be used sparingly and judiciously. |
LINK TO THIS
|
Technicalities.FAQ830
|
REFERENCES
|
What
is method overloading?
What
is the @Override annotation?
What
is a method signature?
What
is a subsignature?
What
are override-equivalent signatures?
Why
doesn't method overriding work as I expect it?
|
CONTENT
PREVIOUS
NEXT
INDEX
|