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.
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.
xxxxxxxxxx
if (expr_bool)
bloco_condicional;
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,
- se o seu valor for
true
, então obloco_condicional
é executado e de seguida o fluxo do programa continua na instrução a seguir aobloco_condicional
;- se o seu valor é
false
, obloco_condicional
não é executado e o fluxo do programa continua na instrução a seguir aobloco_condicional
.
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:
xxxxxxxxxx
...
// nota maior ou igual a 15?
if (nota >= 15){
System.out.println("Quadro de honra");
}
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:
xxxxxxxxxx
if (x < 6)
System.out.println("Menor que 6");
System.out.println("Adeus");
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:
xxxxxxxxxx
if (x < 6) {
System.out.println("Menor que 6");
System.out.println("Eu bem dizia!");
}
System.out.println("Adeus");
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.
xxxxxxxxxx
if (x < 6)
System.out.println("Menor que 6");
System.out.println("Eu bem dizia!");
System.out.println("Adeus");
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.
xxxxxxxxxx
if (x < 6)
System.out.println("Menor que 6");
System.out.println("Eu bem dizia!");
System.out.println("Adeus");
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
.
if-else
A instrução if-else
também nos permite programar comportamento condicional, mais precisamente definindo duas alternativas mutuamente exclusivas:
xxxxxxxxxx
if (expr_bool)
bloco_T;
else
bloco_F;
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
- se o seu valor for
true
, então obloco_T
é executado;- caso contrário o
bloco_F
é executado;- em qualquer dos casos, o fluxo de execução do programa continua na instrução que vem a seguir ao
bloco_F
.
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
.
xxxxxxxxxx
if (x < 1) {
System.out.println("Menor que 1");
} else {
System.out.println("Maior ou igual a 1");
}
System.out.println("Adeus");
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ã.
No seguinte excerto de programa,
xxxxxxxxxx
if (x < 1) {
System.out.println("Menor que 1");
System.out.println("Eu bem dizia!");
} else {
System.out.println("Maior ou igual a 1");
}
System.out.println("Adeus");
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,
xxxxxxxxxx
if (x < 1) {
System.out.println("Menor que 1");
} else {
System.out.println("Maior ou igual a 1");
System.out.println("Eu bem dizia!");
}
System.out.println("Adeus");
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,
xxxxxxxxxx
if (x < 1) {
System.out.println("Menor que 1");
System.out.println("Eu bem dizia!");
} else {
System.out.println("Maior ou igual a 1");
System.out.println("Eu bem dizia!");
}
System.out.println("Adeus");
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.
xxxxxxxxxx
if (x < 1) {
System.out.println("Menor que 1");
} else {
System.out.println("Maior ou igual a 1");
}
System.out.println("Eu bem dizia!");
System.out.println("Adeus");
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.
xxxxxxxxxx
if (x < 1)
System.out.println("Menor que 1");
else
System.out.println("Maior ou igual a 1");
System.out.println("Eu bem dizia!");
System.out.println("Adeus");
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:
xxxxxxxxxx
if (x < 1)
System.out.println("Menor que 1");
else
System.out.println("Maior ou igual a 1");
System.out.println("Eu bem dizia!");
System.out.println("Adeus");
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ã.
Considere agora o seguinte pedaço de código:
xxxxxxxxxx
if (x < 1)
System.out.println("Menor que 1");
System.out.println("Eu bem dizia!");
else
System.out.println("Maior ou igual a 1");
System.out.println("Adeus");
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
...
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")
?
xxxxxxxxxx
if (((x > 1 && x <= 3) || x > 100) && y < 2.3){
System.out.println("Olah");
} else {
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.
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:
xxxxxxxxxx
if (n > 0){
resultado = "Positivo";
} else
?????
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) :
xxxxxxxxxx
if (n > 0){
resultado = "Positivo";
} else {
if (n < 0){
resultado = "Negativo";
} else {
resultado = "Nulo";
}
}
Outra solução para o mesmo caso poderia ser:
xxxxxxxxxx
if (n >= 0){
if (n > 0){
resultado = "Positivo";
} else {
resultado = "Nulo";
}
} else {
resultado = "Negativo";
}
Considere este último bloco de ifs
encaixados; é importante saber responder às questões que se seguem:
Questão: Sob que condição é executado o bloco {resultado = "Negativo";}
do else
correspondente ao primeiro if
?
Resposta: Sob a negação de n >= 0, ou seja, n < 0.
Questão: Sob que condição é executado o bloco {resultado = "Positivo";}
do segundo if
?
Resposta: Basta fazer a conjunção de n >= 0 com n > 0, o que dá n > 0.
Questão: Sob que condição é executado o bloco {resultado = "Nulo";}
do else
correspondente ao segundo if
?
Resposta: Basta fazer a conjunção de n >= 0 com a negação de n > 0, o que dá n >= 0 && n <= 0, que é equivalente a n == 0.
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:
xxxxxxxxxx
int n = 100;
String resultado = "Fora";
if (n >= 0){
if (n < 100){
resultado = "No intervalo [0,99]";
} else {
resultado = "Negativo";
}
}
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":
xxxxxxxxxx
int n = 100;
String resultado = "Fora";
if (n >= 0) {
if (n < 100){
resultado = "No intervalo [0,99]";
}
} else {
resultado = "Negativo";
}
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:
xxxxxxxxxx
if (n > 0){
resultado = "Positivo";
} else if (n < 0){
resultado = "Negativo";
} else {
resultado = "Nulo";
}
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:
xxxxxxxxxx
if (n > 0){
resultado = "Positivo";
}
if (n < 0){
resultado = "Negativo";
}
if (n == 0){
resultado = "Nulo";
}
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ã.
xxxxxxxxxx
public class TresEscolhas {
public static void main (String [] args) {
int i = 2;
int j = -7;
System.out.println(positividade(i));
System.out.println(positividade(j));
System.out.println(positividade(i - 2));
}
/**
* Um numero eh positivo, nulo ou negativo?
* @param num
* @return "Positivo" se num > 0;
"Nulo" se num == 0
"Negativo" se num < 0
*/
static String positividade(int num) {
String resultado;
if (num > 0){
resultado = "Positivo";
} else if (num < 0){
resultado = "Negativo";
} else {
resultado = "Nulo";
}
return resultado;
}
}
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ã:
xpublic class BooleanFunctionExample2 {
public static void main (String [] args) {
int i = 2;
imprimeParidade(i);
imprimeParidade(i + 3);
}
/**
* Imprime mensagem acerca da paridade de um dado numero
* @param num
*/
static void imprimeParidade(int num) {
if (ehPar(num)){
System.out.println(num + " eh par");
} else {
System.out.println(num + " nao eh par");
}
}
/**
* Um numero inteiro eh par?
* @param num
* @return true se num eh par; false caso contrario
*/
static boolean ehPar(int num) {
return num % 2 == 0;
}
}
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
.
A função ehPar
podia ter sido codificada assim (caso 1):
xxxxxxxxxx
static boolean ehPar(int num) {
boolean result;
if (num % 2 == 0){
result = true;
} else {
result = false;
}
return result;
}
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.
xxxxxxxxxx
static boolean ehPar(int num) {
boolean result = num % 2 == 0;
return result;
}
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:
xxxxxxxxxx
static boolean ehPar(int num) {
return num % 2 == 0;
}
Podíamos, ainda, ter codificado a função ehPar
assim (caso 2):
xxxxxxxxxx
static boolean ehPar(int num) {
if (num % 2 == 0){
return true;
} else {
return false;
}
}
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!
xxxxxxxxxx
static boolean ehPar(int num) {
return num % 2 == 0;
}
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:
xxxxxxxxxx
static String positividade(int num) {
String resultado;
if (num > 0){
resultado = "Positivo";
} else if (num < 0){
resultado = "Negativo";
} else {
resultado = "Nulo";
}
return resultado;
}
Anterior: 5.1. O tipo primitivo boolean
Seguinte: 5.3. Ainda a documentação