1. Introdução
Method reference (em português — referência a método) — é uma sintaxe especial em Java que permite passar um método (ou construtor) já existente como implementação de uma interface funcional. Você pode “passar” um método onde se espera uma expressão lambda, se as assinaturas coincidirem.
Sintaxe:
Class::method
object::method
Class::new
Se a lambda é uma mini-função “on the fly”, a method reference é simplesmente “passe um método já existente”. É como, em vez de reescrever uma receita, você fornecer o link para a página com a receita.
Exemplo simples
Em vez de:
list.forEach(s -> System.out.println(s));
Você pode fazer:
list.forEach(System.out::println);
Fica conciso, não é?
2. Tipos de method references
Há quatro formas principais de referências a métodos. São estas — e não precisa de mais nada.
Referência a método estático
Sintaxe: Class::staticMethod
Exemplo:
Function<Integer, String> intToString = String::valueOf;
System.out.println(intToString.apply(123)); // "123"
O mesmo com lambda:
Function<Integer, String> intToString = i -> String.valueOf(i);
Referência a método de instância de um objeto
Sintaxe: object::method
Exemplo:
PrintStream printer = System.out;
Consumer<String> consumer = printer::println;
consumer.accept("Olá, mundo!");
Equivalente à lambda:
Consumer<String> consumer = s -> printer.println(s);
Referência a método de instância pelo tipo da classe
Sintaxe: Class::method
Aqui o primeiro parâmetro da interface funcional se torna o objeto no qual o método é chamado.
Exemplo:
Function<String, Integer> stringLength = String::length;
System.out.println(stringLength.apply("Java")); // 4
Aqui, String::length se transforma na função: (String s) -> s.length()
Referência a construtor
Sintaxe: Class::new
Exemplo:
Supplier<ArrayList<String>> listSupplier = ArrayList::new;
ArrayList<String> list = listSupplier.get();
Equivalente à lambda:
Supplier<ArrayList<String>> listSupplier = () -> new ArrayList<>();
3. Quando usar method reference?
É conveniente usar method reference quando a lambda apenas chama um método existente sem lógica adicional — assim o código fica mais curto e legível.
Exemplo: ordenação de lista
Em vez de:
List<String> names = Arrays.asList("Ivan", "Pedro", "Ana");
names.sort((a, b) -> a.compareToIgnoreCase(b));
Você pode fazer:
names.sort(String::compareToIgnoreCase);
Exemplo: processamento de coleções
Em vez de:
list.forEach(s -> System.out.println(s));
Pode ficar ainda mais curto:
list.forEach(System.out::println);
Exemplo: transformação de elementos
Em vez de:
List<String> numbers = Arrays.asList("1", "2", "3");
List<Integer> ints = numbers.stream()
.map(s -> Integer.parseInt(s))
.collect(Collectors.toList());
Você pode fazer:
List<Integer> ints = numbers.stream()
.map(Integer::parseInt)
.collect(Collectors.toList());
Mais detalhes sobre a Stream API e a classe utilitária Collectors você vai aprender no nível 30 :P
4. Comparação entre method reference e expressão lambda
Equivalência
Method reference e expressão lambda são frequentemente intercambiáveis. Ambas implementam uma interface funcional, se as assinaturas coincidirem.
Exemplo:
Consumer<String> c1 = s -> System.out.println(s);
Consumer<String> c2 = System.out::println;
Quando é melhor usar method reference?
- Quando a lambda apenas chama um método existente sem lógica adicional.
- Para melhorar a legibilidade, especialmente em cadeias longas de chamadas.
- Quando você quer sinalizar claramente: “aqui é apenas a chamada de um método”.
Quando method reference não serve?
- Se é necessária lógica adicional (validação, condições, tratamento de erros).
- Se os parâmetros precisam ser transformados antes de chamar o método.
Exemplo:
list.forEach(s -> {
if (s != null) System.out.println(s);
});
// Aqui method reference não serve; somente lambda.
5. Prática: reescrevendo lambdas usando method references
Exemplo 1: Imprimindo nomes de animais
List<String> animals = Arrays.asList("Gato", "Cachorro", "Papagaio");
animals.forEach(animal -> System.out.println(animal));
Fica assim:
animals.forEach(System.out::println);
Exemplo 2: Convertendo strings em números
List<String> numbers = Arrays.asList("10", "20", "30");
List<Integer> ints = numbers.stream()
.map(s -> Integer.parseInt(s))
.collect(Collectors.toList());
Fica assim:
List<Integer> ints = numbers.stream()
.map(Integer::parseInt)
.collect(Collectors.toList());
Exemplo 3: Ordenando objetos por nome
List<Animal> animalList = ...;
animalList.sort((a, b) -> a.getName().compareTo(b.getName()));
Fica assim:
animalList.sort(Comparator.comparing(Animal::getName));
Aqui, Animal::getName é uma referência a método de instância pelo tipo da classe.
Exemplo 4: Criando objetos via construtor
Supplier<Dog> dogFactory = () -> new Dog();
Dog dog = dogFactory.get();
Fica assim:
Supplier<Dog> dogFactory = Dog::new;
Dog dog = dogFactory.get();
6. Como funciona o mapeamento de assinaturas
Method reference só pode ser usada quando a assinatura do método coincide com o método abstrato da interface funcional.
@FunctionalInterface
interface IntToString {
String convert(int value);
}
public class Demo {
public static String intToHex(int value) {
return Integer.toHexString(value);
}
public static void main(String[] args) {
IntToString converter = Demo::intToHex;
System.out.println(converter.convert(255)); // ff
}
}
Aqui, Demo::intToHex é adequada porque recebe int e retorna String.
7. Method reference e construtores com parâmetros
Se o construtor recebe parâmetros, ainda é possível usar method reference — desde que as assinaturas coincidam.
@FunctionalInterface
interface AnimalFactory {
Animal create(String name);
}
class Animal {
private String name;
public Animal(String name) { this.name = name; }
public String getName() { return name; }
}
AnimalFactory factory = Animal::new;
Animal cat = factory.create("Barsik");
System.out.println(cat.getName()); // Barsik
8. Erros comuns ao usar method references
Erro nº 1: Incompatibilidade de assinaturas.
Se a assinatura do método não coincide com o método abstrato da interface, o compilador emitirá erro. Por exemplo, a interface espera dois parâmetros, mas a referência aponta para um método com um parâmetro.
Erro nº 2: Tentativa de usar referência a método de instância pelo tipo sem objeto.
Quando você usa a forma Class::method, o primeiro parâmetro da interface se torna o objeto da chamada. Se você confundir a quantidade ou a ordem dos parâmetros — receberá um erro de mapeamento.
Erro nº 3: Usar method reference onde é necessária lógica adicional.
Se for necessário um condicional, logging ou tratamento de exceções — use uma lambda, não uma referência a método.
Erro nº 4: Referências a métodos sobrecarregados.
Se a classe tiver vários métodos com o mesmo nome, o compilador pode não saber qual escolher. Às vezes ajuda indicar explicitamente o tipo da interface funcional.
Erro nº 5: Usar referência a método de instância sem objeto.
Por exemplo, String::toUpperCase funciona corretamente em map, porque o primeiro parâmetro é o próprio objeto String. Mas fora de um contexto apropriado, onde se espera um método estático, isso resultará em erro.
GO TO FULL VERSION