Imagine poder adicionar métodos novos a classes que já existem — sem herança, sem wrappers, sem modificar o código original. Parece mágica? Em Kotlin, isso se chama Extension Functions, e é um dos recursos mais queridos da linguagem.
O que são Extension Functions?
Extension Functions permitem que você “estenda” uma classe com novas funções, como se elas fizessem parte da classe original. A sintaxe é simples:
fun String.contarPalavras(): Int {
return this.trim().split("\\s+".toRegex()).size
}
fun main() {
val frase = "Kotlin é incrível demais da conta"
println(frase.contarPalavras()) // 6
}
Repare: contarPalavras() não existe na classe String, mas a gente chamou como se existisse. O this dentro da função refere-se ao objeto receptor (no caso, a String).
Exemplos práticos do dia a dia
Vamos ver extensões que você vai querer copiar agora mesmo pro seu projeto:
Formatação de valores brasileiros
fun Double.toBRL(): String {
return "R$ %,.2f".format(this).replace(",", "X").replace(".", ",").replace("X", ".")
}
fun String.toCpfFormatado(): String {
val limpo = this.replace("[^0-9]".toRegex(), "")
require(limpo.length == 11) { "CPF deve ter 11 dígitos" }
return "${limpo.substring(0,3)}.${limpo.substring(3,6)}.${limpo.substring(6,9)}-${limpo.substring(9)}"
}
fun String.toCnpjFormatado(): String {
val limpo = this.replace("[^0-9]".toRegex(), "")
require(limpo.length == 14) { "CNPJ deve ter 14 dígitos" }
return "${limpo.substring(0,2)}.${limpo.substring(2,5)}.${limpo.substring(5,8)}/${limpo.substring(8,12)}-${limpo.substring(12)}"
}
fun main() {
println(1499.90.toBRL()) // R$ 1.499,90
println("12345678901".toCpfFormatado()) // 123.456.789-01
println("12345678000190".toCnpjFormatado()) // 12.345.678/0001-90
}
Validações
fun String.isEmailValido(): Boolean {
return matches("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}".toRegex())
}
fun String.isCpfValido(): Boolean {
val numeros = this.replace("[^0-9]".toRegex(), "")
if (numeros.length != 11) return false
if (numeros.all { it == numeros[0] }) return false
// Validação dos dígitos verificadores
val dv1 = (0..8).sumOf { (10 - it) * numeros[it].digitToInt() } % 11
val primeiroDigito = if (dv1 < 2) 0 else 11 - dv1
val dv2 = (0..9).sumOf { (11 - it) * numeros[it].digitToInt() } % 11
val segundoDigito = if (dv2 < 2) 0 else 11 - dv2
return numeros[9].digitToInt() == primeiroDigito &&
numeros[10].digitToInt() == segundoDigito
}
fun main() {
println("karina@kotlin.dev.br".isEmailValido()) // true
println("email-invalido".isEmailValido()) // false
}
Manipulação de datas
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.time.Period
fun LocalDate.toBrazilianFormat(): String {
return this.format(DateTimeFormatter.ofPattern("dd/MM/yyyy"))
}
fun LocalDate.idadeEmAnos(): Int {
return Period.between(this, LocalDate.now()).years
}
fun main() {
val nascimento = LocalDate.of(1995, 6, 15)
println(nascimento.toBrazilianFormat()) // 15/06/1995
println("Idade: ${nascimento.idadeEmAnos()} anos")
}
Extension Properties
Não é só função — você também pode criar propriedades de extensão:
val String.primeiraLetra: Char
get() = this.first()
val String.ultimaLetra: Char
get() = this.last()
val List<*>.penultimoElemento: Any?
get() = this[this.size - 2]
fun main() {
println("Kotlin".primeiraLetra) // K
println("Kotlin".ultimaLetra) // n
}
Uma limitação importante: extension properties não podem ter backing field, ou seja, não podem armazenar estado. Elas sempre precisam ser calculadas.
Extensions em coleções
Extensões brilham quando trabalham com coleções:
fun <T> List<T>.segundoOuNull(): T? = if (this.size >= 2) this[1] else null
fun List<Int>.mediana(): Double {
val ordenada = this.sorted()
val meio = ordenada.size / 2
return if (ordenada.size % 2 == 0) {
(ordenada[meio - 1] + ordenada[meio]) / 2.0
} else {
ordenada[meio].toDouble()
}
}
fun <T> List<T>.agruparEmPares(): List<Pair<T, T?>> {
return this.chunked(2).map { chunk ->
Pair(chunk[0], chunk.getOrNull(1))
}
}
fun main() {
val numeros = listOf(3, 1, 4, 1, 5, 9, 2, 6)
println(numeros.mediana()) // 3.5
println(numeros.segundoOuNull()) // 1
println(numeros.agruparEmPares()) // [(3, 1), (4, 1), (5, 9), (2, 6)]
}
Como funciona por baixo dos panos
É importante entender: extension functions não modificam realmente a classe. Elas são resolvidas estaticamente. No bytecode, uma extension function vira uma função estática onde o primeiro parâmetro é o objeto receptor:
// Isso:
fun String.gritando() = this.uppercase() + "!!!"
// Vira essencialmente isso no bytecode:
// public static String gritando(String $this) { return $this.toUpperCase() + "!!!"; }
Isso tem implicações importantes:
- Não há polimorfismo: se uma classe filha e uma classe pai têm extensions com o mesmo nome, o tipo declarado (não o real) determina qual é chamada
- Membros sempre ganham: se a classe já tem um método com o mesmo nome, o membro da classe vence
class Mensagem(val texto: String) {
fun exibir() = "Classe: $texto"
}
fun Mensagem.exibir() = "Extension: $texto" // NUNCA será chamada
fun main() {
println(Mensagem("Oi").exibir()) // "Classe: Oi" — o membro vence
}
Organizando extensions
Em projetos maiores, organize suas extensions em arquivos dedicados:
src/
extensions/
StringExtensions.kt
DateExtensions.kt
CollectionExtensions.kt
ViewExtensions.kt // Android
// StringExtensions.kt
package br.dev.kotlin.extensions
fun String.capitalizar(): String {
return this.split(" ").joinToString(" ") { palavra ->
palavra.replaceFirstChar { it.uppercase() }
}
}
Boas práticas
- Use extensions para adicionar funcionalidade coesa a uma classe
- Não abuse: se a lógica é complexa, talvez uma classe própria faça mais sentido
- Dê nomes descritivos: a extension deve parecer um membro natural da classe
- Documente: extensions podem surpreender quem lê o código pela primeira vez
- Agrupe por tipo: mantenha as extensions organizadas em arquivos temáticos
Conclusão
Extension Functions são um dos recursos que fazem Kotlin ser tão expressivo e prazeroso de programar. Elas permitem que você adapte a linguagem ao seu domínio, criando uma API fluente e natural. Use com sabedoria, e seu código vai ficar muito mais legível e manutenível.
Bora estender tudo!