Reflectione em programação: o que é isso na prática
Reflexão (ou reflection) é a capacidade de um programa inspecionar e modificar seu próprio comportamento durante a execução. Sem ela, tudo tem que ser decidido em tempo de compilação. Com ela, você ganha flexibilidade, mas paga um preço. Sempre paga. No mundo Java e Kotlin, reflexão funciona assim: você usa classes do pacote java.lang.reflect para descobrir métodos, campos e construtores de uma classe que você mal conhece antes de rodar. Você pode chamar um método pelo nome string, ler ou gravar campos privados, criar instâncias sem usar new direto. É poderosos, mas é lento e fácil de estragar.
O que significa reflexiva em termos práticos
Eu trabalhei anos lidando com frameworks que usam reflexão pesada. Um caso comum é quando você precisa mapear um JSON para um objeto e não quer escrever um converter manual pra cada classe. Você pega o campo pelo nome, verifica o tipo, converte o valor e seta. Isso é reflexão pura. O problema é que, numa rota crítica onde você processa milhares de requisições por segundo, o overhead começa a Doer. Eu vi sistemas que levavam 12ms por chamada usando reflection direta e, depois de migrar para código gerado, ficaram em 0,3ms. A diferença não é mágica, é custo de lookup em tempo real versus acesso direto.
Como usar reflexão no dia a dia
Se você está em Java, o caminho básico é: Para inspectar uma classe: use Class.forName("com.exemplo.MinhaClasse") ou simplesmente MinhaClasse.class. Depois, methods = clazz.getDeclaredMethods() te dá todos os métodos, incluindo os private. Campos com getDeclaredFields(). Construtores com getDeclaredConstructors().
Para chamar um método: method.setAccessible(true). Depois method.invoke(objeto, argumentos). É simples, mas o setAccessible(true) pode falhar em módulos encapsulados se você estiver no Java 9+ e não expor o módulo. Já me deparei com isso em aplicações Spring Boot rodando no classpath module system habilitado. A solução foi passar --add-opens para o JVM ou usar o Byte Buddy para gerar code no lugar de reflection direta. Para leitura/escrita de campos: field.get(objeto) e field.set(objeto, valor). Novamente, field.setAccessible(true) costuma ser necessário.
Em Kotlin, a situação é parecida porque roda na JVM, mas há um detalhe importante: os nomes dos parâmetros de função podem sumir se você não compilar com -parameters. Sem isso, reflection não consegue ver os nomes dos argumentos, só seus tipos. Isso quebra muitas bibliotecas de serialização que dependem de nomes.
👉 Clique no botão abaixo para saber mais sobre o assunto!
Custos e armadilhas reais
O maior problema não é a velocidade em si, é a fragilidade. Reflita sobre código que será refatorado e você vai ter surpresas. Se você renomear um método e só deixar a string "meuMetodo", o compilador não avisa. Só vai falhar em runtime, normalmente num momento chato. Outro problema é segurança. setAccessible(true) é desligado por padrão em módulos fechados. Se seu código precisa acessar internos de outra biblioteca e você não controlar o classpath, vai brigar com o module system. Também tem o caso de SecurityManager, que ainda existe em alguns ambientes corporativos, e que pode bloquear reflexão por completo.
E tem a issue de performance que todo mundo subestima. Reflection invoca verificações em tempo real a cada chamada. Se você precisa fazer isso milhões de vezes, o garbage collector vai trabalhar mais porque cada reflection call gera objetos temporários. A solução padrão é usar MethodHandle, que é mais rápido que reflection pura, ou melhor ainda, code generation com Byte Buddy, ASM ou Javassist. Em projetos que eu vi, a migração de reflection direta para Byte Buddy reduziu o throughput de chamadas internas de cerca de 80k ops/s para 2,5M ops/s em média, dependendo da carga.
Quando usar e quando fugir
Use reflexão quando você não tem escolha: frameworks de DI como Spring, bibliotecas de serialização como Jackson, testes com mocking, e ferramentas de debug. Nesses casos, o custo já está no design do framework e não no seu código crítico. Não use reflexão quando você pode escrever o código direto. Se você sabe o tipo em tempo de compilação, não use reflection só porque parece mais genérico. A genéricidade mal aplicada custa cara em manutenção e performance. Um switch ou strategy pattern simples vai ser mais rápido, mais legível e mais seguro.
Pitfalls que ninguém conta
Um detalhe que pega muita gente nova: quando você chama method.invoke() passando argumentos, eles têm que bater exatamente com os tipos do método. Se o método espera um int e você passa um Integer, o autoboxing funciona, mas se passar um Long, vai dar IllegalArgumentException. E em Kotlin, funções extension e propriedades geradas pelo compiler às vezes aparecem como métodos diferentes do que você espera. Eu perdi umas duas horas num projeto caçando porque um getter de propriedade com backing field tinha um nome diferente no bytecode do que eu via no source. Também tem o problema de lambdas e métodos implícitos. Reflection não consegue inspecionar lambdas de forma útil sem ajuda de bibliotecas externas. Se você precisar lidar com isso, considere usar MethodHandles.Lookup com cuidado ou abandonar reflexão e partir para code generation.
Resumindo: reflexão existe e é útil. Mas use com olhos abertos. Ela resolve problemas de flexibilidade, não de performance. Se o seu gargalo é velocidade pura, a resposta quase nunca é reflection direta.