Wednesday, July 28, 2021

The Ultimate Guide of Generics in Java - Examples

Java Generics Tutorial
Generics in Java is one of the important features added in Java 5 along with Enum, autoboxing, and varargs, to provide compile-time type-safety. Generics are also considered to be one of the tough concepts 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 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 their head too much. 

In this Java generics tutorial, I will cover How Generics works in Java,  some mysterious wildcards in Generics, and some important points about generics in Java. I will try explaining generics concepts in simple words and simple examples.

I thought about writing on Java Generics when I completed my post on 10 Examples of Enum in Java. Since Enum and Generics are introduced at the same time in JDK 5, but it took a long time to finish this post. Anyway, I am happy that it's finally out and covers Generics in good detail.





What is Generics in Java? Example

Generic in Java is added to provide compile-time type-safety of code and removing the risk of ClassCastException at runtime which was a 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 the compiler that correct Type is used in the 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, the compiler will complain. Before Java 5 same code will pass compile-time check but will fail at runtime which is worse. Generics allows Java programmers to write more robust and type-safe code. In my opinion generics in Java is a much overdue feature given the popularity of the 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 a 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.

If you like to read about generics then you can also check my other tutorials on generics e.g. 10 generics interview questions in Java and the Difference between bounded and unbounded wildcards in Generics.

How Generics works in Java?

This is a popular Java Generics interview question that comes to 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 :).

The Same thing is done by Java compiler, when it sees code written using Generics it completely erases that code and convert it into raw type i.e. code without Generics. All type related information is removed during erasing. So your ArrayList<Gold> becomes plain old ArrayList  prior to JDK 1.5, formal type parameters e.g. <K, V> or <E> gets replaced by either Object or Super Class of the Type.

Also, when the translated code does not have correct type, the compiler inserts a type casting operator. This all done behind the scene so you don't need to worry about what important to us is that Java compiler guarantees type-safety and flag any type-safety relate error during compilation.

In short Generics in Java is syntactic sugar and doesn’t store any type related information at runtime. All type related information is erased by Type Erasure, this was the main requirement while developing Generics feature in order to reuse all Java code written without Generics. See these Java Collections and Generics courses to learn more. 

Java Generics tutorial


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<T> is subtype of raw type Set and you can assign Set<T> to Set, following code is legal in Java:

Set setOfRawType = new HashSet<String>();
setOfRawType = new HashSet<Integer>();


2) Set<Object> 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 - <Object> can accept any type


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<String> and LinkedHashSet<String> are sub types of Set<String> and legal by compiler as shown in following Generics example in Java :

Set<String> setOfString = new HashSet<String>(); //valid in Generics
setOfString = new LinkedHashSet<String>(); // Ok

But Inheritance on type parameter is not supported means Set<Object> will not accept Set<String> as per following Generics code.

Set<Object> SetOfObject = new HashSet<String>(); //compiler error - incompatible type


5)Set<? extends Number> will store either Number or subtype 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<? super TreeMap> 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 the .class token, parametrized types like List<String> are not allow along with .class literal.

List.class //legal
List<String>.class  //illegal

This is the one place where you need to use Raw type instead of parameterized type in Java.

8) If you are writing Generics method then 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 <T> will result in compile time error. to know more read How to write Generics method in Java

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.

Java Generics Tutorial and Example




Generics notations and naming Convention

Java Generics example tutorial , parametrized type class methodOne of the reasons Generics looks tough is due to the non-familiarity of various Generics terms and naming conventions. Once you know the 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<E>
Generic Type , E is called formal parameter
Set<Integer>
Parametrized type , Integer is actual parameter here
<T extends Comparable>
Bounded type parameter
<T super Comparable>
Bounded type parameter
Set<?>
Unbounded wildcard
<? extends T>
Bounded wildcard type
<? Super T>
Bounded wildcards
Set
Raw type
<T extends Comparable<T>>
Recursive type bound


T – used to denote the type
E – used to denote an element
K – keys
V - values
N – for numbers

Java Generic Example List



Array and Generics in Java

1) Arrays don'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 is a workaround that requires a cast from Object[] to T[] which comes with the risk of unchecked cast and warning. For this reason, it's better to use Collections classes like ArrayList and HashMap over an array.

By the way, those classes are also implemented on top of the 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 <T>
 */

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 a warning about "unsafe cast" which can be suppressed by using annotation @SuppressWarnings("unchecked") with a proper comment that why it will not compromise type safety. This is also one of the Java Generics best practices suggested in the all-time classic book Effective Java by Joshua Bloch.

Java Generics guide



Generics in Java – Benefits and advantages

Generics adds lot of value to Java programming language, here are some of the important benefits of using Generics in Java:



1. Type-safety

Most important advantage of Generics in Java is type-safety. Collections prior to JDK1.5 are not type-safe because they accept the Object type argument which allows them to catch all type of objects instead of only required the 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

ArrayList<Stocks> stockList = new ArrayList<Stocks>();
stockList.add(“coins”); //compiler error , String not allowed

The compiler will guarantee that only Stock objects will be inserted in stockList and will throw a compiler error if you try to insert different type of Object.


2. 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 is required, the result is clear and robust code.


3. No ClassCastException

With Generics compiler ensures that correct types are added into Java collection classes and no cast is required while retrieving an element, So there is no risk of ClassCastException at runtime.

Difference between raw type and generics in Java

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<int> will result in compilation error, This is little counter-intuitive that why auto-boxing can not convert int to Integer. If you try the same thing with our Generic Holder class you will get following compilation error:

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 an instance of parametrized class gets created. In below example of generics class in Java <K,V> are formal parameters.

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 <K,V> for key and value type parameters.


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 to Java Classes but also on methods, so you can write your own generics methods in Java as shown in 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 the parametric declaration of Holder<T extends Closeable>, type parameter list <T extends Closeable> requires that actual parameter T must be either Closeable or sub-type of Closeable. This is called bounded type parameters in Generics . this kind of declaration allows you to call a method of the Closeable interface without casting type parameter into Closeable. read more about these type parameters in bounded and unbounded wildcards in Generics.


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 the diamond operator is much cleaner than the previous one.

On related note Generics in Java supports type inference while calling Generic methods and this feature can be used to create in a combination of Factory design patterns in Java to create static factory method corresponding to each constructor. 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 the constructor with this static factory method as shown below :

HashMap<String, Set<Integer>> contacts = newContacts();

this can be used as an alternative to the 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 the type of Object e.g. ArrayList, HashMap.

Generics Wildcards example bounded

Type-safety means the compiler will verify the type of class during compile-time and throw a compiler error if it found the improper type. For example, if an ArrayList of Gold contains a Silver compiler will throw an error.
ArrayList<Gold> goldList = new ArrayList<Gold>();

<Gold> tells the compiler that this ArrayList must contain only Gold.

Generics can also be used to write parametric classes like Cache<Key, Value> on which type of Key and Value can be specified while creating objects.

Parameters used to write code is called "formal type parameters" and parameters which passed while creating an instance of a generic class in java is called "actual type parameters". For example in our generic cache (below) <K, V> are formal parameter while new LRUCache<String, Integer>() will be actual parameters.


Generics wild cards Example in Java

There are generally two kinds of wildcards 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 <? extends T> and <? super T> are bounded wildcards. To know more about them see my post:  Bounded vs Unbounded wildcards in Generics.

Now let’s see examples of different wildcards in Generics:

<?>
"?" denotes any unknown type, It can represent any Type at in code for. Use this wildcard if you are not sure about Type. for example, if you want to have an 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 an example of generics in Java:

ArrayList<?> unknownList = new ArrayList<Number>();
unknownList = new ArrayList<Float>();

<? extends T>
This is little restrictive than the previous one it will allow All Types which are either "T" or extends T means a subclass of T. for example List<? extends Number> can hold List<Number> or List<Integer>

ArrayList<? extends Number> numberList = new ArrayList<Number>();
numberList = new ArrayList<Integer>();
numberList = new ArrayList<Float>();

<T super ?>
This is just opposite of previous one, It will allow T and super classes of T, e.g. List<? super Integer> can hold List<Integer> or List<Number>.

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, it's 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 methods to get the 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 holds type information at runtime, unlike Generics whose type information is erased by type-erasure during run time.

3) Use Bounded type parameter to increase the 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 the rationale of why this cast is type-safe as code comments.

5) Convert your raw type classes into a type-safe parametric class using Generics in Java as and when time allows, that will make the code more robust.

Generic in Java is a very vast topic and there are a lot more to learn to get expertise on Java Generics. I hope this will serve you a good starting point in terms of reading code is written using Generics and get over with a complex wild card of Generics. Java Generics is one of the beautiful feature and once you used it you won’t write classes or methods without generics.

Initially, Generics looks tough and complex but it's worth learning, given the type-safety benefits it provides. Two things I would suggest you to do as a beginner first write collection code always using Generics and write some type-safe classes which can accept parameters e.g. type-safe cache Cache<Key, Value>


30 comments :

Anonymous said...

Great tutorial on Generics, thanks a lot. I was looking for something which can teach me basics of Generics in Java and at least allow me to understand generics code written in Java and this java generics tutorial is exactly what I was looking.

R. Mohan said...

Thanks for your Java Generics Tutorial man, you have make me to understand those cryptic angle brackets. I agree Generics in Java is little difficult topic and get a while to understand but tutorials like this are very helpful.

Anonymous said...

nice topic

Javin @ remove symbolic link unix said...

Thanks Anonymous , good to know you like this Java Generics Tutorial.

Anonymous said...

Good post for beginners.

I have small note to point 5: provided you mean java.util.Collection and its subtypes, boolean add(E o) is NOT a generic method. It's simply a method of generic class. Generic methods have their own formal type parameter which is completely independent on class itself. In most often cases generic method are static or their class is not generic, but sensible exceptions exist, see http://guava-libraries.googlecode.com/svn/trunk/javadoc/com/google/common/collect/Ordering.html for example. Tomas Zalusky

Rick said...

I agree Generics in Java is a convoluted concept but it makes coding quite easy. What makes Generics difficult it steep learning curve but once you get familiar with basics of Generics in Java , it would be more intuitive. Thanks for such a nice tutorial on Generics. Does anything changes related to Generics in Java from Java5 to Java6 and JDK7 onwards ?

Billy said...

Generics in Java is way too complex specially for common Java developers. Generic tutorial like this will help with certain extent but to get mastery on Generics in Java you will eventually need to write lots of Generic classes, interfaces and code. no substitute for that.

Jimmy said...

I agree with Billy, just reading theory on Generics or Java is not enough, you gotta write down generic classes, method and generic collection code. you got to understand concept of type-erasure and how it achieves type-safety at compile time for generics java code also.

Anonymous said...

Best tutorial in Generics by any standard. Examples of Generics wild-cards are very easy to understand.

Priya said...

How does Generics works in Java, I was looking answer of this questions from long time. Can you suggest where in Java API I can see internal working of Generics, How Type erasure actually works, In which language Generics type erasure are written, can we see the code before Generics and after Type erasing. Thanks to you for bringing key point like :

1) Generics type information is lost during runtime.
2) Generics ensure type-safety during compile time etc.

Priya

Kapil said...

I must say, one of the best Generics tutorial for Java, comprehensive and from basics to best practices. I was asked to explain How does Generics works internally in Java? didn't know about type erasure, but a quick read to this tutorial, gives me enough knowledge to answer such interview question. Thanks dude.

Anonymous said...

Can you tell me two reasons for using Generics in Java? I am coming from Java 1.4 and found learning Generics really hard, so before investing time and making my code changes, I would like to know What benefits Generics provides? What happens if you don't use Generics, as I am not using it currently and my code is perfectly fine.

Anonymous said...

Point 6
setOfAllSuperTypeOfTreeMap = new LinkedHashSet< Map>(); //legal since Map is super type of TreeMap

I think on RHS we can't give Interface, so it should be some concrete implementation of Map Interface.

Anyway...Thanks a lot for such a nice tutorial.... Cheers ... Manish K

Anonymous said...

Well, Generics in Java are not perfect. They're not reified, available at runtime, or specializable, and there are some weird restrictions on what you can do with generics. For example, you can't capture a lower bound into a variable, only into a wildcard, What I mean is you can not do C super T, only ? super T is permitted.

Anonymous said...

Very good explanation ,this site is best for the experienced interview questions,maximum time i am able to answer the question by learning from this site.Thanks writer

Vitaly Zdanevich said...

In your code:

public void add(T content) {
contents[index] = content;
}

Maybe will be better to replace 'index' to 'index++' ? Because now index is always 0.

Shaik said...

This is the best tutorial on Generics which I came across... good job

Anonymous said...

Awesome work!!!

Pradhuman said...

This is first time I am going through Generics and found this article encouraging.
Also refers below link for some more example
https://docs.oracle.com/javase/tutorial/java/generics/QandE/generics-answers.html

Unknown said...

Hi,

In GenericVsArray class:
Shouldn't:

public void add(T content){
contents[index] = content;
}

be replaced with

public void add(T content){
contents[index++] = content;
}

Unknown said...

Here're some useful examples of Generic I have learned from Java tutorial.I guess they make your life a little bit easier.

public class ArrayIsNotIterable {
static void test(Iterable ib) {
for(T t : ib)
System.out.print(t + " ");
}

public static void swap(List a, int i, int j) { //Can not use for ARRAY //
E tmp = a.get(i);
a.set(i, a.get(j));
a.set(j, tmp);
}

public static void shuffle(List list, Random rnd) { //randomly permutes the specified list using the specified source of randomness.
for (int i = list.size(); i > 1; i--) //repeatedly swapping a randomly selected element into the current position.
swap(list, i - 1, rnd.nextInt(i));
}
public static void main(String[] args) {
test(Arrays.asList(1, 2, 3)); print();
ArrayList TI = new ArrayList<>(Arrays.asList(89, 49, 39, 32, 100)); //The Arrays class has a static factory method called asList, which allows an
//array to be viewed as a List. This method does not copy the array.
test(TI); print();
LinkedList IT = new LinkedList<>(TI);
test(IT); print();
// An array works in foreach, but it’s not Iterable:
String[] strings = { "A", "B", "C" }; //! illegal test(strings); // You must explicitly convert it to an Iterable:
test(Arrays.asList(strings)); print();

ArrayIsNotIterable.swap(TI, 0, TI.size() - 1);
print("Swapped two indexed values in a List : " + TI);

Collections.shuffle(IT, new Random(47)); //This algorithm, which is included in the Java platform's Collections class,
print(IT);
}
}
/*1 2 3
89 49 39 32 100
89 49 39 32 100
A B C
Swapped two indexed values in a List : [100, 49, 39, 32, 89]
[39, 89, 100, 49, 32]
*/

javin paul said...

Hello @Huy, Thanks for your program, a little bit of explanation will help more :)

Unknown said...

Jarvin, English is my second language, it takes a little bit time. Please ignore any error.

I) static void test(Iterable ib):
a)This method receives the type variables of the list passes over from main.
b) for ( T t : ib) : for each element in a list , iterates through them and displays.
//===============================================================
II) public static void swap(List a, int i, int j)
a) E is called "formal parameter". It used to denote element.
b) int i and j are indexes within a list. It specifies the position of each element in the list.
c) E tmp = a.get(i); //gets the specified index of a specified element and assigns to tmp (temporary) //tmp = i
d) a.set(i, a.get(j)); //replaces the index i by index j // i = j
e) a.set(j,tmp); //and swaps //j = tmp
//============Linked and Array List==============================
III) public static void shuffle(List list, Random rnd) {
a) List list means "a non-raw List of some specific type, but we just don’t know what that type is." (an unbounded wildcard)
b)for (int i = list.size(); i > 1; i--)
swap(list, i - 1, rnd.nextInt(i));
}
//repeatedly swapping a randomly selected element into the current position. rnd.nextInt(0) generates a random integer


Jarvin, please giving me an advice if there's any. Thanks!

Unknown said...

Jarvin, the index can not replace, you know what i mean

javin paul said...

@Huy Le, that's ok, What I wanted to say is that your should say which problem you are solving or what is your code is doing e..g I can see your shuffling elements in list but you could have better used Collections.shuffle() method, unless you are doing it for practice.

Unknown said...

Set setOfRawType = new HashSet();
setOfRawType = new HashSet();
Set setOfUnknownType = new HashSet();
setOfUnknownType = new HashSet();

What is the difference? i cant understand

PUNIT DHIMAN said...

Kudos!!!

Anonymous said...

Great Tutorial ...
I need some help to understand below code

ArrayList unknownList = new ArrayList();
unknownList = new ArrayList();
unknownList.add(); // what type of Object will be passed to add()?

Any help?

Anonymous said...

How Stocks inherits StockList? (Point no 2 in Generics in Java – Important points)

Anonymous said...

This will store as raw type

Post a Comment