1. Introduction

Type Inference was introduced in Java 5 to complement the introduction of generics and was substantially expanded in following Java releases, which is also referred to as Generalized Target-Type Inference.

In this tutorial, we’ll explore this concept with code samples.

2. Generics

Generics provided us with many benefits such as increased type safety, avoiding type casting errors and generic algorithms. You can read more about generics in this article.

However, the introduction of generics resulted in the necessity of writing boilerplate code due to the need to pass type parameters. Some examples are:

Map<String, Map<String, String>> mapOfMaps = new HashMap<String, Map<String, String>>();
List<String> strList = Collections.<String>emptyList();
List<Integer> intList = Collections.<Integer>emptyList();

3. Type Inference Before Java 8

To reduce the unnecessary code verbosity due, Type Inference was introduced to Java which is the process of automatically deducing unspecified data types of an expression based on the contextual information.

Now, we can invoke the same generic types and methods without specifying the parameter types. The compiler automatically infers the parameter types when needed.

We can see the same code using the new concept:

List<String> strListInferred = Collections.emptyList();
List<Integer> intListInferred = Collections.emptyList();

In the above example, based on the expected return types List and List, the compiler is able to infer the type parameter to the following generic method:

public static final <T> List<T> emptyList()

As we can see, the resulting code is concise. Now, we can call generic methods as an ordinary method if the type parameter can be inferred.

In Java 5, we could do Type-Inference in specific contexts as shown above.

Java 7 expanded the contexts in which it could be performed. It introduced the diamond operator <>. You may read more about the diamond operator in this article.

Now, we can perform this operation for generic class constructors in an assignment context. One such example is:

Map<String, Map<String, String>> mapOfMapsInferred = new HashMap<>();

Here, the Java Compiler uses the expected assignment type to infer the type parameters to HashMap constructor.

4. Generalized Target-Type Inference – Java 8

Java 8 further expanded the scope of Type Inference. We refer to this expanded inference capability as Generalized Target-Type Inference. You may read the technical details here.

Java 8 also introduced Lambda Expressions. Lambda Expressions do not have an explicit type.  Their type is inferred by looking at the target type of the context or situation. The Target-Type of an expression is the data type that the Java Compiler expects depending on where the expression appears.

Java 8 supports inference using Target-Type in a method context. When we invoke a generic method without explicit type arguments, the compiler can look at the method invocation and corresponding method declarations to determine the type argument (or arguments) that make the invocation applicable.

Let us look into an example code:

static <T> List<T> add(List<T> list, T a, T b) {
    list.add(a);
    list.add(b);
    return list;
}

List<String> strListGeneralized = add(new ArrayList<>(), "abc", "def");
List<Integer> intListGeneralized = add(new ArrayList<>(), 1, 2);
List<Number> numListGeneralized = add(new ArrayList<>(), 1, 2.0);

In the code, ArrayList<> does not provide the type argument explicitly. So, the compiler needs to infer it. First, the compiler looks into the arguments of the add method. Then, it looks into the parameters passed at different invocations.

It performs invocation applicability inference analysis to determine whether the method applies to these invocations. If multiple methods are applicable due to overloading, the compiler would choose the most specific method.

Then, the compiler performs invocation type inference analysis to determine the type arguments. The expected target types are also used in this analysis. It deduces the arguments in the three instances as ArrayList, ArrayList and ArrayList.

Target-Type inference allows us to not specify types for lambda expression parameters:

List<Integer> intList = Arrays.asList(5, 2, 4, 2, 1);
Collections.sort(intList, (a, b) -> a.compareTo(b));

List<String> strList = Arrays.asList("Red", "Blue", "Green");
Collections.sort(strList, (a, b) -> a.compareTo(b));

Here, the parameters a and b do not have explicitly defined types.  Their types are inferred as Integer in the first Lambda Expression and as String in the second.

5. Conclusion

In this quick article, we reviewed Type Inference, that along with generics and Lambda Expression enables us to write concise Java code.

As usual, the full source code can be found over on Github.