Quick Refresh : Java 1.5 : Generics
Why Use Generics?
In a nutshell, generics enable types (classes and interfaces) to be parameters when defining classes, interfaces and methods. Much like the more familiar formal parametersused 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. It was introduced in java 1.5.
Benefits of Code that uses generics over non-generic code:
- Type-safety: We can hold only a single type of objects in generics. It doesn't allow to store other objects. Without Generics, we can store any type of objects.
List list = new ArrayList();list.add(10);list.add("10");With Generics, it is requiredto specify the type of object we need to store. List<Integer> list = newArrayList<Integer>(); list.add(10);list.add("10");// compile-time error
- Compile-Time Checking: A Java compiler applies strong type checking to generic code and issues errors if the code violates type safety. The good programming strategy says it is far better to handle the problem at compile time than runtime.
- Type casting is not required: There is no need to typecast the object.
The following code snippet without generics requires casting:When re-written to use generics, the code does not require casting:List list = new ArrayList(); list.add("hello"); String s = (String) list.get(0);
List<String> list = new ArrayList<String>(); list.add("hello"); String s = list.get(0); // no cast
- Enabling programmers to implement generic algorithms: 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.
How Generics works in Java
In the heart of generics is “type safety“. What exactly is type safety? It’s just a guarantee by compiler that if correct Types are used in correct places then there should not be any ClassCastException
in runtime. A usecase can be list of Integer
i.e. List<Integer>
. If you declare a list in java like List<Integer>
, then java guarantees that it will detect and report you any attempt to insert any non-integer type into above list.
Another important term in java generics is “type erasure“. It essentially means that all the extra information added using generics into sourcecode will be removed from bytecode generated from it. Inside bytecode, it will be old java syntax which you will get if you don’t use generics at all. This necessarily helps in generating and executing code written prior java 5 when generics were not added in language. Precisely, Generics in Java is nothing but a syntactic sugar to your code for Type Safety and all such type information is erased by Type Erasure feature by compiler.
Generic class or Interface: A class/interface that can refer to any type is known as a generic class. Here, we are using the T type parameter to create the generic class/interface of specific type.
Let's see a simple example to create and use the generic class.
Creating a generic class:
Using generic class:
Let's see the code to use the generic class.
Output
2
Note: If we don’t provide the type at the time of creation, compiler will produce a warning that “GenericsType is a raw type. References to generic type GenericsType<T> should be parameterized”. When we don’t provide type, the type becomes Object
and hence it’s allowing both String and Integer objects but we should always try to avoid this because we will have to use type casting while working on raw type that can produce runtime errors.
Tip: We can use @SuppressWarnings("
annotation to suppress the compiler warning,
Type Parameters: The type parameters naming conventions are important to learn generics thoroughly. The common type parameters are as follows:
- T - Type
- E - Element
- K - Key
- N - Number
- V - Value
Generic Method or Constructor: Like the generic class, we can create a generic method or constructor that can accept any type of arguments. Here, the scope of arguments is limited to the method where it is declared. It allows static as well as non-static methods.
- All generic method declarations have a type parameter section delimited by angle brackets (< and >) that precedes the method's return type.
- Type parameters can be bounded (bounds are explained later in the article)
- Generic methods can have different type parameters separated by commas in the method signature
- Method body for a generic method is just like a normal method
- We can specify type while calling these methods or we can invoke them like a normal method. Java compiler is smart enough to determine the type of variable to be used, this facility is called as type inference.
Let's see a simple example of java generic method to print array elements. We are using here E to denote the element.
Generic Type Arrays
Array in any language have same meaning i.e. an array is a collection of similar type of elements. In java, pushing any incompatible type in an array on runtime will throw ArrayStoreException
. It means array preserve their type information in runtime, and generics use type erasure or remove any type information in runtime. Due to above conflict, instantiating a generic array in java is not permitted.
public class GenericArray<T> { // this one is fine public T[] notYetInstantiatedArray; // causes compiler error; Cannot create a generic array of T public T[] array = new T[ 5 ]; } |
In the same line as above generic type classes and methods, we can have generic arrays in java. As we know that an array is a collection of similar type of elements and pushing any incompatible type will throw ArrayStoreException
in runtime; which is not the case with Collection
classes.
Object[] array = new String[ 10 ]; array[ 0 ] = "lokesh" ; array[ 1 ] = 10 ; //This will throw ArrayStoreException |
Above mistake is not very hard to make. It can happen anytime. So it’s better to provide the type information to array also so that error is caught at compile time itself.
Another reason why arrays does not support generics is that arrays are co-variant, which means that an array of supertype references is a supertype of an array of subtype references. That is, Object[]
is a supertype of String[]
and a string array can be accessed through a reference variable of type Object[]
.
Object[] objArr = new String[ 10 ]; // fine objArr[ 0 ] = new String(); |
Wildcard in Java Generics
The ? (question mark) symbol represents the wildcard element. It means any type. If we write <? extends Number>, it means any child class of Number, e.g., Integer, Float, and double. Now we can call the method of Number class through any child class object.
We can use a wildcard as a type of a parameter, field, return type, or local variable. However, it is not allowed to use a wildcard as a type argument for a generic method invocation, a generic class instance creation, or a supertype.
Output
drawing rectangle
drawing circle
drawing circle
Upper Bounded Wildcards
The purpose of upper bounded wildcards is to decrease the restrictions on a variable. It restricts the unknown type to be a specific type or a subtype of that type. It is used by declaring wildcard character ("?") followed by the extends (in case of, class) or implements (in case of, interface) keyword, followed by its upper bound.
Syntax
Here,
? is a wildcard character. extends, is a keyword. Number, is a class present in java.lang package
Suppose, we want to write the method for the list of Number and its subtypes (like Integer, Double). Using List<? extends Number> is suitable for a list of type Number or any of its subclasses whereas List<Number> works with the list of type Number only. So, List<? extends Number> is less restrictive than List<Number>.
Example of Upper Bound Wildcard
In this example, we are using the upper bound wildcards to write the method for List<Integer> and List<Double>.
Output
displaying the sum= 30.0
displaying the sum= 70.0
Unbounded Wildcards
The unbounded wildcard type represents the list of an unknown type such as List<?>. This approach can be useful in the following scenarios: -
- When the given method is implemented by using the functionality provided in the Object class.
- When the generic class contains the methods that don't depend on the type parameter.
Example of Unbounded Wildcards
Output
displaying the Integer values
1
2
3
displaying the String values
One
Two
Three
Lower Bounded Wildcards
The purpose of lower bounded wildcards is to restrict the unknown type to be a specific type or a supertype of that type. It is used by declaring wildcard character ("?") followed by the super keyword, followed by its lower bound.
Syntax
Here, ? is a wildcard character. super, is a keyword. Integer, is a wrapper class.
Suppose, we want to write the method for the list of Integer and its supertype (like Number, Object). Using List<? super Integer> is suitable for a list of type Integer or any of its superclasses whereas List<Integer> works with the list of type Integer only. So, List<? super Integer> is less restrictive than List<Integer>.
In this example, we are using the lower bound wildcards to write the method for List<Integer> and List<Number>.
Output
displaying the Integer values
1
2
3
displaying the Number values
1.0
2.0
3.0
Multiple Bounds: A type can also have multiple upper bounds as follows:
1 <T
extends
Number & Comparable>
1 | <T extends Number & Comparable> |
If one of the types that are extended by T is a class (i.e Number), it must be put first in the list of bounds. Otherwise, it will cause a compile-time error.
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.
Subtyping using Generics Wildcard:
List<? extends Number> numList = intList; // OK. List<? extends Integer> is a subtype of List<? extends Number>
Java 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.
What is not allowed to do with Generics?
So far we have learned about a number of things which you can do with generics in java to avoid many ClassCastException
a) You can’t have static field of type
You can not define a static generic parameterized member in your class. Any attempt to do so will generate compile time error: Cannot make a static reference to the non-static type T.
public class GenericsExample< { private static T member; //This is not allowed } |
b) You can not create an instance of T
Any attempt to create an instance of T will fail with error: Cannot instantiate the type T.
public class GenericsExample< { public GenericsExample(){ new T(); } } |
c) Generics are not compatible with primitives in declarations
Yes, it’s true. You can’t declare generic expression like List or Map<String, double>. Definitely you can use the wrapper classes in place of primitives and then use primitives when passing the actual values. These value primitives are accepted by using auto-boxing to convert primitives to respective wrapper classes.
final List< int > ids = new ArrayList<>(); //Not allowed final List<Integer> ids = new ArrayList<>(); //Allowed |
d) You can’t create Generic exception class
Sometimes, programmer might be in need of passing an instance of generic type along with exception being thrown. This is not possible to do in Java.
// causes compiler error public class GenericException< extends Exception {} |
When you try to create such an exception, you will end up with message like this: The generic class GenericException
may not subclass java.lang.Throwable
.
e) Java 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.journaldev.generics;
public class GenericsInheritance {
public static void main(String[] args) {
String str = "abc";
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>{}
}
Comments
Post a Comment