O que e Boxing em Kotlin?
Boxing e o processo de encapsular um tipo primitivo (como Int, Long, Double) dentro de um objeto wrapper correspondente (como java.lang.Integer, java.lang.Long, java.lang.Double). O processo inverso, extrair o valor primitivo do objeto, e chamado de unboxing.
Em Kotlin, diferente do Java, voce nao declara explicitamente int vs Integer. Tudo e Int. Porem, o compilador decide por baixo dos panos se vai usar o tipo primitivo (int) ou o wrapper (Integer) dependendo do contexto. Entender quando o boxing acontece e fundamental para escrever codigo performatico.
Como o compilador decide?
O Kotlin compila tipos como Int para primitivos JVM (int) sempre que possivel. Porem, em certas situacoes, o boxing e obrigatorio:
- Quando o tipo e nullable (
Int?), o valor precisa ser boxed porque primitivos nao podem sernull. - Quando o tipo e usado como argumento generico (
List<Int>), pois generics na JVM trabalham com objetos. - Quando o tipo e usado em contextos que exigem referencia de objeto.
val a: Int = 42 // Compilado como primitivo int
val b: Int? = 42 // Compilado como Integer (boxed)
val lista: List<Int> = listOf(1, 2, 3) // Cada Int e boxed como Integer
Impacto na performance
O boxing tem custo. Cada vez que um primitivo e boxed, um novo objeto e alocado no heap. Em loops intensivos ou colecoes grandes, isso pode gerar pressao significativa no garbage collector.
// Versao com boxing: cada iteracao cria um objeto Integer
fun somaBoxed(lista: List<Int>): Int {
var soma = 0
for (valor in lista) {
soma += valor // unboxing acontece aqui
}
return soma
}
// Versao sem boxing: usa IntArray com primitivos
fun somaPrimitiva(array: IntArray): Int {
var soma = 0
for (valor in array) {
soma += valor // nenhum boxing
}
return soma
}
A segunda versao e significativamente mais rapida para arrays grandes porque nao envolve alocacao de objetos nem garbage collection adicional.
Arrays especializados
Para evitar boxing em arrays, Kotlin oferece tipos especializados:
val intArray: IntArray = intArrayOf(1, 2, 3) // int[] na JVM
val longArray: LongArray = longArrayOf(1L, 2L, 3L) // long[] na JVM
val doubleArray: DoubleArray = doubleArrayOf(1.0, 2.0) // double[] na JVM
// Comparando com Array<Int> que sofre boxing
val boxedArray: Array<Int> = arrayOf(1, 2, 3) // Integer[] na JVM
Sempre prefira IntArray, LongArray, DoubleArray e similares quando performance for importante.
Identidade vs igualdade com boxing
Um detalhe sutil e que o boxing pode afetar a identidade de objetos:
val a: Int = 127
val boxedA: Int? = a
val outroBoxedA: Int? = a
println(boxedA === outroBoxedA) // true (cache de -128 a 127)
val b: Int = 128
val boxedB: Int? = b
val outroBoxedB: Int? = b
println(boxedB === outroBoxedB) // false (fora do cache)
println(boxedB == outroBoxedB) // true (igualdade estrutural)
A JVM faz cache de Integer para valores entre -128 e 127. Fora dessa faixa, cada boxing cria um novo objeto, entao a comparacao de identidade (===) retorna false. Sempre use == para comparar valores numericos.
Boxing em funcoes genericas
Funcoes genericas sempre causam boxing porque a JVM nao suporta generics com primitivos:
fun <T> imprimir(valor: T) {
println(valor)
}
fun main() {
imprimir(42) // 42 e boxed para Integer antes de ser passado
}
Para evitar isso em casos especificos, voce pode usar funcoes inline com reified:
inline fun <reified T> verificarTipo(valor: T): String {
return when (T::class) {
Int::class -> "Inteiro"
String::class -> "String"
else -> "Outro"
}
}
Porem, isso nao elimina completamente o boxing em todos os cenarios. Para performance critica, considere sobrecarregar a funcao com versoes especificas para cada tipo primitivo.
Quando usar tipos que causam boxing
O boxing nao e algo que voce precisa evitar obsessivamente. Na maioria dos casos, o impacto e negligenciavel. Preocupe-se com boxing apenas quando:
- Voce esta processando milhoes de elementos em loops apertados.
- Voce esta trabalhando com colecoes muito grandes onde a alocacao de memoria importa.
- Profiling mostrou que garbage collection esta sendo um gargalo no seu sistema.
- Voce esta desenvolvendo bibliotecas de alta performance ou algoritmos numericos.
Para codigo de aplicacao comum, como chamadas de API, manipulacao de UI ou logica de negocios, o boxing e perfeitamente aceitavel e nao merece otimizacao prematura.
Erros comuns
Comparar tipos nullable com
===: como vimos, boxing pode criar objetos diferentes para o mesmo valor. Sempre use==para comparacoes de valor.Ignorar arrays especializados: usar
Array<Int>em vez deIntArrayquando performance importa. A diferenca pode ser significativa em colecoes grandes.Otimizar prematuramente: evitar
List<Int>em favor deIntArrayem todo lugar, mesmo quando a lista e pequena e a legibilidade do codigo e mais importante.Esquecer que nullable causa boxing: declarar
var contador: Int? = 0quando o valor nunca sera null. UseIntsem interrogacao sempre que possivel.Nao considerar o boxing em benchmarks: ao comparar performance de diferentes abordagens, o boxing pode distorcer resultados se nao for levado em conta.
Colecoes sem boxing
Para cenarios de alta performance, existem bibliotecas que oferecem colecoes especializadas sem boxing, como a biblioteca eclipse-collections ou implementacoes customizadas com IntArray como backing store:
class ListaDeInteiros {
private var dados = IntArray(16)
private var tamanho = 0
fun adicionar(valor: Int) {
if (tamanho == dados.size) {
dados = dados.copyOf(dados.size * 2)
}
dados[tamanho++] = valor
}
fun obter(indice: Int): Int = dados[indice]
fun tamanho(): Int = tamanho
}
Termos relacionados
- Value Class: permite criar tipos que encapsulam um primitivo sem overhead de boxing adicional em tempo de execucao.
- Inline: funcoes inline podem ajudar a reduzir boxing em certos contextos com lambdas.
- Reified: permite acessar o tipo generico em tempo de execucao em funcoes inline, evitando algumas formas de boxing.
- Nullable: tipos nullable (
Int?) sempre causam boxing porque primitivos nao podem ser null na JVM. - IntArray/LongArray/DoubleArray: arrays especializados que evitam boxing, armazenando primitivos diretamente.
Compreender boxing e unboxing e essencial para escrever Kotlin que seja ao mesmo tempo idiomatico e eficiente. Na maioria dos casos, confie no compilador. Nos casos onde performance e critica, use as ferramentas que o Kotlin oferece para manter tudo no mundo dos primitivos.