O que é inline em Kotlin?
O modificador inline em Kotlin instrui o compilador a copiar o corpo da função diretamente no local da chamada, em vez de criar uma chamada de função normal. Isso elimina o overhead de criação de objetos para lambdas, melhorando a performance.
Toda vez que você passa uma lambda como argumento, o Kotlin normalmente cria um objeto anônimo. Com inline, esse objeto não é criado — o código da lambda é copiado diretamente.
Sem inline vs com inline
// Sem inline: cria um objeto Function para cada chamada
fun semInline(bloco: () -> Unit) {
bloco()
}
// Com inline: codigo é copiado no local da chamada
inline fun comInline(bloco: () -> Unit) {
bloco()
}
Na prática, o resultado é o mesmo. A diferença é por baixo dos panos: a versão inline não aloca um objeto Function.
Exemplo prático: medindo tempo
inline fun medirTempo(bloco: () -> Unit) {
val inicio = System.currentTimeMillis()
bloco()
val fim = System.currentTimeMillis()
println("Tempo: ${fim - inicio}ms")
}
fun main() {
medirTempo {
val soma = (1..1_000_000).sum()
println("Soma: $soma")
}
}
Non-local return
Uma vantagem do inline é permitir non-local return — retornar da função que chamou, não só da lambda:
inline fun buscarPrimeiro(lista: List<Int>, condicao: (Int) -> Boolean): Int? {
for (item in lista) {
if (condicao(item)) return item
}
return null
}
fun main() {
val resultado = buscarPrimeiro(listOf(1, 3, 5, 8, 10)) { it % 2 == 0 }
println(resultado) // 8
}
noinline e crossinline
Se a função tem vários parâmetros lambda e você não quer inlinear todos:
inline fun exemplo(
inlineada: () -> Unit,
noinline naoInlineada: () -> Unit
) {
inlineada()
naoInlineada()
}
E crossinline impede non-local return em lambdas que serão executadas em outro contexto:
inline fun executarEmThread(crossinline bloco: () -> Unit) {
Thread {
bloco() // crossinline impede "return" aqui
}.start()
}
O crossinline é necessário quando a lambda será executada em um contexto diferente (como outra thread ou callback), onde um non-local return não faz sentido e causaria comportamento indefinido.
Quando usar?
Use inline em funções que recebem lambdas como parâmetro e são chamadas com frequência. A biblioteca padrão do Kotlin usa inline em let, apply, map, filter e praticamente todas as scope functions.
Reified type parameters
O inline desbloqueia uma funcionalidade exclusiva: os reified type parameters. Normalmente, tipos genéricos são apagados em runtime (type erasure), mas com inline e reified você pode acessá-los:
inline fun <reified T> converterPara(json: String): T {
println("Convertendo para ${T::class.simpleName}")
// Em um caso real, usaria uma biblioteca como Gson ou kotlinx.serialization
throw NotImplementedError("Exemplo ilustrativo")
}
inline fun <reified T> List<*>.filtrarPorTipo(): List<T> {
return this.filterIsInstance<T>()
}
fun main() {
val misturado: List<Any> = listOf(1, "texto", 2.5, "outro", 3)
val strings = misturado.filtrarPorTipo<String>()
println(strings) // [texto, outro]
val inteiros = misturado.filtrarPorTipo<Int>()
println(inteiros) // [1, 3]
}
Sem inline, seria necessário passar a classe como parâmetro: fun <T> converter(json: String, tipo: Class<T>). Com reified, a API fica muito mais limpa.
Inline properties
Além de funções, Kotlin permite marcar getters e setters de propriedades como inline:
class Configuracao(private val mapa: Map<String, String>) {
inline val versao: String
get() = mapa["versao"] ?: "1.0.0"
inline val ambiente: String
get() = mapa["ambiente"] ?: "desenvolvimento"
}
Isso é útil quando a propriedade não tem backing field e o getter é simples o suficiente para ser copiado no local de uso.
Casos de Uso no Mundo Real
- Scope functions da biblioteca padrão:
let,run,with,applyealsosão todasinline, o que significa que usá-las não tem custo de performance em relação a escrever o código diretamente. - DSLs type-safe: frameworks como Jetpack Compose e Ktor usam funções
inlinepara criar DSLs onde o código do usuário é inlineado, mantendo a performance e permitindo non-local returns. - Serialização e deserialização: bibliotecas usam
inlinecomreifiedpara inferir o tipo de destino automaticamente, comojson.decodeFromString<MeuObjeto>(texto)em kotlinx.serialization. - Logging condicional: funções inline de log como
logger.debug { "mensagem cara: ${calcular()}" }evitam a construção da string quando o nível de log está desabilitado, já que o lambda só é avaliado se necessário.
Boas Práticas
- Use
inlineapenas em funções que recebem lambdas. Marcar funções sem parâmetros de função comoinlinetraz pouco benefício e pode aumentar o tamanho do bytecode. - Marque com
noinlineparâmetros lambda que precisam ser armazenados em variáveis, passados para outras funções não-inline ou usados como objetos. - Prefira
crossinlinequando a lambda será executada em outro contexto (threads, callbacks) para evitar bugs sutis com non-local returns. - Combine
inlinecomreifiedpara criar APIs mais limpas que não exigem passagem explícita deClass<T>ouKClass<T>. - Monitore o tamanho do bytecode gerado. Funções inline muito grandes chamadas em muitos lugares podem aumentar significativamente o tamanho do APK ou JAR.
Erros Comuns
- Tornar todas as funções inline indiscriminadamente: o
inlinecopia o corpo da função em cada local de chamada. Funções grandes inlineadas em muitos lugares inflam o bytecode sem ganho real de performance. - Esquecer de marcar parâmetros como
noinlinequando necessário: se você tenta armazenar uma lambda inline em uma variável ou passá-la para outra função, o compilador gera um erro. A solução é marcar esse parâmetro comonoinline. - Non-local return inesperado: em funções
inline, umreturndentro da lambda retorna da função que chamou, não apenas da lambda. Isso pode causar comportamento inesperado se você esperava apenas sair do bloco. - Usar
inlineem funções de interface ou virtuais: funçõesinlinenão podem seropen,overrideou abstratas, pois o compilador precisa do corpo da função em tempo de compilação para copiar. - Confundir
reifiedcom reflexão:reifiedfunciona apenas em tempo de compilação dentro de funçõesinline. Você não pode usarreifiedem funções não-inline ou armazenar o tipo em uma variável para uso posterior.
Perguntas Frequentes
O inline melhora a performance de toda função? Não. O benefício principal é evitar a alocação de objetos Function para lambdas. Em funções que não recebem lambdas, o ganho é mínimo e o custo de aumento do bytecode pode não valer a pena.
Qual a diferença entre noinline e crossinline? noinline impede completamente o inlining da lambda, permitindo que ela seja tratada como objeto. crossinline mantém o inlining mas proíbe non-local returns, necessário quando a lambda é executada em outro contexto como uma thread.
Por que as scope functions do Kotlin são inline? Para que o uso de let, apply, run e similares tenha zero overhead. Sem inline, cada chamada criaria um objeto Function, o que seria inaceitável para funções tão frequentemente utilizadas.
Posso usar reified sem inline? Não. O modificador reified só funciona em funções inline porque depende do compilador substituir o tipo genérico pelo tipo concreto no local de chamada. Sem inlining, o tipo seria apagado por type erasure.
Termos Relacionados
- Higher-Order Function — funções que recebem lambdas e são as principais candidatas para
inline - Lambda — funções anônimas cujo overhead de alocação é eliminado por
inline - Generics — tipos genéricos que podem ser preservados em runtime com
reifiedeinline - Fun — palavra-chave usada para declarar funções que podem receber o modificador
inline