Métodos Avançados
Esta lição mergulha mais fundo nos recursos avançados de métodos, incluindo recursão, métodos estáticos e referências de métodos.
Recursão
Recursão é uma técnica de programação onde um método chama a si mesmo.
Três Elementos da Recursão
| Elemento | Descrição |
|---|---|
| Caso base | A condição que para a recursão |
| Chamada recursiva | O método chama a si mesmo |
| Redução do problema | Cada chamada recursiva reduz o tamanho do problema |
Exemplo: Calcular Fatorial
public class Factorial {
public static long factorial(int n) {
// Caso base
if (n <= 1) {
return 1;
}
// Chamada recursiva
return n * factorial(n - 1);
}
public static void main(String[] args) {
System.out.println("5! = " + factorial(5)); // 120
System.out.println("10! = " + factorial(10)); // 3628800
}
}
Processo de Recursão
factorial(5)
= 5 * factorial(4)
= 5 * 4 * factorial(3)
= 5 * 4 * 3 * factorial(2)
= 5 * 4 * 3 * 2 * factorial(1)
= 5 * 4 * 3 * 2 * 1
= 120
Exemplo: Sequência de Fibonacci
public class Fibonacci {
public static int fibonacci(int n) {
if (n <= 1) {
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.print(fibonacci(i) + " ");
}
// Saída: 0 1 1 2 3 5 8 13 21 34
}
}
Exemplo: Travessia Recursiva de Diretório
import java.io.File;
public class ListFiles {
public static void listFiles(File dir, String indent) {
File[] files = dir.listFiles();
if (files == null) return;
for (File file : files) {
System.out.println(indent + file.getName());
if (file.isDirectory()) {
listFiles(file, indent + " ");
}
}
}
public static void main(String[] args) {
File dir = new File(".");
listFiles(dir, "");
}
}
Métodos Estáticos vs de Instância
Métodos Estáticos
Métodos modificados com static pertencem à classe e podem ser chamados sem criar um objeto.
public class MathUtils {
public static int add(int a, int b) {
return a + b;
}
}
// Chamada
int sum = MathUtils.add(3, 5);
Métodos de Instância
Métodos sem static pertencem a objetos e requerem criar um objeto para chamar.
public class Calculator {
public int add(int a, int b) {
return a + b;
}
}
// Chamada
Calculator calc = new Calculator();
int sum = calc.add(3, 5);
Comparação
| Característica | Método Estático | Método de Instância |
|---|---|---|
| Palavra-chave | static |
Nenhuma |
| Estilo de chamada | NomeClasse.método() |
objeto.método() |
| Acesso a membros | Só pode acessar membros estáticos | Pode acessar todos os membros |
| Palavra-chave this | Não pode usar | Pode usar |
Exemplo: Métodos Estáticos vs de Instância
public class MethodTypeDemo {
private int count = 0;
// Método estático
public static int add(int a, int b) {
return a + b;
}
// Método de instância
public void increment() {
count++;
}
public int getCount() {
return count;
}
public static void main(String[] args) {
// Método estático chamado diretamente
System.out.println(add(3, 5)); // 8
// Método de instância requer objeto
MethodTypeDemo demo = new MethodTypeDemo();
demo.increment();
demo.increment();
System.out.println(demo.getCount()); // 2
}
}
Design de Classe Utilitária
Classes utilitárias tipicamente contêm apenas métodos estáticos e não requerem criar objetos.
Exemplo: Classe Utilitária de String
public class StringUtils {
// Verificar se está vazio
public static boolean isEmpty(String str) {
return str == null || str.isEmpty();
}
// Verificar se está em branco
public static boolean isBlank(String str) {
return str == null || str.trim().isEmpty();
}
// Inverter string
public static String reverse(String str) {
if (str == null) return null;
return new StringBuilder(str).reverse().toString();
}
// Capitalizar primeira letra
public static String capitalize(String str) {
if (isEmpty(str)) return str;
return str.substring(0, 1).toUpperCase() + str.substring(1);
}
public static void main(String[] args) {
System.out.println(isEmpty("")); // true
System.out.println(isEmpty("hello")); // false
System.out.println(reverse("hello")); // olleh
System.out.println(capitalize("hello")); // Hello
}
}
Referências de Métodos
Java 8 introduziu referências de métodos como uma abreviação para expressões Lambda.
Quatro Tipos de Referências de Métodos
| Tipo | Sintaxe | Exemplo |
|---|---|---|
| Método estático | NomeClasse::métodoEstático |
Math::abs |
| Método de instância | objeto::métodoInstância |
System.out::println |
| Método de instância de uma classe | NomeClasse::métodoInstância |
String::length |
| Construtor | NomeClasse::new |
ArrayList::new |
Exemplo: Referências de Métodos
import java.util.Arrays;
import java.util.List;
public class MethodRefDemo {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// Expressão Lambda
names.forEach(name -> System.out.println(name));
// Referência de método (mais conciso)
names.forEach(System.out::println);
// Referência de método estático
List<Integer> numbers = Arrays.asList(-3, -1, 0, 2, 5);
numbers.stream()
.map(Math::abs)
.forEach(System.out::println);
// Saída: 3 1 0 2 5
}
}
Exemplo: Referência de Construtor
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
public class ConstructorRefDemo {
public static void main(String[] args) {
// Expressão Lambda
Supplier<List<String>> listFactory = () -> new ArrayList<>();
// Referência de construtor
Supplier<List<String>> listFactory2 = ArrayList::new;
List<String> list = listFactory2.get();
list.add("Hello");
System.out.println(list); // [Hello]
}
}
Otimização de Recursão
Recursão de Cauda
Recursão de cauda é quando a chamada recursiva é a última operação na função.
// Recursão regular (não é recursão de cauda)
public static long factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1); // Multiplicar n após recursão
}
// Versão com recursão de cauda
public static long factorialTail(int n, long acc) {
if (n <= 1) return acc;
return factorialTail(n - 1, n * acc); // Recursão é o último passo
}
// Chamada
long result = factorialTail(5, 1);
Memoização
Armazene resultados computados em cache para evitar cálculos redundantes.
import java.util.HashMap;
import java.util.Map;
public class MemoFibonacci {
private static Map<Integer, Long> cache = new HashMap<>();
public static long fibonacci(int n) {
if (n <= 1) return n;
// Verificar cache
if (cache.containsKey(n)) {
return cache.get(n);
}
// Computar e armazenar em cache
long result = fibonacci(n - 1) + fibonacci(n - 2);
cache.put(n, result);
return result;
}
public static void main(String[] args) {
System.out.println(fibonacci(50)); // 12586269025
}
}
❓ Perguntas Frequentes
P: Quando devo usar recursão vs loops? R: Use loops para problemas simples—eles são mais eficientes. Recursão é adequada para problemas naturalmente recursivos como estruturas de árvores e algoritmos de divisão e conquista.
P: Quando devo usar métodos estáticos? R: Use métodos estáticos para métodos utilitários que não precisam acessar estado do objeto. Use métodos de instância quando precisar acessar estado do objeto.
P: Qual é a diferença entre referências de métodos e Lambda? R: Referências de métodos são uma abreviação para Lambda, tornando o código mais conciso. No entanto, nem todos os Lambdas podem ser substituídos por referências de métodos.
📖 Resumo
- Recursão é quando um método chama a si mesmo, requerendo um caso base
- Métodos estáticos pertencem a classes, métodos de instância pertencem a objetos
- Classes utilitárias tipicamente contêm apenas métodos estáticos
- Referências de métodos são uma abreviação para expressões Lambda
- Recursão pode ser otimizada com memoização
📝 Exercícios
- Prática de recursão: Use recursão para calcular a soma de 1 a n
- Prática de recursão: Use recursão para inverter uma string
- Classe utilitária: Escreva uma classe utilitária de math com métodos para máximo, mínimo, média, etc.
Próxima Lição
Na próxima lição, aprenderemos sobre a Classe String — operações comuns com strings.



