Java Generics Tutorial
Generics in Java is one of important feature added in Java 5 along with Enum, autoboxing and varargs , to provide compile time type-safety. Generics is also considered to be one of tough concept to understand in Java and somewhat it’s true as well. I have read many articles on generics in Java, some of them are quite good and detailed but I still felt that those are either too much technical or exhaustively detailed, so I thought to write a simple yet informative article on Java generics to give a head start to beginners without bothering there head too much. In this Java generics tutorial I will cover How Generics works in Java, Mysterious wild-cards in Generics and some important points about Generic in Java. I will try explaining generics concept in simple words and simple examples. On a different note, If you like to learn new concepts by following books then you should check Java Generics and Collection, one of the best book on Generics, which covers from basics to best practices.
By the way I thought about writing on Java Generics when I completed my post on Advanced Example of Enum in Java. Since Enum and Generics are introduced at same time in JDK 5. If you like to read about generics than you can also check my other tutorials on generics e.g. 10 generics interview question in Java and Difference between bounded and unbounded wildcards in Generics.
What is Generics in Java
Generic in Java is added to provide compile time type-safety of code and removing risk of ClassCastException at runtime which was quite frequent error in Java code, for those who doesn’t know what is type-safety at compile time, it’s just a check by compiler that correct Type is used in correct place and there should not be any ClassCastException. For example HashSet of String will only contain String object and if you try to put Integer or any other object, compiler will complain. Before Java 5 same code will pass compile time check but will fail at runtime which is worse. Generics allows Java programmer to write more robust and type-safe code. In my opinion generics in Java is much overdue feature given popularity of Collection framework in java and its limitation around handling type-safety. Though Generics may look very complex because of its mysterious angle bracketing <> and various wild cards on Java generics, but once you understand the purpose of Generics in Java or Why Generics is introduced in Java you will be very comfortable and love writing code using Generics. If you are beginner and not familiar with Generics I strongly suggest you put some time and understand the Generics and stop writing collection classes without Generics. It not only saves you from mischievous ClassCastException but also gives you more readability and deterministic behavior from code. In this Java Generics tutorial we will see some important points around Generics and Java and will revise some stuff you may know.
How Generics works in Java
This is a popular Java Generics interview question which comes in my mind little late, It didn't come when I first know about generics in Java but a while later, nevertheless I find it quite useful to know about how generics works in java behind the scene. The buzzing keyword is "Type Erasure", you guessed it right it’s the same thing we used to in our schools for erasing our mistakes in writing or drawing :). Same thing is done by Java compiler, when it sees code written using Generics it completely erases that code and covert it into raw type i.e. code without Generics. All type related information is removed during erasing. So your ArrayList
Rules and Examples of Generics in Java
Let’s see some rule of using Generics in Java on Collections, Type safe class and type safe methods with simple examples:
1) Parametrized type like Set
2) Set is setOfAnyType, it can store String, Integer but you can not assign setOfString or setOfInteger to setOfObject using Generics in Java.
Set<Object> setOfAnyType = new HashSet<Object>();
setOfAnyType.add("abc"); //legal
setOfAnyType.add(new Float(3.0f)); //legal -
3)Set represents SetOfUnknownType and you can assign SetOfString or SetOfInteger to Set as shown in below example of Generics :
Set setOfUnknownType = new LinkedHashSet<String>();
setOfUnknownType = new LinkedHashSet<Integer>();
4)Parametrized Type also follow Inheritance at main Type level means both HashSet
Set<String> setOfString = new HashSet<String>(); //valid in Generics
setOfString = new LinkedHashSet<String>(); // Ok
But Inheritance on type parameter is not supported means Set
Set<Object> SetOfObject = new HashSet<String>(); //compiler error - incompatible type
5)Set will store either Number or sub type of Number like Integer, Float. This is an example of bounded wildcards in Generics
Set extends Number> setOfAllSubTypeOfNumber = new HashSet<Integer>(); //legal - Integer extends Number
setOfAllSubTypeOfNumber = new HashSet<Float>(); //legal - because Float extends Number
6)Set is another example of bounded wildcards, which will store instances of TreeMap or super class of TreeMap. See following Generics example in Java :
Set super TreeMap> setOfAllSuperTypeOfTreeMap = new LinkedHashSet<TreeMap>(); //legal because TreeMap is superType of itself
setOfAllSuperTypeOfTreeMap = new HashSet<SortedMap>(); //legal because SorteMap is super class of TreeMap
setOfAllSuperTypeOfTreeMap = new LinkedHashSet<Map>(); //legal since Map is super type of TreeMap
7) You can not use Generics in .class token, parametrized types like List
List.class //legal
List<String>.class //illegal
This is the one place where you need to use Raw type instead of parametrized type in Java.
8) If you are writing Generics method than you need to declare type parameters in method signature between method modifiers and return type as shown in below Java Generics example :
public static <T> T identical(T source){
return source;
}
failing to declare
Generics notations and naming Convention
One of the reason Generics looks tough is due to non familiarity of various Generics terms and naming conventions. Once you know meaning and name of various terms in generics you will feel more comfortable with Generics in Java. Following are some of the frequently used terms in Generics:
Generic Term | Meaning |
Set | Generic Type , E is called formal parameter |
Set | Parametrized type , Integer is actual parameter here |
Bounded type parameter | |
Bounded type parameter | |
Set | Unbounded wildcard |
Bounded wildcard type | |
Bounded wildcards | |
Set | Raw type |
Recursive type bound |
T – used to denote type
E – used to denote element
K – keys
V - values
N – for numbers
Array and Generics in Java
1) Arrays doesn't support Generics in Java so you can not create Arrays like T[] which makes gentrifying an existing class hard if you are using arrays. Though there are work around which requires a cast from Object[] to T[] which comes with risk of unchecked cast and warning. For this reason it's better to use Collections classes like ArrayList and HashMap over array. by the way those classes are also implemented on top of array in Java but JDK handles there type-safety by effectively using generics. here is an example of casting Object array to generic array in Java :
/**
* Generics and Array doesn't gel very well, Java doesn’t allow Generics array like E[]
* @author Javin Paul
*/
public class GenericVsArray {
public static void main(String args[]){
Holder<Integer> numbers = new Holder<Integer>(10);
numbers.add(101);
System.out.println("Get: " + numbers.get(0));
}
}
/**
* Generic Holder for holding contents of different object type
* Generic in Java eliminates casting required while calling get(index) from client code
* @param
*/
class Holder<T>{
private T[] contents;
private int index = 0;
public Holder(int size){
//contents = new T[size]; //compiler error - generic array creation
contents = (T[]) new Object[size]; //workaround - casting Object[] to generic Type
}
public void add(T content){
contents[index] = content;
}
public T get(int index){
return contents[index];
}
}
Casting code may generate warning about "unsafe cast" which can be suppressed by using annotation @SuppressWarnings("unchecked") with proper comment that why it will not compromise type-safety. This is also one of the Java Generics best practices suggested in all time classic book Effective Java by Joshua Bloch.
Generics in Java – Benefits and advantages
Generics adds lot of value into Java programming language, here are some of important benefits of using Generics in Java:
Type-safety
Most important advantage of Generics in Java is type-safety. Collections prior to JDK1.5 are not type-safe because they accept Object type argument which allows them to catch all type of objects instead of only required type of object. For example if you want to create an ArrayList of Stocks and you don't want that ArrayList also contain any other asset class you can use generics feature of java to create a type-safe collection. Here is an example of using Generics to create a type-safe ArrayList
stockList.add(“coins”); //compiler error , String not allowed
Compiler will guaranteed that only Stock object will be inserted in stockList and will throw compiler error if you try to insert different type of Object.
No Casting
With Generics you don’t need to cast object , Generics will automatically do that for you. For example here is the code for adding and retrieving an element in List with and without Generics in Java:
List items = new ArrayList();
items.add("chocolates");
String item = (String) items.get(0)
List<String> items = new ArrayList();
items.add("biscuits");
String item = items.get(0) //no cast required
Since no cast required, result is clear and robust code.
No ClassCastException
With Generics compiler ensures that correct types are added into Java collection classes and no cast is required while retrieving element, So there is no risk of ClassCastException at runtime.
Generics in Java – Important points
Some important feature of Generics in Java worth remembering:
1) One limitation of Generics in Java is that it can not be applied to primitive type, for example you can not create pass primitives in angle bracket that will result in compilation error, for Example ArrayList
Holder<int> numbers = new Holder<int>(10); //compiler error - unexpected type required: reference found:int
2) Generics in Java eliminates ClassCastException while retrieving objects from Collection, Remember prior to JDK1.5 if you retrieve objects from Collection you first check for a particular type and then cast, no need to do it now.
ArrayList<Stocks> stockList = new ArrayList<StockList>();
Stock sony = new Stock("Sony","6758.T");
stockList.add(sony);
Stock retreivedStock = stockList.get(sony); //no cast requires – automatic casting by compiler
3) A parametrized class in Java use formal type parameters to retrieve Type information when instance of parametrized class gets created. In below example of generics class in Java
interface Cache <K,V>{
public V get();
public V put(K key, V value);
}
As per convention followed on Generics version of Java Collection package we can use
4) Generics are often related to Templates in C++, though Unlike "Template" in C++, which creates a new type for each specific parametrized type, parametrized class in Java is only compiled once and more importantly there is just one single class file which is used to create instances for all the specific types.
5) Generics in Java can not only apply on Java Classes but also on methods, so you can write your own generics methods in Java as shown on Rules of Generics in Java section, here is another example of parametrized method from Java collection package.
boolean add(E o){}
Here E will be replaced by actual type parameter when this method will get called.
6) Another worth noting feature of Generics in Java is its ability to limit Types parameters, for example in parametric declaration of Holder
7) Type inference : Generics in Java does not support type inference while calling constructor or creating instance of Generic Types until JDK7, In Java 7 along with Automatic resource management and String in Switch also added a new operator called Diamond operator and denoted by <> which facilitate type inference while creating instance of Generics classes. this helps to reduce redundancy and clutter. here is an example of Diamond operator in Java7 code:
//prior to JDK 7
HashMap<String, Set<Integer>> contacts = new HashMap<String, Set<Integer>>()
//JDK 7 diamond operator
HashMap<String, Set<Integer>> contacts = new HashMap<>()
code with diamond operator is much cleaner than previous one.
On related note Generics in Java supports type inference while calling Generic methods and this feature can be used to create in combination of Factory design pattern in Java to create static factory method corresponding to each constructors. for example
//type inference in generic method
public static <K,V> HashMap<K,V> newContacts() {
return new HashMap<K,V>();
}
so we can replace call to constructor with this static factory method as shown below :
HashMap<String, Set<Integer>> contacts = newContacts();
this can be used as alternative to diamond operator in Java 5 or 6.
Section for absolute beginners on Generics in Java
If you are absolute beginners in generics those angle bracket "<>" may look strange and unreadable to you. Though is not a complete tutorial on Java Generics and I would suggest you to read Java docs on Generics I will try to give at least some basic idea of generics in Java to get you going. Remember Generics in java are introduced to enforce type-safety especially on collection classes of java which holds type of Object e.g. ArrayList, HashMap.
Type-safety means compiler will verify type of class during compile time and throw compiler error if it found improper type. For example if an ArrayList of Gold contains Silver compiler will throw error.
ArrayList
Generics can also be used to write parametric classes like Cache
Parameters used to write code is called "formal type parameters" and parameters which passed while creating instance of a generic class in java is called "actual type parameters". For example in our generic cache (below)
Generics wild cards Example in Java
There are generally two kinds of wild-cards in Generics, Bounded and unbounded. Bounded wildcards can be written in two ways to denote upper bound and lower bound. is called unbounded wildcards because it can accept any Type while and are bounded wildcards. To know more about them see my post Bounded vs Unbounded wildcards in Generics . Now let’s see example of different wildcards in Generics:
"?" denotes any unknown type, It can represent any Type at in code for. Use this wild card if you are not sure about Type. for example if you want to have a ArrayList which can work with any type than declare it as "ArrayList unknownList" and it can be assigned to any type of ArrayList as shown in following example of generics in Java:
ArrayList unknownList = new ArrayList<Number>();
unknownList = new ArrayList<Float>();
This is little restrictive than previous one it will allow All Types which are either "T" or extends T means subclass of T. for example List can hold List
ArrayList extends Number> numberList = new ArrayList<Number>();
numberList = new ArrayList<Integer>();
numberList = new ArrayList<Float>();
This is just opposite of previous one, It will allow T and super classes of T, e.g. List can hold List
ArrayList super Integer> numberList = new ArrayList<Number>();
numberList = new ArrayList<Integer>();
numberList = new ArrayList<Float>(); //compilation error
Generics Best Practices in Java
After learning about how to use Generics in Java for writing type-safe classes, methods and collection, its worth noting to remember best practices related to Generics coding:
1) Avoid using Raw types for new code. Always use Generics and write parametrized classes and method to get full benefit of compiler checking.
2) Prefer Collection classes over Array in your parametrized class because Generics and Arrays are completely different to each other, Array hold type information at runtime unlike Generics whose type information is erased by type-erasure during run time.
3) Use Bounded type parameter to increase flexibility of method arguments and API
4) Use @SuppressedWarning("unchecked") at as narrow scope as possible like instead of annotating a method, just annotate a line. Also document rational of why this cast is type-safe as code comments.
5) Convert your raw type classes into type-safe parametric class using Generics in Java as and when time allows, that will make code more robust.
Generic in Java is very vast topic and there are lot more to be learn to get expertise on Java Generics. I hope this will serve you a good starting point in terms of reading code written using Generics and get over with complex wild card of Generics. Java Generics is one of the beautiful feature and once you used to it you won’t write classes or methods without generics. Initially Generics looks tough and complex but its worth learning given type-safety benefits it provides. Two things I would suggest you to do as beginner first write collection code always using Generics and write some type-safe classes which can accept parameter e.g. type-safe cache Cache
Further Reading
on Java Generics
Generics is a complex topic and
effective use of Generics is not easy to learn, but following books has done
great job on explaining power of Generics and how to take advantage of that,
while writing parameterized interface and classes. This books not only contains
good details of Generics and it’s benefit but also best practices of using
Generics in Java.
Java Generics and Collection
Effective Java by Joshua Bloch
Java 5.0 Tiger: A Developers notebook
Tidak ada komentar:
Posting Komentar