O que é Operator Overloading em Kotlin?
Operator Overloading (sobrecarga de operadores) permite redefinir o comportamento de operadores como +, -, *, [], == e outros para suas próprias classes. Você escreve funções especiais marcadas com operator e pronto — seus objetos passam a funcionar com a sintaxe natural de operadores.
Exemplo básico: somando vetores
data class Vetor(val x: Double, val y: Double) {
operator fun plus(outro: Vetor) = Vetor(x + outro.x, y + outro.y)
operator fun minus(outro: Vetor) = Vetor(x - outro.x, y - outro.y)
operator fun times(escalar: Double) = Vetor(x * escalar, y * escalar)
}
fun main() {
val a = Vetor(1.0, 2.0)
val b = Vetor(3.0, 4.0)
println(a + b) // Vetor(x=4.0, y=6.0)
println(a - b) // Vetor(x=-2.0, y=-2.0)
println(a * 3.0) // Vetor(x=3.0, y=6.0)
}
Principais operadores
| Operador | Função | Exemplo |
|---|---|---|
+ | plus | a + b |
- | minus | a - b |
* | times | a * b |
/ | div | a / b |
[] | get / set | a[i] |
== | equals | a == b |
> < | compareTo | a > b |
in | contains | x in a |
Operadores de acesso: get e set
class Matriz(private val dados: Array<IntArray>) {
operator fun get(linha: Int, coluna: Int): Int = dados[linha][coluna]
operator fun set(linha: Int, coluna: Int, valor: Int) {
dados[linha][coluna] = valor
}
}
fun main() {
val m = Matriz(arrayOf(intArrayOf(1, 2), intArrayOf(3, 4)))
println(m[0, 1]) // 2
m[1, 0] = 99
println(m[1, 0]) // 99
}
Operador invoke
O operador invoke permite chamar um objeto como se fosse uma função:
class Validador(val regex: Regex) {
operator fun invoke(valor: String): Boolean = regex.matches(valor)
}
fun main() {
val validarEmail = Validador(Regex("^[\\w.-]+@[\\w.-]+\\.[a-z]{2,}$"))
println(validarEmail("karina@kotlin.dev.br")) // true
println(validarEmail("invalido")) // false
}
Operadores de atribuição composta
Além dos operadores básicos, você pode sobrecarregar os operadores de atribuição composta como +=, -= e *=. O Kotlin resolve automaticamente a += b como a = a.plus(b) se a variável for var, ou você pode definir plusAssign explicitamente para tipos mutáveis:
class Carrinho {
private val itens = mutableListOf<String>()
operator fun plusAssign(item: String) {
itens.add(item)
}
operator fun minusAssign(item: String) {
itens.remove(item)
}
operator fun contains(item: String): Boolean = item in itens
override fun toString() = "Carrinho(itens=$itens)"
}
fun main() {
val carrinho = Carrinho()
carrinho += "Teclado"
carrinho += "Mouse"
carrinho += "Monitor"
carrinho -= "Mouse"
println(carrinho) // Carrinho(itens=[Teclado, Monitor])
println("Teclado" in carrinho) // true
println("Mouse" in carrinho) // false
}
Operadores como extensões
Você não precisa ser o dono da classe para definir operadores. Funções de extensão com operator funcionam perfeitamente:
data class Dinheiro(val centavos: Long) {
override fun toString(): String {
val reais = centavos / 100
val cents = centavos % 100
return "R$ $reais,${cents.toString().padStart(2, '0')}"
}
}
operator fun Dinheiro.plus(outro: Dinheiro) = Dinheiro(centavos + outro.centavos)
operator fun Dinheiro.times(quantidade: Int) = Dinheiro(centavos * quantidade)
operator fun Dinheiro.compareTo(outro: Dinheiro) = centavos.compareTo(outro.centavos)
fun main() {
val preco = Dinheiro(2990) // R$ 29,90
val frete = Dinheiro(1500) // R$ 15,00
val total = preco + frete
val triplo = preco * 3
println(total) // R$ 44,90
println(triplo) // R$ 89,70
println(preco > frete) // true
}
Casos de Uso no Mundo Real
- Cálculos financeiros: criar tipos como
DinheiroouMoedacom operadores aritméticos, evitando erros de arredondamento comDoublee garantindo segurança de tipo. - Bibliotecas matemáticas e científicas: definir operações sobre vetores, matrizes, números complexos e outras estruturas algébricas, permitindo que fórmulas no código pareçam com fórmulas matemáticas.
- DSLs (Domain-Specific Languages): usar
invoke,getecontainspara criar APIs fluentes e naturais, como builders de consultas SQL ou configurações. - Coleções customizadas: sobrecarregar
get,set,contains,plusAssigneiteratorpara criar estruturas de dados com interface familiar.
Boas Práticas
- O operador deve ter um significado intuitivo para quem lê o código. Somar dois vetores faz sentido; “somar” dois usuários, nem tanto.
- Mantenha a semântica esperada:
plusdeve ser comutativo quando possível,compareTodeve ser transitivo,equalsdeve ser reflexivo, simétrico e transitivo. - Prefira operadores como extensões quando a classe não é sua. Isso mantém o código desacoplado e permite adicionar operadores a classes de bibliotecas de terceiros.
- Documente operadores não óbvios. Se o significado de
*na sua classe não for imediatamente claro, adicione um comentário explicando. - Evite sobrecarregar operadores em excesso. Se a classe tem dez operadores, talvez ela esteja fazendo coisas demais.
Erros Comuns
- Usar operator overloading sem semântica clara: definir
+para concatenar objetos que não fazem sentido juntos confunde outros desenvolvedores e torna o código difícil de manter. - Esquecer a palavra-chave
operator: sem ela, a função é apenas uma função normal chamadaplusoutimes, e o operador+ou*não vai funcionar. - Confundir
pluscomplusAssign: se a classe é imutável, defina apenaspluse usevar. Se é mutável, definaplusAssign. Definir ambos na mesma classe gera ambiguidade comvar. - Não respeitar contratos de
equalsehashCode: ao sobrecarregarequals, sempre sobrescrevahashCodetambém. Caso contrário, a classe vai se comportar de forma inesperada em coleções comoHashSeteHashMap. - Ignorar efeitos colaterais em operadores: operadores devem ser puros quando possível. Um
plusque faz chamada de rede ou modifica estado global é um design perigoso.
Perguntas Frequentes
Posso criar operadores novos que não existem em Kotlin? Não. Kotlin tem um conjunto fixo de operadores que podem ser sobrecarregados. Você não pode inventar símbolos novos como <> ou **. A lista completa está na documentação oficial.
Operator overloading afeta a performance? Na maioria dos casos, não há impacto significativo. O compilador transforma operadores em chamadas de função normais. Com data class e inline class, o overhead é mínimo ou zero.
Qual a diferença entre == e === ao sobrecarregar? O operador == chama a função equals e pode ser sobrecarregado. Já o === verifica identidade referencial (se dois objetos são a mesma instância na memória) e não pode ser sobrecarregado.
Posso sobrecarregar operadores em interfaces? Sim. Você pode declarar funções operator em interfaces, e as classes que implementarem a interface terão o operador disponível.
Termos Relacionados
- Extension Function — funções de extensão que podem definir operadores para classes existentes
- Data Class — classes que já implementam
equals,hashCodee outros operadores automaticamente - Infix Function — outra forma de criar sintaxe expressiva em Kotlin
- Inline Class — classes wrapper com zero overhead que combinam bem com operadores
Quando bem aplicado, operator overloading deixa o código expressivo e natural.