O que é Type Alias em Kotlin?

Um type alias em Kotlin é um nome alternativo para um tipo existente. Ele não cria um novo tipo, mas oferece uma forma mais legivel de referenciar tipos complexos ou longos. Declarado com a palavra-chave typealias, esse recurso melhora a legibilidade do código sem impacto em performance, pois o compilador substitui o alias pelo tipo original durante a compilação.

Type aliases são especialmente úteis quando você trabalha com tipos genericos compostos, funções de ordem superior com assinaturas longas ou tipos de plataformas externas que possuem nomes pouco descritivos.

Sintaxe básica

A declaracao de um type alias e feita no nível do arquivo (top-level), fora de classes ou funções:

typealias ListaDeUsuarios = List<Usuario>
typealias Callback<T> = (T) -> Unit
typealias MapaDeNotas = Map<String, List<Double>>

Apos a declaracao, você pode usar o alias em qualquer lugar onde usaria o tipo original:

data class Usuario(val nome: String, val idade: Int)

typealias ListaDeUsuarios = List<Usuario>

fun exibirUsuarios(usuarios: ListaDeUsuarios) {
    usuarios.forEach { println("${it.nome} - ${it.idade} anos") }
}

fun main() {
    val lista: ListaDeUsuarios = listOf(
        Usuario("Ana", 28),
        Usuario("Carlos", 35)
    )
    exibirUsuarios(lista)
}

Exemplos práticos

Simplificando tipos de função

Tipos de função em Kotlin podem ficar extensos quando possuem vários parametros. Type aliases resolvem isso:

typealias Validador<T> = (T) -> Boolean
typealias Transformador<T, R> = (T) -> R
typealias GerenciadorDeErro = (Exception) -> Unit

fun <T> filtrar(lista: List<T>, validador: Validador<T>): List<T> {
    return lista.filter(validador)
}

fun <T, R> mapear(lista: List<T>, transformador: Transformador<T, R>): List<R> {
    return lista.map(transformador)
}

fun executarComTratamento(bloco: () -> Unit, onErro: GerenciadorDeErro) {
    try {
        bloco()
    } catch (e: Exception) {
        onErro(e)
    }
}

fun main() {
    val numeros = listOf(1, 2, 3, 4, 5, 6)

    val pares = filtrar(numeros) { it % 2 == 0 }
    println(pares) // [2, 4, 6]

    val textos = mapear(numeros) { "Numero $it" }
    println(textos) // [Numero 1, Numero 2, ...]

    executarComTratamento(
        bloco = { println("Executando operacao") },
        onErro = { println("Erro: ${it.message}") }
    )
}

Trabalhando com tipos genericos complexos

typealias ResultadoApi<T> = Pair<Int, T?>
typealias TabelaDePrecos = Map<String, Map<String, Double>>
typealias MatrizInteira = Array<IntArray>

fun buscarUsuario(id: Int): ResultadoApi<Usuario> {
    return if (id > 0) {
        200 to Usuario("Ana", 28)
    } else {
        404 to null
    }
}

fun criarTabelaPrecos(): TabelaDePrecos {
    return mapOf(
        "eletronicos" to mapOf(
            "notebook" to 4500.00,
            "smartphone" to 2800.00
        ),
        "livros" to mapOf(
            "kotlin-em-acao" to 89.90,
            "clean-code" to 75.00
        )
    )
}

fun criarMatriz(linhas: Int, colunas: Int): MatrizInteira {
    return Array(linhas) { IntArray(colunas) }
}

Type alias para inner classes e nested classes

class Repositorio {
    sealed class Resultado<out T> {
        data class Sucesso<T>(val dados: T) : Resultado<T>()
        data class Erro(val mensagem: String) : Resultado<Nothing>()
        data object Carregando : Resultado<Nothing>()
    }
}

// Sem alias: Repositorio.Resultado.Sucesso<Usuario>
// Com alias:
typealias RepoResultado<T> = Repositorio.Resultado<T>
typealias RepoSucesso<T> = Repositorio.Resultado.Sucesso<T>
typealias RepoErro = Repositorio.Resultado.Erro

fun buscarDados(): RepoResultado<String> {
    return RepoSucesso("Dados carregados")
}

fun main() {
    when (val resultado = buscarDados()) {
        is RepoSucesso -> println(resultado.dados)
        is RepoErro -> println(resultado.mensagem)
        is Repositorio.Resultado.Carregando -> println("Carregando...")
    }
}

Type alias com tipos de plataforma

Quando você integra Kotlin com bibliotecas Java ou APIs externas, os nomes de tipos podem ser longos e pouco descritivos:

import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ScheduledExecutorService

typealias CacheDeTokens = ConcurrentHashMap<String, Pair<String, Long>>
typealias Agendador = ScheduledExecutorService

class GerenciadorDeTokens(
    private val cache: CacheDeTokens = CacheDeTokens(),
    private val agendador: Agendador
) {
    fun armazenar(chave: String, token: String, expiracao: Long) {
        cache[chave] = token to expiracao
    }

    fun obter(chave: String): String? {
        val entrada = cache[chave] ?: return null
        return if (System.currentTimeMillis() < entrada.second) entrada.first else null
    }
}

Quando usar type alias

Type aliases são mais úteis nos seguintes cenários:

  • Tipos genericos profundamente aninhados como Map<String, List<Pair<Int, Double>>> que aparecem em vários pontos do código.
  • Assinaturas de função repetitivas quando um mesmo tipo de callback e usado em múltiplas funções.
  • Nomes de dominio mais expressivos para tornar o código mais legivel no contexto do negócio, como typealias CPF = String ou typealias Reais = Double.
  • Encurtar referências a classes internas de bibliotecas ou de seus próprios modulos.
  • Migração gradual quando você quer renomear um tipo sem alterar todo o código de uma vez.

Casos de Uso no Mundo Real

  1. Camadas de repositório e servico: em projetos com arquitetura em camadas, type aliases simplificam tipos de retorno complexos como Flow<Result<List<Usuario>>> ou suspend () -> Result<List<Produto>>. Isso torna as assinaturas de funções dos repositórios mais legiveis e consistentes em toda a base de código.

  2. Callbacks em SDKs e bibliotecas: bibliotecas que expõe APIs com callbacks usam type aliases para dar nomes semanticos aos tipos de função. Por exemplo, typealias OnItemClick<T> = (T, Int) -> Unit e mais expressivo do que repetir a assinatura completa em cada função publica da API.

  3. Mapeamento de entidades entre camadas: projetos que seguem Clean Architecture frequentemente definem aliases para mapear entre tipos de diferentes camadas, como typealias EntityMapper<Domain, Data> = (Data) -> Domain, criando uma convencao clara para todas as funções de mapeamento.

  4. integração com bibliotecas Java: ao usar bibliotecas Java que possuem tipos com nomes longos ou pouco descritivos (como ConcurrentHashMap<String, List<WeakReference<Callback>>>>), type aliases permitem criar nomes mais concisos e contextualizados no dominio do projeto Kotlin.

Boas Praticas

  • Use type aliases apenas quando o tipo original aparece em múltiplos lugares e sua complexidade prejudica a leitura. Criar aliases para tipos simples como String ou Int raramente agrega valor.
  • Escolha nomes que reflitam o dominio do negócio, não a estrutura tecnica. Prefira typealias TabelaDePrecos = Map<String, Double> em vez de typealias StringDoubleMap = Map<String, Double>.
  • Quando precisar de seguranca de tipos real (impedir que um CPF seja usado onde se espera um Email), use value classes em vez de type aliases. O alias e apenas um apelido e não impede substituicoes indevidas.
  • Declare todos os type aliases no nivel do arquivo (top-level), preferencialmente agrupados em um arquivo dedicado como TypeAliases.kt ou proximo aos tipos que eles referenciam, facilitando a descoberta.
  • Evite criar aliases para tipos que já sao claros no contexto. O excesso de aliases pode dificultar a navegação no código e confundir desenvolvedores que precisam descobrir o tipo real por tras do alias.

Perguntas Frequentes

P: Type alias cria um novo tipo ou apenas um apelido? R: Apenas um apelido. O compilador substitui o alias pelo tipo original durante a compilação. Isso significa que typealias CPF = String permite usar um CPF em qualquer lugar que aceite String e vice-versa. Nao há verificação de tipo adicional em tempo de compilação.

P: Posso declarar um type alias dentro de uma classe ou função? R: Nao. Type aliases em Kotlin só podem ser declarados no nivel do arquivo (top-level). Tentar declarar dentro de uma classe, objeto ou função resulta em erro de compilação. Essa restricao existe porque aliases sao resolvidos em tempo de compilação e precisam ter escopo global.

P: Type alias tem algum impacto em performance? R: Nenhum. O alias e completamente eliminado durante a compilação. No bytecode final, apenas o tipo original aparece. Nao há alocacao extra, indireção ou qualquer custo em tempo de execução.

P: Quando devo usar type alias em vez de value class? R: Use type alias quando o objetivo e apenas melhorar a legibilidade de tipos complexos sem necessidade de seguranca de tipos adicional. Use value class quando você precisa que o compilador impeca a troca acidental de valores com o mesmo tipo subjacente, como evitar que um UserId seja passado onde se espera um ProductId.

Erros comuns

Confundir type alias com novo tipo

typealias CPF = String
typealias Email = String

fun enviarPara(email: Email) {
    println("Enviando para $email")
}

fun main() {
    val cpf: CPF = "123.456.789-00"
    enviarPara(cpf) // Compila sem erro! CPF e Email são ambos String
}

O type alias não cria um novo tipo. CPF e Email são intercambiaveis porque ambos são String. Se você precisa de segurança de tipos, use value classes (inline classes) em vez de type aliases.

Declarar dentro de funções ou classes

// ERRADO: type alias nao pode ser local
fun processar() {
    typealias Dados = List<String> // erro de compilacao
}

// CORRETO: declarar no nível do arquivo
typealias Dados = List<String>
fun processar(dados: Dados) { /* ... */ }

Alias para tipos com variancia conflitante

open class Animal
class Cachorro : Animal()

typealias ListaAnimais = MutableList<Animal>

fun main() {
    val cachorros: MutableList<Cachorro> = mutableListOf(Cachorro())
    // val animais: ListaAnimais = cachorros // Erro: tipos incompativeis
    // MutableList<Cachorro> nao e subtipo de MutableList<Animal>
}

O alias não altera as regras de variancia do tipo original. MutableList e invariante, entao MutableList<Cachorro> não e subtipo de MutableList<Animal>, independentemente do alias.

Excesso de aliases

Criar aliases demais pode dificultar a navegação no código. Se o tipo original já e claro, o alias adiciona uma camada desnecessaria de indirecao. Use com moderacao.

Termos relacionados

  • Value class (inline class): cria um tipo wrapper com segurança de tipos em tempo de compilação, diferente do type alias que e apenas um apelido.
  • Generics: sistema de tipos parametrizados que frequentemente gera tipos longos beneficiados por aliases.
  • Função de ordem superior: funções que recebem ou retornam outras funções, cujas assinaturas são frequentemente simplificadas com aliases.
  • Data class: tipo de classe em Kotlin para armazenar dados, frequentemente referenciada por aliases quando usada em coleções.
  • Sealed class: hierarquia restrita de tipos que pode ter aliases para facilitar a referência a subtipos.

Conclusão

Type aliases são uma ferramenta simples mas eficaz para melhorar a legibilidade do código Kotlin. Eles não criam novos tipos nem adicionam overhead em tempo de execução. Use-os para simplificar tipos genericos complexos, assinaturas de função longas e referências a classes profundamente aninhadas. Quando precisar de segurança de tipos real, considere value classes como alternativa. O equiibrio entre legibilidade e clareza e a chave para usar type aliases de forma produtiva.