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

JAVA
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
    }
}
▶ Experimente

Processo de Recursão

TEXT
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

JAVA
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
    }
}
▶ Experimente
⚠️ Nota: Recursão simples é ineficiente. Use memoização para otimizar. Para Fibonacci, iteração é mais eficiente.

Exemplo: Travessia Recursiva de Diretório

JAVA
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, "");
    }
}
▶ Experimente

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.

JAVA
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.

JAVA
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

JAVA
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
    }
}
▶ Experimente

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

JAVA
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
    }
}
▶ Experimente

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

JAVA
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
    }
}
▶ Experimente

Exemplo: Referência de Construtor

JAVA
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]
    }
}
▶ Experimente

Otimização de Recursão

Recursão de Cauda

Recursão de cauda é quando a chamada recursiva é a última operação na função.

JAVA
// 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);
💡 Nota: Compiladores Java não otimizam automaticamente recursão de cauda, mas entender esse conceito ajuda ao aprender outras linguagens.

Memoização

Armazene resultados computados em cache para evitar cálculos redundantes.

JAVA
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

📝 Exercícios

  1. Prática de recursão: Use recursão para calcular a soma de 1 a n
  2. Prática de recursão: Use recursão para inverter uma string
  3. 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.

100%