Generics was first introduced in JDK 5 version. Now Generics
is one of the most profound feature of Java Programming.Generics provide
facility to write algorithm independent of any specific type of data. It also
provide type-safety. Using Generics, it is possible to create a single class or
method that automatically works with all type of
data(Integer,String,Float,Double etc).
Generics was provide compile-time type checking and
removing risk of
ClassCastException that was common while working with collection classes. The whole
collection framework was re-written to use generics for type-safety. Let’s see
how generics help us using collection classes safely.
List list = new ArrayList();
list.add("Sachin");
list.add(new Integer(5));
//
OK
for (Object obj :
list) {
String
str = (String) obj; // type casting leading to
//
ClassCastException at runtime
}
Above code compiles fine but throws
ClassCastException at runtime because we are trying to cast Object in the list
to String whereas one of the element is of type Integer. After Java 5, we use
collection classes like below.
List<String> list1 = new
ArrayList<String>(); // java 7 ? List<String> list1 = new
ArrayList<>();
list1.add("Sachin");
//list1.add(new
Integer(5)); //compiler error
for(String str :
list1){
//no type casting needed, avoids
ClassCastException
}
Notice that at the time of list creation, we
have specified that the type of elements in the list will be String. So if we
try to add any other type of object in the list, the program will throw compile
time error. Also notice that in for loop, we don’t need type casting of the
element in the list, hence removing the ClassCastException at runtime.
Generics with Class and Interfaces
We
can define our own classes and interfaces with generics type. A generic type is
a class or interface that is parameterized over types. We use angle brackets
(<>) to specify the type parameter.
package com.naresh.generics;
public class GenericsTypeOld
{
private Object t;
public Object get() {
return t;
}
public void set(Object t) {
this.t = t;
}
public static void main(String mnb[]){
GenericsTypeOld
type = new GenericsTypeOld();
type.set("Pawan
Kalyan");
String
str = (String) type.get(); //type casting, error prone and can cause
ClassCastException
}
}
Notice that while using this class, we have to use type casting and it can produce ClassCastException at runtime. Now we will use generics to rewrite the same class with generics type as shown below.
package com.naresh.generics;
public class GenericsType<T>
{
private T t;
public T get(){
return this.t;
}
public void set(T t1){
this.t=t1;
}
public static void main(String mnb[]){
GenericsType<String>
type = new GenericsType<>();
type.set("Pawan
Kalyan"); //valid
GenericsType
type1 = new GenericsType(); //raw type
type1.set("Pawan Kalyan"); //valid
type1.set(10);
//valid
and autoboxing support
}
}
Tip: We can use @SuppressWarnings("rawtypes") annotation to suppress the compiler warning, check out java annotations tutorial.
Also notice that it supports java autoboxing.
Comparable interface is a great
example of Generics in interfaces and it’s written as:
import java.util.*;
public interface Comparable<T>
{
public int compareTo(T o);
}
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<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 type
E – used to denote element
K – keys
V - values
N – for numbers
Generics in Methods and Constructors
Sometimes we don’t want whole
class to be parameterized, in that case we can use generics type in methods
also. Since constructor is a special kind of method, we can use generics type
in constructors too.
Here is a class showing example
of generics type in method.
package com.naresg.generics;
public class GenericsMethods
{
//Generics in
method
public static <T> boolean isEqual(GenericsType<T>
g1, GenericsType<T> g2){
return
g1.get().equals(g2.get());
}
public static void main(String mnb[]){
GenericsType<String>
g1 = new GenericsType<>();
g1.set("Pawan
Kalyan");
GenericsType<String>
g2 = new GenericsType<>();
g2.set("Pawan
Kalyan");
boolean isEqual =
GenericsMethods.<String>isEqual(g1, g2);
//above
statement can be written simply as
isEqual
= GenericsMethods.isEqual(g1, g2);
//This
feature, known as type inference, allows you to invoke a generic method as an
ordinary method, without specifying a type between angle brackets.
//Compiler
will infer the type that is needed
}
}
Generics Bounded Type Parameters
Suppose we want to restrict the
type of objects that can be used in the parameterized type, for example in a
method that compares two objects and we want to make sure that the accepted
objects are Comparables. To declare a bounded type parameter, list the type
parameter’s name, followed by the extends keyword, followed by its upper bound,
similar like below method.
public static <T extends
Comparable<T>> int compare(T t1, T t2){
return
t1.compareTo(t2);
}
Bounded type parameters can be used with methods as well as
classes and interfaces.
Generics supports multiple
bounds also, i.e <T extends A & B & C>. In this case A can be an
interface or class. If A is class then B and C should be interfaces. We can’t
have more than one class in multiple bounds.
Generics and Inheritance
We know that Java inheritance allows us to assign a variable A to
another variable B if A is subclass of B. So we might think that any generic
type of A can be assigned to generic type of B, but it’s not the case. Lets see
this with a simple program.
package com.naresh.generics;
public class GenericsInheritance
{
public static void main(String[] mnb)
{
String
str = "Sachin";
Object
obj = new Object();
obj=str;
//
works because String is-a Object, inheritance in java
MyClass<String>
myClass1 = new MyClass<String>();
MyClass<Object>
myClass2 = new MyClass<Object>();
//myClass2=myClass1;
// compilation error since MyClass<String> is not a MyClass<Object>
obj
= myClass1; // MyClass<T> parent is Object
}
public static class
MyClass<T>{}
}
We are not allowed to assign
MyClass<String> variable to MyClass<Object> variable because they
are not related, in fact MyClass<T> parent is Object.
Generic Classes and Subtyping
We can subtype a generic class or interface by extending or
implementing it. The relationship between the type parameters of one class or
interface and the type parameters of another are determined by the extends and
implements clauses.
For example, ArrayList<E>
implements List<E> that extends Collection<E>, so
ArrayList<String> is a subtype of List<String> and
List<String> is subtype of Collection<String>.
The subtyping relationship is
preserved as long as we don’t change the type argument, below shows an example
of multiple type parameters.
interface MyList<E,T> extends List<E>{}
The subtypes of List<String> can be
MyList<String,Object>, MyList<String,Integer> and so on.
Generics Wildcards
Question mark (?) is the
wildcard in generics and represent an unknown type. The wildcard can be used as
the type of a parameter, field, or local variable and sometimes as a return
type. We can’t use wildcards while invoking a generic method or instantiating a
generic class. In following sections, we will learn about upper bounded
wildcards, lower bounded wildcards, and wildcard capture.
Generics Upper Bounded Wildcard
Upper bounded wildcards are
used to relax the restriction on the type of variable in a method. Suppose we
want to write a method that will return the sum of numbers in the list, so our
implementation will be something like this.
public static double
sum(List<Number> list){
double sum = 0;
for(Number n :
list){
sum
+= n.doubleValue();
}
return sum;
}
Now the problem with above implementation is
that it won’t work with List of Integers or Doubles because we know that
List<Integer> and List<Double> are not related, this is when upper
bounded wildcard is helpful. We use generics wildcard with extends keyword and the
upper bound class or
interface that will allow us to pass argument of upper bound or it’s subclasses
types.
package com.naresh.generics;
import java.util.ArrayList;
import java.util.List;
public class GenericsWildcards
{
public static void main(String[] mnb)
{
List<Integer>
ints = new ArrayList<>();
ints.add(3);
ints.add(5); ints.add(10);
double sum =
sum(ints);
System.out.println("Sum of
ints="+sum);
}
public static double sum(List<? extends Number>
list){
double sum = 0;
for(Number n :
list){
sum
+= n.doubleValue();
}
return sum;
}
}
Generics Unbounded Wildcard
Sometimes we have a situation
where we want our generic method to be working with all types, in this case
unbounded wildcard can be used. Its same as using <? extends Object>.
public static void
printData(List<?> list){
for(Object obj :
list){
System.out.print(obj
+ "::");
}
}
We can provide List<String> or List<Integer> or any
other type of Object list argument to the printData method. Similar to upper bound list,
we are not allowed to add anything to the list.
Generics Lower bounded Wildcard
Suppose we want to add Integers
to a list of integers in a method, we can keep the argument type as
List<Integer> but it will be tied up with Integers whereas
List<Number> and List<Object> can also hold integers, so we can use
lower bound wildcard to achieve this. We use generics wildcard (?) with super keyword and lower bound class to
achieve this.
We can pass lower bound or any
super type of lower bound as an argument in this case, java compiler allows to
add lower bound object types to the list.
public static void
addIntegers(List<? super Integer> list){
list.add(new Integer(50));
}
Subtyping using Generics Wildcard
List<? extends Integer>intList=new ArrayList<>()
List<? extends Number> numList = intList; // OK. List<? extends Integer>
is a subtype of List<? extends Number>
Type Erasure
Generics was added to provide
type-checking at compile time and it has no use at run time, so java compiler
uses type erasure feature to remove all the generics
type checking code in byte code and insert type-casting if necessary. Type
erasure ensures that no new classes are created for parameterized types;
consequently, generics incur no runtime overhead.
For example if we have a
generic class like below;
public class Test<T extends
Comparable<T>> {
private T data;
private Test<T> next;
public Test(T a,
Test<T> n) {
this.data = a;
this.next = n;
}
public T getData() { return this.data; }
}
The Java compiler replaces the bounded type
parameter T with the first bound interface, Comparable, as below code:
public class Test {
private Comparable data;
private Test next;
public Node(Comparable
d:Test t) {
this.data = d;
this.next = n;
}
public Comparable
getData() { return data; }
}
Thats all for generics in java, generics is a
really vast topic and requires a lot of time to understand and use it
effectively. This post here is an attempt to provide basic details of generics
and how can we use it to extend our program with type-safety.
Reference: Java Generics
Tutorial – Example Class, Interface, Methods, Wildcards and much more from JCG
.
No comments:
Post a Comment