Generic List in Java
Java’s List interface ( java.util.List ) can be generified. In other words, instances of List can be given a type, so only instances of that type can be inserted and read from that List . Here is an example:
This list is now targeted at only String instances, meaning only String instances can be put into this list. If you try to put something else into this List , the compiler will complain.
The generic type checks only exists at compile time. At runtime it is possible to tweak your code so that a String List has other objects that String’s inserted. This is a bad idea, though.
Accessing a Generic List
You can get and insert the elements of a generic List like this:
List list = new ArrayList; String string1 = "a string"; list.add(string1); String string2 = list.get(0);
Notice how it is not necessary to cast the object obtained from the List.get() method call, as is normally necessary. The compiler knows that this List can only contain String instances, so casts are not necessary.
Iterating a Generic List
You can iterate a generic List using an iterator, like this:
List list = new ArrayList; Iterator iterator = list.iterator(); while(iterator.hasNext())
Notice how it is not necessary to cast the object returned from the iterator.next() next call. Because the List is generified (has a type), the compiler knows that it contains String instances. Therefore it is not necessary to cast the objects obtained from it, even if it comes from its Iterator .
You can also use the new for-loop, like this:
List list = new ArrayList; for(String aString : list)
Notice how a String variable is declared inside the parantheses of the for-loop. For each iteration (each element in the List ) this variable contains the current element (current String).
Java in a Nutshell, 5th Edition by David Flanagan
Get full access to Java in a Nutshell, 5th Edition and 60K+ other titles, with a free 10-day trial of O’Reilly.
There are also live events, courses curated by job role, and more.
Generic Types
Generic types and methods are the defining new feature of Java 5.0. A generic type is defined using one or more type variables and has one or more methods that use a type variable as a placeholder for an argument or return type. For example, the type java.util.List is a generic type: a list that holds elements of some type represented by the placeholder E . This type has a method named add() , declared to take an argument of type E , and a method named get() , declared to return a value of type E .
In order to use a generic type like this, you specify actual types for the type variable (or variables), producing a parameterized type such as List . [1] The reason to specify this extra type information is that the compiler can provide much stronger compile-time type checking for you, increasing the type safety of your programs. This type checking prevents you from adding a String[] , for example, to a List that is intended to hold only String objects. Also, the additional type information enables the compiler to do some casting for you. The compiler knows that the get( ) method of a List (for example) returns a String object: you are no longer required to cast a return value of type Object to a String .
The collections classes of the java.util package have been made generic in Java 5.0, and you will probably use them frequently in your programs. Typesafe collections are the canonical use case for generic types. Even if you never define generic types of your own and never use generic types other than the collections classes in java.util , the benefits of typesafe collections are so significant that they justify the complexity of this major new language feature.
We begin by exploring the basic use of generics in typesafe collections, then delve into more complex details about the use of generic types. Next we cover type parameter wildcards and bounded wildcards. After describing how to use generic types, we explain how to write your own generic types and generic methods. Our coverage of generics concludes with a tour of important generic types in the core Java API. It explores these types and their use in depth in order to provide a deeper understanding of how generics work.
Typesafe Collections
The java.util package includes the Java Collections Framework for working with sets and lists of objects and mappings from key objects to value objects. Collections are covered in Chapter 5. Here, we discuss the fact that in Java 5.0 the collections classes use type parameters to identify the type of the objects in the collection. This is not the case in Java 1.4 and earlier. Without generics, the use of collections requires the programmer to remember the proper element type for each collection. When you create a collection in Java 1.4, you know what type of objects you intend to store in that collection, but the compiler cannot know this. You must be careful to add elements of the appropriate type. And when querying elements from a collection, you must write explicit casts to convert them from Object to their actual type. Consider the following Java 1.4 code:
public static void main(String[] args) < // This list is intended to hold only strings. // The compiler doesn't know that so we have to remember ourselves. List wordlist = new ArrayList(); // Oops! We added a String[] instead of a String. // The compiler doesn't know that this is an error. wordlist.add(args); // Since List can hold arbitrary objects, the get() method returns // Object. Since the list is intended to hold strings, we cast the // return value to String but get a ClassCastException because of // the error above. String word = (String)wordlist.get(0); >
Generic types solve the type safety problem illustrated by this code. List and the other collection classes in java.util have been rewritten to be generic. As mentioned above, List has been redefined in terms of a type variable named E that represents the type of the elements of the list. The add( ) method is redefined to expect an argument of type E instead of Object and get( ) has been redefined to return E instead of Object .
In Java 5.0, when we declare a List variable or create an instance of an ArrayList , we specify the actual type we want E to represent by placing the actual type in angle brackets following the name of the generic type. A List that holds strings is a List , for example. Note that this is much like passing an argument to a method, except that we use types rather than values and angle brackets instead of parentheses.
The elements of the java.util collection classes must be objects; they cannot be used with primitive values. The introduction of generics does not change this. Generics do not work with primitives: we can’t declare a Set , or a List for example. Note, however, that the autoboxing and autounboxing features of Java 5.0 make working with a Set or a List just as easy as working directly with char and int values. (See Chapter 2 for details on autoboxing and autounboxing).
In Java 5.0, the example above would be rewritten as follows:
public static void main(String[] args) < // This list can only hold String objects Listwordlist = new ArrayList(); // args is a String[], not String, so the compiler won't let us do this wordlist.add(args); // Compilation error! // We can do this, though. // Notice the use of the new for/in looping statement for(String arg : args) wordlist.add(arg); // No cast is required. List.get() returns a String. String word = wordlist.get(0); >
Note that this code isn’t much shorter than the nongeneric example it replaces. The cast, which uses the word String in parentheses, is replaced with the type parameter, which places the word String in angle brackets. The difference is that the type parameter has to be declared only once, but the list can be used any number of times without a cast. This would be more apparent in a longer example. But even in cases where the generic syntax is more verbose than the nongeneric syntax, it is still very much worth using generics because the extra type information allows the compiler to perform much stronger error checking on your code. Errors that would only be apparent at runtime can now be detected at compile time. Furthermore, the compilation error appears at the exact line where the type safety violation occurs. Without generics, a ClassCastException can be thrown far from the actual source of the error.
Just as methods can have any number of arguments, classes can have more than one type variable. The java.util.Map interface is an example. A Map is a mapping from key objects to value objects. The Map interface declares one type variable to represent the type of the keys and one variable to represent the type of the values. As an example, suppose you want to map from String objects to Integer objects:
public static void main(String[] args) < // A map from strings to their position in the args[] array Mapmap = new HashMap(); // Note that we use autoboxing to wrap i in an Integer object. for(int i=0; i < args.length; i++) map.put(args[i], i); // Find the array index of a word. Note no cast is required! Integer position = map.get("hello"); // We can also rely on autounboxing to convert directly to an int, // but this throws a NullPointerException if the key does not exist // in the map int pos = map.get("world"); >
A parameterized type like List is itself a type and can be used as the value of a type parameter for some other type. You might see code like this:
// Look at all those nested angle brackets! Map>> map = getWeirdMap(); // The compiler knows all the types and we can write expressions // like this without casting. We might still get NullPointerException // or ArrayIndexOutOfBounds at runtime, of course. int value = map.get(key).get(0).get(0)[0]; // Here's how we break that expression down step by step. List> listOfLists = map.get(key); List
listOfIntArrays = listOfLists.get(0); int[] array = listOfIntArrays.get(0); int element = array[0];
In the code above, the get( ) methods of java.util.List and java.util.Map return a list or map element of type E and V respectively. Note, however, that generic types can use their variables in more sophisticated ways. Look up List in the reference section of this book, and you’ll find that its iterator( ) method is declared to return an Iterator . That is, the method returns an instance of a parameterized type whose actual type parameter is the same as the actual type parameter of the list. To illustrate this concretely, here is a way to obtain the first element of a List without calling get(0) .
List words = // . initialized elsewhere. Iterator iterator = words.iterator(); String firstword = iterator.next();