1 Lambda precisa ter um tipo funcional
Lambdas não podem ser atribuídas a tipos que não são interfaces funcionais, como Object.

Exemplo que não compila:

Object o = () -> {
System.out.println("eu sou um runnable!");
};
new Thread(o).start(); // Erro

2 Lambda atribuída corretamente a uma interface funcional
Atribuindo explicitamente para Runnable:

Runnable r = () -> {
System.out.println("eu sou um runnable!");
};
new Thread(r).start(); // Correto

3 Lambda passada diretamente como argumento
Quando passamos diretamente no construtor, o compilador usa o contexto para inferir o tipo:

new Thread(() -> {
System.out.println("eu sou um runnable?");
}).start();

O construtor Thread(Runnable r) espera um Runnable, então o compilador infere que a lambda é Runnable.

4 Target Type

O tipo esperado pelo compilador baseado no contexto é chamado de Target Type.

O Target Type permite:

  • Inferir o tipo da expressão lambda;

  • Reconhecer que uma mesma lambda pode representar diferentes interfaces funcionais.

Exemplo com mesma expressão lambda e diferentes tipos:

Callable c = () -> "retorna uma String";
PrivilegedAction p = () -> "retorna uma String";

Na primeira linha: Callable.
Na segunda linha: PrivilegedAction.
O Target Type define o significado.

5 O mesmo acontece com Method Reference

A method reference também usa Target Type para inferência.

Exemplo com method reference
Callable c = callable::call;
PrivilegedAction action = callable::call;

A mesma referência callable::call pode se adaptar a diferentes interfaces.

6 Diferença entre Lambda e Method Reference
Method references tornam a inferência mais forte, porque o tipo está mais explícito.

Além disso, é permitida a conversão entre interfaces funcionais compatíveis.

Exemplo: ExemploTargetType.java