Java: 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.

1. Recursão

Recursão é uma técnica de programação onde um método chama a si mesmo.

(1) 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

(2) Processo de Recursão

TEXT 📖 Somente leitura
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

2. Métodos Estáticos vs de Instância

(1) 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);

(2) Métodos de Instância

Métodos sem static pertencem a objetos e requerem criar um objeto para chamar.

TEXT 📖 Somente leitura
public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
}

// Chamada
Calculator calc = new Calculator();
int sum = calc.add(3, 5);

(3) 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

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

4. Referências de Métodos

Java 8 introduziu referências de métodos como uma abreviação para expressões Lambda.

(1) 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

5. Otimização de Recursão

(1) 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.

(2) 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.

6. Próxima Lição

Na próxima lição, aprenderemos sobre a Classe String — operações comuns com strings.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%