Java Generics FAQs - Type Parameters
Type
Parameters
© Copyright 2004-2022 by Angelika Langer. All
Rights Reserved.
Type Parameters
Fundamentals
What
is a type parameter?
What
is a bounded type parameter?
A type parameter with one or more bounds.
The bounds restrict the set of types that can be used as type arguments
and give access to the methods defined by the bounds.
|
When you declare a type parameter
T
and use it
in the implementation of a generic type or method, the type parameter
T
still denotes an unknown type. The compiler knows that
T
is a place holder for a type, but it does not know anything about the type.
This is okay in some implementations, but insufficient in others.
Example (of a generic type without bounds):
public class Hashtable<Key,Data> {
...
private static class Entry<Key,Data> {
private Key key;
private Data value;
private int hash;
private Entry<Key,Data> next;
...
}
private Entry<Key,Data>[] table;
...
public Data get(Key key) {
int hash =
key.hashCode()
;
for (Entry<Key,Data> e = table[hash &
hashMask]; e != null; e = e.next) {
if ((e.hash == hash) &&
e.
key.equals(key)
) {
return e.value;
}
}
return null;
}
}
The implementation of class
Hashtable
invokes the methods
hashCode
and
equals
on the unknown
Key
type. Since
hashCode
and
equals
are methods defined in class
Object
and available for all reference
types, not much need to be known about the unknown
Key
type.
This changes substantially, when we look into the implementation of sorted
sequence.
Example (of a generic type, so far without bounds):
public interface Comparable<T> {
public int compareTo(T arg);
}
public class TreeMap<Key,Data>{
private static class Entry<K,V> {
K key;
V value;
Entry<K,V> left;
Entry<K,V> right;
Entry<K,V> parent;
}
private transient Entry<Key,Data> root;
...
private Entry<Key,Data> getEntry(Key key) {
Entry<Key,Data> p = root;
Key k = key;
while (p != null) {
int cmp = k.
compareTo(p.key)
;
// error
if (cmp == 0)
return p;
else if (cmp < 0)
p = p.left;
else
p = p.right;
}
return null;
}
public boolean containsKey(Key key) {
return getEntry(key) != null;
}
...
}
The implementation of class
TreeMap
invokes the method
compareTo
on
the unknown
Key
type. Since
compareTo
is not defined
for arbitrary types the compiler refuses to invoke the
compareTo
method on the unknown type
Key
because it does not know whether
the key type has a
compareTo
method.
In order to allow the invocation of the
compareTo
method we
must tell the compiler that the unknown
Key
type has a
compareTo
method. We can do so by saying that the
Key
type implements
the
Comparable<Key>
interface. We can say so by declaring
the type parameter
Key
as a bounded parameter.
Example (of the same generic type, this time
with
bounds):
public interface Comparable<T> {
public int compareTo(T arg);
}
public class TreeMap<Key
extends Comparable<Key>
,Data>{
private static class Entry<K,V> {
K key;
V value;
Entry<K,V> left = null;
Entry<K,V> right = null;
Entry<K,V> parent;
}
private transient Entry<Key,Data> root = null;
...
private Entry<Key,Data> getEntry(Key key) {
Entry<Key,Data> p = root;
Key k = key;
while (p != null) {
int cmp = k.
compareTo(p.key)
;
if (cmp == 0)
return p;
else if (cmp < 0)
p = p.left;
else
p = p.right;
}
return null;
}
public boolean containsKey(Key key) {
return getEntry(key) != null;
}
...
}
In the example above, the type parameter
Key
has the bound
Comparable<Key>
.
Specification of a bound has two effects:
-
It gives access to the methods that the bound specifies
. In
the example, the bound
Comparable<Key>
gives access to the
compareTo
method that we want to invoke in the implementation of our
TreeMap
class.
-
Only types "within bounds" can be used for instantiation of the generic
type.
In the example, a parameterized type such as
TreeMap<Number,String>
would be rejected, because the type
Number
is not a subtype of
Comparable<Number>
.
A parameterized type like
TreeMap<String,String>
would be accepted,
because the type String is within bounds, i.e. is a subtype of
Comparable<String>
.
Note that the suggested bound
Comparable<Key>
in this
example is not the best conceivable solution. A better bound, that
is more relaxed and allows a larger set of type arguments, would be
Comparable<?
super Key>
. A more detailed discussion can be found in a separate
FAQ entry (click
here
). |
LINK TO THIS
|
TypeParameters.FAQ002
|
REFERENCES
|
When
would I use a wildcard parameterized with a lower bound?
What
is a type parameter bound?
Which
types are permitted as type parameter bounds?
Can
I use different instantiations of a same generic type as bounds of a type
parameteer?
Does
a bound that is a class type give access to all its public members?
Can
I use a type parameter as part of its own bounds or in the declaration
of other type parameters?
|
Type Parameter Bounds
What
is a type parameter bound?
A reference type that is used to further
describe a type parameter. It restricts the set of types that can
be used as type arguments and gives access to the non-static methods that
it defines.
|
A type parameter can be unbounded. In this case any
reference type can be used as type argument to replace the unbounded type
parameter in an instantiation of a generic type.
Alternatively can have one or several bounds. In this case the
type argument that replaces the bounded type parameter in an instantiation
of a generic type must be a subtype of all bounds.
The syntax for specification of type parameter bounds is:
<TypeParameter extends Class
&
Interface
1
&
...
&
Interface
N
>
A list of bounds consists of one class and/or several interfaces.
Example (of type parameters with several bounds):
class Pair<A extends
Comparable<A>
& Cloneable
,
B
extends
Comparable<B> & Cloneable
>
implements Comparable<Pair<A,B>>, Cloneable { ...
}
This is a generic class with two type arguments
A
and
B
,
both of which have two bounds. |
LINK TO THIS
|
TypeParameters.FAQ101
|
REFERENCES
|
What
is the difference between a wildcard bound and a type parameter bound?
Which
types are permitted as type parameter bounds?
Can
I use different instantiations of a same generic type as bounds of a type
parameteer?
|
Which
types are permitted as type parameter bounds?
All classes, interfaces and enum
types including parameterized types, but no primitive types and no array
types.
|
All classes, interfaces, and enum types can be used as
type parameter bound, including nested and inner types. Neither primitive
types nor array types be used as type parameter bound.
Examples (of type parameter bounds):
class X0 <T extends
int
>
{ ... }
// error
class X1 <T extends
Object[]
>
{ ... }
// error
class X2 <T extends
Number
>
{ ... }
class X3 <T extends
String
>
{ ... }
class X4 <T extends
Runnable
>
{ ... }
class X5 <T extends
Thread.State
>
{ ... }
class X6 <T extends
List
>
{ ... }
class X7 <T extends
List<String>
>
{ ... }
class X8 <T extends
List<? extends
Number>
> { ... }
class X9 <T extends
Comparable<?
super Number>
> { ... }
class X10<T extends
Map.Entry<?,?>
>
{ ... }
The code sample shows that primitive types such as
int
and array
types such as
Object[]
are not permitted as type parameter bound.
Class types, such as
Number
or
String
, and interface
types, such as
Runnable
, are permitted as type parameter bound.
Enum types, such as
Thread.State
are also permitted as type
parameter bound.
Thread.State
is an example of a nested
type used as type parameter bound. Non-static inner types are also
permitted.
Raw types are permitted as type parameter bound;
List
is an example.
Parameterized types are permitted as type parameter bound, including
concrete parameterized types such as
List<String>
, bounded
wildcard parameterized types such as
List<? extends Number>
and
Comparable<? super Long>
, and unbounded wildcard parameterized
types such as
Map.Entry<?,?>
. A bound that is a wildcard
parameterized type allows as type argument all types that belong to the
type family that the wildcard denotes. The wildcard parameterized
type bound gives only restricted access to fields and methods; the restrictions
depend on the kind of wildcard.
Example (of wildcard parameterized type as type parameter bound):
class X<
T extends List<?
extends Number>
> {
public void someMethod(T t) {
t.add(new Long(0L));
//
error
Number n = t.remove(0);
}
}
class Test {
public static void main(String[] args) {
X<ArrayList<
Long
>>
x1 = new X<ArrayList<Long>>();
X<ArrayList<
String
>>
x2 = new X<ArrayList<String>>();
//
error
}
}
Reference variables of type
T
(the type parameter) are treated
like reference variables of a wildcard type (the type parameter bound).
In our example the consequence is that the compiler rejects invocation
of methods that take an argument of the "unknown" type that the type parameter
stands for, such as
List.add
, because the bound is a wildcard
parameterized type with an upper bound.
At the same time the bound
List<? extends Number>
determines
the types that can be used as type arguments. The compiler accepts all
type arguments that belong to the type family
List<? extends Number>
,
that is, all subtypes of
List
with a type argument that is a subtype
of
Number
.
Note, that even types that do not have subtypes, such as final classes
and enum types, can be used as upper bound. In this case there is
only one type that can be used as type argument, namely the type parameter
bound itself. Basically, the parameterization is pointless then.
Example (of nonsensical parameterization):
class Box<
T extends String
>
{
private
T
theObject;
public Box(
T
t) { theObject
= t; }
...
}
class Test {
public static void main(String[] args) {
Box<
String
>
box1 = Box<String>("Jack");
Box<
Long
>
box2 = Box<Long>(100L);
//
error
}
}
The compiler rejects all type arguments except
String
as "not
being within bounds". The type parameter
T
is not needed and the
Box
class would better be defined as a non-parameterized class. |
LINK TO THIS
|
TypeParameters.FAQ102
|
REFERENCES
|
What
is a type parameter bound?
Can
I use a type parameter as a type parameter bound?
Can
I use different instantiations of a same generic type as bounds of a type
parameter?
Can
I use a type parameter as part of its own bounds or in the declaration
of other type parameters?
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?
Which
methods and fields are accessible/inaccessible through a reference variable
of a wildcard parameterized type?
|
Can
I use a type parameter as a type parameter bound?
Can
I use different instantiations of a same generic type as bounds of a type
parameter?
No, at most one instantiation of the
same generic type can appear in the list of bounds of a type parameter.
|
Example (of illegal use of two instantiations of the same
generic type as bounds of a type parameter):
class ObjectStore<T extends
Comparable<T>
&
Comparable<String>
> {
//
error
private Set<T> theObjects = new
TreeSet<T>
();
...
public boolean equals(ObjectStore<String> other)
{
if (theObjects.size() != other.size())
return false;
Iterator<T> iterThis = theObjects.iterator();
Iterator<String> iterOther = other.theObjects.iterator();
while (iterThis.hasNext() && iterOther.hasNext())
{
T t = iterThis.next();
String string = iterOther.next();
if (
t.compareTo(string)
!= 0) return false;
}
return true;
}
}
error: java.lang.Comparable cannot be inherited with different
arguments: <T> and <java.lang.String>
class ObjectStore<T extends Comparable<T>
& Comparable<String>> {
^
In the example the type parameter
T
is required to be
Comparable<T>
,
that is, comparable to its own type. This is needed for storing objects
of type
T
in a
TreeSet<T>
. At the same time
the type parameter
T
is required to be
Comparable<String>
,
because we want to invoke the type parameter's
compareTo(String)
method. Remember, type parameter bounds are needed to give the compiler
access to the type parameters non-static methods. In this (admittedly
contrived) example, we need to specify two instantiations of the
Comparable
interface as bound of the type parameter, but the compiler rejects it.
The reason for this restriction is that there is no type that is a subtype
of two different instantiations of the
Comparable
interface and
could serve as a type argument. It is prohibited that a type implements
or extends two different instantiations of the same interface. This is
because the bridge method generation process cannot handle this situation.
Details are discussed in a separate FAQ entry (click
here
).
If no class can ever implement both instantiations of
Comparable
,
there is no point to a bounds list that requires it. The class in
our example would not be instantiable because no type can ever be within
bounds, except perhaps class
String
.
In practice, you will need to work around this restriction. Sadly, there
might be situations in which there is no workaround at all. |
LINK TO THIS
|
TypeParameters.FAQ103
|
REFERENCES
|
Can
a class implement different instantiations of the same generic interface?
What
is type erasure?
What
is a bridge method?
How
does type erasure work when a type parameter has several bounds?
How
can
work around the restriction that a type parameter cannot have
different instantiations of a same generic type as its bounds?
|
How
can I work around the restriction that a type parameter cannot have different
instantiations of a same generic type as its bounds?
Usually there is no satisfactory workaround.
|
Let us use the example from the previous question for our
search of a workaround.
Example (of illegal use of two instantiations of the same generic type
as bounds of a type parameter):
class ObjectStore<T extends
Comparable<T>
&
Comparable<String>
> {
//
error
pri
vate Set<T> theObjects = new
TreeSet<T>
();
...
public boolean equals(ObjectStore<String> other)
{
if (theObjects.size() != other.size())
return false;
Iterator<T> iterThis = theObjects.iterator();
Iterator<String> iterOther = other.theObjects.iterator();
while (iterThis.hasNext() && iterOther.hasNext())
{
T t = iterThis.next();
String string = iterOther.next();
if (
t.compareTo(string)
!= 0) return false;
}
return true;
}
}
In the example the type parameter
T
is required to be
Comparable<T>
,
because objects of type
T
are stored in a
TreeSet<T>
.
At the same time the type parameter
T
is required to be
Comparable<String>
,
because we invoke the type parameter's
compareTo(String)
method.
The compiler rejects the attempt of specifying two instantiations of the
Comparable
interface as bound of the type parameter.
One workaround for the example above could be the following: we could
drop the requirement that the parameter
T
must be
Comparable<T>
,
because the corresponding
compareTo(T)
method is not invoked in
the implementation of the generic class itself, but in the operations of
the
Treeset<T>
. By dropping the requirement we would
risk that a type argument is supplied that is not
Comparable<T>
and will cause
ClassCastException
s when operations of the
TreeSet<T>
are invoked. Clearly not a desirable solution, but perhaps a viable
one for this particular example.
However, this might not be a solution if the class uses the type parameter
in a slightly different way. For instance, if both
compareTo
methods
were called in the implementation of the generic class, then we could not
drop any of the bounds.
Example (another class with illegal use of two instantiations of the
same generic type as bounds of a type parameter):
class SomeClass<T extends
Comparable<T>
&
Comparable<String>
> {
//
error
...
private void method(T t1, T t2) {
...
t1.compareTo(t2)
...
...
t1.compareTo("string")
...
}
}
If the methods of the bounds are invoked in the class implementation, then
dropping one of the conflicting bounds does not solve the problem.
One could consider use of an additional interface, such as a
CombinedComparable
interface that combines the two required interfaces into one interface.
Example (conceivable work-around; does not work):
interface CombinedComparable<T>
{
int compareTo(T other);
int compareTo(String other);
}
class SomeClass<T extends
CombinedComparable<T>
{
...
private void m(T t1, T t2) {
... t1.compareTo(t2) ...
... t1.compareTo("string") ...
}
public boolean equals(
SomeClass<String>
other) {
// error
...
}
}
However, this is not really a viable solution, because it excludes class
String
as a type argument.
String
is a class that is comparable to itself
and to
String
, but it is does not implement a
CombinedComparable
interface. Hence type
String
is not within bounds. Another conceivable
alternative is definition of one new interface per instantiation needed,
such as a parameterized
SelfComparable
and a non-parameterized
StringComparable
interface. Again, this excludes class
String
as a potential
type argument. If it acceptable that class String is excluded as
a potential type argument then the definition of additional interfaces
might be a viable workaround.
But there remain some situations, in which additional interfaces do
not help. For instance, if the type parameter is used as type argument
of another parameterized method, then it must itself be within the bounds
of that other type parameter.
Example (another class with illegal use of two instantiations of the
same generic type as bounds of a type parameter):
class AnUnrelatedClass {
public static <T extends
Comparable<String>
>
void f(T t) { ... }
}
class AnotherUnrelatedClass {
public static <T extends
Comparable<T>
>
void g(T t) { ... }
}
class SomeClass<T extends
Comparable<T>
&
Comparable<String>
> {
//
error
...
private void h(T t) {
AnUnrelatedClass.f(t);
AnotherUnrelatedClass.g(t);
}
}
No solution sketched out above would address this situation appropriately.
If we required that the type parameter be
CombinedComparable
,
it would not be within the bounds of at least one of the two invoked methods.
Note, that the
CombinedComparable
interface can be a subinterface
of only one of the two instantiations of
Comparable
, but not both.
Example (conceivable work-around; does not work):
interface CombinedComparable<T> extends Comparable<String>
{
int compareTo(T other);
}
class ObjectStore<T extends
CombinedComparable<T>
{
...
private void h(T t) {
AnUnrelatedClass.f(t);
AnotherUnrelatedClass.g(t);
//
error
}
}
The same happens when we require that the type parameter be
SelfComparable
and
StringComparable
. Even if both were subinterfaces of the respective
instantiation of
Comparable
, there cannot be a class that implements
both, because that class would indirectly implement the two instantiations
of
Comparable
.
Ultimately the realization is that, depending on the circumstances,
there might not be a work around at all. |
LINK TO THIS
|
TypeParameters.FAQ104
|
REFERENCES
|
Can
I use different instantiations of a same generic type as bounds of a type
parameter?
Can
a class implement different instantiations of the same parameterized interface?
|
Does
a bound that is a class type give access to all its public members?
Yes, except any constructors.
|
A bound that is a class gives access to all its public
members, that is, public fields, methods, and nested type. Only constructors
are not made accessible, because there is no guarantee that a subclass
of the bound has the same constructors as the bound.
Example (of a class used as bound of a type parameter):
public class
SuperClass
{
// static members
public enum EnumType {THIS, THAT}
public static Object staticField;
public static void staticMethod() { ... }
// non-static members
public class InnerClass { ... }
public Object nonStaticField;
public void nonStaticMethod() { ... }
// constructors
public SuperClass() { ... }
// private members
private Object privateField;
...
}
public final class SomeClass<T extends
SuperClass
>
{
private T object;
public SomeClass(T t) { object = t; }
public String toString() {
return
"static nested
type : "+T.EnumType.class+"\n"
+"static field
: "+T.staticField+"\n"
+"static method
: "+T.staticMethod()+"\n"
+"non-static nested
type: "+T.InnerClass.class+"\n"
+"non-static field
: "+object.nonStaticField+"\n"
+"non-static method
: "+object.nonStaticMethod()+"\n"
+"constructor
: "+(new T())+"\n"
// error
+"private member
: "+object.privateField+"\n"
// error
;
}
}
The bound
SuperClass
gives access to its nested types, static
fields and methods and non-static fields and methods. Only the constructor
is not accessible. This is because constructors are not inherited.
Every subclass defines its own constructors and need not support its superclass's
constructors. Hence there is no guarantee that a subclass of
SuperClass
will have the same constructor as its superclass.
Although a superclass bound gives access to types, fields and methods
of the type parameter, only the non-static methods are dynamically dispatched.
In the unlikely case that a subclass redefines types, fields and static
methods of its superclass, these redefinitions would not be accessible
through the superclass bound.
Example (of a subclass of the bound used for instantiation):
public final class SubClass extends SuperClass {
// static members
public enum Type {FIX, FOXI}
public static Object staticField;
public static Object staticMethod() {
... }
// non-static members
public class Inner { ... }
public Object nonStaticField;
public Object nonStaticMethod() { ...
}
// constructors
public SubClass(Object o) { ... }
public SubClass(String s) { ... }
...
}
SomeClass<
SubClass
>
ref = new SomeClass<SubClass>(new SubClass("xxx"));
System.out.println(ref);
prints:
static nested type :
SuperClass
.EnumType
static field
:
SuperClass
.staticField
static method :
SuperClass
.staticMethod
=> SuperClass.staticField
non-static nested type:
SuperClass
.InnerClass
non-static field :
SuperClass
.nonStaticField
non-static method :
SubClass
.nonStaticMethod
=> SubClass.nonStaticField
Calling the
nonStaticMethod
results in invocation of the subclass's
overriding version of the
nonStaticMethod
. In contrast, the subclass's
redefinitions of types, fields and static methods are not accessible through
the bounded parameter. This is nothing unusual. First, it is
poor programming style to redefine in a subclass any of the superclass's
nested types, fields and static methods. Only non-static methods
are overridden. Second, the kind of hiding that we observe in the
example above also happens when a subclass object is used through a superclass
reference variable. |
LINK TO THIS
|
TypeParameters.FAQ105
|
REFERENCES
|
|
How
do I decrypt "Enum<E extends Enum<E>>"?
As a type that can only be instantiation
for its subtypes, and those subtypes will inherit some useful methods,
some of which take subtype arguments (or otherwise depend on the subtype).
|
The context in which
"
Enum<E extends
Enum<E>>
"
appears is the declaration of the
Enum
class in package
java.lang
:
public abstract class
Enum<E extends Enum<E>>
{
...
}
The type
Enum
is the common base class of all enumeration types.
In Java an enumeration type such as
Color
is translated into a
class
Color
that extends
Enum<Color>
. The purpose
of the superclass
Enum
is to provide functionality that is common
to all enumeration types.
Here is a sketch of class
Enum
:
public abstract class
Enum<
E
extends Enum<E>>
implements Comparable<
E
>,
Serializable {
private final String name;
public final String name() { ... }
private final int ordinal;
public final int ordinal() { ... }
protected Enum(String name, int ordinal) { ... }
public String
toString() { ... }
public final boolean equals(Object other)
{ ... }
public final int
hashCode() { ... }
protected final Object clone() throws CloneNotSupportedException
{ ... }
public final int
compareTo(
E
o) { ... }
public final Class<
E
>
getDeclaringClass() { ... }
public static <T extends Enum<T>> T valueOf(Class<T>
enumType, String name) { ... }
}
The surprising feature in the declaration
"
Enum<E
extends Enum<E>>
"
is the fact that the newly defined class
Enum
and its newly defined type parameter
E
appear
in the bound of that same type parameter. It means that the
Enum
type must be instantiated for one of its subtypes. In order to understand
why this makes sense, consider that every enum type is translated into
a subtype of
Enum
.
Here is the contrived enum type
Color
:
enum
Color
{RED, BLUE, GREEN}
The compiler translates it into the following class:
public final class
Color
extends
Enum<Color>
{
public static final Color[] values() { return (Color[])$VALUES.clone();
}
public static Color valueOf(String name) { ... }
private Color(String s, int i) { super(s, i); }
public static final Color RED;
public static final Color BLUE;
public static final Color GREEN;
private static final Color $VALUES[];
static {
RED = new Color("RED", 0);
BLUE = new Color("BLUE", 1);
GREEN = new Color("GREEN", 2);
$VALUES = (new Color[] { RED, BLUE, GREEN });
}
}
The inheritance has the effect that the
Color
type inherits all
the methods implemented in
Enum<Color>
. Among them is
the
compareTo
method. The
Color.compareTo
method
should probably take a
Color
as an argument. In order to
make this happen class
Enum
is generic and the
Enum.compareTo
method takes
Enum
's type parameter
E
as an argument.
As a result, type
Color
derived from
Enum<Color>
inherits
a
compareTo
method that takes a
Color
as and argument,
exactly as it should.
If we dissect the declaration
"
Enum<E extends Enum<E>>
"
we can see that this pattern has several aspects.
First, there is the fact that the type parameter bound
is the type itself: "
Enum
<E extends
Enum
<E>>
".
It makes sure that only subtypes of type
Enum
are permitted as
type arguments. (Theoretically, type
Enum
could be instantiated
on itself, like in
Enum<Enum>
, but this is certainly not intended
and it is hard to imagine a situation in which such an instantiation would
be useful.)
Second, there is the fact that the type parameter bound
is the
parameterized type
Enum
<E>
,
which uses the type parameter
E
as the type argument of the bound.
This declaration makes sure that the inheritance relationship between a
subtype and an instantiation of
Enum
is of the form "
X extends
Enum<X>
". A subtype such as "
X extends Enum<Y>
" cannot
be declared because the type argument
Y
would not be within bounds;
only subtypes of
Enum<X>
are within bounds.
Third, there is the fact that
Enum
is generic
in the first place. It means that some of the methods of class
Enum
take
an argument or return a value of an unknown type (or otherwise depend on
an unknown type). As we already know, this unknown type will later be a
subtype
X
of
Enum<X>
. Hence, in the parameterized
type
Enum<X>
, these methods involve the subtype
X
,
and they are inherited into the subtype
X
. The
compareTo
method is an example of such a method; it is inherited from the superclass
into each subclass and has a subclass specific signature in each case.
To sum it up, the declaration
"
Enum<E extends Enum<E>>
"
can be decyphered as:
Enum
is a generic type that can only be
instantiated for its subtypes, and those subtypes will inherit some useful
methods, some of which take subtype specific arguments (or otherwise depend
on the subtype).
|
LINK TO THIS
|
TypeParameters.FAQ106
|
REFERENCES
|
How
is a generic type defined?
What
is a bounded type parameter?
Where
is a type parameter visible (or invisible)?
|
Why
is there no lower bound for type parameters?
Because it does not make sense
for type parameters of classes; it would occasionally be useful in conjunction
with method declarations, though.
|
Type parameters can have several upper bounds, but no lower
bound. This is mainly because lower bound type parameters of classes
would be confusing and not particularly helpful. In conjunctions
with method declarations, type parameters with a lower bound would occasionally
be useful. In the following, we first discuss lower bound type parameters
of
classes
and subsequently
lower bound type parameters of
methods
.
Lower Bound
Type Parameters of Classes
Type parameters can have several bounds, like in
class Box<T
extends Number> {...}
. But a type parameter can have no lower bound,
that is, a construct such as
class Box<T
super
Number> {...}
is not permitted. Why not? The answer is: it is pointless because
it would not buy you anything, were it allowed. Let us see why lower
bound type parameters of classes are confusing by exploring what a upper
bound on a type parameter means.
The upper bound on a type parameter has three effects:
-
Restricted Instantiation.
The upper bound restricts the set
of types that can be used for instantiation of the generic type.
If we declare a
class Box<T extends Number> {...}
then the
compiler would ensure that only subtypes of
Number
can be used
as type argument. That is, a
Box<Number>
or a
Box<Long>
is permitted, but a
Box<Object>
or
Box<String>
would be rejected.
Example (of restricted instantiation due to an upper bound on a type
parameter):
class Box<T extends Number> {
private T value;
public Box(T t) { value = t; }
...
}
class Test {
public static void main(String[] args) {
Box<Long>
boxOfLong = new Box<Long>(0L);
// fine
Box<String> boxOfString
= new Box<String>("");
// error: String is
not within bounds
}
}
-
Access To Non-Static Members.
The upper bound gives access
to all public non-static methods and fields of the upper bound. In
the implementation of our
class Box<T extends Number> {...}
we can invoke all public non-static methods defined in class
Number
,
such as
intValue()
for instance. Without the upper bound
the compiler would reject any such invocation.Example (of access to non-static
members due to an upper bound on a type parameter):
class Box<T extends Number> {
private T value;
public Box(T t) { value = t; }
public int increment() { return value.intValue()+1;
}
// <= would be an error without the Number bound
...
}
-
Type Erasure.
The leftmost upper bound is used for type erasure
and replaces the type parameter in the byte code. In our
class
Box<T extends Number> {...}
all occurrences of
T
would
be replaced by the upper bound
Number
. For instance, if
class
Box
has a private field of type
T
and a method
void
set(T content)
for setting this private field, then the field would
be of type
Number
after type erasure and the method would be translated
to a method
void set(Number content)
.
Example (of use of upper bound on a type parameter in type erasure -
before
type erasure):
class Box<
T extends Number
> {
private
T
value;
public Box(
T
t) { value = t; }
...
}
Example (of use of upper bound on a type parameter in type erasure -
after
type erasure):
class Box {
private
Number
value;
public Box(
Number
t) { value = t; }
...
}
In addition, the leftmost upper bound appears in further locations, such
as automatically inserted casts and bridge methods.
If lower bounds were permitted on type parameters, which side effects
would or should they have? If a construct such as
class Box<T
super
Number> {...}
were permitted, what would it mean? What would
the 3 side effects of an upper type parameter bound - restricted
instantiation, access to non-static member, type erasure - mean for a lower
bound?
Restricted Instantiations.
The compiler could restrict the set
of types that can be used for instantiation of the generic type with a
lower bound type parameter. For instance, the compiler could permit
instantiations such as
Box<Number>
and
Box<Object>
from a
Box<T
super
Number>
and reject instantiations
such as
Box<Long>
or
Box<Short>
. This would
be an effect in line with the restrictive side-effect described for upper
type parameter bounds.
Access To Non-Static Members.
A lower type parameter bound does
not give access to any particular methods beyond those inherited from class
Object
.
In the example of
Box<T
super
Number>
the supertypes
of
Number
have nothing in common, except that they are reference
types and therefore subtypes of
Object
. The compiler cannot
assume that the field of type
T
is of type
Number
or
a subtype thereof. Instead, the field of type
T
can be of
any supertype of
Number
, such as
Serializable
or
Object
.
The invocation of a method such as
intValue()
is no longer type-safe
and the compiler would have to reject it. As a consequence, the lower type
parameter bound would not give access to an non-static members beyond those
defined in class
Object
and thus has the same effect as "no bound".
Type Erasure
. Following this line of logic, it does not
make sense to replace the occurences of the type parameter by its leftmost
lower bound. Declaring a method like the constructor
Box(
T
t)
as a constructor
Box(
Number
t)
does not make sense,
considering that
T
is replaces by a supertype of
Number
.
An
Object
might be rightly passed to the constructor in an instantiation
Box<Object>
and the constructor would reject it. This would be dead wrong.
So, type erasure would replace all occurences of the type variable
T
by type
Object
, and not by its lower bound. Again, the lower
bound would have the same effect as "no bound".
Do you want to figure out what it would mean if both lower
_and_
upper bounds were permitted? Personally, I do not even want
to think about it and would prefer to file it under "not manageable",
if you permit.
The bottom line is: all that a "
super
" bound would buy
you is the restriction that only supertypes of
Number
can be used
as type arguments. And even that is frequently misunderstood.
It would NOT mean, that
class Box<T super Number> {...}
contains
only instances of supertypes of
Number
. Quite the converse
- as the example below demonstrates!
Example (of use of upper bound on a type parameter in type erasure -
before
type erasure):
class Box<
T super Number
> {
private
T
value;
public Box(
T
t) { value = t; }
...
}
Example (of use of upper bound on a type parameter in type erasure -
after
type erasure):
class Box {
private
Object
value;
public Box(
Object
t) { value = t; }
...
}
A
class Box<T
super
Number> {...}
would be translated
by type erasure to a
Box
containing an
Object
field and
it's constructor would be translated to
Box(Object t)
. That's
fundamentally different from a
class Box<T
extends
Number>
{...}
, which would be translated to a
Box
containing a
Number
field and it's constructor would be translated to
Box(Number t)
.
Consequently, a
Box<Number>
instantiated from a
class Box<T
extends Number> {...}
would be different from a
Box<Number>
instantiation from a
class Box<T super Number> {...}
, which
is likely to cause confusion. For this reason lower bounds do not
make sense on type parameters of classes.
Lower Bound
Type Parameters of Methods
In conjunction with methods and their argument types, a type parameter
with a lower bound can occasionally be useful.
Example (of a method that would profit from a type parameter with a
lower bound):
class Pair<X,Y> {
private X first;
private Y second;
...
public <
A super X
,
B super Y
> B addToMap(Map<A,B>
map) { // error: type parameter cannot have lower bound
return map.put(first, second);
}
}
class Test {
public static void main(String[] args) {
Pair<String,Long> pair = new Pair<>("ABC",42L);
Map<CharSequence, Number> map = HashMap<CharSequence,
Number>();
Number number = pair.addToMap(map);
}
}
The
addToMap()
method adds the content of the pair to a map.
Any map that can hold supertypes of
X
and
Y
would do.
The map's
put()
method returns the value found in the map for
the given key, if there already is a key-value entry for the key in the
map. The return value of the map's
put()
method shall be
returned from the
addToMap()
method. Under these circumstances
one would like to declare the method as shown above: The map is parameterized
with supertypes of the pair's type parameters and the
addToMap()
method'S return type is the map's value type.
Since the compiler does not permit lower bounds on type parameters we
need a work-around.
One work-around that comes to mind is use of a wildcard, because wildcards
can have a lower bound. Here is a work-around using a wildcard.
Example (of a work-around for the previous example using wildcards):
class Pair<X,Y> {
private X first;
private Y second;
...
public
Object
addToMap(Map<
? super X
,
?
super Y
> map) {
return map.put(first, second);
}
}
class Test {
public static void main(String[] args) {
Pair<String,Long> pair = new Pair<>("ABC",42L);
Map<CharSequence, Number> map = HashMap<CharSequence,
Number>();
Number number =
(Number)
pair.addToMap(map);
}
}
It works, except that there is no way to declare the return type as desired.
It would be the supertype of
Y
that the compiler captures from
the map type, but there is no syntax for specifying it. We must not
declare the return type a "
? super Y
", because "
? super Y
"
is a wildcard and not a type and therefore not permitted as a return
type. We have no choice and must use
Object
instead as our method's
return type. This rather unspecific return type in turn forces callers
of the
addToMap()
method into casting the return value down from
Object
to its actual type. This is not exactly what we had in mind.
Another work-around is use of static methods. Here is a work-around
with a static instead of a non-static method.
Example (of a work-around for the previous example using a static method):
class Pair<X,Y> {
private X first;
private Y second;
...
public
static
<
A
,
B
,X
extends
A
,Y
extends B
> B addToMap(Pair<X,Y> pair, Map<
A
,
B
>
map) {
return map.put(pair.first,pair.second);
}
}
class Test {
public static void main(String[] args) {
Pair<String,Long> pair = new Pair<>("ABC",42L);
Map<CharSequence, Number> map = HashMap<CharSequence,
Number>();
Number number =
Pair
.addToMap(
pair
,map);
}
}
The generic
addToMap()
method has four type parameters: two placeholders
X
and
Y
for the pair's type and two placeholders
A
and
B
for
the map's type.
A
and
B
are supertypes of
X
and
Y
, because
X
and
Y
are declared with
A
and
B
as their upper bounds. (Note, that the generic method's
type parameters
X
and
Y
have nothing to do with the
Pair
class's
X
and
Y
parameters. The names
X
and
Y
are reused for the generic method to make them easily recognizably
as the pair's type parameters.) Using four type parameters we can
declare the precise return type as desired: it is the same type as the
value type of the map.
The bottom line is that the usefulness of lower bounds on type parameters
is somewhat debatable. They would be confusing and perhaps even misleading
when used as type parameters of a generic class. On the other hand,
generic methods would occasionally profit from a type parameter with a
lower bound. For methods, a work-around for the lack of a lower
bound type parameter can often be found. Such a work-around typically involves
a static generic method or a lower bound wildcard.
|
LINK TO THIS
|
TypeParameters.FAQ107
|
REFERENCES
|
What
is a bounded type parameter?
Does
a bound that is a class type give access to all its public members?
What
is a bounded wildcard?
What
is the difference between a wildcard bound and a type parameter bound?
|
Usage
Can
I use a type parameter like a type?
No, a type parameter is not a type in
the regular sense (different from a regular type such as a non-generic
class or interface).
|
Type parameters can be used for typing (like non-generic
classes and interfaces)::
-
as argument and return types of methods
-
as type of a field or local reference variable
-
as type argument of other parameterized types
-
as target type in casts
-
as explicit type argument of parameterized methods
Type parameters can NOT be used for the following purposes (different from
non-generic classes and interfaces)::
-
for creation of objects
-
for creation of arrays
-
in exception handling
-
in static context
-
in instanceof expressions
-
as supertypes
-
in a class literal
|
LINK TO THIS
|
TypeParameters.FAQ200
|
REFERENCES
|
|
Can
I create an object whose type is a type parameter?
No, because the compiler does not know
how to create objects of an unknown type.
|
Each object creation is accompied by a constructor call.
When we try to create an object whose type is a type parameter then we
need an accessible constructor of the unknown type that the type parameter
is a place holder for. However, there is no way to make sure that
the actual type arguments have the required constructors.
Example (illegal generic object creation):
public final class Pair<
A
,
B
>
{
public final A fst;
public final B snd;
public Pair() {
this.fst =
new
A()
;
// error
this.snd =
new
B()
;
// error
}
public Pair(A fst, B snd) {
this.fst = fst;
this.snd = snd;
}
}
In the example above, we are trying to invoke the no-argument constructors
of two unknown types represented by the type parameters
A
and
B
.
It is not known whether the actual type arguments will have an accessible
no-argument constructor.
In situations like this - when the compiler needs more knowledge about
the unknown type in order to invoke a method - we use type parameter bounds.
However, the bounds only give access to methods of the type parameter.
Constructors cannot be made available through a type parameter bound.
If you need to create objects of unknown type, you can use reflection
as a workaround. It requires that you supply type information, typically
in form of a
Class
object, and then use that type information
to create objects via reflection.
Example (workaround using reflection):
public final class Pair
<A,B>
{
public final A fst;
public final B snd;
public Pair(
Class<A>
typeA,
Class<B>
typeB) {
this.fst =
typeA.
newInstance();
this.snd =
typeB.
newInstance();
}
public Pair(A fst, B snd) {
this.fst = fst;
this.snd = snd;
}
}
|
LINK TO THIS
|
TypeParameters.FAQ201
|
REFERENCES
|
Does
a bound that is a class type give access to all its methods and fields?
How
do I generically create objects and arrays?
How
do I pass type information to a method so that it can be used at runtime?
|
Can
I create an array whose component type is a type parameter?
No, because the compiler does not know
how to create an object of an unknown component type.
|
We can declare array variables whose component type is
a type parameter, but we cannot create the corresponding array objects.
The compiler does not know how to create an array of an unknown component
type.
Example (before type erasure):
class Sequence
<T>
{
...
public T[] asArray() {
T[] array =
new T[size]
;
//
error
...
return array;
}
}
Example (after a conceivable translation by type erasure):
class Sequence {
...
public
Object
[] asArray()
{
Object
[]
array = new
Object
[size];
...
return array;
}
}
The type erasure of a type parameter is its leftmost bound, or type
Object
if no bound was specified. As a result, the compiler would create an array
of
Object
s in our example. This is not what we want.
If we later invoked the as
Array
method on a
Sequence<String>
a
Object[]
would be returned, which is incompatible to the
String[]
that we expect.
Example (invocation of illegal method):
S
equence<String> seq = new Sequence<String>();
...
String[] arr = seq.asArray();
// compile-time error
String[] arr = (String[])seq.asArray(); // runtime failure:
ClassCastException
Not even a ca
st would help because the cast is guaranteed to fail
at runtime. The returned array is really an array of
Object
s,
not just a reference of type
Object[]
refering to a
String[]
.
If you need to create arrays of an unknown component type, you can use
reflection as a workaround. It requires that you supply type information,
typically in form of a
Class
object, and then use that type information
to create arrays via reflection.
Example (workaround using reflection):
class Sequence
<T>
{
...
public T[] asArray(
Class<T>
type) {
T[] array = (T[])Array.
newInstance(type,size)
;
//
unchecked cast
...
return array;
}
}
By the way, the unchecked warning is harmless and can be ignored. It stems
from the need to cast to the unknown array type, because the
newInstance
method returns an
Object[]
as a result. |
LINK TO THIS
|
TypeParameters.FAQ202
|
REFERENCES
|
What
is type erasure?
How
do I generically create objects and arrays?
How
do I pass type information to a method so that it can be used at runtime?
|
Can
I cast to the type that the type parameter stands for?
Yes, you can, but it is not type-safe
and the compiler issues an "unchecked" warning.
|
Type parameters do not have a runtime type representation
of their own. They are represented by their leftmost bound, or type
Object
in case of an unbounded type parameter. A cast to a type parameter would
therefore be a cast to the bound or to type
Object
.
Example (of unchecked cast):
class Twins<T> {
public T fst,snd;
public Twins(T s, T t) { fst
= s; snd = t; }
...
}
class Pair<S,T> {
private S fst;
private T snd;
public Pair(S s, T t) { fst
= s; snd = t; }
...
public <U> Pair(Twins<U>
twins) {
fst =
(S)
twins.fst;
//
unchecked warning
snd =
(T)
twins.snd;
//
unchecked warning
}
}
The two casts to the type parameters are pointless
because they will never fail; at runtime they are casts to type
Object
.
As a result any type of
Pair
can be constructed from
any type of
Twins
.
We could end up with a
Pair<Long,Long>
that contains
String
s instead of
Long
s. This would
be a blatant violation of the type-safety principle, because we would later
trigger an unexpected
ClassCastException
, when we use this offensive
Pair<Long,Long>
that
contains
String
s. In order to draw attention to the potentially
unsafe casts the compiler issues "unchecked" warnings. |
LINK TO THIS
|
TypeParameters.FAQ203
|
REFERENCES
|
What
does type-safety mean?
What
is type erasure?
What
is the type erasure of a type parameter?
|
Can
I use a type parameter in exception handling?
Can
I derive from a type parameter?
No, because a type parameter does not
have a runtime type representation of it own.
|
As part of the translation by type erasure, all type
parameters are replaces by their leftmost bound, or
Object
if
the type parameter is unbounded. Consequently, there is no point
to deriving from a type parameter, because we would be deriving from its
bound, not from the type that the type parameter stands for. In addition,
the actual type argument can be a final class or an enum type, from which
we must not derive anyway.
Example (of illegal derivation from type parameter; before type erasure):
class Printable<T extends Collection<?>>
extends
T
{ // illegal
public void printElements(PrintStream out) {
for (Object o : this) out.println(o);
}
public void printElementsInReverseOrder(PrintStream out)
{
...
}
}
final class Test {
public static void main(String[] args) {
Printable<LinkedList<String>> list = new
Printable<LinkedList<String>>();
list.add(2,"abc");
list.printElements(System.out);
}
}
The idea of this generic subclass is that it adds print functionality to
all collection classes by means of derivation. A
Printable<LinkedList<String>>
would have all the functionality of
LinkedList<String>
plus
the print functionality. (This idiom is known in C++ as the
curiously
recurring template pattern
). Since it is illegal to derive from a type
parameter, this kind of programming technique is not possible in Java.
Consider what the subclass would look like after type erasure.
Example (same example after a conceivable translation by type erasure):
class Printable extends
Collection
{ // error: Collection is an interface, not class
public void printElements(
PrintStream
out) {
for (Object o : this) out.println(o);
}
public void printElementsInReverseOrder(
PrintStream
out) {
...
}
}
final class Test {
public static void main(String[] args) {
Printable list = new Printable();
list.add(2,"abc");
// error: no such method can be found in class Printable
list.printElements(System.out);
}
}
After type erasure the subclass
Printable
would not be a subclass
of
LinkedList
, but a subclass of
Collection
, which is
not even possible, because
Collection
is an interface, not a class.
Even if we used a class as the bound of the type parameter, such as
<T
extends AbstractCollection>
, none of the list-specific methods would
be available in the subclass, which entirely defeats the purpose of this
programming pattern. |
LINK TO THIS
|
TypeParameters.FAQ205
|
REFERENCES
|
What
is type erasure?
Which
types are permitted as type arguments?
The
Curiously Recurring Template Pattern in C++
(James O. Coplien. A Curiously
Recurring Template Pattern. In C++ Gems, 135-144. Cambridge University
Press, New York, 1996)
|
Why
is there no class literal for a type parameter?
Because a type parameter does not have
a runtime type representation of its own.
|
As part of the translation by type erasure, all type
parameters are replaces by their leftmost bound, or
Object
if
the type parameter is unbounded. Consequently, there is no point
to forming class literals such as
T.class
, where
T
is
a type parameter, because no such
Class
objects exist. Only
the bound has a
Class object
that represents its runtime type.
Example (before type erasure):
<T extends Collection> Class<?> someMethod(T arg){
...
return T.class; // error
}
The compiler rejects the expression
T.class
as illegal, but even
if it compiled it would not make sense. After type erasure the method
above could at best look like this:
Example (after type erasure):
Class someMethod(
Collection
arg){
...
return
Collection
.class;
}
The method would always return the bound's type representation, no matter
which instantiation of the generic method was invoked. This would
clearly be misleading.
The point is that type parameters are
non-reifiable
, that is,
they do not have a runtime type representation. Consequently, there is
no
Class
object for type parameters and no
class
literal
for them. |
LINK TO THIS
|
TypeParameters.FAQ206
|
REFERENCES
|
What
is type erasure?
What
is a reifiable type?
|
Scope
Where
is a type parameter visible (or invisible)?
Everywhere in the definition of a generic
type or method, except any static context of a type.
|
Generic Classes
The scope of a class's type parameter is the entire definition of the
class, except any static members or static initializers of the class. This
means that the type parameters cannot be used in the declaration of static
fields or methods or in static nested types or static initializers.
Example (of illegal use of type parameter in static context of a generic
class):
class SomeClass
<T>
{
//
static initializer, static field, static method
static
{
SomeClass<
T
>
test = new SomeClass<
T
>();
//
error
}
private
static
T
globalInfo;
//
error
public
static
T
getGlobalInfo()
{
//
error
return globalInfo;
}
//
non-static initializer, non-static field, non-static
method
{
SomeClass<
T
>
test = new SomeClass<
T
>();
}
private
T
localInfo;
public
T
getLocalInfo() {
return localInfo;
}
//
static nested types
public
static
class
Failure extends Exception
{
private final
T
info;
// error
public Failure(
T
t) { info = t; }
//
error
public
T
getInfo() { return info; }
//
error
}
private
interface
Copyable {
T
copy();
// error
}
private
enum
State {
VALID, INVALID;
private
T
info; // error
public void setInfo(
T
t) { info = t; }
// error
public
T
getInfo() { return info; }
//
error
}
//
non-static nested types
public
class
Accessor {
public
T
getInfo() { return localInfo; }
}
}
The example illustrates that the type parameter cannot be used in the static
context of a generic class. It also shows that nested interfaces
and enum types are considered static type members of the class. Only
inner classes, that is, non-static nested classes, can use the type parameter
of the enclosing generic class.
Generic Interfaces
The scope of an interface's type parameter is the entire definition
of the interface, except any fields or nested types. This is because
fields and nested types defined in an interface are implicitly static.
Example (of illegal use of type parameter in a generic interface):
interface SomeInterface
<T>
{
//
field
SomeClass<
T
> value
= new SomeClass<
T
>();
//
error
//
nested type
class
Accessor {
public
T
getInfo()
{
//
error
return value.getGlobalInfo();
}
}
//
methods
T
getValue();
}
The example shows that fields of an interface are implicitly static, so
that the type parameter cannot be used anywhere in the declaration of a
field of a generic interface. Similarly, the nested class is considered
a static nested class, not an inner class, and for this reason use of the
type parameter anywhere in the nested class is illegal.
Generic Methods
The scope of a method's or constructor's type parameter is the entire
definition of the method; there is no exception, because a method has no
static parts.
Example (of use of type parameter in a generic method):
private interface Copyable<T> {
T copy();
}
//
non-static method
<T extends Copyable<T>>
void
nonStaticMethod(
T
t) {
final
T
copy =
t.copy();
class Task implements Runnable {
public void run() {
T
tmp = copy;
System.out.println(tmp);
}
}
(new Task()).run();
}
//
static method
static
<T extends Copyable<T>>
void staticMethod(
T
t) {
final
T
copy = t.copy();
class Task implements Runnable {
public void run() {
T
tmp = copy;
System.out.println(tmp);
}
}
(new Task()).run();
}
The example illustrates that the type parameter can be used any place in
the definition of a generic method. The type parameter can appear
in the return and argument type. It can appear in the method body
and also in local (or anonymous) classes defined inside the method.
Note, that it does not matter whether the generic method itself is static
or non-static. Methods, different from types, do not have any "static
context"; there is no such thing as a static local variable or static local
class. |
LINK TO THIS
|
TypeParameters.FAQ301
|
REFERENCES
|
Why
can't I use a type parameter in any static context of the generic class?
Can
I use a type parameter as part of its own bounds or in the declaration
of other type parameters?
|
Can
I use a type parameter as part of its own bounds?
Yes, the scope of a type parameter includes
the type parameter section itself.
|
The type parameters of a generic type or method are visible
in the entire declaration of the type or method, including the type parameter
section itself. Therefore, type parameters can appear as parts of their
own bounds, or as bounds of other type parameters declared in the same
section.
Example (of use of a type parameter in the type parameter section itself):
public final class Wrapper
<
T
extends Comparable
<
T
>>
implements Comparable<Wrapper<T>> {
private final T theObject;
public Wrapper(T t) { theObject = t; }
public T getWrapper() { return theObject; }
public int compareTo(Wrapper<T> other) {
return theObject.compareTo(other.theObject);
}
}
In the example above, the type parameter
T
is used as type argument
of its own bound
Comparable<T>
.
Example (of use of a type parameter in the type parameter section itself):
<
S
,T extends
S
>
T create(S arg) { ... }
In the example above, the first type parameter
S
is used as bound
of the second type parameter
T
.
Forward references to type parameters are not permitted. The type
parameter cannot be used in the entire type parameter section, but only
after its point of declaration.
Example (of an illegal forward reference to a type parameter):
<
S
extends
T
,
T
extends Comparable<
S
>>
T create(S
arg) { ... }
// error
In the example above, the type parameter
T
is used in the type
parameter section before it has been defined in the same type parameter
section. This kind of forward reference is illegal.
Forward references to types, not type parameters, are permitted, though.
Example (of an forward reference to a type):
interface
Edge
<N
extends
Node
<?
extends Edge<N>>>
{
N getBeginNode();
void setBeginNode(N n);
N getEndNode();
void setEndNode(N n);
}
interface
Node
<E
extends
Edge
<?
extends Node<E>>>
{
E getOutEdge();
void setOutEdge(E e);
E getInEdge();
void setInEdge(E e);
}
In the example above, the type
Node
is used (in the type parameter
section of type
Edge
) before it has been defined (probably in
a different source file). This kind of forward reference this permitted,
which is not surprising. It is the usual way of defining and using types
in Java. |
LINK TO THIS
|
TypeParameters.FAQ302
|
REFERENCES
|
Where
is a type parameter visible (or invisible)?
Can
I use the type parameter of an outer type as part of the bounds of the
type parameter of an inner type or a method?
|
Can
I use the type parameter of an outer type as part of the bounds of the
type parameter of an inner type or a method?
Yes, the type parameter of an enclosing
generic type or method can be used in the type parameter section of an
inner generic type or method.
|
The type parameters of a generic type or method can appear
as parts of the bounds of the type parameters of any generic type or methods
in that scope.
Example (of use of type parameter of enclosing class in the type parameter
section of a method):
public final class Wrapper
<T
>
{
private final T theObject;
public Wrapper(T t) { theObject = t; }
public
<U extends
T
>
Wrapper(Wrapper<U> w) { theObject = w.theObject;}
public T getWrapper() { return theObject; }
}
In the example above, the type parameter
T
of the class
Wrapper<T>
is used as bound of the type paramter
U
of the class's generic
constructor.
In principle, you can use the type parameters of a generic class anywhere
in the class scope, including the type parameter sections of any generic
methods or nested and inner types. For instance, the type parameters can
appear in the type parameter declaration of an inner class.
Example (of use of type parameter of enclosing class in the type parameter
section of an inner class):
public final class Wrapper
<T>
{
private final T theObject;
public Wrapper(T t) { theObject = t; }
public T getWrapper() { return theObject; }
private final class WrapperComparator
<W
extends Wrapper<? extends Comparable<
T
>>
>
implements Comparator<W> {
public int compare(W lhs, W rhs)
{
return lhs.theObject.compareTo((T)(rhs.theObject));
}
}
public
<V extends Wrapper<?
extends Comparable<
T
>>
>
Comparator<V> comparator() {
return this.new WrapperComparator<V>();
}
}
In this example, the type parameter
T
of the class
Wrapper<T>
is used as part of the bound of the type parameter
W
of inner
class
WrapperComparator
. In addition, it is also used as part
of the bound of the type parameter
V
of the
comparator
method.
Similar rules apply to generic interfaces. Even the type parameters
of a generic method can be used in the declaration of the type parameters
of a local generic type.
Example (of use of type parameter of a method in the type parameter
section of a local class):
class Test {
private static
<T>
void method() {
class Local
<A extends
T
>
{ ... }
...
}
}
However, generic local classes are rather rare in practice.
The type parameters of a generic type or method can appear anywhere
in the declaration of the type parameters of any generic type or methods
in that scope. A type parameter
T
can appear
-
as the bound, as in
<U extends T>
, or
-
as part of the bounds, as in
<U extends Comparable<T>>
,
or
-
as the bound of a wildcard, as in
<U extends <Comparable<?
super T>>
, or
-
as part of the bound of a wildcard, as in
<U
extends Wrapper<? extends Comparable<T>>>
.
There is only one restriction: if a type parameter is used as the bound
of another type parameter then there must not follow any further bounds.
Example (of illegal use of type parameter as a bound)::
class Wrapper<T> implements Cloneable {
private final T theObject;
...
public <U extends
T & Cloneable
>
Wrapper<U>
clone() { ... }
// error
}
The type parameter
T
is followed by another bound, which is illegal. |
LINK TO THIS
|
TypeParameters.FAQ303
|
REFERENCES
|
Where
is a type parameter visible (or invisible)?
Can
I use a type parameter as part of its own bounds?
|
Static Context
Is
there one instances of a static field per instantiation of a generic type?
No, there is only one instance of a static
field for all instantiations of a generic type.
|
If a generic type has a static field, how many instances
of this static field exist?
Example (of a generic class with a static field):
class SomeClass<T> {
public
static
int count;
...
}
The generic type can be instantiated for an arbitrary number of type arguments.
Is there a different instance of the static field for each instantiation
of the generic type?
Example (of several instantiations and usage of the static field(s)):
SomeClass<String> ref1 = new SomeClass<String>();
SomeClass<Long> ref2 = new SomeClass<Long>();
ref1.count++;
ref2.count++;
The question is: are we accessing two different static fields in the code
snippet above? The answer is: no, there is only one instance of a static
field per parameterized type, not several ones per instantiation of the
generic type.
The reason is that the compiler translates the definition of a generic
type into one unique byte code representation of that type. The different
instantiations of the generic type are later mapped to this unique representation
by means of
type erasure
. The consequence is that there is only
one static
count
field in our example, despite of the fact that
we can work with as many instantiations of the generic class as we like.
Example (showing the syntax for access to a static field of a generic
type):
SomeClass<String> ref1 = new SomeClass<String>();
SomeClass<Long> ref2 = new SomeClass<Long>();
ref1.count++;
// discouraged, but legal
ref2.coun
t++;
// discouraged, but legal
SomeClass
.count++;
//
fine, recommended
SomeClass<String>.count++; // error
SomeClass<Long>.count++;
// error
Although we can refer to the static field through reference variables of
different type, namely of type
SomeClass<String>
and
SomeClass<Long>
in
the example, we access the same unique static
count
field.
The uniqueness of the static field is more clearly expressed when we refer
to the static field using the enclosing scope instead of object references.
Saying
SomeClass.count
makes clear that there is only one static
count
field that is independent of the type parameters of the enclosing class
scope. Since the static field is independent of the enclosing class's
type parmeters it is illegal to use any instantiation of the generic enclosing
class as scope qualifier. |
LINK TO THIS
|
TypeParameters.FAQ401
|
REFERENCES
|
What
is type erasure?
How
do I refer to static members of a parameterized type?
Where
is a type parameter visible (or invisible)?
Why
can't I use a type parameter in any static context of the generic class?
|
Why
can't I use a type parameter in any static context of a generic class?
Because the static context is independent
of the type parameters and exists only once per raw type, that is, only
once for all instantiations of a generic type.
|
Type parameters must not appear in any static context of
a generic type, which means that type parameters cannot be used in the
declaration of static fields or methods or in static nested types or static
initializers.
Example (of illegal use of a type parameter in static context):
public final class X
<T>
{
private
static
T
field;
// error
public
static
T
getField() { return field; }
//
error
public
static
void setField(
T
t) { field = t; }
// error
}
The attempt of declaring a static field of the unknown type
T
is non-sensical and rightly rejected by the compiler. There is only one
instance of the static field for
all
instantiations of the generic
class. Of which type could that static field possibly be? The declaration
of a static field, whose type is the type parameter, makes it look like
there were several instances of different types, namely one per instantiation,
which is misleading and confusing. For this reason, the use of type
parameters for declaration of static fields is illegal.
As static methods often operate on static fields it makes sense to extend
the rule to static methods: the type parameter must not appear in a static
method.
Interestingly, the same rule applies to static nested types defined
in a generic class. There is no compelling technical reason for this restriction.
It's just that static nested types are considered independent of any instantiations
of the generic class, like the static fields and methods. For this
reason, use of the type parameter in a static nested type is illegal.
(Note, static nested types include nested static classes, nested interfaces
and nested enum types.)
Example (of illegal use of a type parameter in static context):
class Wrapper
<T>
{
private final
T
theObject;
public Wrapper(
T
t) { theObject = t; }
public
T
getWrappedItem()
{ return theObject; }
public Immutable makeImmutable() {
return new Immutable(theObject);
}
public Mutable makeMutable() {
return new Mutable(theObject);
}
private static <A> A makeClone(A theObject) { ... }
public
static
final class Immutable {
private final
T
theObject;
//
error
public Immutable(
T
arg) {
//
error
theObject = makeClone(arg);
}
public
T
getWrappedItem()
{
// error
return makeClone(theObject);
}
}
public static class Mutable {
... similar ...
}
}
In the example above the type parameter is used in the context of a nested
class type. The compiler rejects the use of the type parameter because
the class type is a nested
static
class.
Workaround for nested static classes and interfaces:
In case of static nested classes and interfaces this is not a major
calamity. As a workaround we can generify the static class or interface
itself.
Example (workaround - generify the nested static type):
class Wrapper
<T>
{
private final
T
theObject;
public Wrapper(
T
t) { theObject = t; }
public
T
getWrapper()
{ return theObject; }
public Immutable
<T>
makeImmutable() {
return new Immutable
<T>
(theObject);
}
public Mutable
<T>
makeMutable() {
return new Mutable
<T>
(theObject);
}
private static <A> A makeClone(A theObject) { ... }
public static final class Immutable
<A>
{
//
is a generic class now
private final
A
theObject;
public Immutable(
A
arg) {
theObject = makeClone(arg);
}
public
A
getWrappedItem()
{
return makeClone(theObject);
}
}
public static class Mutable
<A>
{
... similar ...
}
}
There is no such workaround for nested enum type because they cannot be
generic.
Workaround for nested static classes:
If the nested static type is a class, an alternative workaround would
be turning the static class into an non-static inner class.
Example (workaround - use an inner class):
class Wrapper
<T>
{
private final
T
theObject;
public Wrapper(
T
t) { theObject = t; }
public
T
getWrapper()
{ return theObject; }
public Mutable makeMutable() {
return
this.
new Mutable(theObject);
}
public Immutable makeImmutable() {
return
this.
new Immutable(theObject);
}
private static <A> A makeClone(A theObject) { ... }
public final class Immutable {
// is no longer a
static class
private final
T
theObject;
public Immutable(
T
arg) {
theObject
= makeClone(arg);
}
public
T
getWrappedItem()
{
return makeClone(theObject);
}
}
public class Mutable
... similar ...
}
}
This workaround comes with a certain amount of overhead because all inner
classes have a hidden reference to an instance of the outer type, which
in this example they neither need nor take advantage of; the hidden reference
is just baggage. Often, inner classes are combined with interfaces
in order to keep the inner class a private implementation detail of the
enclosing class. We can do the same here.
Example (the previous workaround refined):
class Wrapper
<T>
{
private final
T
theObject;
public Wrapper(
T
t) { theObject = t; }
public
T
getWrapper()
{ return theObject; }
public Mutable
<T>
makeMutable() {
return this.new Mutable(theObject);
}
public Immutable
<T>
makeImmutable() {
return this.new Immutable(theObject);
}
private static <A> A makeClone(A theObject) { ... }
public interface
Immutable<S> {
S getWrappedItem();
}
public interface
Mutable<S> {
S getWrappedItem();
void setWrappedItem(S arg);
}
private
final class ImmutableImplementation
implements
Immutable<T>
{
private final T theObject;
public ImmutableImplementation(T
arg) {
theObject
= makeClone(arg);
}
public T getWrappedItem() {
return makeClone(theObject);
}
}
private
class MutableImplementation
implements
Mutable<T>
{
... similar ...
}
}
As you can see, the nested public interfaces need to be generic whereas
the inner private classes can use enclosing class's the type parameter. |
LINK TO THIS
|
TypeParameters.FAQ402
|
REFERENCES
|
Where
is a type parameter visible (or invisible)?
Is
there one instances of a static field per instantiation of a generic type?
|
CONTENT
PREVIOUS
NEXT
INDEX
|