Java Generic, Generic with super and wildcard

Generics allow you to specify concrete types to general-purpose classes and methods that operate on java.lang.Object before. It was added to Java from Java 5. The Java collection framework used generics a lot.

Let see some example of life without generics.

List list = new ArrayList(); 
list.add("String");
list.add("1");
list.add(1);

In the above example, we have created a list without specifying any data type. This means we can add data of any type to it and it requires external typecasting during retrieval. As soon as we try to access the element and assign it to a different data type, it will greet you with errors like the below:

Integer num =  list.get(2);

//Type mismatch: cannot convert from Object to Integer

There are two solutions to it.

1. Cast it to Integer
2. Change the type of num to Object.

To avoid such issues we use generic, let’s rewrite the above code with generics.

List<String> list = new ArrayList<>(); 
list.add("String");
list.add("1");
// list.add(1);

Now as soon as we defined the type of List as “String” if we try to add an Integer to it as per the commented line, the compiler will prompt the error as follow:

The method add(int, String) in the type List is not applicable for the arguments (int).

This is the benefit of using generic.

Generic Classes:

Similar to generic classes defined inside Java APIs, we can also define our generic classes.

Refer to the below Generic class Implementation with an example.

class InstanceFactory<T> {

    Class<T> theClass = null;
    
        public InstanceFactory(Class<T> theClass) {
            this.theClass = theClass;
        }
    
        public T getInstance() throws IllegalAccessException, InstantiationException {
            return this.theClass.newInstance();
        }
   }  
      
    public class GenericInDepth {

    public static void main(String[] args) throws IllegalAccessException, InstantiationException {
        
        InstanceFactory<Animal> factory = new InstanceFactory<>(Animal.class);

        Animal instance = factory.getInstance();
        
        instance.showType();
        
    }
}

Generic Methods:

We can also define generic methods in Java-like we did generic classes. Let see the below code snippet.

public class GenericInDepth {

    public static void main(String[] args) {
        
        List<String> list = new ArrayList<>();
        list.add("hello");
        System.out.println("string list 1 "+ list);
    
        addElement("world", list);
        
        System.out.println("string list 2 "+ list);
        
        
        List<Integer> nums = new ArrayList<>();
        
        addElement(12, nums);
        
        System.out.println("int list 1 "+ nums);
        
    }
   
    public static <T> void addElement(T t, Collection<T> col) {
         col.add(t);
    }   
}

Output:

string list 1 [hello]
string list 2 [hello, world]
int list 1 [12]

Generic and wildcard:

The Java generic allows us to cast the collection of a certain class to a collection of subclass or superclass. Let see the problem without a wildcard first.

Let’s create Three classes Pet, Cat, and Dog as below. Where Pet is the parent class of both Cat and Dog.

class Pet {

    private int petId;
    private String type;
    
    public Pet(int petId, String type) {
        this.petId = petId;
        this.type= type;
    }

    @Override
    public String toString() {
        return "Pet [petId=" + petId + ", type=" + type + "]";
    }

    public String getType() {
        return this.type;
    }
}

class Cat extends Pet{

    public Cat(int petId) {
        super(petId, "Cat");
    }
    
}

class Dog extends Pet{

    public Dog(int petId) {
        super(petId, "Dog");
    }
    
}

Now let’s create three List using the Pet, Cat, and Dog classes as following.

public class GenericInDepth {

    public static void main(String[] args) {
        
        List<Pet> pList = new ArrayList<>(); 
        pList.add(new Pet(1, "Cat"));
        
        pList.add(new Cat(4));
        
        
        System.out.println(pList);
        
        List<Cat> cList = new ArrayList<>();
        
        cList.add(new Cat(2));
        
        System.out.println(cList);
        
        List<Dog> dList = new ArrayList<>();
        dList.add(new Dog(3));
    
        System.out.println(dList);
	}
}

We can only add an object of Pet and objects of its subclasses Cat and Dog in pList. But if we do the following it will prompt compile-time error, Type mismatch: cannot convert from List to List

pList = cList;

The reason for the compile-time error is:

If you could make this assignment, it would be possible to insert Pet and Dog instances into the List pointed to by CList. You could do that via the pList reference, which is declared to be of List. Thus you could insert non Cat objects into a list declared to hold Cat (or Cat subclass) instances.

Now suppose we have defined a method to print the type of pet from a collection of pets.

public static void printType(List<Pet> pets) {
  System.out.println("printing type");
  for(Pet p : pets) {
        System.out.println(p.getType());
  }
}

The method printType() will not work if we pass it to the list of Cat or list of Dog.

The need for a wildcard in generic arise to solve the above-mentioned problem.

Type of wildcard generics:

1. The ? extends wildcard.
2. The ? super wildcard.

To understand the use of extends read inheritance in java

Use the ? extends wildcard if you need to retrieve an object from a collection, it is read-only. You can’t add elements to the collection. Which means retrieval of the instance from the collection of Pet or its subclasses.

Use the ? super wildcard if you need to add objects in a collection, it will allow adding an object of Pet or its subclasses.

Java Generic and wildcard

Let’s modify the printType() method to accept the child of Pet (both Cat and Dog) as well to retrieve the object.

public static void printType(List<? extends Pet> pets) {
      // pets.add(new Cat(5)); 
	System.out.println("printing type");
    for(Pet p : pets) {
    	System.out.println(p.getType());
	}
}

But if we try to add an object of Cat to “pets” inside the above method as the commented line. It will prompt compile-time error as extends only allow retrieval.

To add items into this collection we can create a method addPet() like below.

public static void addPet(Pet p, List<? super Pet> pets) {
     pets.add(p);
}

Now if we call addPet(new Cat(6),pList); it will work perfectly.

Reference:

https://docs.oracle.com/javase/tutorial/java/generics/index.html

Happy Learning !!

Leave a Comment