08 Java - Classes (Part II - Generic Class)
Introduction to Generic Classes
If we have an object with a field of type Object
, then this object can hold any type of class because Object
is the parent class of all classes. Therefore, when accessing the field of this class, we should be careful because it can potentially throw compile-time errors. Hence, we need to perform type checking or type casting before using the value. This is why we use generic classes to address this problem.
package learn.generics;
public class Print {
Object value;
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
package learn.generics;
public class Main {
public static void main(String[] args) {
Print printObj = new Print();
printObj.setValue(1);
Object printValue = printObj.getValue();
// We cannot use printValue directly,
// we have to typecast it, else it will be compiled time error
if ((int) printValue == 1){
//do something
}
}
}
Usage of Generic Classes
package learn.generics;
public class Print<T> {
T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
Generic Type (in above example <T>) can be any non-primitive object
package learn.generics;
public class Main {
public static void main(String[] args) {
Print<Integer> printObj = new Print<>();
printObj.setValue(1);
Integer printValue = printObj.getValue();
if (printValue == 1){
//do something
}
}
}
/*
we can also define like this also
Print<Integer> printObj = new Print<Integer>();
this will also work but redundant
*/
Inheritance with Generic Classes
Non Generic Class
package learn.generics;
public class Print<T> {
T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
Inherited to ColorPrint
package learn.generics;
public class ColorPrint extends Print<String>{
}
Usage
package learn.generics;
public class Main {
public static void main(String[] args) {
ColorPrint colorPrint = new ColorPrint();
colorPrint.setValue("hello");
}
}
Generic Sub class
package learn.generics;
public class ColorPrint<T> extends Print<T>{
}
Usage
package learn.generics;
public class Main {
public static void main(String[] args) {
ColorPrint<String> colorPrint = new ColorPrint<>();
colorPrint.setValue("hello");
}
}
More than one of Generic types
class className<T>
class className<T1, T2, T3...Tn>
package learn.generics;
public class Pair<K,V> {
private K key;
private V value;
public void put(K key, V value){
this.key = key;
this.value = value;
}
}
usage
package learn.generics;
public class Main {
public static void main(String[] args) {
Pair<String, Integer> pairObj = new Pair<>();
pairObj.put("Hello", 1);
}
}
Generic Method
What if we conly wants to make Method Generic, not the complete Class, we can write Generic Methods too.
Type Parameter should be before the return type of the method declaration.
Type Parameter scope is limited to method only.
package learn.generics;
public class Pair<K,V> {
private K key;
private V value;
public void put(K key, V value){
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
}
Example of generic method
package learn.generics;
public class GenericMethod {
public <K,V> void printValue(Pair<K,V> pair1, Pair<K,V> pair2){
if (pair2.getKey().equals(pair2.getKey())){
//do something
}
}
}
/*
public class Test{
public <T> void setValue(T value)
}
*/
Raw Type
It's a name of the generic class or interface without any type argument.
package learn.generics;
public class Print<T> {
T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
usage
package learn.generics;
public class Main {
public static void main(String[] args) {
Print<Integer> parameterizeTypePrintObject = new Print<>();
//Internally it passes object as parameterized type.
Print rawTypePrintObject = new Print();
rawTypePrintObject.setValue(1);
rawTypePrintObject.setValue("hello");
}
}
Bounded Generices
It can be used at generic class and method
Upper Bound (<T extends Number>) means T can be of type Number or its Subclass only. Here superclass (in this example Number) we can have interface too.
Multi Bound
Upper Bound
(Here the extends indicated the type of the class/Interface T can be not the actual inheritance)
package learn.generics;
public class Print<T extends Number> {
T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
Multi Bound
<T extends Superclass & interface 1 & interface N>
The first restrictive type should be concrete class
2, 3 and so on... can be interfaces
package learn.generics;
public class A extends ParentClass implements Interface1, Interface2{
}
multi bound usage
package learn.generics;
public class Print<T extends ParentClass & Interface1 & Interface2> {
T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
package learn.generics;
public class Main {
public static void main(String[] args) {
Print<A> obj = new Print<>();
obj.setValue(new A());
}
}
Negative scenario:
package learn.generics;
public class A extends ParentClass implements Interface1{
}
package learn.generics;
public class Print<T extends ParentClass & Interface1 & Interface2> {
T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
This is Compilation error since A didn't implement interface 2.
package learn.generics;
public class Main {
public static void main(String[] args) {
Print<A> obj = new Print<>();
obj.setValue(new A());
}
}
Wildcards:
Upper bounded wildcard: <? extends UpperBoundClassName> i.e. class Name and below.
Lower bounded wildcard: <? Super LowerBoundClassName> i.e. class Name and above
Unbounded wildcard <?> only you can read. The type is nothing but
Object.class
Problem without wildcard
public class Print {
public void setPrintValues(List<Vehicle> vehicleList){
}
}
public class Main {
public static void main(String[] args) {
List<Vehicle> vehicleList = new ArrayList<>();
List<Bus> busList = new ArrayList<>();
Print printObj = new Print();
printObj.setPrintValues(vehicleList); //allowed
printObj.setPrintValues(busList); //Not allowed
}
}
// Vehicle is the parent class of Bus
List<Vehicle> vehicleList = new ArrayList<>();
List<Bus> busList = new ArrayList<>();
vehicleList = busList;
vehicleList.add(new Car()); // Adding a Car to a list of Buses
Bus firstBus = busList.get(0); // This would cause ClassCastException
Solution using wild card
public class Print {
public void setPrintValues(List<? extends Vehicle> vehicleList){
}
}
public class Main {
public static void main(String[] args) {
List<Vehicle> vehicleList = new ArrayList<>();
List<Bus> busList = new ArrayList<>();
Print printObj = new Print();
printObj.setPrintValues(vehicleList); //allowed
printObj.setPrintValues(busList); //allowed
}
}
Example
public class Print {
public void setPrintValues(List<? super Vehicle> vehicleList){
}
}
public class Main {
public static void main(String[] args) {
Print printObj = new Print();
List<Object> objects = new ArrayList<>();
printObj.setPrintValues(objects);
}
}
Difference between Wild card (?) and Generic Type (T)?
In generic type it will force all the parameters of the same where as wild card it is flexible.
We cannot use super keyword in generic type method
In wild card we cannot have more question marks
?,?,?
but in generic type we can have more no of typesK,V,T
public class Print {
//wild card method
public void computeList(List<? extends Number> source, List<? extends Number> destination){
}
//Generic type method
public <T extends Number> void computeList1(List<T> source, List<T> destination){
}
}
public class Main {
public static void main(String[] args) {
List<Integer> wildCardIntegerSourceList = new ArrayList<>();
List<Float> wildCardIntegerDestinationList = new ArrayList<>();
Print printObj = new Print();
printObj.computeList(wildCardIntegerSourceList,
wildCardIntegerDestinationList);
// Error forcing us the parameters to be of same type
//hence compilation error
printObj.computeList1(wildCardIntegerSourceList,
wildCardIntegerDestinationList);
}
}
Type Erasure
Whenever the byte code generated for Generic class all the generic types are replaced with actual types
Generic Class Type erasure
java class
public class Print<T> {
T value;
public void setValue(T val){
this.value = val;
}
}
byte code
public class Print {
Object value;
public void setValue(Object val){
this.value = val;
}
}
Generic Class Bound Type erasure
java class
public class Print<T extends Number> {
T value;
public void setValue(T val){
this.value = val;
}
}
byte code
public class Print {
Number value;
public void setValue(Number val){
this.value = val;
}
}
Generic Method Erasure
java class
public class Print {
public <T> void setValue(T val){
System.out.println("do something");
}
}
byte code
public class Print {
public void setValue(Object val){
System.out.println("do something");
}
}
Generic Bound Type Method Erasure
java class
public class Print {
public <T extends Bus> void setValue(T val){
System.out.println("do something");
}
}
byte code
public class Print {
public void setValue(Bus val){
System.out.println("do something");
}
}
Subscribe to my newsletter
Read articles from Chetan Datta directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Chetan Datta
Chetan Datta
I'm someone deeply engrossed in the world of software developement, and I find joy in sharing my thoughts and insights on various topics. You can explore my exclusive content here, where I meticulously document all things tech-related that spark my curiosity. Stay connected for my latest discoveries and observations.