Skip to content

Instantly share code, notes, and snippets.

@esfand
Last active August 29, 2015 14:02
Show Gist options
  • Save esfand/eb455b800516717ca33e to your computer and use it in GitHub Desktop.
Save esfand/eb455b800516717ca33e to your computer and use it in GitHub Desktop.
Type Inference in Java

Type Inference in Java#

Slides: http://www.ba-horb.de/~pl/talks/Oberseminar_TI_Java8.pdf

Why

Technically speaking, Java does have type inferencing when using generics. With a generic method like

public <T> T foo(T t) {
    return t;
}

The compiler will analyze and understand that when you write

// String
foo("bar");

// Integer
foo(new Integer(42));

A String is going to be returned for the first call and an Integer for the second call based on what was input as an argument. You will get the proper compile-time checking as a result.

Additionally, in Java 7, one can get some additional type inferencing when instantiating generics like so:

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

Java is kind enough to fill in the blank angle brackets for us.

Now why doesn't Java support type inferencing as a part of variable assignment? At one point, there was an RFE for type inferencing in variable declarations, but this was closed as "Will not fix" because:

Humans benefit from the redundancy of the type declaration in two ways.

  • First, the redundant type serves as valuable documentation - readers do not have to search for the declaration of getMap() to find out what type it returns.
  • Second, the redundancy allows the programmer to declare the intended type, and thereby benefit from a cross check performed > by the compiler.

The contributor who closed this also noted that it just feels "un-java-like", which I am one to agree with. Java's verbosity can be both a blessing and a curse, but it does make the language what it is.

Of course that particular RFE was not the end of that conversation. During Java 7, this feature was again considered, with some test implementations being created, including one by James Gosling himself. Again, this feature was ultimately shot down.

With the release of Java 8, we now get type inference as a part of lambdas as such:

List<String> names = Arrays.asList("Tom", "Dick", "Harry");
Collections.sort(names, (first, second) -> first.compareTo(second));

The Java compiler is able to look at the method
Collections#sort(List<T>, Comparator<? super T>)
and then the interface of
Comparator#compare(T o1, T o2)
and determine that first and second should be a String thus allowing the programmer to forgo having to restate the type in the lambda expression.


Type Inference for Generic Instance Creation

You can replace the type arguments required to invoke the constructor of a generic class with an empty set of type parameters (<>) as long as the compiler can infer the type arguments from the context. This pair of angle brackets is informally called the diamond.

For example, consider the following variable declaration:

Map<String, List<String>> myMap = new HashMap<String, List<String>>();

In Java SE 7, you can substitute the parameterized type of the constructor with an empty set of type parameters (<>):

Map<String, List<String>> myMap = new HashMap<>();

Note that to take advantage of automatic type inference during generic class instantiation, you must specify the diamond. In the following example, the compiler generates an unchecked conversion warning because the HashMap() constructor refers to the HashMap raw type, not the Map<String, List<String>> type:

Map<String, List<String>> myMap = new HashMap(); // unchecked conversion warning

Java SE 7 supports limited type inference for generic instance creation; you can only use type inference if the parameterized type of the constructor is obvious from the context. For example, the following example does not compile:

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

  // The following statement should fail since addAll expects
  // Collection<? extends String>

list.addAll(new ArrayList<>());

Note that the diamond often works in method calls; however, it is suggested that you use the diamond primarily for variable declarations.

In comparison, the following example compiles:

// The following statements compile:

List<? extends String> list2 = new ArrayList<>();
list.addAll(list2);

Type Inference and Generic Constructors of Generic and Non-Generic Classes

Note that constructors can be generic (in other words, declare their own formal type parameters) in both generic and non-generic classes. Consider the following example:

class MyClass<X> {
  <T> MyClass(T t) {
    // ...
  }
}

Consider the following instantiation of the class MyClass, which is valid in Java SE 7 and prior releases:

new MyClass<Integer>("")

This statement creates an instance of the parameterized type MyClass; the statement explicitly specifies the type Integer for the formal type parameter, X, of the generic class MyClass. Note that the constructor for this generic class contains a formal type parameter, T. The compiler infers the type String for the formal type parameter, T, of the constructor of this generic class (because the actual parameter of this constructor is a String object).

Compilers from releases prior to Java SE 7 are able to infer the actual type parameters of generic constructors, similar to generic methods. However, the compiler in Java SE 7 can infer the actual type parameters of the generic class being instantiated if you use the diamond (<>). Consider the following example, which is valid for Java SE 7 and later:

MyClass<Integer> myObject = new MyClass<>("");

In this example, the compiler infers the type Integer for the formal type parameter, X, of the generic class MyClass. It infers the type String for the formal type parameter, T, of the constructor of this generic class.


Why do we need Type Inference from Java 7?

http://www.coolcoder.in/2011/08/why-do-we-need-type-inference-from-java.html

Every good programmer likes to write a concise but effective and optimized code. Type Inference is a way introduced in JDK 7 which will surely give you benefits of less typing. Its been a long time that you have using the java code in following manner.

List<string> names = new ArrayList<string>();
Map<string, Object> objectMap = new HashMap<string, Object>();

But have you ever thought of code duplication while initializing the specific implementation of Collections? Why there is a need to write the parameters two times during an intialization?

Now most of you would be thinking of initializing as a raw types as you had been doing in previous JDK version.

Something like this:

List<string> names = new ArrayList();
Map<string, object=""> objectMap = new HashMap();

So whats new in JDK 7? What benefits you will have from the new feature?

So first we need to understand the difference between raw type initialization and generic type intialization.

A statements like this ensures that the implementation will contain the same parameter as specified during initialization.

List<string> names = new ArrayList<string>();

In the following example, the compiler generates an unchecked conversion warning because the HashMap() constructor refers to the HashMap raw type, not the Map<String, List> type:

Map<String, List<String>> myMap = new HashMap(); // unchecked conversion warning

Diamond Operator

Okay Now I will introduce the new feature of JDK 7.

So we have something called Diamond operator in JDK 7 which reduces your extra typing while initialization.

Syntax: List names = new ArrayList<>();

So it not only reduces your code but also ensures the type checking. Here is a more clear example explaining the benefits of type inference.

Advanced Example

class Demo {
void printStudentNames(List<string> names) {
for(String name:names) {
System.out.println("String name:"+name);
}
}

public static void main(String[] args) {
Demo demo = new Demo();
demo.printStudentNames(new ArrayList<>());   // It saved typing here in a method call too.
List<string> names = new ArrayList<>();
printStudentNames(names);
List<string> copyOfNames = new ArrayList<>(names);  // It saved typing here in a copy contructor invocation too.
}
}

Now what are its limitations?

It won't work in the case you use wildcards.

Something like this:

Class Tree<t> {

    public void demoFunction(List<t> objList) {

        //This is not gonna work.
        List<t> copyOfNames = new ArrayList<t>(objList);
    }
}

In the above case the arguments passed in the copy constructor should be Collection<? extends T>

So it won't accept the above inference type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment