5.2. Execução condicional

Agora que já sabemos escrever expressões Java cujos valores são verdade ou falso, já podemos verificar se uma dada condição é verdadeira e, com isso, definir comportamentos condicionais nos nossos programas.

5.2.1. A instrução if

Uma forma de fazer isso é usando a instrução if, que determina um fluxo de execução condicionado por uma expressão que pode tomar valores verdade ou falso.

A expr_bool é uma expressão cujo tipo é boolean, ou seja, cujo resultado é do tipo boolean.

O bloco_condicional é composto por uma sequência de instruções entre chavetas.

A semântica, ou significado, desta instrução, ilustrada na figura acima, é:

A expressão expr_bool é avaliada e,

Como a instrução if define um novo bloco de instruções, devemos usar indentação ao escrever esse bloco, para aumentar a legibilidade. Os espaços que escrevemos a mais para indentar o bloco de instruções não afetam em nada o que o programa realmente faz quando executado.

Voltando à classe NotasFinaisLegivel do Capítulo 4, como faríamos para imprimir uma dada mensagem no ecrã somente no caso em que a nota do aluno fosse maior ou igual a 15? As seguintes instruções, inseridas no método main, produzem esse efeito:

5.2.2. Blocos de uma só instrução

Se tivermos um bloco condicional composto por uma só instrução, então as chavetas são opcionais; no entanto, por uma questão metodológica, aconselhamos a incluir sempre as chavetas para delimitar o bloco_condicional, mesmo quando tem uma só instrução.

Quando decidimos não usar chavetas para delimitar blocos com uma só instrução, devemos ter presente que isso pode ser fonte de erros indesejáveis. Temos muitas vezes que alterar pedaços de código já feitos. Suponha que queremos, por exemplo, acrescentar uma ação num bloco de uma instrução if. Suponha ainda que o bloco_condicional dessa instrução if é composto por uma só instrução e que não é delimitado por chavetas. Se, ao acrescentar uma nova instrução ao bloco_condicional, nos esquecemos de acrescentar as chavetas, obtemos um efeito totalmente diferente do desejado.

Vamos ver um exemplo. Suponha que tem o seguinte pedaço de código, em que o bloco_condicional não está envolto em chavetas:

Se x tiver um valor menor que 6 as frases Menor que 6 e Adeus serão escritas no ecrã. Se o valor de x for maior ou igual a 6, somente a frase Adeus será escrita.

Inspecionemos agora os seguintes casos, resultantes de adicionar uma instrução ao excerto anterior:

Se x tiver um valor menor que 6 as frases Menor que 6, Eu bem dizia! e Adeus serão escritas no ecrã. Se o valor de x for maior ou igual a 6, somente a frase Adeus será escrita.

Se x tiver um valor menor que 6 as frases Menor que 6, Eu bem dizia! e Adeus serão escritas no ecrã. Se o valor de x for maior ou igual a 6, as frases Eu bem dizia! e Adeus serão escritas no ecrã.

Repare que, não havendo chavetas, o bloco do if é composto somente pela instrução System.out.println("Menor que 6");.

A indentação apresentada sugire, erradamente, que a instrução seguinte também pertence ao bloco.Mas (relembre Secção 3.6.3) a indentação não tem qualquer valor semântico em Java! O efeito do pedaço de código seguinte é exatamente o mesmo que no exemplo anterior.

Repare que a única diferença deste caso para o anterior é a indentação da instrução System.out.println("Eu bem dizia!"); que já sugere que essa instrução não faz parte do bloco_condicional.

5.2.3. A instrução if-else

A instrução if-else também nos permite programar comportamento condicional, mais precisamente definindo duas alternativas mutuamente exclusivas:

Como a expr_bool é uma expressão cujo tipo é boolean, então qualquer que seja o seu valor, exatamente um dos blocos é executado.

Cada um dos bloco_T e bloco_F é composto por uma sequência de instruções entre chavetas.

A semântica desta instrução, ilustrada na figura acima, é:

a expressão expr_bool é avaliada

Repare que, se quisermos saber qual é exatamente a condição sob a qual é executado o bloco bloco_F basta calcularmos a negação da expr_bool. Por exemplo, se expr_bool for x > 3 então o bloco_F será executado nos casos em que x <= 3.

Relembre que devemos usar indentação ao escrever os blocos condicionais, para aumentar a legibilidade, sabendo, no entanto, que os espaços que escrevemos a mais para indentar os blocos de instruções não afetam em nada o significado do programa.

Seguem-se alguns pedaços de código java que exemplificam o uso da instrução if-else.

Se x tiver um valor menor que 1, as frases Menor que 1 e Adeus serão escritas no ecrã. Se o valor de x não for menor que 1 (ou seja, se for maior ou igual a 1) as frases Maior ou igual a 1 e Adeus serão escritas no ecrã.

Experimente executar o seguinte programa:

No seguinte excerto de programa,

se x tiver um valor menor que 1 as frases Menor que 1, Eu bem dizia! e Adeus serão escritas no ecrã. Se o valor de x não for menor que 1, as frases Maior ou igual a 1 e Adeus serão escritas no ecrã.

No seguinte,

se x tiver um valor menor que 1 as frases Menor que 1 e Adeus serão escritas no ecrã. Se o valor de x não for menor que 1, as frases Maior ou igual a 1, Eu bem dizia! e Adeus serão escritas no ecrã.

Finalmente, no seguinte,

se x tiver um valor menor que 1 as frases Menor que 1, Eu bem dizia! e Adeus serão escritas no ecrã. Se o valor de x não for menor que 1, as frases Maior ou igual a 1, Eu bem dizia! e Adeus serão escritas no ecrã.

O seguinte excerto de programa tem um efeito equivalente ao anterior.

O que se fez foi extrair a instrução System.out.println("Eu bem dizia!"); dos dois blocos alternativos e colocá-la fora dos blocos, pois ela seria executada qualquer que fosse o valor da condição do if.

Aqui, de novo, se decidirmos não usar chavetas para delimitar blocos com uma só instrução, devemos ter cuidado para não introduzirmos erros.

As instruções seguintes têm exatamente o mesmo efeito que o último excerto apresentado.

Já que os blocos alternativos só tinham uma instrução cada um, pôde-se prescindir das chavetas.

Atente no bloco seguinte de instruções:

Aqui, não havendo chavetas no bloco do else, ele é composto somente pela instrução System.out.println("Maior ou igual a 1"); mesmo que a indentação apresentada sugira que a instrução seguinte também pertence ao bloco. Por isso, se x tiver um valor menor que 1, as frases Menor que 1, Eu bem dizia! e Adeus serão escritas no ecrã. Se o valor de x não for menor que 1, as frases Maior ou igual a 1, Eu bem dizia! e Adeus serão escritas no ecrã.

Veja a execução "ao vivo":

Considere agora o seguinte pedaço de código:

Neste caso ocorreria um erro de compilação pois o bloco do if é composto somente por uma instrução (não há chavetas), que é System.out.println("Menor que 1");. De seguida vem a instrução System.out.println("Eu bem dizia!");. Até aqui tudo bem para o compilador. O pior é quando deteta a palavra else “desgarrada”, ou seja, sem estar “emparelhada” com nenhum if...

5.2.4. Condição alternativa

A expr_bool nas instruções if e if-else pode ser uma expressão do tipo boolean tão complexa quanto desejemos.

Na instrução if-else temos que ter cuidado, no entanto, para percebermos bem quais são as situações em que é executado o bloco_F ou, o que é o mesmo, o que significa a negação da expr_bool. Para isso devemos conhecer bem a semântica dos operadores lógicos e relacionais.

Por exemplo, na seguinte instrução if-else, se x e y forem variáveis dos tipos int e double, respetivamente, em que condições será executada a instrução System.out.println("Adeus")?

A resposta a essa pergunta é: quando a negação de (x > 1 && x <= 3) || x > 100) && y < 2.3) for verdadeira. Sabendo que, pelas leis de De Morgan, a negação da conjunção é a disjunção das negações e a negação da disjunção é a conjunção das negações, temos que:

!(((x > 1 && x <= 3) || x > 100) && y < 2.3) é equivalente a

!((x > 1 && x <= 3) || x > 100) || !(y < 2.3) que, por sua vez, é equivalente a

(!(x > 1 && x <= 3) && !(x > 100)) || (y >= 2.3) por sua vez equivalente a

((!(x > 1) || !(x <= 3)) && (x <= 100)) || (y >= 2.3) por sua vez equivalente a

(((x <= 1) || (x > 3)) && (x <= 100)) || (y >= 2.3)

De qualquer forma, devemos ter cuidado para não usarmos condições muito complexas na instrução if-else para não introduzirmos erros lógicos acidentalmente e mantermos o programa legível.

5.2.5. Instruções condicionais “encaixadas”

Uma instrução if-else permite-nos tratar de situações em que temos duas ações alternativas. Sempre que precisamos de trabalhar com mais do que duas alternativas, podemos “encaixar” instruções condicionais.

Se quisermos, por exemplo, saber se um dado número é positivo, nulo ou negativo, quantas perguntas temos que fazer? Duas no pior caso, aquele em que só acertamos no fim – se as respostas forem ambas negativas então já sabemos que é a terceira hipótese que é a correta.

Vamos supor que queremos escrever numa variável resultado as strings "Positivo", "Negativo" ou "Nulo", conforme o valor de uma variável n. Podemos começar por perguntar se o valor de n é positivo:

Que instrução devemos ter no lugar dos ???????

Depende! Se n tiver o valor zero queremos ter resultado = "Nulo";. Se n for negativo queremos ter resultado = "Negativo";.

Então, a instrução que devemos ter no lugar dos ????? é uma outra instrução condicional, que nos permita decidir entre as duas alternativas que restam.

Obtemos o excerto de código seguinte (ao qual corresponde o fluxo de execução ilustrado em baixo) :

Outra solução para o mesmo caso poderia ser:

Considere este último bloco de ifs encaixados; é importante saber responder às questões que se seguem:

Tenha em atenção o facto de que um ramo else emparelha sempre com o if imediatamente anterior. Por isso, num caso como o seguinte:

a variável resultado ficaria com o valor "Negativo" pois o else corresponderia à condição n >= 0 && n >= 100. Se quisermos que este else emparelhe com o if exterior, temos que usar as chavetas de modo a delimitar bem o fim do if interior.

Com o código seguinte a variável resultado ficaria com o valor "Fora":

Imagine agora mais níveis de “encaixe” – a certa altura teríamos pouco espaço à direita para escrevermos as nossas instruções além de que a legibilidade seria seriamente afetada.

É muito comum não usar chavetas nos casos em que temos um if como única instrução do bloco de um else. Se adotarmos essa simplificação e, além disso, alinharmos os if-else encaixados, obtemos uma série de instruções mais compacta e legível.

A primeira versão do exemplo que determina se um número é positivo, negativo ou nulo (acima) poderia então ser escrito da seguinte forma:

Será que para obter mais do que duas ações alternativas temos que ter sempre instruções if encaixadas?

Não necessariamente. Podemos usar instruções if sequenciais como as que se seguem (ver também fluxo de execução ilustrado a seguir) embora seja pior solução:

Porque é que é pior? Esta opção obriga a testar sempre as três condições, mesmo que alguma das anteriores já tenha sido verdadeira.

Mas por outro lado é mais legível pois estão explícitas todas as condições relevantes. De qualquer forma, tem que se ter cuidado, pois se as condições não forem mutuamente exclusivas, não obtemos o mesmo comportamento da versão que usa os else-if.

Finalmente, o programa seguinte imprime as palavras Positivo, Negativo e Nulo no ecrã.

Experimente executá-lo "ao vivo":

 

Retomemos agora a função ehPar definida na Secção 5.1. O seguinte programa imprime as frases 2 eh par e 5 nao eh par no ecrã:

Repare na condição da instrução if no método imprimeParidade. É uma invocação da função ehPar . Como a função ehPar tem tipo boolean, então a expressão ehPar(num) tem tipo boolean e por isso pode ser usada como condição de uma instrução if.

Experimente executá-lo:

5.2.6. Retorno de funções

A função ehPar podia ter sido codificada assim (caso 1):

Nesta versão estamos a ser redundantes pois a instrução if está a fazer o seguinte: avalia a expressão num % 2 == 0 e testa o seu valor: se for verdadeiro, escreve o valor true na variável result; se for falso, escreve o valor false em result. Então, mais vale escrever diretamente o valor da expressão num % 2 == 0 na variável result e terminar a função devolvendo esse valor.

Mas agora a variável result é desnecessária, pois o seu valor só é usado uma vez – como resultado da função. Então mais vale escolhermos a versão original da Secção 5.1:

 

Podíamos, ainda, ter codificado a função ehPar assim (caso 2):

Nesta versão estamos também a ser redundantes pois a instrução if avalia a expressão num % 2 == 0 e testa o seu valor: se for verdadeiro, termina a função devolvendo o valor true; se for falso, termina a função devolvendo o valor false. Então, mais vale terminar a função devolvendo diretamente o valor da expressão num % 2 == 0. Que é precisamente a versão da Secção 5.1!

Já vimos no capítulo anterior que as funções têm que ter uma instrução return. Podem ter várias, aliás, como exemplificado numa das versões da função ehPar.

No entanto, quantas mais instruções return tiver uma função, mais complicado se torna percebê-la, ou seja, a legibilidade da função pode ficar comprometida.

Vamos então, sempre que possível, ter uma única instrução return nas nossas funções.

Se o valor devolvido depender de uma ou mais instruções condicionais, então podemos sempre definir uma variável local à função, à qual será atribuído um valor que depende das diversas condições; no fim da função retornamos o valor dessa variável: relembre o exemplo da função positividade:

 


 

Anterior: 5.1. O tipo primitivo boolean

Seguinte: 5.3. Ainda a documentação