Fork me on GitHub


Polymorphism is the ability to express the same thing in different forms (Poly=Many, Morph=Form). There are multiple flavors to polymorphism. One very well known flavor is in OOP with inheritance. But there are several different flavors which are quite powerful never the less. One of them is Parametric Polymorphism.

To simply explain it, one can write a data type or function generically without depending upon their precise parametric (aka generic) type. And this parametric type is instantiated with particular type when needed. One very interesting property of this is that: All the instances of a generic function/data type behave the same irrespective of their parametric type.

Java gives us ability to do Parametric Polymorphism with a construct called Generics.


Generics are named and implemented differently in different languages. Two of the well known implementations are using:

Type Erasure

Compiler by a process called as Homogeneous Translation erases all possible reference translations of a class at compile time to one class at runtime. This is what Java compiler also does:

List<Dog> ls = new ArrayList<Dog>()
List<Long> ls = new ArrayList<Long>()

At runtime, both the statements on left gets converted to:

List ls = new ArrayList();

This technique is called as Type Erasure. Simply put, generics are a compile-time creation and runtime has no clue about their existence. Compiler provides adequate type-safety at compile time but erases them at runtime.

In Java8, there is no special byte-code for Generic classes or generic methods. They dont exist in reality. This also means, it is difficult to know the exact generic type of list at runtime. (Not impossible though. See Capture Type). As a side-effect:

public void evaluate(List<Integer> ls)
public void evaluate(List<String> ls)
//both the above gets compiled to the same method below (to verify do `javap -c file.class`):

public void evaluate(List ls)
//This is why you cannot do method-overloading based on generic type as they compile down to same method causing conflict

Nor can you do below:

 class Sample<T>{
   T ob = new T(); //not possible

Never the less Type-Erasure has been a vital reason for the success of JVM (more on it below)


An alternative way to implement generics is using a process called Heterogenous Translation. Languages like C++ and C# have a provision where they create a specialized class for each instantiation of a template. An example of this in Java, is something called Specializations to be introduced in java9 under Project-Valhalla

import java.anyutil.ArrayList;
import java.anyutil.List;

  * For fun build java9 yourself by following instructions here: 
class Java9{
  public static void main(String[] args){	
    System.out.println(List<int>.class);//interface java.anyutil.List${0=I}
    System.out.println(List<double>.class);//interface java.anyutil.List${0=D}		
    System.out.println(java.util.List.class);//interface java.util.List

Notice List<int>.class prints java.anyutil.List${0=I} and java.util.List.class prints java.util.List. This means information about Parametric Types is also embedded at runtime. List<int> is aware of it being a list of int's. The information is not erased. (Though this functionality will be available only for primitives and value classes in Java9. A special proposed type variable identifier any is introduced for this enhanced treatment)

This ability to contain the type information at runtime is called reifiability. In Java all primitives, non-generic classes are reifiable (More details in JLS). Another classic example are Arrays in Java. Arrays in Java are refifiable:

Dog[] d = new Dog[1];
//It knows it is an array of dogs
Object[] ob = d;
ob[0] = new Animal(); 
//throws ArrayStoreException at runtime. This is because it has information available at runtime regarding dogness of `ob`

public class Animal {}
class Dog extends Animal{}
class Cat extends Animal{}

class Canine extends Dog{}

Generics in java were introduced in java5. They are erased for so as to be compatible with pre-java5 code.

public void get(List ls){}
get(new ArrayList<Animal>()) //This would not have compiled if the types were reified as then the byte-code would have changed

Type-Erasure has been a vital success on JVM. More so as other JVM languages like Groovy, Clojure, Scala, Kotlin etc have been able to do their innovation with no limitation on the runtime-side. This would had certainly not been possible if generics were reified. To explain further: Being able to inspect types at runtime undermines the knowledge you have about types at compile time. We will see more on this later.

Sub-Typing (<:)

What do we mean when we say Dog is a sub-type of Animal?

Liskov Substitution

Liskov Substitution Principle tells that:

If "A" extends "B" (A <: B), then all the things that can be done with "B" should also be legal with "A"

An easier way to remember this is to think A <: B as A is subset of B.

Glossary of notations:

We will now see the sub-typing of generic class and its influence on api design.


This is as intense as it can get, but hold on. It is the one ring to rule them all in Generics world.


Let M<X> be a class with X as its generic type. M is said to be co-variant with its generic type if:

If A <: B, then M<A> <: M<B>

Arrays in Java are co-variant.

Animal a = new Dog(); // Dog <: Animal
Dog[] dog = new Dog[1];
Animal[] animals = dog; // Dog[] <: Animal[]
Object[] objects = dog; // Dog[] <: Animal[]


Let M<X> be a class with X as its generic type. M is said to be contra-variant with its generic type if:

If A <: B, then M<A> >: M<B>

Examples for this can be found later in this document after we cover some more ground. A theoretical example would be:

// Integer <: Number
List<Integer> ls = new ArrayList<Number>(); //illegal in java


If generic class is neither co-varianct nor contra-variant then it is invariant. Generics in Java are Invariant.

List<Animal> ls = new ArrayList<Dog>(); // co-variance. Invalid in Java
List<Dog> ls = new ArrayList<Animal>(); // contra-variance. Invalid in Java
List<Animal> ls = new ArrayList<Animal>(); // in-variant. valid.

If you observe, a very interesting behavior pops up when you compare arrays and generics.

Arrays are covariant and generics are invariant. Why?
The rest of this post will affirm on why generics are rightly invariant and the dangers posed by arrays by being covariant.

Wildcards (?)

? is a wild card or a hack. A wild-card can be read as some unknown type. So when we say:

List<?> ls ;

It means `ls` is a list of some unknown type which we do not precisely know. Wild card is a bridge between pre and post Java5 code.
List<Integer>  <:  List<?>
List<?>  <: List
// Hence by transitivity,
List<Integer> <: List

List<?> ls = new ArrayList<Integer>();
List ls2 = ls;
// Hence by transitivity,
List ls3 = new ArrayList<Integer>();

This sub-typing relationship made post java-5 code backward compatible. Wild cards are only possible in a language where generics are implemented using erasure. List<?> would make not make much sense in reifiable world (C++ does have template methods, so it retains behavioral parametricity but not data parametricity).

Java's type system does not give a provison to have co-variant and contra-variant generic classess. But willd card's let you provide bounds, thus enabling co-variance and contra-variance like behavior in Java (Frankly the experience is far from right). In Java, every type variable declared as a type parameter has a bound. If no bound is declared for a type variable, Object is assumed. Wildcards are useful in situations where only partial knowledge about the type parameter is required. This property combined with bounds can be very useful:

List<?> ls ;// ls is a list of some unknown type. 
List<? extends Dog> dog; // dog is a list of elements of some unknown type.
// In java, all reference types extends Object. So now we know that:
//	-	This unknown type <: Object
//	-	Thus unknown type <: Dog
// What we do not know is, the precise type of the unknown type. It can be anything in between the bounds
List<? super Dog> dog; 		
//	 Dog <: This unknown type <: Object

In Java, co-variance and contra-variance of generics is not possible first-hand. But using bounds with wildcards, you can replicate the behavior as a second class citizen.

For people interested in theory behind wildcards: Wildcards are a restricted form of existential types (Remember Existential Quantifier in Mathematics). So say if you have `SomeClass<X extends String>`, then `SomeClass<?>` is a shorthand for`SomeClass<T> forSome{type T <: String}`.


Co-Variant Arrays

We saw at Co-Variance example, that arrays in java are co-variant:

public void arrays(){
	Dog[] dogs = new Dog[3];
	Animal[] ob = dogs; //because of array covariance. line-a
	ob[0] = new Cat(); //(1)
	ob[1] = "";//(2)
	ob[2] = SimpleBeanFactoryAwareAspectInstanceFactory
	//Thats how folks in java world name classes right?

Line (1),(2),(3) would throw a ArrayStoreException at runtime. Why? because if it doesn't, then this will:

Dog d = dogs[1];
//dogs[1] is a String
//BOOM! ClassCastException

Shouldn't the compiler have caught this wrong doing (i.e. Line(1),(2),(3))) at compile time?. The culprit is making arrays as co-variant. i.e. the ability to do Animal[] ob = dogs at line-a has put us in trouble.

Co-Variant Arrays is language blunder. In one of the talks, Martin Odersky (creator of javac for java5) had mentioned that co-variance of arrays decision was made as a tradeoff to be able to do below in initial java version:

public static void sort(Object[] a) //Arrays.sort

Without arrays being co-variant it would had been impossible to have a method like sort, which could take any kind of arrays(Remember there was no generics then, so the only way to be able to take all kinds of array as argument was to make them co-variant). Obviously many modern languages have marked Arrays as invariant and use generics to exhibit parametric polymorphism. (i.e. String[] becomes Array<String>)

Co-Variant Arrays are also a reason why arrays of generic classes are illegal:

ArrayList<String>[] ls = 
	new ArrayList<String>[3];
//Above is illegal in Java.

//If it was allowed, you could do:
Object[] ob = ls;
ob[0] = new ArrayList<Animal>();
//An ArrayStoreExcepton should have been thrown,
//but the runtime can't detect it
ArrayList<String> s = ls[0];
String name = s.get(0); 
//BOOM! BOOM! ClassCastException
//s.get(0) will return an Animal and not String		

Hence the compiler does not let you create arrays of generic classes. Though there is an exception:

ArrayList<?>[] ls = new ArrayList<?>[0];

The explanation on why its legal is left as food for thought to readers.

An important insight we get from above experience is: Co-variance might not go well with mutability

Writes & Reads


Lets look at some examples. All the commented ones represent a compiler error:

public void contravariant(){
    List<? super Animal> ls1 = new ArrayList<Animal>();
    ls1.add(new Animal());
    ls1.add(new Dog());
    //ls1.add(new Object());

ls1 is a list of some unknown type. This unknown type is a super type of Animal. When we try adding elements to this list, the add method becomes:

public void add((? super Animal) elem)

So now you can pass any element of a type say t to add such that, t <: (? super Animal). And:

Animal <: (? super Animal)
Dog <: (? super Animal)
//Object is not a super type of (? extends Animal). Hence ls1.add(new Object()) gives error

Some more examples:

List<? super Dog> ls2 = new ArrayList<Dog>();
//ls2.add(new Animal()); Animal does not <: (? super Dog)
ls2.add(new Dog());
//ls2.add(new Object()); Object does not <: (? super Dog)

List<? super Object> ls3 = new ArrayList<Object>();
ls3.add(new Animal()); // Animal <: (? super Animal)
ls3.add(new Dog());
ls3.add(new Object());

Some examples with covariant variables

public void covariant(){
    List<? extends Animal> lls1 = new ArrayList<Animal>();
    //lls1.add(new Animal());
    //lls1.add(new Dog());
    //lls1.add(new Object());

ls1 is a list of some unknown type. This unknown type is a sub type of Animal. When we try adding elements to this list, the add method becomes:

public void add((? extends Animal) elem)

So now you can pass any element of a type say t to add such that, t <: (? extends Animal). When we say ? extends Animal, we mean some unknown type which is a subtype of Animal. Unless we do not know this unknown type, it is impossible to find t which is subtype of this unknown type. Hence the compiler gives error for ls1.add(new Dog()) and so. Some more brain teasers:

List<? extends Dog> lls2 = new ArrayList<Dog>();
//lls2.add(new Animal()); Animal does not <: (? extends Dog)
//lls2.add(new Dog());
//lls2.add(new Object()); Object does not <: (? extends Dog)

List<? extends Object> lls3 = new ArrayList<Object>();
//lls3.add(new Animal()); // Animal <: (? extends Animal)
//lls3.add(new Dog());
//lls3.add(new Object());

After all the above examples, it is apparent that:

Contra-Variance are great when it comes to consuming something. Co-variance is horrible in those cases


public void covariant(){
    List<? extends Animal> ls1 = new ArrayList<Animal>();
    Animal animal = ls1.get(0);
    Object ob = ls1.get(0);
    //Dog dog = ls1.get(0);

ls1 is a list of some unknown type. This unknown type extends Animal. When we try to do ls1.get(0), it returns a variable of type ? extends Animal. So the refernce type of the variable which gets it has to be:

(? extends Animal) <: The type t of reference variable

This means all types equal to Animal and above are suitable to get it. Hence it workds for Animal & Object but not Dog. Some other similar cases:

List<? extends Dog> ls2 = new ArrayList<Dog>();
Dog dog1 = ls2.get(0);
Animal animal1 = ls2.get(0);
Object ob1 = ls2.get(0);

List<? extends Object> ls3 = new ArrayList<Object>();
//Dog dog2 = ls3.get(0);
//Animal animal2 = ls3.get(0);
Object ob2 = ls3.get(0);

Examples with ? super

public void contravariance(){
    List<? super Animal> lls1 = new ArrayList<Animal>();
    //Animal animal = lls1.get(0);
    Object ob = lls1.get(0);
    //Dog dog = lls1.get(0);

When we do lls1.get(0), it returns variable of unknown type which is also a super type of Animal. So the reference type which points to this variable has to be of type t such that

(? super Animal) <: t

Only one such t can exist, and that is Object class. Hence it gives errors at other lines. More cases:

List<? super Dog> lls2 = new ArrayList<Dog>();
//Dog dog1 = lls2.get(0);
//Animal animal1 = lls2.get(0);
Object ob1 = lls2.get(0);

List<? super Object> lls3 = new ArrayList<Object>();
//Dog dog2 = lls3.get(0);
//Animal animal2 = lls3.get(0);
Object ob2 = lls3.get(0);

Another interesting insight from above:

Co-variance are greate when it comes to producing/reads. Contra-Variance is horrible in those case

Summing above experiences:

Co-Variance is great for producing/reads. Contra-Variance is great for consuming/writes

Some more brain teasers:

List<? super Dog> ls4 = new ArrayList<Animal>();//legal
ls4.add(new Dog());
ls4.add(new Canine());
//ls4.add(new Animal());

//List<? super Animal> ls5 = new ArrayList<Dog>(); //error
//List<? extends Dog> ls4 = new ArrayList<Animal>();//error

List<? extends Animal> ls5 = new ArrayList<Dog>();
//ls5.add(new Animal()); How can `t` be subtype of (? extends Animal)

Library Examples

After knowing the stregths and weeknesses of covariance and contravariance, lets see their impact on class design in several classes in JDK



If we were to design foreach method in class ArrayList<T>, how would it be? foreach is like a side-effect, you pass the element of the list iteratively to extenal consumer. The first simplest option would be to:

public void forEach(Consumer<T> action)

This does the job, but it levies an unnecessary constraint on generic type of Consumer to be as same type T of List. What if we wanted to do this:

ArrayList<Animal> fe = new ArrayList<>();
fe.forEach((Object o) -> System.out.println(o));

This would throw error with our definition of foreach. And it shouldn't. So lets improvize. Let Consumer argument be of type variable E. Because we know consumer will be consuming elements, the type E has to:

E >: T
// This means
? super T //Hence we get:
public void forEach(Consumer<? super T> consumer)

This is also how JDK defines it in ArrayList.foreach. Notice again how nicely contra-variance fits in when we have to consume something. Other similar examples where we consume elements:

public boolean removeIf(Predicate<? super E> filter)
public void sort(Comparator<? super E> c)


If we were to design addAll method in class ArrayList<T>, the simplest solution would be to:
public boolean addAll(Collection<T> c)

This again is too restrictive. What if you wanted to do:

public void addAll(){
    ArrayList<Animal> addA = new ArrayList<>();
    ArrayList<Dog> b = new ArrayList<>();
    //fine to add dogs to a list of animals

This would give a compiler error with our definition of addAll. We can improve this. With addAll we want to be able to take any collection of a type E such that:

E <: T
public <E extends T> boolean addAll(Collection<E> c)

This is also how jdk defines it (Instead of type variable, they use bound). Notice we will be reading from collection and how nicely covariance fits in.


Lets try defining Collections.min. This method should:

The simplest way to do it would be by:

public static <T extends Comparator<T>> T min1(Collection<T> elem)

From our experience, we know it is restrictive. Lets improve it. We know, we will be:

So what we get is this:

static <T extends Comparator<? super T>> T min2(Collection<? extends T> elem)

Saw how simple it was? It looks scary but understanding covariance and contravariance helped us. JDK defines the method as:

static <T extends Object & Comparable<? super T>> T
                                        min(Collection<? extends T> coll)

They also do T extends Object. This is required for backward compatibility purpose. In Pre-Java5 era, min would take a collection of Object's and return an Object. In our definition of min, it would return a Comparable and not Object. Putting a dummy T extends Object tricks the compiler to be able to return an Object. This is a good read on it.


Lets try implemention a copy function that will copy elements from a list to another. We will be reading from a list and writing in another:

static <T, E extends T> void copy(List<T> dest, List<E> src)

This does the job. There is also an alternative way. We know that we will be:

static <T> void copy2(List<? super T> dest, List<? extends T> src){

This will also do the job. Some more similar interesting scenarios:

<T> int binarySearch(List<? extends Comparable<? super T>> list, T key)
<T> void fill(List<? super T> list, T obj)
<T> List<T> unmodifiableList(List<? extends T> list)
<T> T max(Collection<? extends T> coll, Comparator<? super T> comp)


A tricky one. Object.getClass is defined as:

public final native Class<?> getClass();

But in reality, the compiler returns Class<? extends |X|> where |X| is the erasure of the reference class on which getClass is called. For example:

Dog a = new Dog();
Class<? extends Dog> aa = a.getClass();
Animal b = a;
Class<? extends Animal> bb = b.getClass();

Compiler is doing some extra magic here. The question is: Why does it return Class<? extends |X|> rather than Class<|X|>? i.e. why doesnt it do:

Class<Dog> aa = a.getClass();
Class<Animal> bb = b.getClass();

The above should not be allowed. Because if it did, then b.getClass would return Class<Animal>. But in reality, the object b is a Dog. Which means Class<Dog> is the actual class object returned when we do b.getClass. And Class<Animal> != Class<Dog>. Hence Class<? extends |X|> is a safer bet. (Covariance again as reads).

A similar scenario can be found with Class.asSubClass

<U> Class<? extends U> asSubclass(Class<U> clazz)


In all our cases till now, we have been dealing with classes with only one parametric type. Rules and behavior do not change will increasing rank in parametricity.

A Function like in true mathematical sense, takes an input and returns an output (Ideally with no side-effects). An example:

Function<Integer, String> f = (Integer i) -> String.valueOf(i);
//`f` is a function from `Integer => String`
We can do transformations with functions:
Let f = Integer => String
Let g = String => String

Then f o g = Integer => Integer
     g o f = String  => String

Lets try building this transformation. The simplest solution would be to:

public static <A,B,C> Function<A,C> andThen(Function<A,B> first,
                                            Function<B,C> later){
    return first.andThen(later);

This isn't a great solution. Because:

private static void andThenFail(){
    Function<Animal, Dog> f = (Animal a) -> new Dog(); //Animal => Dog
    Function<Object, Animal> g = (Object a) -> new Animal();//Object => Animal
    //Their composition should give: Animal => Animal. But
    //Function1.andThen(f,g); error

So we need to do better. Lets analyze it. For the composition g o f:

Hence we get:

public static  <A,B,C> Function<A,C> andThen2
                                    (Function<? super A,? extends B> first,
                                    Function<? super B,? extends C> later){
    return (A t) -> later.apply(first.apply(t));
public void gof(){
    Function<Animal, Dog> f = (Animal a) -> new Dog();
    Function<Object, Animal> g = (Object a) -> new Animal();
    Function<? super Animal, ? extends Animal> function = andThen2(f, g);
    Animal apply = function.apply(new Animal());
    Function<Animal, Dog> h = (Animal a) -> new Dog();
    andThen2(andThen2(f,g), h);

andThen and compose of are built on the same principles.

More Interesting Stuff

New Instance

There are some strategies that can be helpful to retrieve the runtime information of parametric type:

class KnowType<T>{
	private final Class<T> claz;
	public KnowType(Class<T> claz){
		this.claz = claz;

	public Class<T> getTypeInfo(){
		return claz;
KnowType<Animal> a = new KnowType<Animal>(Animal.class);
Animal a = a.getTypeInfo().newInstance();

Class<T> preserves the information. Please refer Class api for creating instances with constructors and much more. In case of generic arrays, you can:

class KnowArray<T>{
    T[] arr;
    KnowArray(Class<T> claz){
        this.arr = (T[]) java.lang.reflect.Array.newInstance(claz,10);
    public T[] getArr(){
        return arr;

Capture Type

Say in cases you cannot obtain parametric type information via above tactics, then there are other ways possible to obtain the same. But it is dirty. It possible to obtain information about generic types at runtime? in some cases:

class Outer{
  //Aim is to capture the type of generic type below. i.e. String
  List<String> ls = new ArrayList<String>();
Field field = Outer.class.getDeclaredField("ls");
ParameterizedType p = (ParameterizedType) field.getGenericType();
Class<?> claz = (Class<?>)p.getActualTypeArguments()[0];
// prints class java.lang.String

Once you have claz variable, you can then create its instances. Lets go a step ahead...

Low lets try building a class which will make it very easy to access parametric type information. I have called the class CaptureType and it lets you capture the generic type of a parametrized class. Some use cases:

static class Example<S> extends CaptureType<S> {}
static class SubClass extends Example<Integer> {}

static void test6() {
    SubClass test6 = new SubClass();
    Class<?> type = (Class<?>)test6.getTypeParam();
    equals(type, Integer.class);

So now we are able to get Integer.class object at runtime. Some more bazooka stuff:

class X<T> extends CaptureType<T> {}
class Y<A, B> extends X<B> {}
class Z<Q> extends Y<Q, Map<Integer, List<List<List<Integer>>>>> {}

void test7() {
    Z<String> z = new Z<>();
    TypeADT param = z.getParamADT();
    equals(param.getRawType(), Map.class);
    List<TypeADT> parameters = param.getParameters();
    equals(parameters.get(0).getRawType(), Integer.class);
    equals(parameters.get(1).getRawType(), List.class);
    equals(parameters.get(1).getParameters().get(0).getRawType(), List.class);
    equals(parameters.get(1).getParameters().get(0).getParameters().get(0).getRawType(), List.class);
    equals(parameters.get(1).getParameters().get(0).getParameters().get(0).getParameters().get(0).getRawType(), Integer.class);

Works! This is what mockito exactly does (In fact CaptureType was inspired from mockito). Though there are some limitations:

class Sample<T> extends CaptureType<T> {}

Sample<String> sample = new Sample<String>();
sample.getRawType(); //Will throw exception
//In this case, you Field.getGenericType() to optaing parametric type information

Please look at the source to know more. You might also want to look at TypeTools which is a library to resolve generic type information declared on a class, method or even lambda. But they use similar techniques as above to obtain the information.

Java Type-System

We have hardly scratched the generics surface. The impact of Type-Erasure on Lambdas is quite an interesting one. Hopefully we will cover that sometime in future. For those who cant wait, read this.

Some random interesting things about Java:

Static & Strong

Java is statically typed. Which means the compiler does not let you do:

Animal a = "123";

Statically typed means that the compiler wont let you assign data/construct with a type which does not define it. Java is Strongly Typed. This means even at runtime you cannot wrongly assign a type to something it is not:

Integer i = 23;
String s = (String) i;
//ClassCastException. It knows at runtime that it an Int and not a String.

Nominal Typing

Java is mostly Nominally Typed. What it means is:

class City{
    public String getName(){}

class Country{
    public String getName(){}

static void storeInDb(City city){}
City city;
Country country;
storeInDb(country); // compiler error

Even though city and country are exactly the same in class body and meaning, you cannot pass country to storeInDb. This is because the method argument is nominal to take only City. Basically, class names inside objects are part of what it means to be an object. Class name information inside an object is called its nominal information. To emphasize the fact that objects in class-based OOP have class names as part of their meaning they are sometimes called nominal objects.

Some programming languages instead take arguments not based on nominal type but rather based on structure of the data type. ML and Haskell are structurally typed. Scala has a some flavors of it:

def foo(x: { def getName: String }) = println(x.getName)

foo(new City) //work
foo(new Country) //works

Here foo can take any argument which has a method getName in it. Its based on structure rather than the name of the type. If you think even java8 has some small flavors of it in lambda. For example:

Comparator<Integer> a1 = (x, y) ->, y);
BiFunction<Integer, Integer, Integer> a2 = (x, y) ->, y);
BinaryOperator<Integer> a3 = (x, y) ->, y);

You can assign any Functional Interface based on the structure of lambda. The evolution and design of lambdas is quite interesting, especially after the influence of Type-Erasure. This is a very very good read.

Type Inference

Type Inference refers to the abiliy to deduce the type of a datatype or an expression automatically. Java Compiler leverages its type inference power to deduce type automatically. Some simple examples would be:

List<String> ls = new ArrayList<>();

Here the compiler it self realizes that the object is of type ArrayList<String>. Some other examples:

List<String> ls = Collections.emptyList();
//compiler infers `Collections.emptyList()` to `Collections.<String>emptyList()`
Collections.max(Arrays.asList(1,2,3),(x,y) ->,y));
//compiler infers the lambda to Comparator<Integer>

Many functional languages go very far to infer types. Even to an extent that you do not specify types anywhere in your program. Interested people should have a look at Hindley-Miler Type System. It has been extremely influentional in type system of ML, OCaml, Haskell, F# etc.