Java Generics : What and Why?

Aditya Mahajan - Aug 8 - - Dev Community

What are Java Generics?

According to Javadocs

A generic type is a generic class or interface that is parameterized over types.

Now assuming you already know what is class and interface in Java(if not, please have a look here), we will go through what does parameterized over types means.

Now suppose we want to create a class, with a field name as fullName and field type as Object

public class ThisIsNonGenericClass {

  private Object fullName;

  public ThisIsNonGenericClass(Object fullName) {
    this.fullName = fullName;
  }

  public Object getFullName() {
    return fullName;
  }

  public void setFullName(Object fullName) {
    this.fullName = fullName;
  }
}
Enter fullscreen mode Exit fullscreen mode

Remember, here we are using Object 3 times.

Now if we want to print value for field fullName, we need
casting to String, since the compiler requires, explicit cast, for getting String out of Object.

public class ThisIsDevArticle {
  public static void main(String[] args) {
    ThisIsNonGenericClass thisIsNonGenericClass = new ThisIsNonGenericClass("AdityaMahajan");
    String name = (String) thisIsNonGenericClass.getFullName();
    System.out.println("This is name: " + name);
  }
}
Enter fullscreen mode Exit fullscreen mode

Lets introduce a parameter T at class level. This can replace the type for field as well as the return type of methods. In this way we just introduced a parameter to a class and made the class type parameterized. Here T has replaced Object when compared to ThisIsNonGenericClass

public class ThisIsGenericClass<T> {
  private T fullName;

  public T getFullName() {
    return fullName;
  }

  public ThisIsGenericClass(T fullName) {
    this.fullName = fullName;
  }

  public void setFullName(T fullName) {
    this.fullName = fullName;
  }
}
Enter fullscreen mode Exit fullscreen mode

Now lets try to print value for field fullName.

public class ThisIsDevArticle {
  public static void main(String[] args) {
    ThisIsGenericClass<String> thisIsGenericClass = new ThisIsGenericClass<>("AdityaMahajan");
    String name = thisIsGenericClass.getFullName();
  }
}
Enter fullscreen mode Exit fullscreen mode

What's the difference now?

By specifying the type as String here, ThisIsGenericClass<String> thisIsGenericClass, we were able to tell the class what type to use and now the parameter is identified as String and the getFullName is returning String. We even reduced the need of casting. Voila! This is Java Generics, and one advantage here is avoiding casting, by specifying the type argument as String.

Quick Terminology here.

ThisIsGenericClass<T> : T here is called Type Parameter.
ThisIsGenericClass<String> : String here is called Type argument.

Another callout is the diamond operator <> at the right can be empty as long as <> has String in the left.

ThisIsGenericClass<String> thisIsGenericClass = new ThisIsGenericClass<>("AdityaMahajan");
Enter fullscreen mode Exit fullscreen mode

The compiler is smart enough to figure out itself when the class is instantiated. This is called type inference, more on this here. It is equivalent to

ThisIsGenericClass<String> thisIsGenericClass = new ThisIsGenericClass<String>("AdityaMahajan");
Enter fullscreen mode Exit fullscreen mode

We could always use multiple parameters in the class or interface like,

public class ThisIsGenericClassWithMutipleParameters<T1,T2> {

  private T1 fullName;

  public T1 getFullName() {
    return fullName;
  }

  public void setFullName(T1 fullName) {
    this.fullName = fullName;
  }

  private T2 age;

  public T2 getAge() {
    return age;
  }

  public void setAge(T2 age) {
    this.age = age;
  }

public ThisIsGenericClassWithMutipleParameters(T1 fullName, T2 age) {
    this.fullName = fullName;
    this.age = age;
  }
}
Enter fullscreen mode Exit fullscreen mode

We can use above with something like

ThisIsGenericClassWithMutipleParameters<String,Integer> thisIsGenericClass2 
        = new ThisIsGenericClassWithMutipleParameters<>("AdityaMahajan", 26);
Enter fullscreen mode Exit fullscreen mode

Generic Methods

We can also use parameters in methods as well, and in next example, see how this can avoid runtime exception, catching compile time errors.

Suppose we have a method to calculate total length of string accepting List as method argument.

  public int getTotalLengthOfString(List names) {
    int len = 0 ;
    for(int i = 0; i<names.size(); i++){
      String name = (String) names.get(i);
      len += name.length();
    }
    return len;
  }
Enter fullscreen mode Exit fullscreen mode

If we do something like.

List names = new ArrayList<>();
names.add("Medvedev");
names.add("Tsitsipas");
System.out.println("This is total length : " + getTotalLengthOfString(names));
Enter fullscreen mode Exit fullscreen mode

The output will be

This is total length : 17
Enter fullscreen mode Exit fullscreen mode

However let say, for some reason we added another item, here an Integer to the List.

String nameWithoutCast = thisIsGenericClass.getFullName();
List names = new ArrayList<>();
names.add("Medvedev");
names.add("Tsitsipas");
names.add(32);
System.out.println("This is total length : " + getTotalLengthOfString(names));
Enter fullscreen mode Exit fullscreen mode

And running the program led to a ClassCastException, which occurs while we run a program, a child class of RuntimeException. Following is the output.

Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')
Enter fullscreen mode Exit fullscreen mode

How to avoid this?

Now lets update the method parameter from List to List<String>. Now doing this will also make us change the instantiation from
List names = new ArrayList<>() to List<String> names = new ArrayList<>(). And if you see, we dont even need casting.

  public int getTotalLengthOfString(List<String> names) {
    int len = 0 ;
    for(int i =0; i<names.size(); i++){
      len += name.length();
    }
    return len;
  }
Enter fullscreen mode Exit fullscreen mode

Now if someone try something like this,

List<String> names = new ArrayList<>();
names.add("Medvedev");
names.add("Tsitsipas");
names.add(32);
System.out.println("This is total length : " + getTotalLengthOfString(names));
Enter fullscreen mode Exit fullscreen mode

compiler will complain and ask developer to fix it.

Compiler complaining! strong type check!

With this we made the compiler do tighter type checks, during compile time, and avoiding ClassCastException during runtime. Another advantage of using Generics!

Some terminology here :
List<T> names : Here we are using generic-type. And is a parameterized type with no argument.
List<String> names : Here we are using generic-type. And is a parameterized type with argument String.
List names : Here the names is raw-type of List<T>, with no parameters, no arguments.

That is all for introduction, let me know your thoughts in the comments.

Reference

  1. https://docs.oracle.com/javase/tutorial/java/generics/why.html and others in the same section
  2. https://www.youtube.com/watch?v=CKWw7J5MsyY by Jakob Jenkov
  3. https://docs.oracle.com/javase/tutorial/java/generics/QandE/generics-answers.html
.
Terabox Video Player