O que e Extension Function em Kotlin?

Extension Functions permitem adicionar novas funções a classes existentes sem precisar herdar delas ou usar padrões como Decorator. Voce estende o comportamento de qualquer classe – inclusive as que não sao suas, como String, Int ou List.

E uma das funcionalidades mais elegantes do Kotlin é uma das que mais faz a galera se apaixonar pela linguagem.

Imagine que você comprou uma estante pronta. Voce não pode modificar o projeto original da fabrica, mas pode parafusar uma prateleira extra nela. Extension functions fazem isso com classes: adicionam comportamento novo sem alterar o código original. A classe não sabe que foi estendida, mas quem usa ganha uma função nova disponivel como se fosse nativa.

Esse recurso e fundamental para criar DSLs, APIs fluentes e utilitarios reutilizaveis. A própria biblioteca padrão do Kotlin e repleta de extension functions – funções como let, also, map e filter em colecoes sao todas extensions.

Sintaxe básica

fun String.contarPalavras(): Int {
    return this.trim().split("\\s+".toRegex()).size
}

fun main() {
    val frase = "Kotlin e muito massa"
    println(frase.contarPalavras()) // 4
}

A função contarPalavras() agora pode ser chamada em qualquer String, como se fizesse parte da classe original. O this dentro da função se refere ao objeto em que ela foi chamada.

Extension em tipos numericos

fun Double.formatarReais(): String {
    return "R$ ${"%.2f".format(this)}"
}

fun Int.ehPar(): Boolean = this % 2 == 0

fun main() {
    println(1599.90.formatarReais()) // R$ 1599.90
    println(42.ehPar())              // true
    println(7.ehPar())               // false
}

Extension em colecoes

fun <T> List<T>.segundoOuNull(): T? {
    return if (this.size >= 2) this[1] else null
}

fun main() {
    val nomes = listOf("Ana", "Bruno", "Carlos")
    println(nomes.segundoOuNull()) // Bruno

    val vazia = emptyList<String>()
    println(vazia.segundoOuNull()) // null
}

Como funciona por tras

Na real, extension functions sao resolvidas estaticamente. O compilador transforma a função numa função estática com o objeto como primeiro parametro. Isso significa que elas não suportam polimorfismo – e a classe declarada que conta, não a real.

open class Animal
class Cachorro : Animal()

fun Animal.som() = "..."
fun Cachorro.som() = "Au au!"

fun fazerSom(animal: Animal) = animal.som()

fun main() {
    println(fazerSom(Cachorro())) // "..." -- usa o tipo declarado
}

Extension Properties

Alem de funções, você pode criar propriedades de extensao. Elas não armazenam estado (não tem backing field), mas podem calcular valores:

val String.primeiraLetraMaiuscula: String
    get() = if (isNotEmpty()) {
        this[0].uppercase() + substring(1)
    } else {
        this
    }

val List<Int>.media: Double
    get() = if (isNotEmpty()) sum().toDouble() / size else 0.0

fun main() {
    println("kotlin".primeiraLetraMaiuscula) // Kotlin
    println(listOf(10, 20, 30).media)        // 20.0
}

Extension Functions com generics

Extensions combinadas com generics permitem criar funções reutilizaveis para qualquer tipo:

fun <T> T.tambemLogar(tag: String = "LOG"): T {
    println("[$tag] $this")
    return this
}

fun <T> List<T>.agruparEmPares(): List<List<T>> {
    return chunked(2)
}

fun main() {
    val resultado = 42.tambemLogar("Numero") // [Numero] 42
    println(resultado) // 42

    val pares = listOf("A", "B", "C", "D", "E").agruparEmPares()
    println(pares) // [[A, B], [C, D], [E]]
}

Extension Functions em tipos nullable

Voce pode criar extensions que funcionam mesmo quando o objeto e null:

fun String?.ouPadrao(padrao: String = "N/A"): String {
    return this ?: padrao
}

fun main() {
    val nome: String? = null
    val apelido: String? = "Kari"

    println(nome.ouPadrao())        // N/A
    println(apelido.ouPadrao())     // Kari
    println(nome.ouPadrao("Sem nome")) // Sem nome
}

Casos de Uso no Mundo Real

  • Formatacao de dados: criar extensions como Date.formatarBrasileiro() ou Double.formatarMoeda() que encapsulam regras de formatacao usadas em todo o projeto.
  • Validacao: funções como String.ehCpfValido() ou String.ehEmailValido() que centralizam lógica de validacao e podem ser chamadas de qualquer lugar.
  • Android e Jetpack: extensions como View.esconder(), View.mostrar(), Context.toast() sao extremamente comuns em projetos Android para reduzir boilerplate.
  • Conversao de tipos: funções como String.toUsuario() ou Map.toConfig() que transformam dados brutos em objetos de dominio usando data classes.
  • Testes: criar extensions que facilitam assercoes, como List.deveConter(item) ou String.deveSerVazia().

Boas Praticas

  • Use extension functions para adicionar comportamento a classes que você não controla. Se você controla a classe, avalie se a função não deveria ser um método normal.
  • Mantenha extensions em arquivos organizados por tipo (por exemplo, StringExtensions.kt, ListExtensions.kt). Isso facilita a descoberta e reutilização.
  • Evite criar extensions que dependem de estado externo ou efeitos colaterais. Extensions devem ser previsiveis e puras sempre que possível.
  • Prefira extensions a funções utilitarias com o objeto como parametro. email.ehValido() e mais natural que ehValido(email).
  • Documente suas extensions, especialmente as que serao usadas por outros desenvolvedores. O autocomplete da IDE mostra extensions, mas sem documentação pode ser difícil entender o que cada uma faz.

Erros Comuns

  • Esperar comportamento polimorfico: como extensions sao resolvidas estaticamente, chamar animal.som() sempre usa o tipo declarado da variavel, não o tipo real do objeto. Se precisar de polimorfismo, use métodos de instancia ou interfaces.
  • Criar extensions demais: adicionar dezenas de extensions a String ou List polui o autocomplete da IDE e dificulta a manutenção. Seja seletivo.
  • Conflito com métodos existentes: se uma extension tem a mesma assinatura que um método da classe, o método da classe sempre vence. A extension nunca sera chamada, sem nenhum aviso do compilador.
  • Nao importar a extension: extensions precisam ser importadas para serem usadas fora do arquivo onde foram declaradas. Esquecimento de import e um erro frequente.

Perguntas Frequentes

Extension functions alteram a classe original? Nao. A classe original permanece intacta. O compilador transforma a extension em uma função estática que recebe o objeto como primeiro parametro. E apenas acucar sintatico.

Posso criar extension functions para companion objects? Sim. Voce pode estender o companion object de uma classe, permitindo chamar a extension como se fosse uma função estática: MinhaClasse.minhaExtension().

Extensions funcionam em Kotlin Multiplatform? Sim. Extensions sao um recurso da linguagem Kotlin e funcionam em todas as plataformas: JVM, JS e Native. Elas sao especialmente úteis para criar APIs consistentes entre plataformas usando expect/actual.

Qual a diferenca entre extension function e higher-order function? Sao conceitos diferentes. Extension functions adicionam funções a tipos existentes. Higher-order functions recebem ou retornam funções como parametro. Porem, elas se combinam muito bem – por exemplo, List.filter {} e uma extension function que recebe uma lambda como parametro.

Quando usar? Use extension functions para adicionar utilidades a classes que você não controla, criar APIs mais fluentes e manter o código organizado. A biblioteca padrão do Kotlin e cheia delas.