|
|
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 3 of 9
Although generics are widely used in the Java Collections Framework, they are not exclusive to it. Generics are also used in other parts of Java's standard class library, including java.lang.Class, java.lang.Comparable, java.lang.ThreadLocal, and java.lang.ref.WeakReference.
Consider the following code fragment, which demonstrates the lack of type safety that was common in Java code 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 ClassCastException to be thrown when confronted with a non-Double object.
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 to discover. Clearly it would be better to have the compiler detect the problem. Generics aid the compiler by letting the developer mark the list as containing a specific type of object, as follows:
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 Doubles only. This enforcement removes the need for the (Double) cast.
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 with <E> as its formal type parameter list and E as this list's solitary type parameter. java.util.Map<K, V> is another example.
Java programming convention dictates that type parameter names be single uppercase letters, such as E for element, K for key, V for value, and T for type. If possible, avoid using a meaningless name like "P" -- java.util.List<E> means a list of elements, but what could you possibly mean by List<P>?
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:
List<Animal>, Animal is passed to E.
Set<List<Shape>>, List<Shape> is passed to E.
Map<String, String[]>, String is passed to K and String[] is passed to V.
class Container<E> { Set<E> elements; }, E is passed to E.
?) is passed to the type parameter. For example, in Class<?>, ? is passed to T.
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.
Declaring a generic type involves specifying a formal type parameter list and using these type parameters throughout its implementation. Using the generic type involves passing actual type arguments to its type parameters when instantiating the generic type. See Listing 5.
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 5 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 5 (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's no way to violate type safety in this example. It's simply not possible to store a non-E object in the internal array. I'll show you how to suppress this warning message in a future article.
java.util.concurrent.
java.time classes you're most likely to need.