As estruturas de controle de fluxo são utilizadas para manipular a ordem em que os comandos de um programa são executados. De forma natural, isso acontece linha a linha e de cima para baixo, o que não é suficiente para que um programa represente o maior número de situações do mundo real. A fim de compensar essa limitação utilizamos tais estruturas, que são abordadas neste artigo.
Guia do artigo:
Por exemplo, é possível que em um programa seja necessário determinar qual dentre dois números é o maior para se saber qual é o dia para pagamento mais distante. Escrever tal código só é possível utilizando uma estrutura condicional, como veremos na próxima seção.
Esse é apenas um dentre os muitos exemplos que justificam a utilização de estruturas de controle de fluxo em um programa. Ao longo deste artigo conheceremos outras situações semelhantes e como resolvê-las utilizando a estrutura mais adequada.
if
A estrutura condicional if permite ao programa executar um dentre diferentes blocos de código dependendo do valor retornado por uma expressão lógica. A forma de se escrever essa estrutura pode ser vista em pseudocódigo no Código 1.
if expressão-lógica
código
Por exemplo, para saber qual é o maior valor entre duas variáveis podemos usar o Código 2.
val a = 9
val b = 10
var max = a
if(b > a) {
max = b
}
Dessa forma, caso a expressão lógica b > a retorne true, a expressão max = b será executada e o valor de max será 10. Caso contrário, o código dentro do if será ignorado, fazendo com que max termine com o valor 9.
A estrutura if pode ser estendida em um ou mais blocos else ou else...if.
Por exemplo, caso tenhamos que determinar qual dentre dois valores é o maior ou imprimir "valores iguais" podemos fazer conforme o Código 3.
val a = 2
val b = 4
if(b == a) {
println("valores iguais")
}
Utilizando else...if podemos imprimir, por exemplo, "valores iguais" ou "valores diferentes". O resultado está no Código 4.
if (b > a) {
println("valores diferentes")
} else if(a == b) {
println("valores iguais")
}
Podemos utilizar quantos if, if...else ou else...if forem necessários para implementar uma lógica.
Usando if como expressão
Em Kotlin if é uma expressão e retorna um valor ao ser avaliado durante a execução do programa. Por isso aqui não temos o operador ternário ( : ?), assim como em outras linguagens. Isso porque apenas com o if conseguimos criar expressões semelhantes às obtidas com o operador ternário.
Na seção anterior aprendemos como utilizar o if de forma semelhante ao que aprendemos na matéria de algoritmos. Abaixo, no Código 5 temos um exemplo de como utilizar o if como expressão.
val max = if(a > b) a else b
Quanto a utilizar o if como expressão temos duas regras. Primeiro, o uso dos parênteses é obrigatório, de forma que escrever a expressão acima como val max = if a > b a else b estaria errado. Segundo, é obrigatório que exista um bloco else nesse caso.
Ao utilizar if como expressão o último comando no bloco if ou else retornará por padrão. Por exemplo, no Código 6 o valor da variável max será a ou b.
val max = if(a > b) {
println(a)
a
} else {
println(b)
b
}
Perceba que para max obter o valor de a ou b não é necessário utilizar o comando return dentro do if/else, pois o último comando encontrado em um desses blocos será retornado por padrão.
when
A estrutura de múltipla seleção when é utilizada para comparar um valor com conjunto de opções. Dessa forma, cada opção é verificada sequencialmente e uma vez que haja uma correspondência um valor pode ser retornado ou um comando executado.
Essa estrutura é muito utilizada quando uma variável pode assumir um dentre alguns valores conhecidos. No Código 7, por exemplo, estado pode ter os valores ativo ou inativo e qual mensagem será exibida para o usuário depende disso.
val estado = "ativo"
when(estado) {
"ativo" -> print("Clique para desativar")
"inativo" -> print("Clique para ativar")
}
Opcionalmente podemos adicionar um bloco else às ramificações de when. Dessa forma, quando o valor informado não corresponder a nenhuma das opções esse bloco será executado, como mostra o Código 8.
val estado = "ativo"
when(estado) {
"ativo" -> print("Clique para desativar")
"inativo" -> print("Clique para ativar")
else -> {
print("valor de estado é desconhecido")
}
}
Assim como o bloco else acima, qualquer ramificação dentro de when pode ser um bloco de código delimitado por chaves { }, ou um único comando ou expressão e nesse caso as chaves são opcionais.
É possível ainda agrupar diferentes valores dentro de uma única ramificação em when. Com isso o bloco de código pertencente a ela será executado caso haja uma correspondência por qualquer um dos seus valores, como mostra o Código 9.
val estado = ""
when(estado) {
"ativo", "inativo" -> print("estado conhecido)
else -> {
print("estado desconhecido")
}
}
No código acima, caso estado seja "ativo" ou "inativo" o trecho de código print("estado conhecido") será executado. Caso contrário, o código print("estado desconhecido") no else será executado.
Usando when como expressão
É possível também utilizar when como expressão. Por exemplo, no Código 10 atribuímos o valor true ou false a variável resultado dependendo do valor da variável estado.
val estado = "ativo"
val resultado = when(estado) {
"ativo" -> true
else -> false
}
Ao utilizar when como expressão é obrigatória a presença de um bloco else. No caso acima, uma vez que o valor de estado é ativo a variável resultado receberá o valor true.
💡 when tem super-poderes
A condição em uma ramificação de when também pode ser uma expressão que retorna algum valor, desde que o valor retornado seja no mesmo tipo que o valor passado para when. Por exemplo, para comparar um número com uma série podemos escrever o seguinte código:
val poltrona = 2
when(poltrona) {
in 1..10 -> println("Fileira A")
in 11..20 -> println("Fileira B")
in 21..30 -> println("Fileira C")
in 31..40 -> println("Fileira D")
}
Nesse exemplo, caso o valor da variável esteja ente os número 1 e 10, a mensagem "Fileira A" será impressa. Do contrário esse valor será comparado com as séries 11 a 20, 21 a 30 e, por fim, 31 a 40, executando cada respectivo código quando houver uma correspondência.
Nesse caso, o uso do operador in é obrigatório para que a comparação possa ser feita entre o valor cada número na série. Caso se queira saber se o número não está na série, por alguma razão, usamos !in, mas é pouco comum usarmos in dessa maneira.
for
A estrutura de repetição for é muito utilizada para percorrer coleções. Em Kotlin a sintaxe dessa estrutura pode ser vista no Código 11.
for([item] in [coleção]) {
[código]
}
Coleção, neste contexto, é uma lista de valores que pode ser armazenada em uma variável. Ela não armazena os itens, mas a lista, de forma que ela continua contendo um único objeto, que nesse caso é uma estrutura de dados capaz de armazenar múltiplos valores.
Existem diversas formas de se gerar coleções em Kotlin que possamos utilizar para exemplificar como utilizar for. Uma delas é com uma expressão de série, como 1..4, para gerar os números de 1 a 4, 3..100 para os números de 3 a 100 e assim por diante.
Por exemplo, no Código 12 a variável colecao contém os números de 1 a 9. Ao passarmos essa coleção para for serão impressos cada um dos seus itens, gerando a saída 123456789.
val colecao = 1..9
for (item in colecao) {
print(item)
}
while
A segunda e última estrutura de repetição disponibilizada pela linguagem é while. Ela possui duas variações, sendo a primeira while e a segunda do..while. A sintaxe dessa estrutura podemos ver no Código 13, onde expressão-lógica deve ser avaliada como verdadeira para que a repetição continue.
while([expressão-lógica]) {
[código]
}
do {
[código]
} while([expressão-lógica])
A principal diferença entre while ou do..while está na forma como eles verificam se a repetição deve acontecer. Enquanto while primeiro avalia a expressão lógica e depois executa a repetição, do..while faz o oposto, primeiro executando a repetição e depois avaliando a expressão lógica. Dessa forma, é certo que do..while repetirá pelo menos uma vez, enquanto while pode nunca se repetir.
Por exemplo, no Código 14 temos a mesma expressão lógica sendo aplicada a while e do..while.
while (false) {
println("Olá mundo!")
}
do {
println("Olá mundo!")
} while (false)
Como resultado, no caso de while a mensagem Olá mundo! nunca será impressa, uma vez que a expressão lógica logo é avaliada como falsa. Em do..while a mensagem Olá mundo! será impressa uma vez, então a expressão lógica será avaliada como falsa e as repetições se encerrarão.
A principal diferença entre while e for é que no caso de while o controle de quando as repetições se encerrarão fica com o programador. Voltando ao Código 12 perceba que as repetições ocorrerão apenas enquanto houver itens na série 1..9. Sendo assim, o momento de parada é aquele em que o último item na série for impresso.
Com while devemos nós mesmos nos certificar de que, em algum momento, as repetições se encerrarão, o que normalmente é feito utilizando variáveis de controle. Sendo assim, um código para percorrer uma série de número utilizando while poderia ser este do Código 15.
var nums = 0
val serie = 1..9
while (nums < serie.last) {
println(serie.elementAt(nums))
nums++
}
Aqui utilizamos a variável de controle nums, que vai sendo incrementada a cada repetição. Ela é utilizada também na expressão lógica nums < serie.last para determinar que as repetições devem se encerrar assim que nums chegar ao valor 9.
Além disso, note que agora precisamos acessar o valor que corresponde a repetição atual na série a partir de um índice.
A função elementAt() é utilizada para acessar um número na série a partir da sua posição. Aqui e em muitas outras coleções de valores a primeira posição é 0. Sendo assim, na primeira repetição nums terá o valor 0 e o valor acessado na série estará no índice 0, que contém o número 1. Na próxima repetição, nums terá o valor 1 e acessará o valor no índice 1 da série, que é 2. Isso continuará acontecendo até que nums assuma o valor 8, índice que contém o valor 9 na série.
Conclusão
As estruturas de controle de fluxo são a base para se conseguir implementar algoritmos em uma linguagem de programação. Ao avançar na linguagem até o ponto em que criaremos nossos próprios programas completos isso ficará ainda mais evidente. Kotlin possui as mesmas estruturas que a maioria das linguagens de programação, com exceção de que elas podem retornar valores, quando necessário. Essa pequena alteração na linguagem facilita o desenvolvimento e torna tais estruturas ainda mais essenciais no desenvolvimento de aplicações.