When you take an element out of aCollection, youmust cast it to the type of element that is stored in thecollection. Besides being inconvenient, this is unsafe. Thecompiler does not check that your cast is the same as thecollection's type, so the cast can fail at run time.
Generics provides a way for you to communicate the type of acollection to the compiler, so that it can be checked. Once thecompiler knows the element type of the collection, the compiler cancheck that you have used the collection consistently and can insertthe correct casts on values being taken out of the collection.
Here is a simple example taken from the existing Collectionstutorial:
// Removes 4-letter words from c.Elements must be stringsstatic void expurgate(Collection c) { for (Iterator i = c.iterator(); i.hasNext(); ) if (((String) i.next()).length() == 4) i.remove();}Here is the same example modified to use generics:
// Removes the 4-letter words from cstatic void expurgate(Collection<String> c) { for (Iterator<String> i = c.iterator(); i.hasNext(); ) if (i.next().length() == 4) i.remove();}
When you see the code<Type>, read it as "ofType"; the declaration above reads as"Collection ofString c." The codeusing generics is clearer and safer. We have eliminated an unsafecast and a number of extra parentheses. More importantly, we havemoved part of the specification of the method from a comment to itssignature, so the compiler can verify at compile time that the typeconstraints are not violated at run time. Because the programcompiles without warnings, we can state with certainty that it willnot throw aClassCastException at run time. The neteffect of using generics, especially in large programs, is improvedreadability and robustness.
To paraphrase Generics Specification Lead Gilad Bracha, when wedeclarec to be of typeCollection<String>, this tells us somethingabout the variablec that holds true wherever andwhenever it is used, and the compiler guarantees it (assuming theprogram compiles without warnings). A cast, on the other hand,tells us something the programmer thinks is true at a single pointin the code, and the VM checks whether the programmer is right onlyat run time.
While the primary use of generics is collections, there are manyother uses. "Holder classes," such asWeakReferenceandThreadLocal,have all beengenerified, that is, they have beenretrofitted to make use of generics. More surprisingly, classClasshas been generified. Class literals now function astypetokens, providing both run-time and compile-time typeinformation. This enables a style of static factories exemplifiedby thegetAnnotation method in the newAnnotatedElementinterface:
<T extends Annotation> T getAnnotation(Class<T> annotationType);This is ageneric method. It infers the value of itstypeparameter
T from its argument, and returns anappropriate instance ofT, as illustrated by thefollowing snippet:Author a = Othello.class.getAnnotation(Author.class);Prior to generics, you would have had to cast the result to
Author. Also you would have had no way to make thecompiler check that the actual parameter represented a subclass ofAnnotation.Generics are implemented bytype erasure: generic typeinformation is present only at compile time, after which it iserased by the compiler. The main advantage of this approachis that it provides total interoperability between generic code andlegacy code that uses non-parameterized types (which aretechnically known asraw types). The main disadvantages arethat parameter type information is not available at run time, andthat automatically generated casts may fail when interoperatingwith ill-behaved legacy code. There is, however, a way to achieveguaranteed run-time type safety for generic collections even wheninteroperating with ill-behaved legacy code.
Thejava.util.Collections class has been outfittedwith wrapper classes that provide guaranteed run-time type safety.They are similar in structure to the synchronized and unmodifiablewrappers. These "checked collection wrappers" are very useful fordebugging. Suppose you have a set of strings,s, intowhich some legacy code is mysteriously inserting an integer.Without the wrapper, you will not find out about the problem untilyou read the problem element from the set, and an automaticallygenerated cast toString fails. At this point, it istoo late to determine the source of the problem. If, however, youreplace the declaration:
Set<String> s = new HashSet<String>();with this declaration:
Set<String> s = Collections.checkedSet(new HashSet<String>(), String.class);the collection will throw a
ClassCastException at thepoint where the legacy code attempts to insert the integer. Theresulting stack trace will allow you to diagnose and repair theproblem.You should use generics everywhere you can. The extra effort ingenerifying code is well worth the gains in clarity and typesafety. It is straightforward to use a generic library, but itrequires some expertise to write a generic library, or to generifyan existing library. There is one caveat: You may not use generics(or any other Tiger features) if you intend to deploy the compiledcode on a pre-5.0 virtual machine.
If you are familiar with C++'stemplate mechanism, youmight think that generics are similar, but the similarity issuperficial. Generics do not generate a new class for eachspecialization, nor do they permit "template metaprogramming."
There is much more to learn about generics. See theGenerics lesson in the Java Tutorials.