Ifs PDF
Ifs PDF
Ifs PDF
En este tema vamos a presentar dos tipos de sentencias de control: las sentencias
condicionales, y las excepciones. Ambos tipos de sentencias son necesarias en muchos casos
para poder proporcionar algoritmos eficientes y correctos. Explicaremos la semántica asociada a
dichas sentencias, así como su sintaxis en el lenguaje Java.
1. Sentencias condicionales: if
Las sentencias condicionales permiten comprobar el resultado de una expresión booleana (ver
Tema 5, apartado 2), y elegir acciones alternativas dependiendo del resultado de la
comprobación.
Java tiene dos instrucciones de selección:
• la instrucción if, y
• la instrucción switch
Sentencia IF
if (expresion booleana){ //los paréntesis son obligatorios
sentenciasTrue;
}
else {
sentenciasFalse;
};
En una sentencia if, se evalúa la expresión booleana, que necesariamente tiene que aparecer
entre paréntesis. Si el resultado de dicha evaluación es true, entonces se ejecuta el conjunto de
sentencias que hemos denominado sentenciasTrue. Nos referiremos a estas sentencias
como parte then. Si este conjunto está formado por una única sentencia, entonces se pueden
omitir las llaves. Si el resultado de la expresión booleana es false, entonces se ejecutarán las
sentencias que hemos denominado sentenciasFalse, y que referiremos como parte
else. Dicha parte es opcional.
Las sentencias de las partes then y else de una construcción if pueden ser cualesquiera, incluso
otra sentencia if. A menudo, puede ser necesario anidar varias sentencias if para ejecutar
diferentes acciones en función de varias condiciones, por ejemplo:
Para una mayor claridad a la hora de "leer" el código fuente, y para facilitar la comprensión del
mismo, se suelen incluir espacios delante de las sentencias if, para que así se aprecie
claramente el ámbito de cada una de ellas. Estos espacios en blanco, en cambio, son ignorados
por el compilador de Java.
Tal y como está escrito el código anterior, se podría suponer que la asignación t=2 se realiza
cuando no se cumple la condición del primer if (x>0), pero se podría haber seguido el criterio de
considerar dicha sentencia como parte else del segundo if. Para evitar que distintos
compiladores tome diferentes criterios, se establece la siguiente norma:
Una sentencia else se asocia siempre con el if más cercano que no tenga parte else..
Así, en el ejemplo anterior, la sentencia t=2 se ejecuta como parte else del segundo if, es
decir, cuando (x>0) & (y≤0).
La intención al escribir el código es que si el valor de x es mayor que 1⋅1038 se muestre por
pantalla el mensaje. El problema está en que cuando se ejecuta dicho código, siempre aparece
en pantalla el mensaje, independientemente del valor de x. ¿Dónde está el error? Justamente en
el punto y coma que aparece después de la expresión booleana. Recordemos que el punto y
coma indica la finalización de una sentencia. Por lo tanto el código anterior evaluaría la expresión
y no haría nada, es decir, se trataría de un if sin parte then, ni parte else. La sintaxis del código
es correcta, por lo tanto no detectaremos el error hasta que ejecutemos el programa y
comprobemos el resultado con distintos valores de x. Si casualmente, en varias ejecuciones del
programa la expresión booleana toma el valor cierto, podremos deducir de forma errónea que el
programa es correcto, cuando no lo es. De esta forma resultará difícil la posterior depuración del
programa, cuando dicho error se manifieste (en el caso de que x sea menor o igual que 1⋅1038).
Por lo tanto:
1
Recordad que el proceso de detección y corrección de errores de denomina "depuración". Dicho
concepto se estudió en el Tema 2.
2
Programación para la Gestión
Precaución
La sentencia if no requiere una terminación en punto y coma. La única razón para incluir un
punto y coma en una sentencia if, viene dada por las sentencias individuales controladas por
dicho if.
en donde se aprecia mejor esta situación. No hay que olvidar que la inclusión de espacios en
blanco solamente sirve para las personas que van a leer el código. El compilador siempre quitará
todos los espacios en blanco, tabuladores, o retornos de línea, con lo cual dichos elementos no
tendrán ningún efecto en la ejecución del programa.
Precaución
No debemos olvidarnos de incluir llaves cuando sea necesario. Una buena práctica es incluir una
pareja de llaves inmediatamente después de la expresión booleana del if, o de la palabra
reservada else, y posteriormente escribir el código de las correspondientes partes then y/o else.
De esta forma evitaremos un olvido accidental de los paréntesis.
Recordemos que cuando un método devuelve un valor utiliza la sentencia return como última
sentencia de dicho método. Si dicha sentencia no es la última, las sentencias siguientes al return
no se ejecutarán, puesto que dicho método devolverá el control a la sentencia siguiente a la
llamada a dicho método. Si embargo, es posible incluir varias sentencias return en un mismo
método, de forma que finalice la ejecución del mismo dependiendo de ciertas acciones.
Supongamos, por ejemplo, que estamos escribiendo un programa para calcular ciertas tasas, y
que uno de los métodos, taxOn(), toma como entrada una cantidad, y devuelve la tasa a aplicar a
dicha entrada. Suponemos que, según las normas, entradas en el rango 0...19450,00 son
tasadas con un porcentaje del 15%, las entradas en el rango 19450,01...47050,00 se tasan con
un porcentaje del 28%, las entradas en el rango 47050,01...97620,00 se tasan con un 33%,
finalmente, las entradas superiores a 97620,00 se tasan con un 50%. Según esta definición de
método, una posible implementación sería:
Listado 6.2. Método taxOn que usa else para controlar la ejecución
private double taxOn(double entrada) {
double tasa; //valor de retorno
if (entrada <= 19450.0)
tasa= 0.15 * entrada;
else if (entrada <=47050.0)
tasa= 0.28*entrada;
else if (entrada <=97620.0)
tasa=0.33*entrada;
else tasa= 0.50*entrada;
return tasa;
}
3
Tema 6. Sentencias de control (2)
Otra alternativa podría haber sido utilizar la sentencia return una vez que se ha satisfecho la
condición correspondiente. Esta situación se ilustra en el listado 6.3
Listado 6.3. Método taxOn que usa sentencias return para controlar la ejecución
private double taxOn(double entrada) {
if (entrada <= 19450.0)
return 0.15 * entrada;
if (entrada <=47050.0)
return 0.28*entrada;
if (entrada <=97620.0)
return 0.33*entrada;
else return 0.50*entrada;
}
Esta versión es más simple que la del listado 6.2 puesto que no necesitamos declarar ninguna
variable local para almacenar la cantidad resultante de aplicar la tasa al valor de entrada, pero
requiere tener en cuenta el hecho de que la sentencia return fuerza una terminación
inmediata de la ejecución del método. Además, no ha sido necesario anidar las sentencias if. Si
intentamos combinar la solución de los listados 6.2 y 6.3, obtenemos una solución como la que
se plantea en el listado 6.4, que es incorrecta, puesto que el resultado que se obtiene no es el
deseado.
Si realizamos la traza2 de la ejecución de este método, observaremos que a todas las entradas
con valor inferior o igual a 97620 se les aplica la tasa equivalente a un porcentaje del 33%. Por lo
tanto:
Precaución
Hasta que no estemos lo suficientemente acostumbrados a utilizar la sentencia if, es una buena
práctica no utilizar sentencias return como parte del cuerpo then y/o else de dichas sentencias.
En su lugar, es mejor utilizar secuencias de sentencias if, o sentencias if anidadas.
2
Una traza consiste en la ejecución de un algoritmo paso a paso, mostrando el estado (valores de todas las
variables) del programa en cada paso.
4
Programación para la Gestión
Sentencia SWITCH
switch (expresion){ //los paréntesis son obligatorios
case constante1:
sentencias;
case constante2:
sentencias;
...
default:
sentencias;
}
La expresión puede devolver valores de tipo char, byte, short, o int, y las constantes
deben ser literales3 o variables con el modificador final. La etiqueta default puede
omitirse, pero no es una buena idea, puesto que si el resultado de la expresión no coincide con
ninguna constante se producirá un error en tiempo de ejecución (run time error).
Es importante hacer notar que este comportamiento difiere del de una colección de sentencias if
anidadas. Ello es debido a que, a menos que introduzcamos alguna acción que fuerce a terminar
la sentencia switch, todas las sentencias situadas entre la etiqueta case y el final del
switch serán ejecutadas. Si se quiere separar las acciones de una sentencia switch en
unidades discretas y evitar este comportamiento, se deben incluir sentencias break.
Cuando se encuentra una sentencia break, se fuerza la terminación del switch. Normalmente
siempre se utiliza una sentencia break en cada sección case del switch. Solamente
dejaremos de usarla cuando queramos que se evalúen varias secciones case.
El listado 6.5 muestra un trozo de código que contiene una sentencia switch. La variable
currentAlignment contiene un valor asociado a la alineación de la clase Label, y
queremos que este valor cambie según la secuencia LEFT, CENTER y RIGHT. Debido a que las
constantes de la clase que describen las alineaciones de las etiquetas se representan
internamente como constantes de tipo entero, se puede utilizar currentAlignment como
expresón en una sentencia switch.
3
Un literal es una representación del valor de un tipo primitivo, String o null. Por ejemplo 3.08 es un
literal double, y "pepe" es un literal String
5
Tema 6. Sentencias de control (2)
Para ver la importancia de la sentencia break, supongamos que no la hemos utilizado, tal y
como aparece en el listado 6.6.
Precaución
Olvidar una sentencia break en una sentencia switch es una causa frecuente de errores.
4. Excepciones
Algunas causas que pueden ocasionar un error durante la ejecución del programa posiblemente
no puedan ser detectadas en tiempo de compilación. Supongamos por ejemplo que en un
programa incluimos la sentencia:
offset = x /n;
siendo x y n variables de tipo int. La sentencia es correcta desde el punto de vista del
compilador. Sin embargo, durante la ejecución del programa, podría ocurrir que n tomase un
valor igual a 0. Este es un caso excepcional, que Java detectará indicando que algo problemático
ha ocurrido y "lanzará" una excepción. Eso significa que internamente se activa una alarma,
indicando a la parte del sistema operativo que ejecuta el programa, que se debe lleva a cabo
alguna acción de emergencia. El sistema entonces detiene automáticamente la ejecución normal
del programa y "pide" ayuda. Con suerte, el sistema encontrará alguna parte del código en el
programa que "capturará" la excepción y la tratará, posiblemente visualizando algún mensaje de
6
Programación para la Gestión
error o iniciando alguna acción adecuada, como por ejemplo asignar a offset algún valor por
defecto. Una vez que la excepción ha sido "capturada", la alarma se desactiva y el sistema
retoma la ejecución en el bloque siguiente que contenía la sentencia que provocó el error.
Otra alternativa para manejar excepciones consiste en escribir código para detectar el error
antes de que ocurra, probablemente mediante el uso de guardas (expresiones booleanas). En el
caso anterior podríamos evitar el error producido por la división por cero escribiendo una guarda
como la siguiente:
if (n > 0) //guarda para prevenir la división por cero
offset = x /n;
else
offset = 10;
Mediante el uso de excepciones, sin embargo, se pueden advertir situaciones inusuales y tomar
las acciones adecuadas en cualquier parte del programa. No hay razones concretas para preferir
una estrategia frente a la otra. Cada una de las técnicas para tratar con las situaciones
excepcionales tienen sus ventajas y desventajas, como veremos a lo largo del tema. Más
adelante, presentaremos la sintaxis y semántica de las excepciones en Java.
En Java existen 16 o más clases que tienen que ver con las excepciones. Todas ellas derivan de
la clase Exception, la cual a su vez es subclase de la clase Throwable. En la Figura 6.1
aparece reflejada una parte de la jerarquía de clases relacionadas con las excepciones.
Object
Throwable
Error
LinkageError
Exception
IllegalAccessException
IOException RunTimeException
Las excepciones se usan para indicar situaciones que generalmente somos capaces de manejar,
como por ejemplo intentar acceder a un carácter de un String en una posición ilegal del
mismo. En la Tabla 6.1 se indican algunas de las subclases de Exception más comunes.
7
Tema 6. Sentencias de control (2)
java.lang.ArithmeticException
Indica que algo ha funcionado mal en una expresión aritmética, como por ejemplo una división
por cero.
java.lang.ArrayIndexOutOfBoundsException
Indica que se ha intentado acceder a un elemento de un array en una posición ilegal (por
ejemplo menor que cero, o mayor que la longitud del array, por ejemplo).
java.lang.ArrayStoreException
Indica que se ha intentado introducir un elemento en un array de un tipo inadecuado.
java.FileNotFoundException
Indica que se ha hecho una referencia a un fichero que no se ha podido encontrar.
java.IllegalArgumentException
Indica que se ha hecho una llamada a un método con un argumento inválido.
java.IOException
Indica un error en sentencias de entrada/salida (éstas se verán en el tema siguiente).
La clase Exception es muy simple. Está formada únicamente por dos constructores:
• Exception()
• Exception(String message)
La segunda de ellas permite especificar un mensaje formado por una cadena de caracteres, por
ejemplo para proporcionar detalles acerca de la naturaleza de la excepción. Para recuperar el
mensaje, se puede utilizar el método de la clase padre Throwable, getMessage(), que
devuelve el mensaje, si existe, asociado con la excepción. Generalmente no crearemos nuevas
instancias de la clase Exception, sino que utilizaremos los constructores de alguna de sus
subclases, con o sin mensaje como argumento. También podemos crear nuevas subclases de
Exception para crear nuestras propias excepciones.
Hay muchos métodos en Java capaces de capturar excepciones, vamos a enumerar buena parte
de ellos, (ver Tabla 6.1):
8
Programación para la Gestión
9
Tema 6. Sentencias de control (2)
5. Manejo de excepciones
La idea básica de las excepciones en Java es muy simple: se trata de indicar la intención de
manejar una excepción marcando el bloque en donde la excepción podría ocasionarse, y
proporcionar algún código, que será llamado solamente cuando tenga lugar dicha excepción y se
deba tratar.
Para marcar el bloque de sentencias entre las que presumiblemente se puede producir la
excepción que deberá capturarse, se utiliza la palabra reservada try:
Con ello indicamos nuestra intención de manejar alguna o todas las excepciones que se
produzcan dentro del bloque. Si no se produce ninguna excepción mientras se ejecutan las
sentencias del bloque, no se debe ejecutar ninguna acción especial.
El siguiente paso es añadir a continuación del bloque try una o más sentencias catch. Una
sentencia catch consiste en un segmento de código muy parecido a la declaración de un
método, con un argumento formal y un cuerpo de sentencias entre paréntesis.
Sentencia CATCH
catch (TipoExcepcion variable) {
//código para manejar la excepción que usa la variable
//argumento. Dicha variable solamente es conocida dentro
//del bloque
}
Si no se produce ninguna excepción en el bloque try, las sentencias del bloque catch no se
ejecutan. Si por el contrario, sí se produce una excepción, y hay una sentencia catch a
continuación del bloque try que es compatible con el tipo de la excepción que se ha producido,
entonces el control se transfiere automáticamente a la sentencia catch adecuada. Las
sentencias del bloque catch se ejecutan, y el control se transfiere a la primera sentencia
después del bloque try (ignorando cualquier sentencia catch posterior). Por ejemplo, la
división por cero contemplada en el apartado 4 podría haberse implementado como sigue:
try {
offset = x/n;
//cualquier sentencia a partir de aquí será
//ignorada si x=0
}
catch (ArithmeticException e) {
offset = 10;
}
//después de manejar la excepción el control se
//transfiere aquí
10
Programación para la Gestión
que una vez que el control se abandona por parte del bloque try, éste nunca vuelve a
él4.
• Las sentencias catch se evalúan desde la primera a la última, deteniéndose la
búsqueda en la primera sentencia catch cuyo argumento sea compatible con la
excepción producida, obviando así posteriores sentencias catch. Ello implica que las
sentencias catch deberían ordenarse siguiendo un criterio de mayor a menor
especificidad. Por ejemplo, el siguiente código podría suponer un esfuerzo vano, ya que
la segunda sentencia catch podría no ejecutarse nunca.
try {
...
}
catch (Exception e) //Este argumento es compatible
//con cualquier excepción
{ ... }
catch (ArithmeticException e)
{ ... }
Un bloque try debe seguirse inmediatamente por una o más sentencias catch, y no pueden
exsistir de forma separada, es decir, no podemos escribir un bloque try, sin una sentencia
catch, ni al revés.
Supongamos que ocurre una excepción fuera de un bloque try, o dentro de un blque try que
no tiene ninguna sentencia catch asociada con un tipo de excepción compatible. ¿Qué ocurre
en ese caso?
Pues el sistema primero intenta buscar una sentencia catch que pueda tratar dicha excepción.
Si no la encentra, el sistema busca en las setencias try...catch del bloque externo. Por
ejemplo, debido a que en un bloque try pueden aparecer cualquier conjunto de sentencias, el
programa prodría parecerse al siguiente código:
try {
...
try
{
//excepción producida aquí
}
catch (AlgunaExcepcion e)
{
//pero no se captura aquí
}
catch (AlgunaOtraException e)
{
//entonces se capturará aquí
}
Esta búsqueda continúa hasta que se alcanza el bloque más externo. Si no se encuentra
ninguna sentencia catch compatible, entonces la búsqueda se extiende para incluir el bloque
que contiene la llamada al método, como éste:
4
A menos que el control haga que se vuelva a entrar en el bloque try desde el principio, en el curso
normal de ejecución, como podría ocurrir por ejemplo si dicho bloque estuviese dentro de un bucle.
11
Tema 6. Sentencias de control (2)
void oops() {
try
{
//excepción generada
}
catch (AlgunaExcepcion e)
{
//pero no se captura aquí
}
} //final del método oops()
...
//ahora estamos en cualquier otro método, justamente
//en la llamada original a oops(), en donde se origina
//la excepción
try{
oops(); //excepción generada por esta llamada
//no capturada en oops()
}
catch (AlgunaOtraExcepción e)
{
//el siguiente paso es buscar aquí, después
//de haber buscado en oops()
}
Hay veces que nos interesará generar una excepción bajo nuestro control, en lugar de
permanecer pasivos esperando a que Java la genere por nosotros. En ese caso utilizamos la
sentencia throw.
Sentencia THROW
throw instanciaDeExcepción;
12
Programación para la Gestión
Este es un ejemplo un tanto "artificial", ya que podría haber sido mucho más simple el situar el
código para tratar con números impares en la parte then, y así ahorrarnos la sentencia catch.
Pero, por ejemplo, supongamos que lo habitual va a ser que el número sea par, y que sólo
excepcionalmente tendrá valores impares. En este caso, sí es buena idea utilizar la sentencia
catch, ya que dicho bloque casi nunca se ejecutará, entonces ¿por qué incluirlo dentro de la
sentencia if y emplear mucho tiempo leyéndolo en la parte then del if?
Cuando estamos diseñando un método que puede provocar una excepción tenemos dos
posibilidades:
• utilizar las sentencias try..catch para manejar la excepción dentro del método, o
• no capturar la excepción y dejar que el mecanismo de propagación capture la excepción
en alguna otra parte.
En este último caso, podríamos (o en algunos casos deberíamos) indicar al compilador que el
método podría provocar una excepción que no va a capturar por sí mismo. Esto se realiza
utilizando una sentencia throws en la cabecera del método.
Sentencia THROWS
void miMétodo() throws ExceptionTipo1, ExceptionTipo2,...
Por ejemplo, el siguiente código indica que el método puede provocar dos tipos de excepciones,
IllegalArgumentException, que se genera explícitamente en el método, o NullPointerException
que se genera con la sentencia equals(), si la cadena de caracteres que se le pasa como
argumento tiene un valor null.
5
Software que forma parte del sistema operativo y que permite la ejecución de aplicaciones.
13
Tema 6. Sentencias de control (2)
{
if (s.equals("Cookie"))
return "Thanks!"
else
throw new IllegalArgumentException("Quiero galletas!");
//no necesitamos un return, ya que nunca llegaremos
//aquí
}
Las excepciones definidas por el programador se tratan igual que las excepciones predefinidas,
excepto, claro está, que debemos ocuparnos nosotros mismos de generarlas. A continuación
mostramos un ejemplo en el que creamos dos tipos de excepciones. Una de ellas,
FaltanDatos, se prevé que sea generada cuando uno o más de los campos de texto de tipo
Textfield están vacíos. La declaración es la siguiente:
14
Programación para la Gestión
...
else if (source == bSubmit)
{
try {
processSubmitButton();
}
catch (FaltanDatos ex) //falta algún dato
{
pedido.appendText("*** "+ ex.getMessage()+ '\n');
}
}
7. Ejercicios
7.1 Escribe un método denominado calcularNomina, que tiene como argumentos el nombre del
trabajador y el número de horas trabajadas de forma que: si él número de horas es inferior o
igual a 40 se pagan a 5000 pts/hora; si dicho número de horas está entre 40 y 60, entonces
se pagan a 10000 pts/hora las horas que excedan de 40; finalmente si son superiores a 60,
entonces las horas excedidas de 60 se pagan a 20000 pts/hora. El extracto de la nómina
debe aparecer en pantalla de la siguiente forma:
Nombre empleado: Luis
Nª Horas trabajadas (normales): 40 Importe: 80.000 pts
Nª Horas trabajadas (extras): 20 Importe: 200.000 pts
Nª Horas trabajadas (super-extras): 0 Importe: 0 pts
TOTAL NOMINA: 280.000 pts
7.2 Escribe un método denominado calcularNota, que tenga como parámetros el nombre de un
estudiante, y las notas obtenidas en los exámenes teórico y práctico de una asignatura. Se
trata de calcular la nota final teniendo en cuenta que para aprobar la asignatura se debe tener
aprobado cada examen (nota superior o igual a 5). Una nota superior o igual a 7 y menor que
9 tiene una calificación de Notable, si dicha calificación es igual a 9 o inferior a 10 la
calificación es de Sobresaliente, una nota de 10 equivale a una Matrícula de Honor. A la hora
de ponderar la nota se debe tener en cuenta que el examen teórico cuenta el doble que el
práctico. Un posible ejemplo de salida de dicho método sería:
Nombre alumno: Salvador
Nota examen teórico: 4
Nota examen práctico: 6 Calificación: SUSPENSO
7.3 Modifica el ejemplo que trataba de coger un par de calcetines del mismo color de un cajón de
calcetines (apartado 3 del Tema 5), para que contemple los siguientes casos: puede que
inicialmente en el cajón no haya calcetines o solamente haya uno (con lo cual no se puede
obtener un par), si es así, el método cogerUnCalcetin() genera una excepción llamada
NoCalcetines, de forma que se muestra un mensaje por pantalla: "Lo siento, no hay
calcetines suficientes en el cajón". El método cogerUnCalcetín() genera la excepción llamada
ErrorMecánico si el brazo del robot experimenta un fallo mecánico, en cuyo caso se imprime
por pantalla: "Lo siento, fallo mecánico del robot". Los calcetines pueden ser verdes, rojos o
azules: el método que proporciona las órdenes al robot para intentar coger los calcetines se
llama cogerCalcetines y tiene como parámetro el color deseado del par de calcetines. Para
averiguar el color de cada calcetín disponemos de un método "color()" que devuelve 0 si el
15
Tema 6. Sentencias de control (2)
7.4 Unos cheques falsificados están en circulación y los bancos han detectado que todos tienen
las mismas propiedades distintivas. Un cheque puede ser falso si en el número de cheque de
10 dígitos hay:
• tres o más ceros en sucesión,
• y/o cuatro o más ceros seguidos.
Implementar un método llamado detectarFalsos(long numero) que detecte que la numeración
que se pasa por parámetro se corresponde con un cheque falsificado. Posibles ejemplos de
ejecución del método serían:
*** Detección de cheques falsificados ***
Número a chequear: 0033300333
OK
16