Java Generics and Type Inference

Java is a statically typed language. This means the data type of every variable is known at compile time. While this saves time in catching errors, sometimes you need the flexibility of a class or interface that accepts more than one data type. By using generics, you can do just that. In this article, we discuss the concept of generics in Java. We'll explain how generics work to parametrize data types and provide examples of declaring generic methods and classes. We'll also discuss the concept of type inference and the role it plays in working with generics.

What are Generics?

Generics are methods and classes that parametrize data types. Originally introduced in Java 5, generics provide more flexibility with the classes, methods, and interfaces you define.

A great example of generics can be found with collections. If you've worked with a Hash Map or Array in Java, you've already worked with generics!

Take an Array for example. If you wanted to create a simple array of integers, it could look something like this:

int[] myInts = new int[]{1,2,3}

What about an array of strings?

String[] myStrings = new String[]{"hello","goodbye"}

So how do collections like Array support multiple data types? The answer is generics. Specifically, the Array class parameterizes the type so it has the flexibility to instantiate an array having any data type.

Generic Method Example

While util classes like Array demonstrate the value of generics, a better example can be illustrated through creating our own generic method. The following is an example of a generic method defined inside a class MyPerson:

public class  MyPerson {
 public static <E> void printVar(E var) {
    System.out.println(var);
 }
 public static void main(String args[]) {
    String myName = "John";
    Integer myAge = 23;
    printVar(myName);
    printVar(myAge);
 }
}

Notice how our MyPerson class defines a single method printVar(). This method takes a single parameter var of generic type E. This works because we include a special type parameter section <E> before defining the return type. While you can use any alphabetical set of characters to represent a generic type, single uppercase letters are the conventional way of representing generic data types. We've stuck with the commonly used E. Other popular type parameter names include:

  • K - Key
  • N - Number
  • T - Type
  • V - Value

Remember that you can use any combination of letters however these are the most commonly used.

Finally, notice how our main() function invokes the printVar method twice: once with the myName variable of type String and once with the myAge variable of type Integer. It is because of defining the printVar() method with a generic type E that we can use both data types with our method.

Generic Class Example

We can define a generic class in a similar way:

public class MyPerson<T> {
  private T t;
  public T get(){
    return this.t;
  }
  public void set(T t1){
    this.t=t1;
  }
  public static void main(String args[]){
    MyPerson<String> type = new MyPerson<>();
    type.set("Josh");
    MyPerson<Integer> type1 = new MyPerson();
    type1.set(23);
  }
}

Similar to our generic method example, we are able to initialize two instances of MyClass taking a generic type T.

Type Inference

Take another look at the generic class example above. Notice how we didn't specify anything inside the <> in our first instantiation of MyClass. Also notice the absence of <> altogether in the second instantiation. It is because of type inference that we can create instances of these generic classes without specifying a data type.

Type inference is the automatic detection of data types. It is a characteristic of functional programming that allows us to invoke a generic type or method without having to specify the type. This is because the Java compiler is smart enough to deduce the type based on the left hand side of the expression.

Type inference makes programming tasks easier as you can safely omit type signatures without losing the advantages of type checking. Whenever a generic type is being created or a generic method is being invoked, the compiler can reason with context and save you the task of adding type signatures to every method, type, class you invoke.

Conclusion

Generics are an important feature of Java programming. Using generics, it's possible to create classes and methods that accept multiple data types. Type inference makes this even easier as the compiler can deduce what data types are being used with generics. This makes your code more flexible and easier to follow without jeopardizing the power of type checking in Java.

Your thoughts?