Neste tutorial, você vai aprender tudo sobre Extension Functions em Kotlin — um dos recursos mais elegantes da linguagem. Extension functions permitem adicionar novas funcionalidades a classes existentes sem precisar herdar delas ou usar padrões como Decorator. Ao final, você vai dominar a sintaxe, entender extension properties, trabalhar com nullable receivers, generics e aplicar esse conhecimento em casos de uso práticos do dia a dia.
O que São Extension Functions?
Uma extension function é uma função que “estende” uma classe existente com um novo comportamento, sem modificar o código-fonte original da classe. Isso é particularmente útil quando você quer adicionar métodos a classes que não controla, como classes da standard library do Kotlin, do Android SDK ou de bibliotecas de terceiros.
Em Java, o equivalente seria criar classes utilitárias estáticas (como StringUtils, CollectionUtils). Em Kotlin, extension functions oferecem uma abordagem muito mais natural e legível.
Sintaxe Básica
A sintaxe de uma extension function coloca o tipo que está sendo estendido (chamado de receiver type) antes do nome da função:
fun String.contarPalavras(): Int {
return this.trim().split("\\s+".toRegex()).size
}
fun main() {
val texto = "Kotlin é uma linguagem incrível"
println(texto.contarPalavras()) // 5
// Funciona com qualquer String
println("Hello World".contarPalavras()) // 2
}
Dentro da extension function, a palavra this se refere ao objeto receiver — neste caso, a instância de String na qual a função foi chamada. Você pode omitir this ao acessar membros do receiver, assim como faria dentro de uma função membro normal da classe.
fun String.primeiraMaiuscula(): String {
if (isEmpty()) return this
return this[0].uppercase() + substring(1).lowercase()
}
println("kotlin".primeiraMaiuscula()) // Kotlin
println("BRASIL".primeiraMaiuscula()) // Brasil
Extension Properties
Além de funções, você pode criar extension properties. A única restrição é que elas não podem ter backing field — ou seja, não podem armazenar estado. Elas precisam ser definidas com get() (e opcionalmente set()):
val String.primeiraLetra: Char
get() = this[0]
val String.ultimaLetra: Char
get() = this[length - 1]
val List<Int>.media: Double
get() = if (isEmpty()) 0.0 else sum().toDouble() / size
fun main() {
println("Kotlin".primeiraLetra) // K
println("Kotlin".ultimaLetra) // n
val notas = listOf(8, 7, 9, 10, 6)
println("Média: ${notas.media}") // Média: 8.0
}
Extension properties são ótimas para criar acessos legíveis a dados derivados. O código fica muito mais fluente do que chamar funções utilitárias.
Extensions em Companion Objects
Você pode estender o companion object de uma classe, permitindo criar o equivalente a métodos estáticos via extensão:
class Usuario(val nome: String, val email: String) {
companion object
}
fun Usuario.Companion.fromCsv(csv: String): Usuario {
val partes = csv.split(",")
return Usuario(partes[0].trim(), partes[1].trim())
}
fun main() {
val usuario = Usuario.fromCsv("Ana Silva, ana@email.com")
println("${usuario.nome} — ${usuario.email}")
// Ana Silva — ana@email.com
}
Isso é especialmente útil para criar factory methods para classes de bibliotecas externas que já possuem um companion object definido.
Escopo de Extension Functions
Extension functions são resolvidas estaticamente, não dinamicamente. Isso significa que a função chamada é determinada pelo tipo declarado da variável, não pelo tipo real em tempo de execução:
open class Animal
class Cachorro : Animal()
fun Animal.descricao() = "Eu sou um animal"
fun Cachorro.descricao() = "Eu sou um cachorro"
fun imprimir(animal: Animal) {
println(animal.descricao())
}
fun main() {
imprimir(Cachorro()) // "Eu sou um animal" — NÃO "Eu sou um cachorro"!
}
Esse comportamento é diferente de sobrescrita de métodos em classes (polimorfismo). É fundamental entender essa distinção para evitar bugs sutis.
Além disso, se uma extension function tem a mesma assinatura que uma função membro da classe, a função membro sempre vence:
class MinhaClasse {
fun saudacao() = "Olá do membro!"
}
fun MinhaClasse.saudacao() = "Olá da extensão!"
fun main() {
println(MinhaClasse().saudacao()) // "Olá do membro!"
}
Nullable Receiver Extensions
Você pode definir extension functions em tipos nullable, o que permite chamá-las mesmo quando o objeto é null:
fun String?.ouPadrao(padrao: String = "N/A"): String {
return this ?: padrao
}
fun Any?.descricaoSegura(): String {
return this?.toString() ?: "null"
}
fun main() {
val nome: String? = null
println(nome.ouPadrao()) // N/A
println(nome.ouPadrao("Anônimo")) // Anônimo
val valor: Int? = null
println(valor.descricaoSegura()) // null
println(42.descricaoSegura()) // 42
}
Esse padrão é muito poderoso para criar APIs seguras que lidam com nullability de forma elegante, sem poluir o código com verificações if (x != null) por toda parte.
Generic Extensions
Combinando extension functions com generics, você pode criar funções extremamente reutilizáveis:
fun <T> List<T>.segundoOuNull(): T? {
return if (size >= 2) this[1] else null
}
fun <T : Comparable<T>> List<T>.estaOrdenada(): Boolean {
for (i in 0 until size - 1) {
if (this[i] > this[i + 1]) return false
}
return true
}
fun <K, V> Map<K, V>.imprimir() {
forEach { (chave, valor) ->
println("$chave -> $valor")
}
}
fun main() {
val numeros = listOf(10, 20, 30)
println(numeros.segundoOuNull()) // 20
println(numeros.estaOrdenada()) // true
val vazio = emptyList<String>()
println(vazio.segundoOuNull()) // null
mapOf("nome" to "Ana", "cidade" to "SP").imprimir()
}
Casos de Uso Práticos
Formatação de dados
fun Double.formatarMoeda(): String {
return "R$ ${"%.2f".format(this).replace(".", ",")}"
}
fun Long.formatarCpf(): String {
val s = this.toString().padStart(11, '0')
return "${s.substring(0,3)}.${s.substring(3,6)}.${s.substring(6,9)}-${s.substring(9)}"
}
println(1599.90.formatarMoeda()) // R$ 1599,90
println(12345678901L.formatarCpf()) // 123.456.789-01
Validações
fun String.ehEmailValido(): Boolean {
return matches(Regex("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"))
}
fun String.ehCepValido(): Boolean {
return matches(Regex("^\\d{5}-?\\d{3}$"))
}
println("ana@email.com".ehEmailValido()) // true
println("01310-100".ehCepValido()) // true
Simplificando Android com extensions
// Exemplo típico de extension para desenvolvimento Android
fun Context.toast(mensagem: String, duracao: Int = Toast.LENGTH_SHORT) {
Toast.makeText(this, mensagem, duracao).show()
}
fun View.visivel() { visibility = View.VISIBLE }
fun View.invisivel() { visibility = View.GONE }
// Uso na Activity:
// toast("Salvo com sucesso!")
// meuBotao.invisivel()
DSL (Domain-Specific Language)
Extension functions são a base para criar DSLs em Kotlin:
class HtmlBuilder {
private val elementos = mutableListOf<String>()
fun p(texto: String) { elementos.add("<p>$texto</p>") }
fun h1(texto: String) { elementos.add("<h1>$texto</h1>") }
fun construir(): String = elementos.joinToString("\n")
}
fun html(bloco: HtmlBuilder.() -> Unit): String {
val builder = HtmlBuilder()
builder.bloco()
return builder.construir()
}
val pagina = html {
h1("Bem-vindo ao Kotlin Brasil")
p("Aprenda Kotlin em português.")
p("Tutoriais, glossário e muito mais.")
}
println(pagina)
Erros Comuns
Esperar comportamento polimórfico: extension functions são resolvidas estaticamente. Se você precisa de polimorfismo, use funções membro com
overrideem vez de extensions.Abusar de extension functions: nem tudo precisa ser uma extension. Use-as quando fizer sentido semântico — quando a função realmente “pertence” ao tipo. Funções utilitárias genéricas podem ser funções top-level normais.
Esquecer que membros vencem extensions: se a classe já tem um método com a mesma assinatura, sua extension será ignorada silenciosamente. Sempre verifique a API da classe antes de criar uma extension.
Não importar extensions de outros arquivos: extension functions definidas em outros pacotes precisam ser importadas com
import. Se a sua extension não está disponível, verifique os imports.Tentar armazenar estado em extension properties: extension properties não podem ter backing field. Se você precisa associar dados extras a um objeto, considere usar um
Mapexterno ou o padrão de delegation.
Conclusão e Próximos Passos
Extension functions são um dos recursos que tornam Kotlin tão expressivo e agradável de usar. Elas permitem criar código legível, modular e reutilizável sem modificar classes existentes. Neste tutorial, cobrimos a sintaxe básica, extension properties, companion object extensions, escopo e resolução estática, nullable receivers, generics e diversos casos de uso práticos.
Para aprofundar seus conhecimentos, explore os seguintes tópicos:
- Lambdas para combinar com extension functions em APIs fluentes
- Data Classes para entender melhor as classes que você pode estender
- Generics para criar extensions ainda mais flexíveis
- DSLs para aprender como extension functions são usadas para criar linguagens de domínio específico
Comece criando extensions simples para tipos que você usa com frequência e, com o tempo, você vai desenvolver um estilo de código Kotlin verdadeiramente idiomático.