Last Updated on 14 June 2019   |   Print Email
Perhaps generics are ones of the most significant improvements for the Java programming language, alongside with Lambda expressions.Historically, generics were added to Java 5 in 2005 and Lambda expressions have been added to Java 8 recently, in 2014.Basically, generics change the ways programmers write code related to collections and generic algorithms. Let’s look at deeper into this interesting language feature.
1. What are Generics?
Generics are features of the Java programming language that allow programmers to write parameterized code. They were originally created to address some problems with collections. Thus the term generics-collections is usually used together today.To understand generics, let’s see how it solves issues with collections.Consider the following code that uses a List collection without generics:
List listNames = new ArrayList();
listNames.add("Tom");
listNames.add("Mary");
listNames.add("Peter");
This list, listNames, is supposed to store String objects. The following statement takes the 2nd element in this list:
String name2 = (String) listNames.get(1);
Here, the cast to String is needed because the get() method returns an object of type Object.
So far so good. Suppose that at some points in the program, you may be mistakenly adding an object other than String as the 2nd element in the list:
Date startDate = new Date();
listNames.add(startDate);
This code is still legal, as the add() method can accept any kind of object. Now, a problem will rise in this statement:
String name2 = (String) listNames.get(1);
Why?
This assignment expects a String object on the right side, but the actual object returned is a Date object. This problem cannot be detected at compile time by the compiler because everything seems fine until that statement is executed at runtime, a ClassCastException will be thrown:
java.lang.ClassCastException: java.util.Date cannot be cast to java.lang.String
What will be the effect?
This kind of problem would produce unsafe code with potential bugs as the type of the objects added to the collection cannot be verified by the compiler and compile time.Therefore generics come to rescue. The above code snippet can be re-written using generics like this:
List<String> listNames = new ArrayList<String>();
listNames.add("Tom");
listNames.add("Mary");
listNames.add("Peter");
Here, the list listNames is parameterized with the String type (read: list of Strings). From now on, if you mistakenly add a non-String object into this collection, the compiler will issue an error. For example:
Date startDate = new Date();
listNames.add(startDate);
Compile error message:
cannot find symbol
symbol : method add(java.util.Date)
location: interface java.util.List<java.lang.String>
This generic code is safe as it guarantees all elements added to the collection are of the parameterized type. Otherwise a compile error occurs.With generics, the compiler can detect violations at compile time, thus eliminating potential bugs.And as a consequence, we don’t need to cast objects when getting an element out of the generic collection:
String name2 = listNames.get(1);
This is safe, because generics ensure that the collection does not store wrong object types.That’s how generics addressed the issues with collections. To let you see the differences clearly, I put the two different code snippets together as shown below:
Code without generics:
List listNames = new ArrayList();
listNames.add("Tom");
listNames.add("Mary");
listNames.add("Peter");
String name2 = (String) listNames.get(1);
You see the differences, don’t you? Generics do not only make the code safer, but also more readable, right?In addition, generics allow you to write generic code. That means you can write parameterized interfaces, classes and methods.Let’s see a couple of examples. The first example, we don’t have to go far, just take the Listinterface in the Java Collections Framework:
public interface List<E> extends Collection<E> {
boolean add(E e);
E get(int index);
}
Here, the class Listis parameterized with the parameter type called E - which stands for element. This parameterization allows us to specify a concrete type when creating a collection as seen in the above examples.Note that all the interfaces and classes in the Java Collections Framework are parameterized like this, e.g. Set<E>, Queue<E>, Map<K, V>, ArrayList<E>, HashSet<E>,etc.Now, let’s look at how amethod can be parameterized using generics. Take a sort() method of the Collections utility class for example:
public static <T> void sort(List<T> list, Comparator<? super T> c) {
// code goes here...
}
Don’t worry if you don’ t understand this code right now. Just to know that you can write parameterized methods like that, for the sake of readability, safety and reusability.I’ll show you some good resources to learn generics later in this article.
2. Why Use Generics?
Due to the demand of safer code, generics were added to the Java programming language. Indeed, it adds stability to the code, as potential bugs can be detected at compile time.Here are the benefits of using generics:
Stronger type checks at compile time:
As you can see in the above examples, generics guarantee that there’s no wrong types added to the collections. Stronger type checking makes the code more readable:
List<String> listNames = new ArrayList<String>();
This code tells us listNames is a list of Strings and only Strings. Objects of other types if added to the list will cause compile error.
Elimination of casts:
As you can see in the above examples, we don’t have to use casts when taking elements out of a collection:
String name2 = listNames.get(1);
This makes the code simpler and more readable. Let’s look at another example.Below is the code snippet to iterate over a List collection without using generics:
List bookTitles = new ArrayList();
bookTitles.add("Effective Java");
bookTitles.add("Thinking in Java");
bookTitles.add("Head First Java");
Iterator it = bookTitles.iterator();
while (it.hasNext()) {
String nextTitle = (String) it.next();
System.out.println(nextTitle);
}
Using generics, the above code can be re-written as:
List<String> bookTitles = new ArrayList<String>();
bookTitles.add("Effective Java");
bookTitles.add("Thinking in Java");
bookTitles.add("Head First Java");
Iterator<String> it = bookTitles.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
- Enable writing generic algorithms: with generics, we can generalize our code for reusability, as you see in the code of the List interface and sort() method above.
3. How to Learn to Use Generics?
At a glance, generics seem easy to use (as you see in the above examples), however it will be more complex and hard to understand if going deeper with concepts like wildcards, bounded types, type erasure, etc.To master the Java programming language, you should able to understand and apply generics effectively into your daily coding. Here are my recommendations for learning generics:
The Java Tutorials Trail - Generics (Updated):this is official document published by Oracle, thus highly recommended. It explains the concepts like generic types, raw types, bounded type parameters, wildcards, type erasure, etc.
Lesson: Generics by Gilad Bracha: he explains the corner cases around generics that help us understand generics correctly. The section on how to convert legacy code to use generics is also worth reading.
The book Java Generics & Collections by Naftalin & Philip Wadler: a comprehensive guide about generics and collections in Java, especially the sections Effective Generics and Design Patterns are worth reading.
I also recommend you to look at Java source files of the Java Collections Framework. The interfaces and classes like Iterable, Collection, List, ArrayList, Map, HashMap, etc to understand how generic code is written.
Nam Ha Minh is certified Java programmer (SCJP and SCWCD). He began programming with Java back in the days of Java 1.4 and has been passionate about it ever since. You can connect with him on Facebook and watch his Java videos on YouTube.
Comments