A class or
interface whose declaration has one or more type
parameters is a generic class or interface [JLS, 8.1.2,
9.1.2]. For example, as of release 1.5, the List
interface
has a single type parameter, E, representing
the element type of the list. Technically the name of the interface is now List<E> (read “list of E”), but people often call it List for short. Generic classes and interfaces are
collectively known as generic types.
Each generic
type defines a raw type, which is the name of the
generic type used without any accompanying actual type parameters [JLS, 4.8].
For example, the raw type corresponding to List<E>
is
List.
Before release
1.5, this would have been an exemplary collection declaration:
// Now
a raw collection type - don't do this!
/**
* My stamp collection. Contains only Stamp instances.
*/
private final Collection
stamps = ... ;
If you
accidentally put a coin into your stamp collection, the erroneous insertion
compiles and runs without error:
// Erroneous insertion of coin into stamp collection
stamps.add(new Coin( ... ));
You don’t get
an error until you retrieve the coin from the stamp collection:
// Now a raw iterator type - don't do this!
for (Iterator
i = stamps.iterator(); i.hasNext();
) {
Stamp s = (Stamp)
i.next(); // Throws ClassCastException
... // Do something with the stamp
}
It pays to
discover errors as soon as possible after they are made, ideally at compile
time.
With generics,
you replace the comment with an improved type declaration for the collection
that tells the compiler the information that was previously hidden in the
comment:
// Parameterized collection type - typesafe
private final Collection<Stamp>
stamps = ... ;
When stamps is declared with a parameterized type, the
erroneous insertion generates a compile-time error message that tells you
exactly what is wrong:
Test.java:9: add(Stamp) in Collection<Stamp>
cannot be applied
to (Coin)
stamps.add(new Coin());
^
As an added
benefit, you no longer have to cast manually when removing elements from
collections.
It had to be
legal to pass instances of parameterized types to methods that were designed
for use with ordinary types, and vice versa. This requirement, known as migration compatibility, drove the decision to support raw types.
You lose type
safety if you use a raw type like List, but not if you
use a parameterized type like List<Object>.
To make this
concrete, consider the following program:
// Uses raw type (List) - fails at runtime!
public static void main(String[] args) {
List<String> strings = new
ArrayList<String>();
unsafeAdd(strings, new Integer(42));
String s = strings.get(0);
// Compiler-generated cast
}
private static void unsafeAdd(List list,
Object o) {
list.add(o);
}
This program
compiles, but because it uses the raw type List, you get a
warning:
Test.java:10: warning: unchecked call to add(E) in raw
type List
list.add(o);
^
And indeed, if
you run the program, you get a ClassCastException
when
the program tries to cast the result of the invocation strings.get(0) to a String.
If you replace
the raw type List with the parameterized type List<Object> in the unsafeAdd
declaration
and try to recompile the program, you’ll find that it no longer compiles. Here
is the error message:
Test.java:5: unsafeAdd(List<Object>,Object) cannot
be applied
to (List<String>,Integer)
unsafeAdd(strings, new Integer(42));
^
Since release
1.5, Java has provided a safe alternative known as unbounded wildcard types. If you want to use a generic type but you
don’t know or care what the actual type parameter is, you can use a question
mark instead. For example, the unbounded wildcard type for the generic type Set<E> is Set<?>
(read
“set of some type”). It is the most general parameterized Set type, capable of holding any set.
// Unbounded wildcard type - typesafe and flexible
static int numElementsInCommon(Set<?> s1,
Set<?> s2) {
int result = 0;
for (Object o1 : s1)
if (s2.contains(o1))
result++;
return result;
}
you can’t put
any element (other than null) into a Collection<?>. Attempting to
do so will generate a compile-time error message like this:
WildCard.java:13: cannot find symbol
symbol : method add(String)
location: interface Collection<capture#825 of ?>
c.add("verboten");
^
You must use raw
types in class literals. The specification does not permit the use of
parameterized types (though it does permit array types and primitive types)
[JLS, 15.8.2]. In other words, List.class, String[].class, and int.class
are
all legal, but List<String>.class and List<?>.class are not.
This is the
preferred way to use the instanceof
operator
with generic types:
// Legitimate use of raw type - instanceof operator
if (o instanceof Set) { // Raw type
Set<?> m
= (Set<?>) o; // Wildcard type
...
}
In summary,
using raw types can lead to exceptions at runtime, so don’t use them in new
code. They are provided only for compatibility and interoperability with legacy
code that predates the introduction of generics. As a quick review, Set<Object> is a parameterized type representing a set
that can contain objects of any type, Set<?>
is
a wildcard type representing a set that can contain only objects of some
unknown type, and Set is a raw type, which opts out of
the generic type system. The first two are safe and the last is not.
Reference: Effective Java 2nd Edition by Joshua Bloch