Using generics results in more robust code and avoids ClassCastExceptions in your Java programs. This in-depth tutorial introduces you to generics and their types and methods.
Generics are used in Java to allow types or methods to operate on objects of various types while providing compile-time type safety. Generics address the problem of ClassCastException
s being thrown at runtime as a result of code that is not type safe.
In this Java 101: Learn Java article, I introduce you to generics and how to use generic features in your Java programs.
What you’ll learn in this Java tutorial
- What is type safety?
- About generic types and type arguments
- How to declare and use generic types in Java
- How to specify upper bounds
- About generic methods
- About generics and type inference
- Type erasure and other limitations of generics in Java
- What you need to know about generics and heap pollution
What is type safety?
ClassCastException
s happen when you cast objects from their current types to incompatible types. Type safe code ensures this does not happen.
The following code fragment demonstrates the lack of type safety in the context of the Java Collections Framework’s java.util.LinkedList
class. This type of code was common before generics were introduced:
List doubleList = new LinkedList();
doubleList.add(new Double(3.5));
Double d = (Double) doubleList.iterator().next();
Although the goal of the above program is to store only java.lang.Double
objects in the list, nothing prevents other kinds of objects from being stored. For example, you could specify doubleList.add("Hello");
to add a java.lang.String
object. However, when storing another kind of object, the final line’s (Double)
cast operator causes a ClassCastException
to be thrown.
Because this lack of type safety isn’t detected until runtime, a developer might not be aware of the problem, leaving it to the client, instead of the compiler, to discover. Generics solve this issue by allowing the developer to mark the list as containing only Double
objects. The compiler then alerts the developer if there is an attempt to store an object with a non-Double
type in the list. This example of type safety is demonstrated below:
List<Double> doubleList = new LinkedList<Double>();
doubleList.add(new Double(3.5));
Double d = doubleList.iterator().next();
List<Double>
now reads “List
of Double
.” List
is a generic interface, expressed as List<E>
, that takes a Double
type argument, which is also specified when creating the actual object. The compiler can now enforce type correctness when adding an object to the list—for instance, the list could store Double
values only. This enforcement removes the need for the (Double)
cast.
About generic types and type arguments
A generic type is a class or interface that introduces a set of parameterized types via a formal type parameter list, which is a comma-separated list of type parameter names between a pair of angle brackets. Generic types adhere to the following syntax:
class identifier<formalTypeParameterList>
{
// class body
}
interface identifier<formalTypeParameterList>
{
// interface body
}
The Java Collections Framework offers many examples of generic types and their parameter lists. For example, java.util.Set<E>
is a generic type, <E>
is its formal type parameter list, and E
is the list’s solitary type parameter. Another example is java.util.Map<K,
V>
.
A parameterized type is a generic type instance where the generic type’s type parameters are replaced with actual type arguments (type names). For example, Set<String>
is a parameterized type where String
is the actual type argument replacing type parameter E
.
The Java language supports the following kinds of actual type arguments:
- Concrete type: A class or other reference type name is passed to the type parameter. For example, in
List<Animal>
,Animal
is passed toE
. - Concrete parameterized type: A parameterized type name is passed to the type parameter. For example, in
Set<List<Shape>>
,List<Shape>
is passed toE
. - Array type: An array is passed to the type parameter. For example, in
Map<String, String[]>
,String
is passed toK
andString[]
is passed toV
. - Type parameter: A type parameter is passed to the type parameter. For example, in
class Container<E> { Set<E> elements; }
,E
is passed toE
. - Wildcard: The question mark (
?
) is passed to the type parameter. For example, inClass<?>
,?
is passed toT
.
Each generic type implies the existence of a raw type, which is a generic type without a formal type parameter list. For example, Class
is the raw type for Class<T>
. Unlike generic types, raw types can be used with any kind of object.
How to declare and use generic types in Java
Declaring a generic type involves specifying a formal type parameter list and accessing these type parameters throughout its implementation. Using the generic type involves passing actual type arguments to its type parameters when instantiating the generic type, as shown in Listing 1.
Listing 1. GenDemo.java (version 1)
class Container<E>
{
private E[] elements;
private int index;
Container(int size)
{
elements = (E[]) new Object[size];
index = 0;
}
void add(E element)
{
elements[index++] = element;
}
E get(int index)
{
return elements[index];
}
int size()
{
return index;
}
}
public class GenDemo
{
public static void main(String[] args)
{
Container<String> con = new Container<String>(5);
con.add("North");
con.add("South");
con.add("East");
con.add("West");
for (int i = 0; i < con.size(); i++)
System.out.println(con.get(i));
}
}
Listing 1 demonstrates generic type declaration and usage in the context of a simple container type that stores objects of the appropriate argument type. To keep the code simple, I’ve omitted error checking.
The Container
class declares itself to be a generic type by specifying the <E>
formal type parameter list. Type parameter E
is used to identify the type of stored elements, the element to be added to the internal array, and the return type when retrieving an element.
The Container(int size)
constructor creates the array via elements = (E[]) new Object[size];
. If you’re wondering why I didn’t specify elements = new E[size];
, the reason is that it isn’t possible. Doing so could lead to a ClassCastException
.
Compile Listing 1 (javac GenDemo.java
). The (E[])
cast causes the compiler to output a warning about the cast being unchecked. It flags the possibility that downcasting from Object[]
to E[]
might violate type safety because Object[]
can store any type of object.
Note, however, that there is no way to violate type safety in this example. It’s simply not possible to store a non-E
object in the internal array. Prefixing the Container(int size)
constructor with @SuppressWarnings("unchecked")
would suppress this warning message.
Execute java GenDemo
to run this application. You should observe the following output:
North
South
East
West
Specifying upper bounds
The E
in Set<E>
is an example of an unbounded type parameter because you can pass any actual type argument to E
. For example, you can specify Set<Marble>
, Set<Employee>
, or Set<String>
.
Sometimes you’ll want to restrict the types of actual type arguments that can be passed to a type parameter. For example, perhaps you want to restrict a type parameter to accept only Employee
and its subclasses.
You can limit a type parameter by specifying an upper bound, which is a type that serves as the upper limit on the types that can be passed as actual type arguments. Specify the upper bound by using the reserved word extends
followed by the upper bound’s type name.
For example, class Employees<E extends Employee>
restricts the types that can be passed to Employees
to Employee
or a subclass (e.g., Accountant
). Specifying new
Employees<Accountant>
would be legal, whereas new Employees<String>
would be illegal.
You can assign more than one upper bound to a type parameter. However, the first bound must always be a class, and the additional bounds must always be interfaces. Each bound is separated from its predecessor by an ampersand (&
). This is shown in Listing 2.
Listing 2. GenDemo.java (version 2)
import java.math.BigDecimal;
import java.util.Arrays;
abstract class Employee
{
private BigDecimal hourlySalary;
private String name;
Employee(String name, BigDecimal hourlySalary)
{
this.name = name;
this.hourlySalary = hourlySalary;
}
public BigDecimal getHourlySalary()
{
return hourlySalary;
}
public String getName()
{
return name;
}
public String toString()
{
return name + ": " + hourlySalary.toString();
}
}
class Accountant extends Employee implements Comparable<Accountant>
{
Accountant(String name, BigDecimal hourlySalary)
{
super(name, hourlySalary);
}
public int compareTo(Accountant acct)
{
return getHourlySalary().compareTo(acct.getHourlySalary());
}
}
class SortedEmployees<E extends Employee & Comparable<E>>
{
private E[] employees;
private int index;
@SuppressWarnings("unchecked")
SortedEmployees(int size)
{
employees = (E[]) new Employee[size];
int index = 0;
}
void add(E emp)
{
employees[index++] = emp;
Arrays.sort(employees, 0, index);
}
E get(int index)
{
return employees[index];
}
int size()
{
return index;
}
}
public class GenDemo
{
public static void main(String[] args)
{
SortedEmployees<Accountant> se = new SortedEmployees<Accountant>(10);
se.add(new Accountant("John Doe", new BigDecimal("35.40")));
se.add(new Accountant("George Smith", new BigDecimal("15.20")));
se.add(new Accountant("Jane Jones", new BigDecimal("25.60")));
for (int i = 0; i < se.size(); i++)
System.out.println(se.get(i));
}
}
Listing 2’s Employee
class abstracts the concept of an employee that receives an hourly wage. This class is subclassed by Accountant
, which also implements Comparable<Accountant>
to indicate that Accountant
s can be compared according to their natural order, which happens to be hourly wage in this example.
The java.lang.Comparable
interface is declared as a generic type with a single type parameter named T
. This interface provides an int compareTo(T o)
method that compares the current object with the argument (of type T
), returning a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object.
The SortedEmployees
class lets you store Employee
subclass instances that implement Comparable
in an internal array. This array is sorted (via the java.util.Arrays
class’s void sort(Object[] a, int fromIndex, int toIndex)
class method) in ascending order of the hourly wage after an Employee
subclass instance is added.
Compile Listing 2 (javac GenDemo.java
) and run the application (java GenDemo
). You should observe the following output:
George Smith: 15.20
Jane Jones: 25.60
John Doe: 35.40
Considering wildcards
Let’s say you want to print out a list of objects, regardless of whether these objects are strings, employees, shapes, or some other type. Your first attempt might look like what’s shown in Listing 3.
Listing 3. GenDemo.java (version 3)
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class GenDemo
{
public static void main(String[] args)
{
List<String> directions = new ArrayList();
directions.add("north");
directions.add("south");
directions.add("east");
directions.add("west");
printList(directions);
List<Integer> grades = new ArrayList();
grades.add(new Integer(98));
grades.add(new Integer(63));
grades.add(new Integer(87));
printList(grades);
}
static void printList(List<Object> list)
{
Iterator<Object> iter = list.iterator();
while (iter.hasNext())
System.out.println(iter.next());
}
}
It seems logical that a list of strings or a list of integers is a subtype of a list of objects, yet the compiler complains when you attempt to compile this listing. Specifically, it tells you that a list-of-string cannot be converted to a list-of-object, and similarly for a list-of-integer.
The error message you’ve received is related to the fundamental rule of generics:
For a given subtype x of type y, and given G as a raw type declaration, G<x> is not a subtype of G<y>.
According to this rule, although String
and java.lang.Integer
are subtypes of java.lang.Object
, it’s not true that List<String>
and List<Integer>
are subtypes of List<Object>
.
Why do we have this rule? Remember that generics are designed to catch type-safety violations at compile time, which is helpful. Without generics, you are much more likely to be called in to work at 2 a.m. because your Java program has thrown a ClassCastException
and crashed!
As a demonstration, let’s assume that List<String>
was a subtype of List<Object>
. If this was true, you might end up with the following code:
List<String> directions = new ArrayList<String>();
List<Object> objects = directions;
objects.add(new Integer());
String s = objects.get(0);
This code fragment creates a list of strings based on an array list. It then upcasts this list to a list of objects (which isn’t legal, but for now just pretend it is). Next, it adds an integer to the list of objects, which violates type safety. The problem occurs in the final line, which throws ClassCastException
because the stored integer cannot be cast to a string.
You could prevent such a violation of type safety by passing an object of type List<Object>
to the printList()
method in Listing 3. However, doing so wouldn’t be very useful. Instead, you can use wildcards to solve the problem, as shown in Listing 4.
Listing 4. GenDemo.java (version 4)
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class GenDemo
{
public static void main(String[] args)
{
List<String> directions = new ArrayList<String>();
directions.add("north");
directions.add("south");
directions.add("east");
directions.add("west");
printList(directions);
List<Integer> grades = new ArrayList<Integer>();
grades.add(Integer.valueOf(98));
grades.add(Integer.valueOf(63));
grades.add(Integer.valueOf(87));
printList(grades);
}
static void printList(List<?> list)
{
Iterator<?> iter = list.iterator();
while (iter.hasNext())
System.out.println(iter.next());
}
}
In Listing 4, I use a wildcard (the ?
symbol) in place of Object
in the parameter list and body of printList()
. Because this symbol stands for any type, it’s legal to pass List<String>
and List<Integer>
to this method.
Compile Listing 4 (javac GenDemo.java
) and run the application (java GenDemo
). You should observe the following output:
north
south
east
west
98
63
87
About generic methods
Now say you want to copy a list of objects to another list subject to a filter. You might consider declaring a void copy(List<Object> src, List<Object> dst, Filter filter)
method, but this method would only be able to copy List
s of Object
s and nothing else.
To pass source and destination lists of arbitrary type, you need to use the wildcard for a type placeholder. For example, consider the following copy()
method:
void copy(List<?> src, List<?> dest, Filter filter)
{
for (int i = 0; i < src.size(); i++)
if (filter.accept(src.get(i)))
dest.add(src.get(i));
}
This method’s parameter list is correct, but there’s a problem. According to the compiler, dest.add(src.get(i));
violates type safety. The ?
implies that any kind of object can be the list’s element type, and it’s possible that the source and destination element types are incompatible.
For example, if the source list was a List
of Shape
and the destination list was a List
of String
, and copy()
was allowed to proceed, ClassCastException
would be thrown when attempting to retrieve the destination list’s elements.
You could partially solve this problem by providing upper and lower bounds for the wildcards, as follows:
void copy(List<? extends String> src, List<? super String> dest, Filter filter)
{
for (int i = 0; i < src.size(); i++)
if (filter.accept(src.get(i)))
dest.add(src.get(i));
}
You can provide an upper bound for a wildcard by specifying extends
followed by a type name. Similarly, you can supply a lower bound for a wildcard by specifying super
followed by a type name. These bounds limit the types that can be passed as actual type arguments.
In the example, you can interpret ? extends String
as any actual type argument that happens to be String
or a subclass. Similarly, you can interpret ? super String
as any actual type argument that happens to be String
or a superclass. Because String
is final
, which means that it cannot be extended, only source lists of String
objects and destination lists of String
or Object
objects can be passed, which isn’t very useful.
You can fully solve this problem by using a generic method, which is a class or instance method with a type-generalized implementation. A generic method declaration adheres to the following syntax:
<formalTypeParameterList> returnType identifier(parameterList)
A generic method’s formal type parameter list precedes its return type. It consists of type parameters and optional upper bounds. A type parameter can be used as the return type and can appear in the parameter list.
Listing 5 demonstrates how to declare and invoke (call) a generic copy()
method.
Listing 5. GenDemo.java (version 5)
import java.util.ArrayList;
import java.util.List;
public class GenDemo
{
public static void main(String[] args)
{
List<Integer> grades = new ArrayList<Integer>();
Integer[] gradeValues =
{
Integer.valueOf(96),
Integer.valueOf(95),
Integer.valueOf(27),
Integer.valueOf(100),
Integer.valueOf(43),
Integer.valueOf(68)
};
for (int i = 0; i < gradeValues.length; i++)
grades.add(gradeValues[i]);
List<Integer> failedGrades = new ArrayList<Integer>();
copy(grades, failedGrades, new Filter<Integer>()
{
@Override
public boolean accept(Integer grade)
{
return grade.intValue() <= 50;
}
});
for (int i = 0; i < failedGrades.size(); i++)
System.out.println(failedGrades.get(i));
}
static <T> void copy(List<T> src, List<T> dest, Filter<T> filter)
{
for (int i = 0; i < src.size(); i++)
if (filter.accept(src.get(i)))
dest.add(src.get(i));
}
}
interface Filter<T>
{
boolean accept(T o);
}
In Listing 5 I’ve declared a <T> void copy(List<T> src, List<T> dest, Filter<T>
filter)
generic method. The compiler notes that the type of each of the src
, dest
, and filter
parameters includes the type parameter T
. This means that the same actual type argument must be passed during a method invocation, and the compiler infers this argument by examining the invocation.
If you compile Listing 5 (javac GenDemo.java
) and run the application (java GenDemo
) you should observe the following output:
27
43
About generics and type inference
The Java compiler includes a type inference algorithm for identifying the actual type argument(s) when instantiating a generic class, invoking a class’s generic constructor, or invoking a generic method.
Generic class instantiation
Before Java SE 7, you had to specify the same actual type argument(s) for both a variable’s generic type and the constructor when instantiating a generic class. Consider the following example:
Map<String, Set<String>> marbles = new HashMap<String, Set<Integer>>();
The redundant String, Set<String>
actual type arguments in the constructor invocation clutter the source code. To help you eliminate this clutter, Java SE 7 modified the type inference algorithm so that you can replace the constructor’s actual type arguments with an empty list (<>
), provided that the compiler can infer the type arguments from the instantiation context.
Informally, <>
is referred to as the diamond operator, although it isn’t a real operator. Use of the diamond operator results in the following more concise example:
Map<String, Set<String>> marbles = new HashMap<>();
To leverage type inference during generic class instantiation, you must specify the diamond operator. Consider the following example:
Map<String, Set<String>> marbles = new HashMap();
The compiler generates an “unchecked conversion warning” because the HashMap()
constructor refers to the java.util.HashMap
raw type and not to the Map<String, Set<String>>
type.
Generic constructor invocation
Generic and non-generic classes can declare generic constructors in which a constructor has a formal type parameter list. For example, you could declare the following generic class with a generic constructor:
public class Box<E>
{
public <T> Box(T t)
{
// ...
}
}
This declaration specifies generic class Box<E>
with formal type parameter E
. It also specifies a generic constructor with formal type parameter T
. Consider the following example:
new Box<Marble>("Aggies")
This expression instantiates Box<Marble>
, passing Marble
to E
. Also, the compiler infers String
as T
’s actual type argument because the invoked constructor’s argument is a String
object.
We can go further by leveraging the diamond operator to eliminate the Marble
actual type argument in the constructor invocation, as long as the compiler can infer this type argument from the instantiation context:
Box<Marble> box = new Box<>("Aggies");
The compiler infers the type Marble
for formal type parameter E
of generic class Box<E>
, and infers type String
for formal type parameter T
of this generic class’s constructor.
Generic method invocation
When invoking a generic method, you don’t have to supply actual type arguments. Instead, the type inference algorithm examines the invocation and corresponding method declaration to figure out the invocation’s type argument(s). The inference algorithm identifies argument types and (when available) the type of the assigned or returned result.
The algorithm attempts to identify the most specific type that works with all arguments. For example, in the following code fragment, type inference determines that the java.io.Serializable
interface is the type of the second argument (new TreeSet<String>()
) that is passed to select()
— TreeSet
implements Serializable
:
Serializable s = select("x", new TreeSet<String>());
static <T> T select(T a1, T a2)
{
return a2;
}
I previously presented a generic static <T> void copy(List<T> src, List<T> dest,
Filter<T> filter)
class method that copies a source list to a destination list, and is subject to a filter for deciding which source objects are copied. Thanks to type inference, you can specify copy(/*...*/);
to invoke this method. It’s not necessary to specify an actual type argument.
You might encounter a situation where you need to specify an actual type argument. For copy()
or another class method, you would specify the argument(s) after the class name and member access operator (.
) as follows:
GenDemo.<Integer>copy(grades, failedGrades, new Filter() /*...*/);
For an instance method, the syntax is nearly identical. Instead of following a class name and operator, however, the actual type argument would follow the constructor call and member access operator:
new GenDemo().<Integer>copy(grades, failedGrades, new Filter() /*...*/);
Type erasure and other limitations of generics in Java
While generics as such might not be controversial, their particular implementation in the Java language has been. Generics were implemented as a compile-time feature that amounts to syntactic sugar for eliminating casts. The compiler throws away a generic type or generic method’s formal type parameter list after compiling the source code. This “throwing away” behavior is known as type erasure (or erasure, for short). Other examples of erasure in generics include inserting casts to the appropriate types when code isn’t type correct, and replacing type parameters by their upper bounds (such as Object
).
Erasure prevents a generic type from being reifiable (exposing complete type information at runtime). As a result, the Java virtual machine doesn’t know the difference between. Take, for example, Set<String>
and Set<Marble>
; at runtime, only the raw type Set
is available. In contrast, primitive types, non-generic types (reference types prior to Java 5), raw types, and invocations of wildcards are reifiable.
The inability for generic types to be reifiable has resulted in several limitations:
- With one exception, the
instanceof
operator cannot be used with parameterized types. The exception is an unbounded wildcard. For example, you cannot specifySet<Shape> shapes = null; if (shapes instanceof ArrayList<Shape>) {}
. Instead, you need to change theinstanceof
expression toshapes instanceof ArrayList<?>
, which demonstrates an unbounded wildcard. Alternatively, you could specifyshapes instanceof ArrayList
, which demonstrates a raw type (and which is the preferred use). - Some developers have pointed out that you cannot use Java Reflection to obtain generics information, which isn’t present in the class file. However, in Java Reflection: Generics developer Jakob Jenkov points out a few cases where generics information is stored in a class file, and this information can be accessed reflectively.
- You cannot use type parameters in array-creation expressions; for example
elements = new E[size];
. The compiler will report ageneric array creation
error message if you try to do so.
Given the limitations of erasure, you might wonder why generics were implemented with erasure. The reason is simple: The Java compiler was refactored to use erasure so that generic code could interoperate with legacy Java code, which isn’t generic (reference types cannot be parameterized). Without that backward compatibility, legacy Java code would fail to compile in a Java compiler supporting generics.
Generics and heap pollution
While working with generics, you may encounter heap pollution, in which a variable of a parameterized type refers to an object that isn’t of that parameterized type (for instance if a raw type has been mixed with a parameterized type). In this situation, the compiler reports an “unchecked warning” because the correctness of an operation involving a parameterized type (like a cast or method call) cannot be verified. Consider Listing 6.
Listing 6. Demonstrating heap pollution
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
public class HeapPollutionDemo
{
public static void main(String[] args)
{
Set s = new TreeSet<Integer>();
Set<String> ss = s; // unchecked warning
s.add(Integer.valueOf(42)); // another unchecked warning
Iterator<String> iter = ss.iterator();
while (iter.hasNext())
{
String str = iter.next(); // ClassCastException thrown
System.out.println(str);
}
}
}
Variable ss
has parameterized type Set<String>
. When the Set
that is referenced by s
is assigned to ss
, the compiler generates an unchecked warning. It does so because the compiler cannot determine that s
refers to a Set<String>
type (it does not). The result is heap pollution. (The compiler allows this assignment to preserve backward compatibility with legacy Java versions that don’t support generics. Furthermore, erasure transforms Set<String>
into Set
, which results in one Set
being assigned to another Set
.)
The compiler generates a second unchecked warning on the line that invokes Set
’s add()
method. It does so because it cannot determine if variable s
refers to a Set<String>
or Set<Integer>
type. This is another heap pollution situation. (The compiler allows this method call because erasure transforms Set
’s boolean add(E e)
method to boolean add(Object
o)
, which can add any kind of object to the set, including the Integer
subtype of Object
.)
Generic methods that include variable arguments (varargs) parameters can also cause heap pollution. This scenario is demonstrated in Listing 7.
Listing 7. Demonstrating heap pollution in an unsafe varargs context
import java.util.Arrays;
import java.util.List;
public class UnsafeVarargsDemo
{
public static void main(String[] args)
{
unsafe(Arrays.asList("A", "B", "C"),
Arrays.asList("D", "E", "F"));
}
static void unsafe(List<String>... l)
{
Object[] oArray = l;
oArray[0] = Arrays.asList(Double.valueOf(3.5));
String s = l[0].get(0);
}
}
The Object[] oArray = l
; assignment introduces the possibility of heap pollution. A value whose List
type’s parameterized type doesn’t match the parameterized type (String
) of the varargs parameter l
can be assigned to array variable oArray
. However, the compiler doesn’t generate an unchecked warning because it has already done so when translating List<String>... l
to List[] l
. This assignment is valid because variable l
has the type List[]
, which subtypes Object[]
.
Also, the compiler doesn’t issue a warning or error when assigning a List
object of any type to any of oArray
’s array components; for example, oArray[0] = Arrays.asList(Double.valueOf(3.5));
. This assignment assigns to the first array component of oArray
a List
object containing a single Double
object.
The String s = l[0].get(0);
assignment is problematic. The object stored in the first array component of variable l
has the type List<Double>
, but this assignment expects an object of type List<String>
. As a result, the JVM throws ClassCastException
.
Compile the Listing 7 source code (javac -Xlint:unchecked UnsafeVarargsDemo.java
). You should observe the following output (slightly reformatted for readability):
UnsafeVarargsDemo.java:8: warning: [unchecked] unchecked generic array
creation for varargs parameter of
type List<String>[]
unsafe(Arrays.asList("A", "B", "C"),
^
UnsafeVarargsDemo.java:12: warning: [unchecked] Possible heap pollution
from parameterized vararg type
List<String>
static void unsafe(List<String>... l)
^
2 warnings
Earlier in this article, I stated that you cannot use type parameters in array-creation expressions. For example, you cannot specify elements = new E[size];
. The compiler reports a “generic array creation error” message when you try to do so. However, it’s still possible to create a generic array, but only in a varargs context, and that is what the first warning message is reporting. Behind the scenes, the compiler transforms List<String>...
l
to List<String>[] l
and then to List[] l
.
Notice that the heap pollution warning is generated at the unsafe()
method’s declaration site. This message isn’t generated at this method’s call site, which is the case with Java 5 and Java 6 compilers.
Not all varargs methods will contribute to heap pollution. However, a warning message will still be issued at the method’s declaration site. If you know that your method doesn’t contribute to heap pollution, you can suppress this warning by declaring it with the @SafeVarargs
annotation—Java SE 7 introduced the java.lang.SafeVarargs
annotation type. For example, because there is no way for the Arrays
class’s asList()
method to contribute to heap pollution, this method’s declaration has been annotated with @SafeVarargs
, as follows:
@SafeVarargs
public static <T> List<T> asList(T... a)
The @SafeVarargs
annotation eliminates the generic array creation and heap pollution warning messages. It is a documented part of the method’s contract and asserts that the method’s implementation will not improperly handle the varargs
formal parameter.
Do you want to practice more with Java generics? See How to use generics in your Java programs.