O que é reified em Kotlin?

A palavra-chave reified permite acessar informações de tipo genérico em tempo de execução. Normalmente, por causa do type erasure da JVM, tipos genéricos são apagados após a compilação. Com reified, o Kotlin mantém essa informação disponível.

Para usar reified, a função precisa ser inline. As duas coisas andam juntas.

O problema sem reified

// Isso NÃO funciona:
fun <T> ehDoTipo(valor: Any): Boolean {
    // return valor is T  // Erro! Não dá pra checar tipo genérico
    return false
}

Na JVM, o tipo T é apagado em tempo de execução. O compilador não sabe qual tipo é T quando o código roda.

A solução com reified

inline fun <reified T> ehDoTipo(valor: Any): Boolean {
    return valor is T
}

fun main() {
    println(ehDoTipo<String>("Kotlin"))  // true
    println(ehDoTipo<Int>("Kotlin"))     // false
    println(ehDoTipo<Int>(42))           // true
}

Com reified, o tipo T é conhecido em tempo de execução. Você pode usar is T, T::class e até criar instâncias (com reflexão).

Exemplo prático: parsing de JSON

inline fun <reified T> parsear(json: String): T {
    println("Parseando para ${T::class.simpleName}")
    // Aqui voce usaria uma biblioteca como Gson ou Moshi
    // return Gson().fromJson(json, T::class.java)
    throw NotImplementedError("Exemplo simplificado")
}

Sem reified, seria necessário passar Class<T> como parâmetro extra. Com reified, a informação de tipo já tá disponível.

Filtrando por tipo em coleções

inline fun <reified T> List<Any>.filtrarPorTipo(): List<T> {
    return this.filterIsInstance<T>()
}

fun main() {
    val mistura = listOf(1, "Kotlin", 2.5, "Brasil", 3, true)

    val strings = mistura.filtrarPorTipo<String>()
    println(strings) // [Kotlin, Brasil]

    val inteiros = mistura.filtrarPorTipo<Int>()
    println(inteiros) // [1, 3]
}

Criando instâncias com reified

Com reified você pode acessar o construtor de um tipo genérico via reflexão, algo impossível sem essa palavra-chave:

inline fun <reified T : Any> criarInstancia(): T {
    val construtor = T::class.constructors.firstOrNull { it.parameters.isEmpty() }
        ?: throw IllegalArgumentException("${T::class.simpleName} nao tem construtor sem argumentos")
    return construtor.call()
}

class Motor {
    val tipo = "V8"
    override fun toString() = "Motor(tipo=$tipo)"
}

class Roda {
    val aro = 17
    override fun toString() = "Roda(aro=$aro)"
}

fun main() {
    val motor = criarInstancia<Motor>()
    val roda = criarInstancia<Roda>()
    println(motor) // Motor(tipo=V8)
    println(roda)  // Roda(aro=17)
}

Reified com funções de extensão

A combinação de reified com funções de extensão cria APIs muito elegantes. Veja um exemplo inspirado em frameworks de injeção de dependência:

class Container {
    private val servicos = mutableMapOf<String, Any>()

    fun registrar(servico: Any) {
        servicos[servico::class.qualifiedName ?: ""] = servico
    }

    inline fun <reified T : Any> obter(): T {
        val chave = T::class.qualifiedName
        return servicos[chave] as? T
            ?: throw IllegalStateException("Serviço ${T::class.simpleName} nao registrado")
    }
}

class BancoDeDados {
    fun consultar(sql: String) = "Resultado de: $sql"
}

class ServicoDeEmail {
    fun enviar(para: String) = println("Email enviado para $para")
}

fun main() {
    val container = Container()
    container.registrar(BancoDeDados())
    container.registrar(ServicoDeEmail())

    val db = container.obter<BancoDeDados>()
    println(db.consultar("SELECT * FROM usuarios"))

    val email = container.obter<ServicoDeEmail>()
    email.enviar("karina@kotlin.dev.br")
}

Limitações

  • Só funciona com funções inline
  • Não pode ser usado em classes
  • Não pode ser usado com parâmetros noinline
  • Não pode ser chamado a partir de código Java (funções inline com reified não são visíveis para Java)
  • Funções inline com reified não podem ser armazenadas em variáveis de tipo função

Casos de Uso no Mundo Real

  • Serialização e desserialização: bibliotecas como Gson, Moshi e kotlinx.serialization usam reified para determinar o tipo de destino ao converter JSON, XML ou outros formatos em objetos Kotlin.
  • Injeção de dependência: frameworks como Koin usam inline fun <reified T> get(): T para resolver dependências sem passar Class<T> explicitamente, tornando a API muito mais limpa.
  • Logging com tipo automático: criar funções de log que automaticamente incluem o nome da classe sem precisar receber parâmetros adicionais, como inline fun <reified T> T.logger() = LoggerFactory.getLogger(T::class.java).
  • Navegação no Android: usar reified para criar funções de navegação tipadas, como inline fun <reified T : Activity> Context.iniciarActivity(), eliminando a necessidade de passar a classe explicitamente.

Boas Práticas

  • Use reified apenas quando realmente precisar acessar o tipo em runtime. Se a lógica não depende de T::class, is T ou reflexão, não precisa de reified.
  • Lembre-se de que funções inline com reified são copiadas em cada ponto de chamada. Mantenha o corpo da função enxuto para não inflar o bytecode.
  • Combine com upper bounds (<reified T : AlgumaInterface>) para restringir os tipos aceitos e garantir segurança em tempo de compilação.
  • Prefira reified a receber Class<T> ou KClass<T> como parâmetro. A API fica mais limpa e idiomática.
  • Documente as restrições de tipo quando usar reified em APIs públicas, já que o compilador nem sempre consegue dar mensagens de erro claras sobre tipos incompatíveis.

Erros Comuns

  • Esquecer de marcar a função como inline: o compilador vai reclamar, mas a mensagem de erro pode confundir iniciantes. Lembre-se: reified exige inline, sempre.
  • Criar funções inline muito grandes com reified: como o corpo da função é copiado em cada chamada, funções longas geram muito bytecode duplicado. Extraia a lógica não-inline para funções auxiliares.
  • Tentar usar reified em parâmetros de classe: class Repositorio<reified T> não funciona. Reified é exclusivo de funções inline. Para classes, passe KClass<T> no construtor.
  • Assumir que reified funciona com herança de tipos: ehDoTipo<Number>(42) retorna false para is Number em alguns cenários com tipos primitivos, porque a JVM trata primitivos de forma especial.
  • Chamar funções reified a partir de Java: código Java não consegue chamar funções inline com reified. Se sua API precisa ser compatível com Java, forneça uma alternativa que receba Class<T>.

Perguntas Frequentes

Por que reified precisa de inline? Porque o mecanismo funciona substituindo o corpo da função no ponto de chamada. Ao fazer isso, o compilador conhece o tipo concreto usado e pode inserir a informação de tipo diretamente no bytecode gerado.

Posso usar reified com múltiplos parâmetros de tipo? Sim. Você pode ter vários parâmetros reified na mesma função, como inline fun <reified A, reified B> converter(valor: A): B. Cada um será resolvido independentemente.

Qual a diferença entre reified e passar Class<T> como parâmetro? Funcionalmente, o resultado é o mesmo. A diferença é ergonomia: com reified, você escreve parsear<Usuario>(json) em vez de parsear(json, Usuario::class.java). O código fica mais limpo e idiomático.

O reified tem impacto na performance? O impacto é o mesmo do inline em geral: o corpo da função é copiado em cada chamada, o que pode aumentar o tamanho do bytecode mas elimina o overhead de chamada de função. Para funções pequenas, o impacto é negligível ou até positivo.

Termos Relacionados

  • Inline Function — funções que são expandidas no ponto de chamada, requisito para reified
  • Generics — o sistema de tipos genéricos do Kotlin
  • Extension Function — funções de extensão que combinam bem com reified
  • Reflection — reflexão em Kotlin, frequentemente usada junto com reified

reified é uma ferramenta que resolve elegantemente um problema chato da JVM. Sempre que precisar acessar o tipo genérico em runtime, essa é a saída.