-
Notifications
You must be signed in to change notification settings - Fork 17
Java Generics Basic Guide
As we know that, Generics was added in Java 5 to 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.
Generics enable types (classes and interfaces) to be parameters when defining classes, interfaces and methods. Much like the more familiar formal parameters used in method declarations, type parameters provide a way for you to re-use the same code with different inputs. The difference is that the inputs to formal parameters are values, while the inputs to type parameters are types.
A Java compiler applies strong type checking to generic code and issues errors if the code violates type safety. Fixing compile-time errors is easier than fixing runtime errors, which can be difficult to find.
Example: In this example, List hold only a String type of objects in generics. It doesn’t allow to store other objects
List<String> list = new ArrayList<String>();
list.add("abc");
The following code snippet without generics requires casting:
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);
When re-written to use generics, the code does not require casting:
List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0); // no cast
By using generics, programmers can implement generic algorithms that work on collections of different types, can be customized, and are type safe and easier to read.
A generic type is a class or interface that is parameterized over types. We use angle brackets (<>) to specify the type parameter. We can define our own classes with generics type.
Begin by examining a non-generic Box class that operates on objects of any type. It needs only to provide two methods: set, which adds an object to the box, and get, which retrieves it:
public class Box {
private Object object;
public void set(Object object) {
this.object = object;
}
public Object get() {
return object;
}
public static void main(String[] args) {
Box type = new Box();
type.set("String");
System.out.println(type.get());
Box type1 = new Box();
type1.set(100);
System.out.println(type1.get());
Integer integer = (Integer) type.get();
System.out.println(integer);
}
}
Since its methods accept or return an Object, you are free to pass in whatever you want, provided that it is not one of the primitive types.
There is no way to verify, at compile time, how the class is used. One part of the code may place an Integer in the box and expect to get Integers out of it, while another part of the code may mistakenly pass in a String, resulting in a runtime error.
For Example, If we set String value to first object like:
Box type = new Box();
type.set("String");
Now, we don't know what type it is so let's try to get integer value from it like:
Integer integer = (Integer) type.get();
System.out.println(integer);
In above, compiler forces to make a cast and this code throws exception like:
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
at com.javaguides.generics.classes.Box.main(Box.java:21)
A generic class is defined with the following format:
class name<T1, T2, ..., Tn> { /* ... */ }
The type parameter section, delimited by angle brackets (<>), follows the class name. It specifies the type parameters (also called type variables) T1, T2, ..., and Tn.
Let's re-write the Box class using Generics.
public class Box<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
public static void main(String[] args) {
Box<String> type = new Box<>();
type.set("String");
Box<Integer> type1 = new Box<>();
type1.set(100);
/*Integer integer = (Integer) type.get(); // compiler error
System.out.println(integer);*/
}
}
Note that from above program, cast is not required there and compiler gives an error:
Cannot cast from String to Integer
Collection Framework classes and interfaces are examples for Generics.
For Example: ArrayList class declaration from java.util package.
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
.......
}
For Example: HashSet class declaration from java.util package.
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
.....
}
For Example: HashMap class declaration from java.util package.
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
....
}
Iterable interface from jdk 8 - java.lang package is example for Generic interface
public interface Iterable<T> {
Iterator<T> iterator();
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
}
One more example for Generic interface is Comparable interface.
public interface Comparable<T> {
public int compareTo(T o);
}