Neste tutorial, você vai aprender a trabalhar com todas as estruturas de repetição disponíveis em Kotlin: for, while e do-while. Vamos explorar ranges, iteração sobre coleções, controle de fluxo com break e continue, e padrões idiomáticos que tornam seus loops mais expressivos e seguros.

O Loop For em Kotlin

O loop for em Kotlin é diferente do for tradicional de Java ou C. Ele não usa a sintaxe de três partes (inicialização; condição; incremento). Em vez disso, Kotlin usa uma abordagem mais moderna baseada em iteradores, semelhante ao for-each de outras linguagens.

A forma mais comum do for em Kotlin itera sobre um range (intervalo) ou sobre uma coleção. Essa abordagem é mais segura porque elimina erros comuns como off-by-one e índices fora dos limites.

fun main() {
    // For com range (intervalo inclusivo)
    println("=== Contando de 1 a 10 ===")
    for (i in 1..10) {
        print("$i ")
    }
    println()

    // For com range exclusivo (until / ..<)
    println("\n=== Índices de 0 a 4 (exclusivo no final) ===")
    for (i in 0 until 5) {
        print("$i ")
    }
    println()

    // For com passo (step)
    println("\n=== Números pares de 2 a 20 ===")
    for (i in 2..20 step 2) {
        print("$i ")
    }
    println()

    // For decrescente (downTo)
    println("\n=== Contagem regressiva ===")
    for (i in 10 downTo 1) {
        print("$i ")
    }
    println("\nFogo!")

    // For decrescente com passo
    println("\n=== De 100 a 0, de 10 em 10 ===")
    for (i in 100 downTo 0 step 10) {
        print("$i ")
    }
    println()
}

Os ranges são uma funcionalidade poderosa do Kotlin. O operador .. cria um range inclusivo (ambos os limites são incluídos), enquanto until ou ..< cria um range exclusivo no final (útil para trabalhar com índices de arrays). O downTo permite iteração decrescente, e step define o incremento entre os valores.

Iterando sobre Coleções

Na prática do dia a dia, você vai usar for mais frequentemente para iterar sobre listas, arrays e outras coleções do que sobre ranges numéricos. Kotlin oferece várias formas elegantes de fazer isso.

fun main() {
    val frutas = listOf("Maçã", "Banana", "Laranja", "Manga", "Uva")

    // Iteração simples sobre lista
    println("=== Frutas ===")
    for (fruta in frutas) {
        println("  - $fruta")
    }

    // Iteração com índice usando withIndex()
    println("\n=== Frutas com índice ===")
    for ((indice, fruta) in frutas.withIndex()) {
        println("  $indice: $fruta")
    }

    // Iteração sobre Map (chave-valor)
    val capitais = mapOf(
        "SP" to "São Paulo",
        "RJ" to "Rio de Janeiro",
        "MG" to "Belo Horizonte",
        "BA" to "Salvador",
        "RS" to "Porto Alegre"
    )

    println("\n=== Capitais ===")
    for ((sigla, capital) in capitais) {
        println("  $sigla -> $capital")
    }

    // Iteração sobre String (caracteres)
    val palavra = "KOTLIN"
    println("\n=== Letras de '$palavra' ===")
    for (letra in palavra) {
        print("[$letra] ")
    }
    println()

    // Iteração sobre array com índices
    val numeros = intArrayOf(10, 20, 30, 40, 50)
    println("\n=== Array com indices ===")
    for (i in numeros.indices) {
        println("  numeros[$i] = ${numeros[i]}")
    }
}

A desestruturação com (indice, valor) no withIndex() e (chave, valor) nos maps é uma sintaxe muito prática que torna o código mais legível. Ela funciona com qualquer classe que declare os operadores component1(), component2(), etc., como as data classes.

While e Do-While

Os loops while e do-while funcionam de maneira semelhante a outras linguagens. Use while quando não souber antecipadamente quantas iterações serão necessárias e do-while quando precisar garantir que o bloco execute pelo menos uma vez.

fun main() {
    // While básico
    println("=== Sequência de Fibonacci até 100 ===")
    var a = 0
    var b = 1
    while (a <= 100) {
        print("$a ")
        val temp = a + b
        a = b
        b = temp
    }
    println()

    // Do-while — executa pelo menos uma vez
    println("\n=== Adivinhe o número (simulação) ===")
    val numeroSecreto = 7
    var tentativa = 0
    var palpite: Int

    do {
        tentativa++
        palpite = tentativa * 2 + 1  // simulando palpites
        println("Tentativa $tentativa: palpite = $palpite")
    } while (palpite != numeroSecreto)

    println("Acertou em $tentativa tentativas!")

    // While para processar dados
    println("\n=== Processando fila ===")
    val fila = mutableListOf("Pedido-001", "Pedido-002", "Pedido-003", "Pedido-004")
    while (fila.isNotEmpty()) {
        val pedido = fila.removeFirst()
        println("Processando: $pedido (restam ${fila.size})")
    }
    println("Fila processada!")
}

A diferença fundamental entre while e do-while é que o do-while verifica a condição após executar o bloco, garantindo pelo menos uma execução. Isso é útil em cenários como validação de entrada do usuário, onde você precisa pedir o valor antes de verificar se ele é válido.

Controle de Fluxo: break, continue e Labels

Kotlin oferece as instruções break e continue para controlar o fluxo dentro de loops. Além disso, o sistema de labels permite direcionar essas instruções para loops específicos em estruturas aninhadas, evitando ambiguidade.

fun main() {
    // break — interrompe o loop
    println("=== Procurando primeiro múltiplo de 7 ===")
    for (i in 1..100) {
        if (i % 7 == 0) {
            println("Encontrado: $i")
            break
        }
    }

    // continue — pula para próxima iteração
    println("\n=== Números não divisíveis por 3 (de 1 a 15) ===")
    for (i in 1..15) {
        if (i % 3 == 0) continue
        print("$i ")
    }
    println()

    // Labels para loops aninhados
    println("\n=== Busca em matriz com label ===")
    val matriz = arrayOf(
        intArrayOf(1, 2, 3),
        intArrayOf(4, 5, 6),
        intArrayOf(7, 8, 9)
    )
    val alvo = 5

    busca@ for (i in matriz.indices) {
        for (j in matriz[i].indices) {
            if (matriz[i][j] == alvo) {
                println("Valor $alvo encontrado na posição [$i][$j]")
                break@busca  // sai de AMBOS os loops
            }
        }
    }

    // continue com label
    println("\n=== Pares de números (sem repetição do primeiro) ===")
    externo@ for (i in 1..3) {
        for (j in 1..3) {
            if (i == j) continue@externo
            println("  ($i, $j)")
        }
    }
}

Os labels são nomeados com o sufixo @ e podem ser aplicados a qualquer loop. break@label interrompe o loop marcado, enquanto continue@label pula para a próxima iteração do loop marcado. Use labels com moderação — código com muitos labels pode se tornar difícil de ler. Considere extrair a lógica para uma função separada como alternativa.

Alternativas Funcionais aos Loops

Kotlin oferece uma rica biblioteca de funções de alta ordem que frequentemente substituem loops tradicionais com código mais expressivo e conciso. Embora loops explícitos sejam perfeitamente válidos, conhecer essas alternativas é importante para escrever código idiomático.

As funções forEach, map, filter, reduce e muitas outras estão disponíveis em todas as coleções do Kotlin. Elas serão exploradas em detalhes nos tutoriais sobre Listas, Sets e Maps e Lambdas e Higher-Order Functions, mas aqui vai uma amostra para que você saiba que essas alternativas existem.

Dicas e Erros Comuns

Ao trabalhar com loops em Kotlin, esteja atento a estes pontos:

  1. Usar range inclusivo quando deveria ser exclusivo: for (i in 0..lista.size) vai causar IndexOutOfBoundsException porque inclui lista.size. Use 0 until lista.size ou melhor ainda, lista.indices.

  2. Tentar modificar uma coleção durante iteração com for: isso lança ConcurrentModificationException. Use iterator.remove() ou crie uma nova coleção filtrada.

  3. Loops infinitos acidentais com while: sempre garanta que a condição do while vai eventualmente se tornar falsa. Um while (true) sem break adequado trava o programa.

  4. Não usar indices ou withIndex(): em vez de for (i in 0 until lista.size), prefira for (i in lista.indices) ou for ((i, v) in lista.withIndex()). É mais idiomático e seguro.

  5. Abusar de break e continue com labels: se o código tem muitos labels, provavelmente deveria ser refatorado. Extraia a lógica para funções menores e mais claras.

  6. Usar loops onde funções de coleção seriam mais claras: para transformações simples como filtrar ou mapear, as extension functions de coleção geralmente produzem código mais legível que loops manuais.

Conclusão e Próximos Passos

Neste tutorial, você dominou todas as estruturas de repetição do Kotlin. O loop for com ranges e coleções, while e do-while para iterações condicionais, e os mecanismos de controle de fluxo com break, continue e labels. Esses são ferramentas essenciais que você vai usar em praticamente todo programa que escrever.

Para continuar evoluindo, siga para estes tutoriais:

Pratique criando programas que combinem loops com as estruturas condicionais que aprendemos anteriormente. Bons exercícios incluem tabuada, sequência de Fibonacci, números primos e ordenação de listas. Até o próximo tutorial!