Java Generics

Introduction

Java generics is a powerful feature introduced in JDK 5.0 that was directed towards improving the quality of code through abstraction. It aims to improve code reusability by providing a mechanism to write classes and functions in such a way that types can be passed as parameters.

Let’s see some code with and without generics. Now, you all must be aware of the list data structure, let us see it in action!

// A list of strings without using Generics
List listWithoutGenerics = new ArrayList();
listWithoutGenerics.add("Hello");
String helloString1 = (String) listWithoutGenerics.get(0);

// A list of string using Generics
List<String> listWithGenerics = new ArrayList<String>();
listWithGenerics.add("Hello");
String helloString2 = listWithGenerics.get(0);

Using generics helps us avoid explicit type casting in such scenarios. By adding the <> operator, we helped the compiler narrow down the type of the list, i.e., String which can then be enforced by it.

Generic Methods

Generic methods provide great reusability, a single method can be written for which works for arguments of multiple types. Generic methods have some important properties:

  • They have a diamond <> operator before the type.
  • The diamond operator can contain multiple types separated by commas. (Example – <T, V>)

public class Main {
    public static <T, V> void addNumbers(T t, V v) {
        System.out.println(t.getClass()); // prints - class java.lang.Integer
        System.out.println(v.getClass()); // prints - class java.lang.String
    }

    public static void main(String[] args) {
        addNumbers(5, "Hello!");
    }
}

The example below shows how to convert an array of objects to a list of objects:

public class Main {
    public static <T> List<T> fromArrayToList(T[] array) {
        return Arrays.stream(array).collect(Collectors.toList());
    }

    public static void main(String[] args) {
        String[] stringArray = {"Hello", "World"};
        Integer[] integerArray = {1, 2, 3, 4, 5};
        fromArrayToList(stringArray).forEach(System.out::print);
        System.out.println();
        fromArrayToList(integerArray).forEach(System.out::print);
    }
}

Bounded Generics

Imagine yourself writing the function to add two numbers. Your first instinct would to be to write a generic method that looks something like this:

public static <T> void addTwoNumbers(T a, T b){
    System.out.println(a + b);
}

Sadly, this snippet of code will give you an error. Why, you ask? Simply put, the compiler is not a magician and thus, cannot do something like adding two cars or pigeons!

The good news is that we do not need to be magicians to perform an addition of two numbers. We can restrict the type of an object that is passed to the function (a Number in this case). By doing so, we are “binding” the values of T to Number.

public static <T extends Number> void addTwoNumbers(T a, T b){
    // Assume that we need only integer addition for this example
    System.out.println(a.intValue() + b.intValue());
}

Wild cards and Generics

You must be aware that Object is the super type of java classes. However, it is very important to note that a collection of Object is not a super type for any collection. For example, Object is a super type for String class but List<Object> is not a super type for List<String>.

public static void driveVehicles(List<Vehicle> vehicles){
    vehicles.forEach(Vehicle::drive);
}

If you have a function like this, it would not work if you pass a list of cars List<Car> (assuming Vehicle is the super type for Car). You can get around this problem using wildcards (?). Using the solution below will allow you to use driveVehicles() with all subtypes of Vehicle.

public static void driveVehicles(List<? extends Vehicle> vehicles){
    vehicles.forEach(Vehicle::drive);
}

Type Erasure

Contrary to popular belief, Java Generics are not a runtime feature but a compile-time feature. This essentially means that Java replaces all the type parameters with the bounded object if available, Object class otherwise. This concept is called Type Erasure.

This means that a List<T> actually becomes List<Object> after compilation. It also explains the reason why you cannot pass primitive data types (Ex. Object is not a supertype for int).

No Comments

Post A Comment