Generics in Java with example programs

What is Generics in Java?

Generics in Java is one of the most important topic and is also a frequently asked topic in Java interviews.

Most developers when they hear the word ‘Generics‘ they simply think that it is tough concept to understand. Let us uncover that mystery now.

Generics was introduced in Java 5. It  allows a class or method to operate on objects of various types while providing compile-time type safety. It adds compile-time type safety to the Collections Framework and eliminates the need for casting. This helps in detecting bugs at compile time itself. Fixing compile-time errors is easier than fixing run-time errors.

Let see an example. Writing a List without Generics like this,

In the above code, as you can see, you can add any type of Object to the list. The compiler will not tell you that you cannot add string to a integer list nor you have mentioned it as an integer list. Hence there is no compile time type safety here. What if we had accidentally written as list.get(1) in line #4? The compiler will not tell you that you are trying to cast String to integer. Only in run time you will get a ClassCastException.

With Generics you can write the same code as below,

The above code will show a compile time error at line #3, thus providing compile time type safety. It can also be seen that in line #4 there is no need for explicit casting when using Generics.

Lets see another advantage provided by generics. Think of a scenario where you want to write a method which does some common operation like removing the first and last element of a list and return the modified list. You can write a common method without generics like below,

The above code is absolutely fine. But if you accidentally write as intlist = modifiedList(strlist); the compiler will not show you a compile time error. You will only get a run time error when you do some operation with the modified intlist variable.

Instead if the same method is written with generics as below,

Now there will be a compile time error at line#10. So generics provides compile time type safety even when writing a common or generic method. What the compiler does here is the “Type inference“.

Type inference is a Java compiler’s ability to look at each method invocation and corresponding declaration to determine the type argument (or arguments) that make the invocation applicable. The inference algorithm determines the types of the arguments and, if available, the type that the result is being assigned, or returned. Finally, the inference algorithm tries to find the most specific type that works with all of the arguments.

Generic Methods introduce you to type inference, which enables you to invoke a generic method as you would an ordinary method, without specifying a type between angle brackets. Here T is nothing but a type parameter which indicates an object of any type can be passed to the method.

Here T is just a place holder used to indicate a type parameter. You can use any letter for that case. But it is a standard naming convention to use T for type parameter, E for elements, K – key, V – value, N – Number etc.

By using type parameters Generics enables classes and interfaces to be parameters when defining classes, interfaces and methods. Type parameters provide a way for you to re-use the same code with different types of inputs.

Generic Classes

A generic class is an ordinary class with type parameter(s). For example,

Wildcards in Generics

In generic code, the question mark (?), called the wildcard, represents an unknown type. There are  two kinds of wildcards in Generics, Bounded and unbounded. The bounded wildcard is further classified into upper bounded wildcard and lower bounded wildcard. Let us see each type in detail.

Unbounded Generics

The unbounded wildcard type is specified using the wildcard character (?), for example, List<?>. This is called a list of unknown type. Unbounded generics is useful when you are writing a method that does only read only operation or uses only common methods defined in the interface and doesn’t use any implementation specific methods.

For example you can write a common method that just displays the elements of the list or calculates the size of the list.

Upper Bounded Wildcards

To declare an upper-bounded wildcard, use the wildcard character (‘?‘), followed by the extends keyword, followed by its upper bound(type). Note that, in this context, extends is used in a general sense to mean either “extends” (as in classes) or “implements” (as in interfaces). upper bounded wildcard restricts the unknown type to be a specific type or a subtype of that type.

Consider the following method,

Here the argument list is bound to the Number type. This method works on lists of Number and the sub types of Number, such as Integer, Double, and Float.

Lower Bounded Wildcards

To declare an upper-bounded wildcard, use the wildcard character (‘?‘), followed by the super keyword, followed by its lower bound(type). A lower bounded wildcard restricts the unknown type to be a specific type or a super type of that type.

For example, List<? super Integer>  can hold List<Integer> or List<Number> types.

Difference between bounded and unbounded generics is one of the important java interview questions. So understand the usage properly.

Type Erasure

Generics were introduced to provide tighter type checks at compile time and to support generic programming. Type erasure is nothing but the Java compiler replaces the type parameters with their bounded types or Object if the type parameters are unbounded. The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods. It also Insert type casts if necessary to preserve type safety.

Type erasure ensures that no new classes are created for parameterized types; consequently, generics incur no run-time overhead.

For example consider the below class definition,

Here T is an unbounded parameter so during type erasure process Java compiler replaces it with the Object. So the code after compilation is as follows,

Some of the points to be noted:

  • List<Object> and raw type List is not same. Compiler will not check type-safety of raw type at compile time but it will do that for parameterized type and by using Object as Type parameter it informs compiler that it can hold any type of Object e.g. String or Integer. You can pass any parameterized type to raw type List but you can not pass List<String> to any method which accepts List<Object>, it will result in compilation error.
  • List<?> and List<Object> is not same. List<?> is a List of unknown type while List<Object> is essentially a List of any type . You can assign List<String>, List<Integer> to List<?> but you can not assign List<String> to List<Object >.

 
Some of the interview questions on Generics

  • What is Generics in Java ? What are advantages of using Generics?
  • What is type erasure ?
  • What is bounded and unbounded Generics?

 

Generics in Java is very vast topic and there are lot more things to be learnt. I hope this post has helped you in understanding the basic concepts and syntax used in Generics. For further study refer to Oracle’s tutorial on Generics.

If you have any comments post it in the comments section.

The following two tabs change content below.
Working as a Java developer since 2010. Passionate about programming in Java. I am a part time blogger.
One comment

Add Comment

Required fields are marked *. Your email address will not be published.