Download Apuntes Fundamentos Java

Document related concepts
no text concepts found
Transcript
Capítulo
1
ObjetosyClases
Centro Asociado Palma de Mallorca
Tutor: Antonio Rivero Cuesta
1
1 ObjetosyClases
1.1 Objetos y Clases
Clase: Una clase es una plantilla donde vamos a definir unos atributos y unos métodos.
Una clase es la implementación de un tipo de objeto, considerando los objetos como instancias de las
clases.
Objeto: Cuando se crea un objeto, se instancia una clase, mediante el operador new.
Se ha de especificar de qué clase es el objeto instanciado, para que el compilador comprenda las
características del objeto.
1.2 Creación de Objetos
Cuando se instancia un objeto el compilador crea en la memoria dinámica un espacio para tantas
variables como atributos tenga la clase a la que pertenece el objeto.
1.3 Invocación de métodos
Podemos comunicarnos con los objetos invocando sus métodos. Generalmente, los objetos hacen algo
cuando invocamos un método.
1.4 Parámetros
Los métodos pueden tener parámetros para aportar información adicional para realizar una tarea.
El encabezado de un método se denomina signatura, proporciona la información necesaria para
invocar dicho método.
Los métodos pueden tener cualquier número de parámetro.
1.5 Tipos de dato
Los parámetros tienen tipos.
El tipo define la clase de valor que un parámetro puede tomar.
1.6 Instancias múltiples
Se pueden crear muchos objetos similares a partir de una sola clase.
Cada uno de los atributos del objeto tendrá sus propios valores.
1.7 Estado
Los objetos tienen un estado.
El estado está representado por todos los valores almacenados en los campos o atributos.
1.8 ¿Qué es lo que contiene un objeto?
Cuando se crea un objeto, se instancia una clase, mediante el operador new.
Todos los objetos de la misma clase tienen los mismos campos.
Los valores concretos de cada campo particular de cada objeto pueden ser diferentes.
Los métodos se definen en la clase del objeto.
Todos los objetos de la misma clase tienen los mismos métodos.
2
1.9 Código Java
Cuando programamos en Java, escribimos instrucciones para invocar métodos sobre objetos.
Tenemos que escribir los comandos correspondientes de forma textual.
Cuando creamos un objeto, lo que hacemos es almacenar ese objeto en una variable.
Para llamar a un método, escribimos el nombre del objeto seguido de punto y seguido del nombre del
método, terminamos con una lista de parámetros o con un par de paréntesis vacíos si no hay
parámetros.
Todas las instrucciones Java terminan con un punto y coma.
1.10 Interacción entre Objetos
Lo normal es crear una clase principal que inicie el resto.
1.11 Código Fuente
Cada clase tiene algún código fuente asociado. El código fuente es un texto que define los detalles de
la clase.
El código fuente es un texto escrito en lenguaje de programación Java y define qué campos y métodos
tiene la clase y qué ocurre cuando se invoca un método.
El arte de la programación, que no es tarea fácil, consiste en aprender cómo escribir estas definiciones
de clases.
Cuando realiza algún cambio en el código, la clase necesita ser compilada haciendo clic en el botón
Compile. Una vez que una clase ha sido compilada, se pueden crear nuevamente objetos y probar sus
cambios.
1.13 Valores de retorno
Los métodos que devuelven o retornan valores nos permiten obtener información sobre un objeto
mediante una llamada al método.
Quiere decir que podemos usar métodos tanto para cambiar el estado de un objeto como para
investigar su estado.
Con la palabra void indicamos que ese método no retoma ningún resultado.
1.14 Objetos como parámetros
Los objetos pueden ser pasados como parámetros a los métodos de otros objetos.
En el caso de que un método espere un objeto como parámetro, el nombre de la clase del objeto que
espera se especifica como el tipo de parámetro en la signatura de dicho método.
3
1 Objetos y Clases ............................................................................................................................... 2 1.1 Objetos y Clases ........................................................................................................................ 2 1.2 Creación de Objetos ................................................................................................................... 2 1.3 Invocación de métodos .............................................................................................................. 2 1.4 Parámetros ................................................................................................................................. 2 1.5 Tipos de dato.............................................................................................................................. 2 1.6 Instancias múltiples ................................................................................................................... 2 1.7 Estado ........................................................................................................................................ 2 1.8 ¿Qué es lo que contiene un objeto? ........................................................................................... 2 1.9 Código Java ............................................................................................................................... 3 1.10 Interacción entre Objetos ........................................................................................................... 3 1.11 Código Fuente ............................................................................................................................ 3 1.13 Valores de retorno...................................................................................................................... 3 1.14 Objetos como parámetros .......................................................................................................... 3 4
Capítulo
2
De fin ici ones
deClases
Centro Asociado Palma de Mallorca
Tutor: Antonio Rivero Cuesta
1
2 DefinicionesdeClases
2.3 La cabecera de la clase
El código de las clases puede dividirse en dos partes principales: un envoltorio exterior que
simplemente da nombre a la clase y una parte interna mucho más grande que hace todo el trabajo.
El envoltorio exterior tiene la siguiente apariencia:
public class <NombreClase>
{
<parte interna>
}
El envoltorio exterior de las diferentes clases es muy parecida, su principal finalidad es proporcionar
un nombre a la clase.
Por convenio, los nombres de las clases comienzan siempre con una letra mayúscula.
2.3.1 Palabras clave o reservadas
Las palabras reservadas son identificadores predefinidos que tienen un significado para el compilador
y por tanto no pueden usarse como identificadores creados por el usuario en los programas.
Las palabras reservadas en Java ordenadas alfabéticamente son las siguientes:
abstract
assert
boolean
break
byte
case
catch
char
class
const
continue
default
do
double
else
enum
extends
final
finally
float
for
goto
if
implements
import
instanceof
int
interface
long
native
new
package
private
protected
public
return
short
static
strictfp
super
switch
synchronized
this
throw
throws
transient
try
void
volatile
while
2.4 Campos, constructores y métodos
La parte interna de la clase es el lugar en el que definimos los campos, constructores y métodos que
dan a los objetos de la clase sus características particulares y su comportamiento.
Podemos resumir las características esenciales de estos tres componentes de una clase como sigue:
 Los campos almacenan datos para que cada objeto los use.
 Los constructores permiten que cada objeto se prepare adecuadamente cuando es creado.
 Los métodos implementan el comportamiento de los objetos.
En Java existen muy pocas reglas sobre el orden que se puede elegir para definir los campos, los
constructores y los métodos dentro de una clase.
Es importante elegir un estilo y luego usarlo de manera consistente, porque de este modo las clases
serán más fáciles de leer y de comprender.
2
Orden de campos, constructores y métodos:
public class NombreDeClase
{
Campos
Constructores
Métodos
}
2.4.1 Campos
Los campos almacenan datos de manera persistente dentro de un objeto y son accesibles para todos los
métodos del objeto.
Los campos también son conocidos como variables de instancia.
Como los campos pueden almacenar valores que pueden variar a lo largo del tiempo, podemos
llamarlos variables.
Los campos son pequeñas cantidades de espacio dentro de un objeto que pueden usarse para almacenar
datos de manera persistente.
Todos los objetos una vez creados dispondrán de un espacio para cada campo declarado en su clase.
Las variables de clase, también se conocen como variables estáticas, siempre tienen el mismo valor
para todos los objetos de una determinada clase. En realidad no son variables sino constantes
Los comentarios se insertan en el código de una clase para proporcionar explicaciones a los lectores
humanos. No tienen ningún efecto sobre la funcionalidad de la clase.
Los comentarios de una sola línea van precedidos de los caracteres “//”.
Los comentarios más detallados, que frecuentemente ocupan varias líneas, se escriben generalmente en
la forma de comentarios multilínea: comienzan con el par de caracteres “/*” y terminan con el par
“*/”.
Para definir una variable de campo dentro de una clase seguiremos el siguiente patrón:
 Normalmente, comienzan con la palabra clave private.
 Incluyen un nombre de tipo, int, String, etc.
 Incluyen un nombre elegido por el usuario para la variable de campo.
 Terminan en punto y coma.
El tipo de un campo especifica la naturaleza del valor que puede almacenarse en dicho campo. Si el
tipo es una clase, el campo puede contener objetos de esa clase.
2.4.2 Constructores
Los constructores permiten que cada objeto sea preparado adecuadamente cuando es creado. Esta
operación se denomina inicialización. El constructor inicializa el objeto en un estado razonable.
Uno de los rasgos distintivos de los constructores es que tienen el mismo nombre que la clase en la
que son definidos.
El nombre del constructor sigue inmediatamente a la palabra public.
Los campos del objeto se inicializan en el constructor, bien con valores fijos, o bien con parámetros
del propio constructor.
En Java todos los campos son inicializados automáticamente con un valor por defecto, si es que no
están inicializados explícitamente.
3
El valor por defecto para los campos enteros es 0. Sin embargo, es preferible escribir explícitamente
las asignaciones. No hay ninguna desventaja en hacer esto y sirve para documentar lo que está
ocurriendo realmente.
2.5 Parámetros: recepción de datos
La manera en que los constructores y los métodos reciben valores es mediante sus Parámetros.
Los parámetros son otro tipo de variable, igual que los campos, por lo que se utilizan para almacenar
datos.
Los parámetros se definen en el encabezado de un constructor o un método.
Los parámetros transportan datos que tienen su origen fuera del constructor o método y hacen que esos
datos estén disponibles en el interior del constructor o método.
Puesto que permiten almacenar valores, los parámetros formales constituyen otra clase de variables.
Distinguimos entre nombres de los parámetros dentro de un constructor o un método y valores de los
parámetros fuera de un constructor o un método.
Hacemos referencia a los nombres como parámetros formales y a los valores como parámetros reales.
Un parámetro formal está disponible para un objeto sólo dentro del cuerpo del constructor o del
método que lo declara.
Decimos que el alcance de un parámetro está restringido al cuerpo del constructor o del método en el
que es declarado.
El tiempo de vida de un parámetro se limita a una sola llamada de un constructor o método.
Cuando se invoca un constructor o método, se crea el espacio adicional para las variables de parámetro
y los valores externos se copia en dicho espacio.
Una vez que completó su tarea, los parámetros formales desaparecen y se pierden los valores que
contienen.
Por el contrario, el tiempo de vida de un campo es el mismo tiempo de vida que el del objeto al que
pertenece.
2.5.1 Elección de los nombres de variable
Es conveniente elegir nombres que proporcionen algo de información al lector.
2.6 Asignación
Destacamos la necesidad de almacenar el valor de corta vida de un parámetro dentro de algún lugar
más permanente, una variable de campo.
Las sentencias de asignación almacenan el valor representado por el lado derecho de la sentencia en
una variable nombrada a la izquierda.
Una regla sobre las sentencias de asignación es que el tipo de una expresión debe coincidir con el tipo
de la variable a la que es asignada.
La misma regla se aplica también entre los parámetros formales y los parámetros reales: el tipo de una
expresión de un parámetro real debe coincidir con el tipo de una variable parámetro formal.
2.7 Métodos
Los métodos se componen de dos partes: una cabecera y un cuerpo.
Es importante distinguir entre la cabecera del método y declaración de campos porque son muy
parecidos.
Podemos decir que algo es un método y no un campo porque está seguido de un par de paréntesis: «(»
y «)». Obsérvese también que no hay un punto y coma al final de la signatura.
4
El cuerpo del método es la parte restante del método, que aparece a continuación de la cabecera.
Está siempre encerrado entre llaves: «{« y »}».
Los cuerpos de los métodos contienen las declaraciones y las sentencias que definen qué ocurre dentro
de un objeto cuando es invocado ese método.
Las declaraciones se utilizan para crear espacio adicional de variables temporales, mientras que las
instrucciones describen las acciones del método.
Cualquier conjunto de declaraciones y sentencias, ubicado entre un par de llaves, es conocido como un
bloque. Por lo que el cuerpo de una clase y los cuerpos de todos los métodos de las clases son bloques.
Existen, dos diferencias significativas entre las cabeceras de los constructores de una clase y de los
demás métodos:
Por un lado los constructores tienen el mismo nombre que la clase en la que están definidos, y por otro
los métodos siempre tienen un tipo de retorno (aunque sea void) mientras que el constructor no tiene
tipo de retorno. El tipo de retorno se escribe exactamente antes del nombre del método.
Es una regla de Java que el constructor no puede tener ningún tipo de retorno.
Por otro lado, tanto los constructores como los métodos pueden tener cualquier número de parámetros
formales, inclusive pueden no tener ninguno.
Los métodos pueden tener una sentencia return y es la responsable de devolver un valor que coincida
con el tipo de retorno de la signatura del método. Cuando un método contiene una sentencia return,
siempre es la última sentencia que se ejecuta del mismo porque una vez que se ejecutó esta sentencia
no se ejecutarán más sentencias en el método.
Los tipos de retorno y las instrucciones de retorno funcionan conjuntamente.
Podemos decir que una llamada a un método es una especie de pregunta que se la hace el objeto y el
valor de retorno proporcionado por el método es la respuesta que el objeto da a esa pregunta.
2.8 Métodos Selectores y Mutadores
Los métodos selectores “get” devuelven información sobre el estado del objeto. Proporcionan acceso
a información acerca del estado del objeto.
Un método selector contiene generalmente una sentencia return para devolver información de un valor
en particular.
Devolver un valor significa que se pasa una cierta información internamente entre dos partes diferentes
del programa.
A los métodos que modifican el estado de su objeto los llamamos métodos mutadores.
Los métodos mutadores cambian el estado de un objeto.
La forma básica de mutador admite un único parámetro y este valor se utiliza para sobreescribir
directamente lo que haya almacenado en uno de los campos del objeto.
Los métodos mutadores los denominamos métodos "set".
La cabecera de un método de mutador, "set", normalmente tiene tipo de retorno void y un solo
parámetro formal, el nuevo valor del campo a modificar. Un tipo de retorno void significa que el
método no devuelve ningún valor cuando es llamado; es significativamente diferente de todos los otros
tipos de retorno. En el cuerpo de un método void, esta diferencia se refleja en el hecho de que no hay
ninguna sentencia return.
Los métodos mutadores siempre tienen al menos una sentencia de asignación. El sumar (o restar) una
cantidad al valor de una variable es algo tan común que existe un operador de asignación compuesto,
especial para hacerlo: «+=».
5
2.9 Imprimir desde Métodos
El método System.out.println(<parametro>) imprime su parámetro en la terminal de
texto.
Una sentencia como System.out.println("# Línea BlueJ"); imprime literalmente la
cadena que aparece entre el par de comillas dobles.
Todas estas sentencias de impresión son invocaciones al método println del objeto System.out que
está construido dentro del lenguaje Java.
Cuando se usa el símbolo «+» entre una cadena y cualquier otra cosa, este símbolo es un operador de
concatenación de cadenas (es decir, concatena o reúne cadenas para crear una nueva cadena) en lugar
de ser el operador aritmético de suma.
El método println se puede llamar sin contener ningún parámetro de tipo cadena. Esto está
permitido y el resultado de la llamada será dejar una línea en blanco entre esta salida y cualquier otra
que le siga.
2.13 Tomas de decisión: la instrucción condicional
Una sentencia condicional realiza una de dos acciones posibles basándose en el resultado de una
prueba.
También son conocidas como sentencias if.
Se evalúa el resultado de una verificación o prueba, si el resultado es verdadero entonces hacemos una
cosa, de lo contrario hacemos algo diferente.
Una sentencia condicional tiene la forma general descrita en el siguiente pseudo-código:
if (llevar a cabo alguna prueba que dé un resultado verdadero o falso){
Si la prueba dio resultado verdadero, ejecutar estas sentencias
}
else{
Si el resultado dio falso, ejecutar estas sentencias
}
La prueba que se usa en una sentencia condicional es un ejemplo de una expresión booleana.
Las expresiones booleanas tienen sólo dos valores posibles: verdadero o falso (true o false).
Se las encuentra comúnmente controlando la elección entre los dos caminos posibles de una sentencia
condicional.
2.16 Variables locales
Una variable local es una variable que se declara y se usa dentro de un solo método.
Las declaraciones de las variables locales son muy similares a las declaraciones de los campos pero las
palabras private o public nunca forman parte de ellas.
Es muy común inicializar variables locales cuando se las declara.
Se crean cuando se invoca un método y se destruyen cuando el método termina.
Los constructores también pueden tener variables locales.
Las variables locales se usan frecuentemente como lugares de almacenamiento temporal para ayudar a
un método a completar su tarea.
Podemos considerarlas como un almacenamiento de datos para un único método.
Su alcance y tiempo de vida se limitan a los del método.
Un error común es usar una variable local del mismo nombre que un campo evitará que el campo sea
accedido dentro de un método.
6
2.17 Campos, parámetros y variables locales
Las tres clases de variables pueden almacenar un valor acorde a su definición de tipo de dato.
Los campos se definen fuera de los constructores y de los métodos.
Los campos se usan para almacenar datos que persisten durante la vida del objeto, de esta manera
mantienen el estado actual de un objeto. Tienen un tiempo de vida que finaliza cuando termina el
objeto.
El alcance de los campos es la clase: la accesibilidad de los campos se extiende a toda la clase y por
este motivo pueden usarse dentro de cualquier constructor o método de clase en la que estén definidos.
Como son definidos como privados (private), los campos no pueden ser accedidos desde el exterior
de la clase.
Los parámetros formales y las variables locales persisten solamente en el lapso durante el cual se
ejecuta un constructor o un método. Su tiempo de vida es tan largo como una llamada, por lo que sus
valores se pierden entre llamadas. Por este motivo, actúan como lugares de almacenamiento
temporales antes que permanentes.
Los parámetros formales se definen en el encabezado de un constructor o de un método. Reciben sus
valores desde el exterior, se inicializan con los valores de los parámetros actuales que forman parte de
la llamada al constructor o al método.
Los parámetros formales tienen un alcance limitado a su definición de constructor o de método.
Las variables locales se declaran dentro del cuerpo de un constructor o de un método. Pueden ser
inicializadas y usadas solamente dentro del cuerpo de las definiciones de constructores o métodos. Las
variables locales deben ser inicializadas antes de ser usadas en una expresión, no tienen un valor por
defecto.
Las variables locales tienen un alcance limitado al bloque en el que son declaradas. No son accesibles
desde ningún lugar fuera de ese bloque.
7
Contenido
2 Definiciones de Clases ..................................................................................................................... 2 2.3 La cabecera de la clase .............................................................................................................. 2 2.3.1 2.4 Palabras clave o reservadas ................................................................................................ 2 Campos, constructores y métodos ............................................................................................. 2 2.4.1 Campos ............................................................................................................................... 3 2.4.2 Constructores ...................................................................................................................... 3 2.5 Parámetros: recepción de datos ................................................................................................. 4 2.5.1 Elección de los nombres de variable .................................................................................. 4 2.6 Asignación ................................................................................................................................. 4 2.7 Métodos ..................................................................................................................................... 4 2.8 Métodos Selectores y Mutadores ............................................................................................... 5 2.9 Imprimir desde Métodos ............................................................................................................ 6 2.13 Tomas de decisión: la instrucción condicional .......................................................................... 6 2.16 Variables locales ........................................................................................................................ 6 2.17 Campos, parámetros y variables locales .................................................................................... 7 8
Capítulo
3
Interacción
deObjetos
Centro Asociado Palma de Mallorca
Tutor: Antonio Rivero Cuesta
1
3 InteraccióndeObjetos
3.2 Abstracción y Modularización
Cuando un problema se agranda se vuelve más difícil mantener todos los detalles al mismo tiempo,
aumenta la complejidad.
La solución que usamos para tratar el problema de la complejidad es la abstracción.
La abstracción es la habilidad de ignorar los detalles de las partes para centrar la atención en un nivel
más alto de un problema. Dividimos el problema en subproblemas, luego en sub-subproblemas y así
sucesivamente, hasta que los problemas resultan suficientemente fáciles de tratar.
Una vez que resolvemos uno de los subproblemas no pensamos más sobre los detalles de esa parte,
pero tratamos la solución hallada como un bloque de construcción para nuestro siguiente problema.
Esta técnica se conoce como la técnica del divide y vencerás.
La modularización es el proceso de dividir un todo en partes bien definidas que pueden ser construidas
y examinadas separadamente, las cuales interactúan entre sí de maneras bien definidas.
La modularización y la abstracción se complementan mutuamente.
La modularización es el proceso de dividir cosas grandes (problemas) en partes más pequeñas.
La abstracción es la habilidad de ignorar los detalles para concentrarse en el cuadro más grande.
3.3 Abstracción en el Software
En el caso de programas complejos, para mantener una visión global del problema tratamos de
identificar los componentes que podemos programar como entidades independientes, y luego
intentamos utilizar esos componentes como si fueran partes simples sin tener en cuenta su complejidad
interna.
En programación orientada a objetos, estos componentes y subcomponentes son objetos.
Las clases definen tipos. El nombre de una clase puede ser usado como el tipo de una variable. Las
variables cuyo tipo es una clase pueden almacenar objetos de dicha clase.
El tipo de un campo especifica la naturaleza del valor que puede almacenarse en dicho campo. Si el
tipo es una clase, el campo puede contener objetos de esa clase.
3.6 Diagramas de Clases con Diagramas de Objetos
El diagrama de clases muestra las clases de una aplicación y las relaciones entre ellas. Da información
sobre el código. Representa la vista estática de un programa.
El diagrama de objetos muestra los objetos y sus relaciones en un instante determinado de la ejecución
de una aplicación. Da información sobre los objetos en tiempo de ejecución. Representa la vista
dinámica de un programa.
a) diagrama de objetos;
b) diagrama de clases
2
El diagrama de objetos también muestra otro detalle importante, cuando una variable almacena un
objeto, éste no es almacenado directamente en la variable sino que en la variable sólo se almacena una
referencia al objeto.
Referencia a un objeto. Las variables de tipo objeto almacenan referencias a los objetos.
3.7 Tipos Primitivos y Tipos Objeto
Java reconoce dos clases de tipos muy diferentes: los tipos primitivos y los tipos objeto. Los tipos
primitivos están todos predefinidos en el lenguaje Java
Los tipos primitivos en Java son todos los tipos que no son objetos. Los tipos primitivos más comunes
son los tipos int, booleano, char, double y long. Los tipos primitivos no poseen métodos.
Tanto los tipos primitivos como los tipos objeto pueden ser usados como tipos, pero existen
situaciones en las que se comportan de manera muy diferente.
Una diferencia radica en cómo se almacenan los valores.
Los valores primitivos se almacenan directamente en una variable.
Por otro lado, los objetos no se almacenan directamente en una variable sino que se almacena una
referencia al objeto.
3.8 El código fuente para ClockDisplay
Ver código 3.3, página 71.
Operadores Lógicos
Los operadores lógicos operan con valores booleanos (verdadero o falso) y producen como resultado
un nuevo valor booleano. Los tres operadores lógicos más importantes son «y», «o» y «no». En Java se
escriben: && (y), || (o) y ! (no).
La expresión a && b es verdadera si tanto a como b son verdaderas, en todos los otros casos es falsa.
La expresión a || b es verdadera si alguna de las dos es verdadera, puede ser a o puede ser b o pueden
ser las dos; si ambas son falsas el resultado es falso.
La expresión !a es verdadera si a es falso, y es falsa si a es verdadera.
3.8.2
Concatenación de cadenas
El operador suma (+) tiene diferentes significados dependiendo del tipo de sus operandos.
Si ambos operandos son números, el operador + representa la adición.
Si los operandos son cadenas, el significado del signo más es la concatenación de cadenas y el
resultado es una única cadena compuesta por los dos operandos.
Si uno de los operandos del operador más es una cadena y el otro no, el operando que no es cadena es
convertido automáticamente en una cadena y luego se realiza la concatenación correspondiente.
Esta conversión funciona para todos los tipos.
Cualquier tipo que se «sume» con una cadena, automáticamente es convertido a una cadena y luego
concatenado.
3.8.3
El Operador Módulo
El operador módulo (%) calcula el resto de una división entera. (27%4) será 3.
3
3.9 Objetos que Crean Objetos
Los objetos pueden crear otros objetos usando el operador new.
La sintaxis de una operación para crear un objeto nuevo es:
new NombreDeClase(lista-de-parámetros)
La operación new hace dos cosas:
 Crea un nuevo objeto de la clase nombrada.
 Ejecuta el constructor de dicha clase.
Si el constructor de la clase tiene parámetros, los parámetros actuales deben ser proporcionados en la
sentencia new.
3.10 Constructores Múltiples
Es común que las declaraciones de clases contengan versiones alternativas de constructores o métodos
que proporcionan varias maneras de llevar a cabo una tarea en particular mediante diferentes conjuntos
de parámetros.
Este punto se conoce como sobrecarga de un constructor o método.
Una clase puede contener más de un constructor o más de un método con el mismo nombre, siempre y
cuando tengan distintos conjuntos de parámetros que se diferencien por sus tipos.
3.11 Llamadas a Métodos
3.11.1 Llamada a métodos internos
Los métodos pueden llamar a otros métodos de la misma clase como parte de su implementación.
Se denomina llamada a método interno.
Se denomina así porque este método está ubicado en la misma clase en que se produce su llamada.
Las llamadas a métodos internos tienen la siguiente sintaxis:
nombreDelMétodo(lista-de-parámetros)
Cuando se encuentra una llamada a un método, se ejecuta este último, y después de su ejecución se
vuelve a la llamada al método y se continúa con la sentencia que sigue a la invocación.
Para que la llamada a un método coincida con la signatura del mismo, deben coincidir tanto el nombre
del método como su lista de parámetros.
3.11.2 Llamada a métodos externos
Los métodos pueden llamar a métodos de otros objetos usando la notación de punto: se denomina
llamada a método externo.
La sintaxis de una llamada a un método externo es:
objeto.nombreDelMétodo(lista-de-parámetros)
Esta sintaxis se conoce con el nombre de “notación con punto”.
Consiste en un nombre de objeto, un punto, el nombre del método y los parámetros para la llamada
Es particularmente importante apreciar que usamos aquí el nombre de un objeto y no el nombre de una
clase.
El conjunto de métodos de un objeto que está disponible para otros objetos se denomina su interfaz.
4
3.12 Otro ejemplo de interacción de objetos
3.12.2 La Palabra Clave this
Observe la siguiente sentencia con la palabra clave this:
this.from = from;
La línea en su totalidad es una sentencia de asignación, asigna el valor del lado derecho (from) a la
variable que está del lado izquierdo (this.from) del símbolo igual (=).
El motivo por el que se usa esta construcción radica en que tenemos una situación que se conoce como
sobrecarga de nombres, y significa que el mismo nombre es usado por entidades diferentes.
Es importante comprender que los campos de un objeto y los parámetros o variables locales son
variables que existen independientemente unas de otras, aun cuando compartan nombres similares.
Un parámetro o variable local y un campo que comparten un nombre no representan un problema
para Java.
La especificación de Java responde a esta pregunta: Java especifica que siempre se usará la declaración
más cercana encerrada en un bloque.
Dado que el parámetro o variable local from está declarado en el método y el campo from está
declarado en la clase, se usará el parámetro pues su declaración es la más cercana a la sentencia que lo
usa.
Lo que necesitamos es un mecanismo para acceder a un campo cuando existe una variable con el
mismo nombre declarada más cerca de la sentencia que la usa.
Este mecanismo es justamente lo que significa la palabra clave this.
La expresión this hace referencia al objeto actual.
Al escribir this.from estamos haciendo referencia al campo del objeto actual, por lo que esta
construcción nos ofrece una forma de referirnos a los campos en lugar de a los parámetros cuando
tienen el mismo nombre.
Ahora podemos leer la sentencia de asignación nuevamente:
this.from = from;
Como podemos ver, esta sentencia tiene el mismo efecto que la siguiente:
campo de nombre "from" = parámetro de nombre "from";
Asigna el valor del parámetro from al campo del mismo nombre y por supuesto, esto es exactamente
lo que necesitamos hacer para inicializar el objeto adecuadamente.
La razón por lo que hacemos esto radica en la legibilidad del código.
Si un nombre describe perfectamente la finalidad, resulta razonable usarlo como nombre de parámetro
y de campo y eliminar los conflictos de nombres usando la palabra clave this en la asignación.
5
3.13 Uso del depurador, Debugger
Un depurador es una herramienta de software que ayuda a examinar cómo se ejecuta una aplicación.
Puede usarse para encontrar problemas.
Un depurador es un programa que permite que los programadores ejecuten una aplicación paso a paso.
Generalmente, ofrece funciones para detener y comenzar la ejecución de un programa en un punto
seleccionado del código y para examinar los valores de las variables.
6
3 Interacción de Objetos ...................................................................................................................... 2 3.2 Abstracción y Modularización ................................................................................................... 2 3.3 Abstracción en el Software ........................................................................................................ 2 3.6 Diagramas de Clases con Diagramas de Objetos ...................................................................... 2 3.7 Tipos Primitivos y Tipos Objeto................................................................................................ 3 3.8 El código fuente para ClockDisplay .......................................................................................... 3 3.8.2 Concatenación de cadenas .................................................................................................. 3 3.8.3 El Operador Módulo ........................................................................................................... 3 3.9 Objetos que Crean Objetos ........................................................................................................ 4 3.10 Constructores Múltiples ............................................................................................................. 4 3.11 Llamadas a Métodos .................................................................................................................. 4 3.11.1 Llamada a métodos internos ............................................................................................... 4 3.11.2 Llamada a métodos externos .............................................................................................. 4 3.12 Otro ejemplo de interacción de objetos ..................................................................................... 5 3.12.2 La Palabra Clave this .......................................................................................................... 5 3.13 Uso del depurador, Debugger .................................................................................................... 6 7
Capítulo
4
Ag rupar
Objetos
Centro Asociado Palma de Mallorca
Tutor: Antonio Rivero Cuesta
1
4 AgruparObjetos
4.1 La colección como abstracción
Una colección es una abstracción y es el concepto de agrupar cosas para poder referirnos a ellas y
manejarlas de manera conjunta.
Una colección puede ser grande, pequeña o vacía.
Operaciones típicas son:
 Añadir.
 Eliminar.
 Ordenar.
La abstracción colección se convierte en una clase de algún tipo.
Las operaciones serían los métodos de esa clase.
Una colección de música Rock sería una instancia de la clase.
Los elementos almacenados en una instancia de colección serían, ellos mismos, objetos.
Necesitaremos una solución genérica para agrupar los objetos en colecciones.
En ocasiones el número de elementos almacenados en la colección varía a lo largo del tiempo.
Una solución adecuada sería aquella que no requiera que conozcamos anticipadamente la cantidad de
elementos que queremos agrupar o bien, establecer un límite mayor que dicho número.
4.4 Utilización de una clase de librería
Tenemos en cuenta que las clases de librería no aparecen en el diagrama de clases de BlueJ
Los lenguajes orientados a objetos suelen estar acompañados de librerías de clases.
Estas bibliotecas contienen varios cientos o miles de clases diferentes que han demostrado ser de gran
ayuda para los desarrolladores en un amplio rango de proyectos diferentes.
Java cuenta con varias de estas bibliotecas y las denomina paquetes, packages.
La clase ArrayList, que es un ejemplo de una clase estándar de librería está definida en el paquete
java.util.
ArrayList es una clase de colección de propósito general, no está restringida en lo que respecta a
los tipos de objeto que puede almacenar
Las colecciones de objetos son objetos que pueden almacenar un número arbitrario de otros objetos.
4.4.1
Importación de una clase de librería
El acceso a una clase estándar de librería de Java se obtiene mediante la sentencia import, para
ArrayList sería:
import java.util.ArrayList;
Esta sentencia hace que la clase ArrayList del paquete java.util esté disponible para nuestra
clase.
Las sentencias import deben ubicarse en el texto de la clase, siempre antes del comienzo de la
declaración de la clase.
2
Una vez que el nombre de una clase ha sido importado desde un paquete de esta manera, podemos usar
dicha clase tal como si fuera una de nuestras propias clases.
Por ejemplo como en la siguiente declaración:
private ArrayList<String> files;
Se lee: "una colección ArrayList de objetos tipo String"
Aquí se observa una nueva estructura sintáctica: la mención de String entre símbolos de menor “<” y
de mayor “>”: <String>.
Cuando usamos colecciones, debemos especificar dos tipos:
 El tipo propio de la colección, en este caso ArrayList.
 El tipo de los elementos que planeamos almacenar en la colección, en este caso String.
4.4.2
Notación diamante
Para instanciar un nuevo objeto se necesita especificar de nuevo el tipo completo con el tipo de
elemento entre los símbolos de menor y de mayor, seguido de los paréntesis para la lista de
parámetros:
files = new ArrayList<String>();
Desde la versión 7 de Java el compilador puede inferir el tipo parametrizado del objeto que se está
creando a partir del tipo de la variable a la que se está realizando la asignación.
Se conoce como notación diamante:
files = new ArrayList<>();
4.4.3
Principales métodos de ArrayList
La clase ArrayList declara muchos métodos entre ellos: add, size, get y remove.
 add almacena un objeto en la lista.
 size devuelve el número total de elementos almacenados en la lista.
 get devuelve un elemento, sin eliminarlo de la colección.
 remove elimina un objeto en la lista.
3
4.5 Estructuras de objetos con colecciones
Características importantes de la clase ArrayList son:
 Es capaz de aumentar su capacidad interna tanto como se requiera: cuando se agregan más
elementos, simplemente crea el espacio necesario para ellos.
 Mantiene su propia cuenta privada de la cantidad de elementos almacenados, que se obtiene
mediante size.
 Mantiene el orden de los elementos que se agregan, el método add almacena cada nuevo
elemento al final de la lista, posteriormente se pueden recuperar en el mismo orden.
4.6 Clases genéricas
La nueva notación, ArrayList<String>, es una clase pero requiere que se especifique un segundo
tipo como parámetro cuando se usa para declarar campos u otras variables.
Las clases que requieren este tipo de parámetro se denominan clases genéricas.
Las clases genéricas no definen un tipo único en Java sino potencialmente muchos tipos.
Por ejemplo, la clase ArrayList puede usarse para especificar un ArrayList de Strings, un
ArrayList de Personas, un ArrayList de Rectángulos, o un ArrayList de cualquier otra
clase.
Cada ArrayList en particular es un tipo distinto que puede usarse en declaraciones de campos,
parámetros y tipos de retorno.
private ArrayList<Persona> miembros;
private ArrayList<MaquinaDeBoletos> misMaquinas;
Estas declaraciones establecen que:
 miembros contiene un ArrayList que puede almacenar objetos Persona.
 misMaquinas contiene un ArrayList que almacena objetos MaquinaDeBoletos.
Hay que tener en cuenta que ArrayList<Persona> y ArrayList<MaquinaDeBoletos> son
tipos diferentes.
Los campos no pueden ser asignados uno a otro, aun cuando sus tipos deriven de la misma clase.
4.7 Numeración dentro de las colecciones
Los elementos almacenados en las colecciones tienen una numeración implícita o posicionamiento que
comienza a partir de cero.
La posición que ocupa un objeto en una colección se conoce como su índice. El primer elemento que
se agrega a una colección tiene por índice al número 0, el segundo tiene al número 1, y así
sucesivamente hasta size − 1.
Los métodos que se utilicen deben asegurar que el valor de su parámetro se encuentre dentro del rango
de valores de índice permitidos [0...size()−1].
Si se intenta acceder a un elemento de una colección que está fuera de los índices válidos del
ArrayList se obtendrá un mensaje del error denominado desbordamiento, que en Java será
IndexOutBoundsException.
4
4.7.1
El efecto de las eliminaciones sobre la numeración
La clase ArrayList tiene un método remove que toma como parámetro el índice del elemento o el
propio elemento a eliminar.
Una complicación del proceso de eliminación es que se modifican los valores de los índices de los
restantes objetos que están almacenados en la colección.
También es posible insertar elementos en un ArrayList en otros lugares distintos que el final de la
colección.
Esto significa que los elementos que ya están en la lista deben incrementar sus índices cuando se
agrega un nuevo elemento.
Los usuarios deben ser conscientes de estos cambios en los índices cuando agregan o eliminan notas.
4.9 Procesamiento de una colección completa
A la hora de procesar una colección y realizar una determinada acción varias veces utilizaremos
instrucciones de bucle o estructuras iterativas de control.
Un ciclo o bucle puede usarse para ejecutar repetidamente un bloque de sentencias sin tener que
escribirlas varias veces.
4.9.1
El ciclo for-each
for-each es un tipo de bucle.
Las acciones de un ciclo for-each se pueden resumir en el siguiente pseudocódigo:
for(TipoDelElemento elemento: colección){
cuerpo del ciclo
}
Consta de dos partes:
 Una cabecera del bucle (la primera línea del ciclo).
 Un cuerpo a continuación del encabezado.
El cuerpo contiene aquellas instrucciones que queremos ejecutar una y otra vez.
Una forma de entender este bucle sería:
para cada elemento en la colección hacer: {
cuerpo del bucle
}
En cada vuelta, antes de que la sentencia se ejecute, la variable elemento se configura para contener
uno de los elementos de la lista: primero el del índice 0, luego el del índice 1, y así sucesivamente.
La palabra clave for inicia el bucle, seguida por un par de paréntesis en los que se definen los detalles
del bucle, lo primero vemos TipoDelElemento elemento, que declara una variable local
elemento que se usará para almacenar los distintos elementos de la lista.
Llamaremos variable de bucle a la variable que se usará para almacenar los elementos de la lista.
El tipo de la variable de bucle debe ser el mismo que el tipo del elemento declarado para la colección
que estamos usando, a continuación aparecen dos puntos y la variable que contiene la colección que
deseamos procesar. Cada elemento de esta colección será asignado en su turno a la variable de bucle, y
para cada una de estas asignaciones el cuerpo del bucle se ejecutará una sola vez.
5
4.10 Iteración Indefinida
Una acción se repetirá un número de veces no predecible hasta que se complete la tarea.
4.10.1 El bucle while
Un bucle while consta de una cabecera y de un cuerpo, el cuerpo puede ejecutarse repetidamente.
La estructura de un bucle while sería:
while (condición booleana){
cuerpo del ciclo
}
El ciclo while comienza con la palabra clave while, seguida de una condición booleana.
Este ciclo es más flexible que el ciclo for-each.
Puede recorrer un número variable de elementos de la colección, dependiendo de la condición del
bucle.
La condición booleana es una expresión lógica que se usa para determinar si el cuerpo debe ejecutarse
al menos una vez.
Si la condición se evalúa verdadera, se ejecuta el cuerpo del ciclo.
Cada vez que se ejecuta el cuerpo del ciclo, la condición se vuelve a controlar nuevamente.
Este proceso continúa repetidamente hasta que la condición resulta falsa, que es el punto en el que se
salta del cuerpo del ciclo y la ejecución continúa con la sentencia que esté ubicada inmediatamente
después del cuerpo.
4.10.2 Iteración mediante una variable índice
Utilizar un bucle while requiere más esfuerzo de programación:
Hay que declarar fuera del bucle una variable para el índice e iniciarlo por nuestros propios medios a 0
para acceder al primer elemento de la lista.
La condición tiene que estar bien definida, sino el bucle será infinito.
También tenemos que llevar nuestra propia cuenta del índice para recordar la posición en que
estábamos.
Existe un operador especial para incrementar una variable numérica en 1: variable++;
Que es equivalente a: variable = variable + 1;
Hay dos puntos más a destacar sobre el bucle while:
 No necesita estar relacionado con una colección.
 No necesitamos procesar cada uno de sus elementos de la colección.
Una ventaja de tener una variable de índice explícita es que podemos utilizar su valor tanto dentro
como fuera del bucle.
Una variable de índice local nos resultará útil a la hora de realizar búsquedas en una lista:
 Nos proporciona información dónde está ubicado el elemento
 Podemos hacer que esta información siga estando disponible una vez que el bucle haya
finalizado.
6
4.10.3 Búsquedas en una colección
La característica clave de una búsqueda infinita es que implica una iteración indefinida.
En situaciones reales de búsqueda consideramos dos posibilidades:
 La búsqueda tiene éxito después de un número indefinido de iteraciones.
 La búsqueda falla después de agotar todas las posibilidades.
Uno de estos dos criterios debe evaluar la condición como false para detener el bucle.
4.10.4 Algunos ejemplos no relacionados con colecciones
Los bucles podemos utilizarlos en situaciones distintas a la iteración de una colección.
Los bucles each-for no son válidos para este propósito.
Podemos usar el bucle while o el bucle for.
4.12 El tipo iterador
Existe una tercera variante para recorrer una colección, que está entre medio de los ciclos while y
for-each.
Usa un ciclo while para llevar a cabo el recorrido y un objeto iterador en lugar de una variable entera
como índice para controlar la posición dentro de la lista.
Iterator, con la I mayúscula es un tipo de Java, también existe el método iterator.
Un iterador es un objeto que proporciona funcionalidad para recorrer todos los elementos de una
colección.
El método iterator de ArrayList devuelve un objeto Iterator.
La clase Iterator también está definida en el paquete java.util.
Para poder utilizarlo:
Import java.util.ArrayList;
Import java.util.Iterator;
Un Iterator provee dos métodos para iterar una colección: hasNext y next.
La manera de uso en pseudocódigo sería:
Iterator<TipoDelElemento> it = miColeccion.iterator ();
while (it.hasNext ( )) {
Invocar it. next () para obtener el siguiente elemento
Hacer algo con dicho elemento
}
Iterator también es de tipo genérico por lo que hay que parametrizarlo con el tipo de los elementos de
la colección.
Luego usamos dicho iterador para controlar repetidamente si hay más elementos it.hastNext() y
para obtener el siguiente elemento it.next().
Un punto a destacar es que le pedimos al iterador que devuelva el siguiente elemento y no a la
colección.
La llamada a next hace que el objeto Iterator devuelva el siguiente elemento de la colección y luego
avance más allá de ese elemento.
7
4.12.1 Comparación entre los iteradores y el acceso mediante índices
El bucle for-each, es la técnica estándar que se usa si deben procesarse todos los elementos de una
colección porque es el más breve para este caso, pero la menos flexible.
El bucle while (con un índice y el método get) y el iterador tienen la ventaja de que la iteración
puede ser detenida más fácilmente en mitad del proceso, de modo que son mejores cuando se quiere
procesar sólo una parte de una colección.
Para algunas colecciones, es imposible o muy ineficiente acceder a elementos individuales mediante
un índice y se accede mediante for-each o iterador.
El Iterator está disponible para todas las colecciones de las clases de las bibliotecas de Java y es
un patrón importante que se usará a menudo.
4.12.2 Eliminación de elementos
Puede darse el caso que necesitemos eliminar elementos de la colección mientras estamos iterando.
La solución apropiada es utilizar un Iterator.
Tiene un tercer método, remove.
No admite ningún parámetro y tiene un tipo de retorno void.
Invocar a remove hará que sea eliminado el elemento devuelto por la llamada más reciente a next.
Este tipo de eliminación no es posible con el bucle for-each ya que no dispone de un Iterator
con el que trabajar.
Podemos utilizar el bucle while con un Iterator.
4.14 Otro ejemplo: un sistema de subastas
4.14.1 La palabra clave null.
La palabra clave null se usa para significar que «no hay objeto», es decir cuando una variable objeto
no está haciendo referencia realmente ningún objeto.
Un campo que no haya sido inicializado explícitamente contendrá el valor por defecto null.
4.14.5 Objetos anónimos
La siguiente sentencia ilustra el uso de objetos anónimos:
miColeccion.add(new Elemento());
Aquí estamos haciendo dos cosas:
 Crear un nuevo objeto Elemento
 Pasar este nuevo objeto al método add de miColeccion.
Podríamos haber escrito lo mismo en dos líneas de código, una para declarar el nuevo elemento y otra
para pasárselo a la colección.
Ambas versiones son equivalentes, pero la primera versión evita declarar una variable que puede tener
un uso muy limitado. Se crea un objeto anónimo, un objeto sin nombre, pasándoselo directamente al
método que lo utiliza.
Dos objetos String s1 y s2 pueden compararse para ver si son iguales mediante la expresión lógica:
s1.equals(s2).
8
4.16 Colecciones de tamaño fijo
A veces conocemos anticipadamente cuántos elementos deseamos almacenar en la colección y este
número permanece invariable durante la vida de la colección.
En estas circunstancias, tenemos la opción de utilizar una colección de objetos de tamaño fijo.
Una colección de tamaño fijo se denomina array o vector.
Un vector es un tipo especial de colección que puede almacenar un número fijo de elementos.
Se obtienen ventajas con respecto a las clases de colecciones de tamaño flexible:
 El acceso a los elementos de un vector es generalmente más eficiente que el acceso a los
elementos de una colección de tamaño flexible.
 Los vectores son capaces de almacenar objetos o valores de tipos primitivos. Las colecciones
de tamaño flexible sólo pueden almacenar objetos.
4.16.2 Declaración de variables vectores
La característica distintiva de la declaración de una variable de tipo vector es un par de corchetes que
forman parte del nombre del tipo: int[].
Este detalle indica que la variable declarada es de tipo vector de enteros.
Decimos que int es el tipo base de este vector en particular.
La declaración de una variable vector no crea en sí misma un objeto vector, sólo reserva un espacio de
memoria para que en un próximo paso, usando el operador new, se cree el vector tal como con los
otros objetos.
4.16.3 Creación de objetos vector
La siguiente sentencia muestra como se asocia una variable vector con un objeto vector:
obejetoVector = new int[tamaño];
La forma general de la construcción de un objeto vector es:
new tipo[expresión-entera]
La elección del tipo especifica de qué tipo serán todos los elementos que se almacenarán en el vector.
La expresión-entera especifica el tamaño del vector.
Cuando se asigna un objeto vector a una variable vector, el tipo del objeto vector debe coincidir con la
declaración del tipo de la variable.
Cuando se crea un vector no crea tantos objetos como tiene capacidad para almacenar, solo crea una
colección de tamaño fijo que es capaz de almacenar dichos objetos.
4.16.4 Utilizar objetos de vector
Se accede a los elementos individuales de un objeto vector mediante un índice.
Un índice es una expresión entera escrita entre un par de corchetes a continuación del nombre de una
variable vector.
Los valores válidos para una expresión que funciona como índice dependen de la longitud del vector
en el que se usarán.
Los índices de los vectores siempre comienzan por cero y van hasta el valor del tamaño del vector
menos uno.
9
Las expresiones que seleccionan un elemento de un vector se pueden usar en cualquier lugar que
requiera un valor del tipo base del vector. Esto quiere decir que podemos usarlas, por ejemplo, en
ambos lados de una asignación.
El uso de un índice de un vector en el lado izquierdo de una asignación es equivalente a un método
mutador (o método set) del vector porque cambiará el contenido del mismo.
Los restantes usos del índice son equivalentes a los métodos selectores (o métodos get).
4.16.5 El bucle for
Java define dos variantes para el ciclo for, el bucle for-each, y el bucle for, que es una estructura de
control repetitiva alternativa que resulta particularmente adecuada cuando:
 Queremos ejecutar un conjunto de sentencias un número exacto de veces.
 Necesitamos una variable dentro del ciclo cuyo valor cambie en una cantidad fija, generalmente
en 1, en cada iteración.
Es común el uso del ciclo for cuando queremos hacer algo con cada elemento de un vector tal como
imprimir el contenido de cada elemento. Un ciclo for tiene la siguiente forma general:
for(inicialización;condición;incremento){
instrucciones a repetir
}
En este ciclo for, los paréntesis contienen tres secciones distintas separadas por símbolos de punto y
coma (;).
Su equivalente while:
Inicialización;
while (condición){
Instrucciones a repetir
incremento
}
Todos los vectores contienen un campo length que contiene el valor del tamaño del vector.
El valor de este campo coincide siempre con el valor entero usado para crear el objeto vector.
Por eso la condición del bucle suele usar el operador menor que “<” para controlar el valor del índice
respecto de la longitud del vector.
Por lo general, cuando deseamos acceder a cada elemento de un vector, el encabezado del bucle for
tendrá la siguiente forma:
for (int indice = 0; indice < vector.length; indice ++)
10
4.17 ¿Qué ciclo debo usar?
Si se necesita recorrer todos los elementos de una colección, el ciclo for-each es, casi siempre, el ciclo
más elegante para usar, aunque no provee una variable contadora de ciclo.
Si se tiene un bucle que no está relacionado con una colección (pero lleva a cabo un conjunto de
acciones repetidamente), el ciclo for-each no resulta útil. En este caso, se puede elegir entre el ciclo for
y el ciclo while. El ciclo for-each es sólo para colecciones.
El ciclo for es bueno si conoce anticipadamente la cantidad de repeticiones necesarias. Esta
información puede estar dada por una variable, pero no puede modificarse durante la ejecución del
ciclo. Este ciclo también resulta muy bueno cuando necesita usar explícitamente una variable
contadora.
El ciclo while será el adecuado si, al comienzo del ciclo, no se conoce la cantidad de iteraciones que se
deben realizar. El fin del ciclo puede determinarse previamente mediante alguna condición.
Si tenemos que eliminar elementos de la colección mientras la recorremos en bucle, conviene utilizar
un bucle for con un Iterator si se quiere examinar la colección completa, o un bucle while cuando
queramos terminar antes de alcanzar el final de la colección.
11
4 Agrupar Objetos ............................................................................................................................... 2 4.1 La colección como abstracción .................................................................................................. 2 4.4 Utilización de una clase de librería ............................................................................................ 2 4.4.1 Importación de una clase de librería................................................................................... 2 4.4.2 Notación diamante .............................................................................................................. 3 4.4.3 Principales métodos de ArrayList ...................................................................................... 3 4.5 Estructuras de objetos con colecciones...................................................................................... 4 4.6 Clases genéricas ......................................................................................................................... 4 4.7 Numeración dentro de las colecciones ...................................................................................... 4 4.7.1 4.9 El efecto de las eliminaciones sobre la numeración ........................................................... 5 Procesamiento de una colección completa ................................................................................ 5 4.9.1 El ciclo for-each ................................................................................................................. 5 4.10 Iteración Indefinida .................................................................................................................... 6 4.10.1 El bucle while ..................................................................................................................... 6 4.10.2 Iteración mediante una variable índice ............................................................................... 6 4.10.3 Búsquedas en una colección ............................................................................................... 7 4.10.4 Algunos ejemplos no relacionados con colecciones .......................................................... 7 4.12 El tipo iterador ........................................................................................................................... 7 4.12.1 Comparación entre los iteradores y el acceso mediante índices ........................................ 8 4.12.2 Eliminación de elementos .................................................................................................. 8 4.14 Otro ejemplo: un sistema de subastas ........................................................................................ 8 4.14.1 La palabra clave null. ...................................................................................................... 8 4.14.5 Objetos anónimos ............................................................................................................... 8 4.16 Colecciones de tamaño fijo ........................................................................................................ 9 4.16.2 Declaración de variables vectores ...................................................................................... 9 4.16.3 Creación de objetos vector ................................................................................................. 9 4.16.4 Utilizar objetos de vector ................................................................................................... 9 4.16.5 El bucle for ....................................................................................................................... 10 4.17 ¿Qué ciclo debo usar? .............................................................................................................. 11 12
Capítulo
5
Comportamientos
mássofisticados
Centro Asociado Palma de Mallorca
Tutor: Antonio Rivero Cuesta
1
5 Comportamientosmássofisticados
5.1 Documentación de las clases de librería
La librería de Java contiene muchas clases que son muy útiles.
Es importante:
 Conocer algunas de las clases más importantes de la biblioteca por su nombre.
 La forma de encontrar otras clases y buscar sus detalles.
Lo más importante es que tenemos que ser capaces de explorar y comprender la biblioteca por nuestros
propios medios.
Es importante poder leer y comprender la documentación Java.
Las clases las utilizamos sin mirar su código fuente; no es necesario para usar su funcionalidad.
Todo lo que necesitamos saber es el nombre de la clase, los nombres de los métodos, los parámetros y
los tipos de retorno de los métodos y saber exactamente qué hacen estos métodos.
La misma cuestión es cierta para otras clases no estándar en proyectos de software grandes.
Generalmente, algunas personas trabajan juntas en un proyecto pero trabajando sobre partes diferentes.
Cada miembro del equipo debe escribir la documentación de la clase en forma similar a la
documentación de la biblioteca estándar de Java de modo que permita a otras personas usar la clase sin
necesidad de leer el código.
5.3 Lectura de la documentación de las clases
5.3.1
Interfaces e implementación
La documentación incluye diferentes elementos de información:
 El nombre de la clase.
 Una descripción general del propósito de la clase.
 Una lista de los constructores y los métodos de la clase.
 Los parámetros y los tipos de retorno de cada constructor y de cada método.
 Una descripción del propósito de cada constructor y cada método.
Toda esta información conjunta recibe el nombre de interfaz de la clase.
La interfaz de una clase describe lo que es capaz de hacer dicha clase y la manera en que se puede usar
sin mostrar su implementación.
La interfaz no muestra el código con que está implementada la clase.
Estamos nuevamente frente a la abstracción en acción.
El código completo que define una clase se denomina la implementación de dicha clase.
También se utiliza la terminología interfaz referida a métodos individuales.
La interfaz de un método consta de la signatura y un comentario.
2
La signatura de un método incluye, en este orden:
 Un modificador de acceso, (public, private,…).
 El tipo de retorno del método.
 El nombre del método.
 Una lista de parámetros.
La interfaz de un método proporciona todos los elementos necesarios para saber cómo usarlo.
5.3.2
Utilización de métodos de clases de librería
Se dice que un objeto es inmutable si su contenido o su estado no puede ser cambiado una vez que se
ha creado.
Los objetos String, cadenas de caracteres, son un ejemplo de objetos inmutables.
entrada
=
entrada.trim();
Este código le solicita al objeto almacenado en la variable entrada crear una nueva cadena similar a la
dada, pero eliminados los espacios en blanco antes y después de la palabra.
Ahora podemos insertar esta línea en nuestro código de modo que quede así:
String entrada = lector.getEntrada();
entrada = entrada.trim();
Las primeras dos líneas podrían unirse para formar una sola línea:
String entrada = lector.getEntrada().trim();
5.3.3
Comprobación de la igualdad entre cadenas
Con variables de objeto, el operador (==) evalúa si ambos operandos hacen referencia al mismo
objeto, no si sus valores son iguales.
Esta es una diferencia importante.
La solución para este problema es usar el método equals, que deben tener todas las clases, ya que al
menos lo heredan de la Clase Object que es superclase de todas las demás.
5.4 Adición de comportamiento aleatorio
Aleatorio y pseudo-aleatorio: Las computadoras operan de una manera bien definida y determinística
que se apoya en el hecho de que todo cálculo es predecible y repetible, en consecuencia existe poco
espacio para un comportamiento realmente aleatorio.
Se han propuesto muchos algoritmos para producir secuencias semejantes a los números aleatorios.
Estos números no son típicamente números aleatorios verdaderos, aunque siguen reglas muy
complicadas.
Estos números se conocen como números pseudo-aleatorios.
En Java la generación de números pseudoaleatorios ha sido implementada en una clase de la
biblioteca.
3
5.4.1
La clase Random
La biblioteca de clases de Java contiene una clase de nombre Random capaz de generar datos
pseudoaleatorios.
Para generar un número aleatorio tenemos que:
 Crear una instancia de la clase Random.
 Hacer una llamada a un método de esa instancia para obtener un número.
Ejemplo de uso:
Random randomGenerator;
randomGenerator = new Random();
int indice = randomGenerator.nextInt () ;
System.out.println(indice);
Este fragmento de código crea una nueva instancia de la clase Random y la almacena en la variable
randomGenerator.
Luego, invoca al método nextInt de esta variable para obtener un número por azar, almacena el
número generado en la variable indice y eventualmente lo imprime en pantalla.
5.4.2
Números aleatorios con rango limitado
Lo más frecuente es que necesitemos números aleatorios dentro de un rango limitado específico.
La clase Random posee el método nextInt, pero con un parámetro para especificar el rango de
números que queremos usar.
nextInt(int n)
El método nextInt(int n) de la clase Random de la biblioteca de Java especifica que genera
números desde 0 (inclusive) hasta n (exclusive).
Esto quiere decir que el valor 0 está incluido entre los posibles valores de los resultados, mientras que
el valor especificado por n no está incluido.
El máximo número posible que devuelve es n − 1.
5.4.3
Lectura de la documentación de las clases parametrizadas
En la ayuda, algunos nombres de las clases que aparecen en la lista de la documentación tienen un
formato ligeramente diferente, tal es el caso de ArrayList<E>.
Las clases similares a éstas se denominan clases parametrizadas o clases genéricas.
La información contenida entre los símbolos de menor y de mayor nos dice que, cuando usemos estas
clases deberemos suministrar uno o más nombres de tipos entre dichos símbolos, para completar la
definición.
Por lo tanto, si busca en la lista de métodos de ArrayList<E> verá métodos tales como:
 boolean add(E o)
 E get(int index)
Estas signaturas nos indican que el tipo de objetos que podemos agregar u obtener de un ArrayList
depende del tipo usado para parametrizarlo.
4
5.5 Paquetes e importación
Las clases de Java almacenadas en la librería de clases no están disponibles automáticamente para su
uso, tal como las otras clases del proyecto actual.
Para poder disponer de ellas, debemos explicitar en nuestro código que queremos usar una clase de la
librería.
Esta acción se denomina importación de la clase y se implementa mediante la sentencia import.
La sentencia import tiene la forma general:
import nombre-de-clase-calificado;
Java utiliza paquetes (packages) para agrupar las clases de la librería en grupos de clase relacionadas.
Los paquetes pueden estar anidados, es decir, los paquetes pueden contener otros paquetes.
El nombre completo o nombre cualificado de una clase es el nombre de su paquete, seguido por un
punto y por el nombre de la clase, por ejemplo:
java.util.ArrayList
java.util.Random
Java también nos permite importar paquetes completos con sentencias de la forma:
import nombre-del-paquete.*;
por lo que la siguiente sentencia importaría todas las clases del paquete java.util:
import java.util.*;
La enumeración de todas las clases utilizadas separadamente da un poco más de trabajo en términos de
escritura pero funciona bien como parte de la documentación.
Existe una excepción a esta regla: algunas clases se usan tan frecuentemente que casi todas las clases
deberían importarlas.
Estas clases se han ubicado en el paquete java.lang y este paquete se importa automáticamente
dentro de cada clase.
La clase String es un ejemplo de una clase ubicada en java.lang.
5.6 Utilización de mapas para asociaciones
5.6.1
Concepto de mapa
Un mapa es una colección que almacena parejas clave/valor de objetos. Los valores se pueden buscar
proporcionando la clave. Un mapa puede almacenar un número flexible de entradas.
En un Map cada entrada no es un único objeto sino una pareja de objetos.
Esta pareja está compuesto por un objeto clave y un objeto valor.
En lugar de buscar las entradas en esta colección mediante un índice entero usamos el objeto clave
para buscar el objeto valor.
Un mapa puede organizarse de manera tal que resulte fácil buscar en él un valor para una clave.
Los mapas son ideales para una única forma de búsqueda, en la que conocemos la clave a buscar y
necesitamos conocer solamente el valor asociado a esta clave.
5
5.6.2
Usar un HashMap
Un HashMap es una implementación específica de un Map.
Los métodos más importantes de la clase HashMap son put y get.
El método put inserta una entrada en el mapa y el método get recupera el valor correspondiente a
una clave determinada.
HashMap<String, String> phoneBook = new HashMap<String, String>();
phoneBook.put ("Charles James",(531) 2356 8547");
phoneBook.put ("Lisa Jones",(402) 4536 4674");
phoneBook.put ("William Smith",(998) 4555 5219");
la especificación de tipo genérico del lado derecho de la asignación puede omitirse, como en la
siguiente instrucción:
HashMap<String, String> phoneBook = new HashMap<>();
Esto se conoce con el nombre de operador diamante.
Si a un HashMap se le proporciona un par clave, valor, con una clave ya existente se reemplaza el
valor anterior de esa clave.
Si se intenta acceder al valor correspondiente a una clave inexistente, devuelve null; también se pueden
almacenar valores nulos, o incluso guardar una clave null, por lo que existe el método boolean
containsKey(E clave), que nos informa si un mapa contiene una determinada clave.
El método int size() devuelve el número de pares clave, valor almacenados.
5.7 Utilización de conjuntos
La librería estándar de Java incluye diferentes variantes de conjuntos, implementados en clases
diferentes.
Una de ellas es HashSet.
Un conjunto es una colección que almacena cada elemento individual una sola vez como máximo.
No mantiene un orden específico.
Los dos tipos de funcionalidad que necesitamos de un conjunto son:
 Ingresar elementos en él y más tarde,
 Recuperar estos elementos.
import java.util.HashSet;
HashSet<String> miConjunto = new HashSet<String>();
miConjunto.add("uno");
miConjunto.add("dos");
Comparamos este código con las sentencias que necesitamos para entrar elementos en un ArrayList.
No hay prácticamente ninguna diferencia, excepto que esta vez creamos un HashSet en lugar de un
ArrayList.
for(String miConjunto) {
Hacer algo con cada elemento
}
6
Las diferencias reales residen en el comportamiento de cada colección.
Por ejemplo, una lista contiene todos los elementos ingresados en el orden deseado, provee acceso a
sus elementos a través de un índice y puede contener el mismo elemento varias veces.
Por otro lado, un conjunto no mantiene un orden específico (el iterador puede devolver los elementos
en diferente orden del que fueron ingresados) y asegura que cada elemento en el conjunto está una
única vez.
En un conjunto, el ingresar un elemento por segunda vez simplemente no tiene ningún efecto.
List, Map y Set. Cuando tratamos de comprender la forma en que se usan las diferentes clases de
colecciones, la segunda parte del nombre es la mejor indicación de los datos que almacenan, y la
primera palabra describe la forma en que se almacenan. Generalmente estamos más interesados en el
“qué” (la segunda parte) antes que en el “cómo”. De modo que un TreeSet debiera usarse de manera
similar a un HashSet, mientras que un TreeMap debiera usarse de manera similar a un HashMap.
5.8 División de cadenas de caracteres
El método split de la clase String puede dividir una cadena en distintas subcadenas y las
devuelve en un array de cadenas.
El parámetro del método split establece la clase de caracteres de la cadena original que producirá la
división en palabras.
5.10 Escritura de la documentación de las clases
La documentación de una clase debiera ser suficientemente detallada como para que otros
programadores puedan usarla sin tener que leer su implementación.
El sistema Java incluye una herramienta denominada javadoc que se puede utilizar para generar la
interfaz que describa nuestros archivos fuente.
5.10.1 Utilización de javadoc en BlueJ
El entorno BlueJ utiliza javadoc para posibilitar la creación de la documentación de las clases de dos
formas:
 Podemos ver la documentación para una única clase pasando el selector emergente situado en
la parte superior derecha de la ventana del editor de Source Code a Documentation o
seleccionando Toggle Documentatiton View.
 Podemos usar la función Project Documentation disponible en el menú Tools de la ventana
principal para generar la documentación correspondiente a todas las clases del proyecto.
5.10.2 Elementos de la documentación de una clase
La documentación de una clase debe incluir como mínimo:
 El nombre de la clase.
 Un comentario que describa el propósito general y las características de la clase.
 Un número de versión.
 El nombre del autor (o autores).
 La documentación de cada constructor y de cada método.
7
La documentación de cada constructor y de cada método debe incluir:
 El nombre del método.
 El tipo de retorno.
 Los nombres y tipos de los parámetros.
 Una descripción del propósito y de la función del método.
 Una descripción de cada parámetro.
 Una descripción del valor que devuelve.
Además, cada proyecto debe tener un comentario general, frecuentemente guardado en un archivo de
nombre “Leeme” o “ReadMe”.
En Java, los comentarios de estilo javadoc se escriben con un símbolo especial de al comienzo:
/**
Este es un comentario javadoc
*/
El símbolo de inicio de un comentario debe tener dos asteriscos para que javadoc lo reconozca.
Este tipo de comentario, ubicado inmediatamente antes de la declaración de clase es interpretado como
un comentario de clase. Si el comentario está ubicado directamente arriba de la signatura de un
método, es considerado como un comentario de método.
En Java y mediante javadoc, se dispone de varios símbolos especiales para dar formato a la
documentación. Estos símbolos comienzan con el símbolo @ e incluyen entre otros:
@version
@autor
@param
@return
5.11 públic y private
Los modificadores de acceso son las palabras clave como public o private que aparecen al
comienzo de las declaraciones de campos y de las signaturas de los métodos.
Los modificadores de acceso definen la visibilidad de un campo, de un constructor o de un método.
Los elementos públicos son accesibles dentro de la misma clase o fuera de ella.
Los elementos privados son accesibles solamente dentro de la misma clase.
Los campos, los constructores y los métodos pueden ser públicos o privados; a la mayoría de los
campos suelen ser privados y la mayoría de los constructores y de los métodos suelen ser públicos.
La interfaz de una clase es el conjunto de detalles que necesita ver otro programador que utilice dicha
clase. La interfaz puede verse como la parte pública de una clase. Su propósito es definir lo que hace
la clase.
La implementación es la sección de una clase que define precisamente cómo funciona la clase.
También nos referimos a la implementación como la parte privada de una clase.
El usuario de una clase no necesita conocer su implementación. En realidad, existen buenas razones
para evitar que un usuario conozca la implementación o por lo menos, que use ese conocimiento.
Este principio se denomina ocultamiento de la información.
8
La palabra clave public declara que un elemento de una clase, un campo o un método, forma parte
de la interfaz, es decir, es visible públicamente.
La palabra clave private declara que un elemento es parte de la implementación, es decir,
permanece oculto para los accesos externos.
5.11.1 Ocultamiento de la información
El ocultamiento de la información es un principio que establece que los detalles internos de
implementación de una clase deben permanecer ocultos para las otras clases.
Garantiza una mejor modularización de la aplicación.
En muchos lenguajes de programación orientados a objetos, el interior de una clase, su
implementación, permanece oculta para las otras clases.
Hay dos aspectos en este punto:
 Primero, un programador que hace uso de una clase no necesita conocer su interior;
 Segundo, a un usuario no se le permite conocer los detalles internos.
El primer principio, necesidad de conocer, tiene que ver con la abstracción y la modularización. Si
necesitáramos conocer todos los detalles internos de todas las clases que queremos usar, no
terminaríamos nunca de implementar sistemas grandes.
El segundo principio, que no se permite conocer, es diferente. También tiene que ver con la
modularización pero en un contexto diferente. El lenguaje de programación no permite el acceso a una
sección privada de una clase mediante sentencias en otra clase. Esto asegura que una clase no dependa
de cómo está implementada exactamente otra clase.
Este punto es muy importante para el trabajo de mantenimiento. Una tarea muy común de
mantenimiento de un programa es la modificación o extensión de la implementación de una clase para
mejorarlo o para solucionar defectos.
Idealmente, las modificaciones en la implementación de una clase no debieran generar la necesidad de
cambiar también las otras clases.
Esta característica se conoce como acoplamiento.
Si se cambia una parte de un programa no debiera ser necesario hacer cambios en otras partes del
programa, cuestión que se conoce como fuerte y débil acoplamiento.
El acoplamiento débil es bueno porque hace que el trabajo de mantenimiento del programador sea
mucho más fácil, en lugar de comprender y modificar muchas clases, deberá comprender y modificar
sólo una clase.
Para ser más precisos, la regla de que a un usuario «no se le debe permitir conocer el interior de una
clase» no se refiere al programador de otras clases sino a la clase en sí misma.
Generalmente, no es un problema el hecho de que un programador conozca los detalles de
implementación, pero una clase no debiera “conocer”, ser dependiente de, los detalles internos de
otras clases.
El programador de ambas clases podría ser hasta la misma persona pero las clases aún tendrían que
permanecer débilmente acopladas.
La palabra clave private provoca el ocultamiento de la información al impedir el acceso a esta parte
de la clase desde otras clases.
Esto asegura el acoplamiento débil y hace que la aplicación resulte más modular y más fácil de
mantener.
9
5.11.2 Métodos privados y campos públicos
En general los métodos de las clases son públicos, aunque no siempre es así.
A veces existen métodos de apoyo dentro de una clase que son usados por otros métodos de dicha
clase; normalmente esas subtareas no tienen la finalidad de ser invocadas directamente desde el
exterior de la clase pero se las ubica como métodos separados con la intencionalidad de lograr que la
implementación de una clase sea más fácil de leer.
En este caso, tales métodos deben ser privados.
Otra buena razón para tener un método privado es cuando una tarea necesita ser usada (como una
subtarea) en varios métodos de una clase.
En lugar de escribir el código varias veces, podemos escribirlo una única vez en un solo método
privado y luego invocar este método desde diferentes lugares de la clase.
En Java, los campos también pueden ser declarados privados o públicos.
La declaración de los campos como públicos rompe con el principio de ocultamiento de la
información.
Hace que una clase que depende de esa información sea vulnerable a operaciones incorrectas, si se
modifica la implementación.
Una razón más para mantener los campos como privados reside en que permiten que un objeto crezca
manteniendo el control sobre su estado.
Si el acceso a los campos privados se canaliza a través de métodos selectores y de mutadores, entonces
un objeto tiene la habilidad de asegurar que el campo nunca se configura con un valor que resulte
inconsistente con su estado.
Este nivel de integridad no es posible si los campos son públicos. Abreviando, los campos debieran ser
siempre privados.
5.13 Variables de clase y constantes
5.13.1 La palabra clave static
La palabra clave static se usa en la sintaxis de Java para definir variables de clase.
Las variables de clase son campos que se almacenan en la misma clase y no en un objeto.
Este hecho produce diferencias fundamentales con respecto a las variables de instancia.
public class MiClase
{
<acceso> static <tipo> <identificador>;
…
}
Las clases pueden tener campos.
Estos campos se conocen como variables de clase o variables estáticas.
En todo momento, existe exactamente una copia de una variable de clase, independientemente del
número de instancias que se hayan creado de dicha clase.
El código de la clase puede acceder, leer y configurar, a esta clase de variable de la misma forma en
que accede a las variables de instancia.
Se puede acceder a la variable de clase desde cualquiera de las instancias de la clase; como resultado,
los objetos comparten esta variable.
Las variables de clase se usan frecuentemente en los casos en que un valor debe ser siempre el mismo
para todas las instancias de una clase.
10
En lugar de almacenarse una copia con el mismo valor en cada objeto, que sería un desperdicio de
espacio y puede ser más difícil de coordinar, puede compartirse un único valor entre todas las
instancias.
Java también soporta métodos de clase, es decir métodos a los que se accede directamente desde la
clase y que no necesitan una instancia para ser llamados.
5.13.2 Constantes
Un uso frecuente de la palabra clave static se produce en la declaración de constantes.
Las constantes son similares a las variables pero no pueden cambiar su valor durante la ejecución de
una aplicación.
En Java, las constantes se definen con la palabra clave final:
private final int SIZE = 10;
En esta instrucción definimos una constante de nombre SIZE con el valor 10.
Observamos que las declaraciones de constantes son similares a las declaraciones de campos pero con
dos diferencias:
 Deben incluir la palabra clave final antes del nombre del tipo.
 Deben ser inicializadas con un valor en el momento de su declaración (o en los constructores de
la clase).
Si se pretende que un valor no cambie nunca, es una buena idea declararlo como final.
Esto garantiza que no pueda ser modificado posteriormente de manera accidental.
Cualquier intento posterior de cambiar un campo constante dará por resultado un mensaje de error en
tiempo de compilación.
Por convención, las constantes se escriben frecuentemente con letras mayúsculas.
En la práctica, es muy frecuente el caso en que las constantes se relacionen con todas las instancias de
una clase.
En esta situación declaramos constantes de clase.
Las constantes de clase son campos de clase constantes.
Se declaran usando una combinación de las palabras clave static y final.
private static final int TOPE = 10;
11
5 Comportamientos más sofisticados .................................................................................................. 2 5.1 Documentación de las clases de librería .................................................................................... 2 5.3 Lectura de la documentación de las clases ................................................................................ 2 5.3.1 Interfaces e implementación ............................................................................................... 2 5.3.2 Utilización de métodos de clases de librería ...................................................................... 3 5.3.3 Comprobación de la igualdad entre cadenas ...................................................................... 3 5.4 Adición de comportamiento aleatorio ....................................................................................... 3 5.4.1 La clase Random ................................................................................................................ 4 5.4.2 Números aleatorios con rango limitado.............................................................................. 4 5.4.3 Lectura de la documentación de las clases parametrizadas ................................................ 4 5.5 Paquetes e importación .............................................................................................................. 5 5.6 Utilización de mapas para asociaciones .................................................................................... 5 5.6.1 Concepto de mapa .............................................................................................................. 5 5.6.2 Usar un HashMap ............................................................................................................... 6 5.7 Utilización de conjuntos ............................................................................................................ 6 5.8 División de cadenas de caracteres ............................................................................................. 7 5.10 Escritura de la documentación de las clases .............................................................................. 7 5.10.1 Utilización de javadoc en BlueJ ......................................................................................... 7 5.10.2 Elementos de la documentación de una clase .................................................................... 7 5.11 públic y private .......................................................................................................................... 8 5.11.1 Ocultamiento de la información ......................................................................................... 9 5.11.2 Métodos privados y campos públicos .............................................................................. 10 5.13 Variables de clase y constantes................................................................................................ 10 5.13.1 La palabra clave static ................................................................................................ 10 5.13.2 Constantes ........................................................................................................................ 11 12
Capítulo
6
Di seño
deClases
Centro Asociado Palma de Mallorca
Tutor: Antonio Rivero Cuesta
1
6 DiseñodeClases
6.1 Introducción
Es posible implementar una aplicación y lograr que realice su tarea mediante un diseño de clases mal
diseñado.
El problema surge, típicamente, cuando un programador de mantenimiento quiere hacer algunos
cambios en una aplicación existente.
Si por ejemplo, el programador intenta solucionar un fallo o quiere agregar una nueva funcionalidad a
un programa, una tarea que debiera ser fácil y obvia con un buen diseño de clases, podría resultar muy
difícil de manejar y consumir una gran cantidad de trabajo si las clases están mal diseñadas.
En las aplicaciones grandes, este efecto ya ocurre durante la implementación original. Si la
implementación comienza con una mala estructura, su finalización puede volverse muy compleja y
puede que no se termine de completar el programa, o que contenga fallos o que su construcción tome
más tiempo de lo necesario.
El diseño de mala calidad no es, generalmente, consecuencia de tener un problema difícil para
resolver.
La mala calidad del diseño tiene más que ver con las decisiones que se toman cuando se resuelve un
problema en especial.
No podemos usar el argumento de que no había otra manera de resolver el problema como una excusa
para un diseño de mala calidad.
6.3 Introducción al acoplamiento y a la cohesión
Hay dos términos que son fundamentales cuando hablamos sobre la calidad de un diseño de clases:
acoplamiento y cohesión.
El término acoplamiento describe la interconexión de las clases. Nos esforzamos por lograr
acoplamiento débil en un sistema, es decir, un sistema en el que cada clase sea altamente
independiente y se comunique con otras clases mediante una interfaz compacta y bien definida.
El grado de acoplamiento determina el grado de dificultad de realizar modificaciones en una
aplicación. En una estructura de clases fuertemente acopladas, un cambio en una clase hace necesario
también cambiar otras varias clases. Este hecho es el que tratamos de evitar porque el efecto de hacer
un pequeño cambio puede rápidamente propagarse a la aplicación completa. Además, encontrar todos
los lugares en que resulta necesario hacer los cambios y realmente llevar a cabo estos cambios puede
ser dificultoso y consumir demasiado tiempo.
Por otro lado, en un sistema débilmente acoplado, podemos con frecuencia modificar una clase sin
tener que realizar cambios en ninguna otra y la aplicación continúa funcionando.
El término cohesión describe cuánto se ajusta una unidad de código a una tarea lógica o a una entidad.
En un sistema altamente cohesivo cada unidad de código (método, clase o módulo) es responsable de
una tarea bien definida o de una entidad. Un diseño de clases de buena calidad exhibe un alto grado de
cohesión.
La cohesión es relevante para unidades formadas por una sola clase y para métodos individuales, así
como para módulos o paquetes.
Idealmente, una unidad de código debiera ser responsable de una tarea coherente, es decir, una tarea
que pueda ser vista como una unidad lógica.
Un método debiera implementar una operación lógica y una clase debiera representar un tipo de
entidad.
2
La razón principal que subyace al principio de cohesión es la reutilización: si un método de una clase
es responsable de una única cosa bien definida es más probable que pueda ser usado nuevamente en un
contexto diferente.
Una ventaja complementaria, consecuencia de este principio, es que, cuando se requiere un cambio de
un aspecto de una aplicación, probablemente encontremos todas las piezas de código relevantes
ubicadas en la misma unidad.
6.4 Duplicación de código
La duplicación de código, es decir, tener el mismo segmento de código en una aplicación más de una
vez es una señal de mal diseño y debe ser evitada.
Generalmente, la duplicación de código es un síntoma de mala cohesión.
6.5 Cómo hacer ampliaciones
Ampliar el código de una aplicación sin un buen nivel de cohesión implica tener que realizar
modificaciones en más sitios.
6.6 Acoplamiento
Idealmente, cuando sólo se cambia la implementación de una clase, las restantes clases no debieran
verse afectadas por el cambio.
Este sería un caso de acoplamiento débil.
En cambio un alto acoplamiento obliga a modificar varias clases.
6.6.1
Utilización de la encapsulación para reducir el acoplamiento
El uso de campos públicos expuestos en la interfaz de una clase no solo aporta la información que
contiene la clase, sino también cómo se almacena exactamente la información de esos campos.
Esto rompe uno de los principios fundamentales del diseño de clases de buena calidad, la
encapsulación.
Una pauta para el encapsulamiento (ocultar la información de la implementación a ojos de otras clases)
sugiere que solamente debe hacerse visible para el exterior la información acerca de lo que hace una
clase, no la información acerca de cómo lo hace.
Esto tiene una gran ventaja: si ninguna otra clase conoce cómo está almacenada nuestra información
entonces podemos cambiar fácilmente la forma de almacenarla sin romper otras clases.
Un adecuado encapsulamiento de las clases reduce el acoplamiento y por lo tanto lleva a un mejor
diseño.
Podemos obligar a esta separación entre lo que se hace y cómo se hace definiendo los campos como
privados y utilizando un método selector para acceder a ellos.
Así en cualquier momento se puede cambiar la representación interna de los datos sin que se tenga que
modificar la interfaz, ni, por lo tanto, el resto de clases que la usen.
Es importante notar que jamás podemos desacoplar completamente las clases en una aplicación, de lo
contrario, no podrían interactuar entre ellos objetos de diferentes clases.
Más bien tratamos de mantener un grado de acoplamiento tan bajo como sea posible.
3
6.7 Diseño dirigido por responsabilidad
El encapsulamiento no es el único factor que influye en el grado de acoplamiento, otro aspecto se
conoce como diseño dirigido por responsabilidades.
El diseño dirigido por responsabilidades es el proceso de diseñar clases asignando responsabilidades
bien definidas a cada una. Este proceso puede usarse para determinar las clases que deben implementar
una parte de cierta función de una aplicación.
Expresa la idea de que cada clase será responsable de manejar sus propios datos.
Un buen diseño dirigido por responsabilidades influye en el grado de acoplamiento y por consiguiente,
también influye en la facilidad con que una aplicación puede ser modificada o extendida.
6.8 Localidad de cambios
Uno de los principales objetivos de un diseño de clases de buena calidad es la localidad de los
cambios: las modificaciones en una clase debieran tener efectos mínimos sobre las otras clases.
Queremos crear un diseño de clases que facilite las modificaciones posteriores haciendo que los
efectos de un cambio sean locales.
Idealmente, debería cambiarse una única clase para realizar una modificación.
Algunas veces, es necesario cambiar varias clases, pero apuntamos a que el cambio afecte a la menor
cantidad de clases posible.
Además, los cambios que requieran las otras clases debieran ser obvios, fáciles de detectar y fáciles de
llevar adelante.
En los proyectos grandes, logramos este objetivo siguiendo las reglas de diseño de buena calidad tales
como usar diseño dirigido por responsabilidades y apuntar a un acoplamiento débil y a una alta
cohesión.
6.9 Acoplamiento implícito
El uso de campos públicos es una práctica que probablemente crea un acoplamiento fuerte entre las
clases.
Con este acoplamiento fuerte, puede ser necesario hacer cambios en más de una clase para algo que
podría ser una simple modificación.
Los campos públicos deben evitarse.
Sin embargo, existe aún una forma peor de acoplamiento: el acoplamiento implícito.
El acoplamiento implícito es una situación en la que una clase depende de la información interna de
otra pero esta dependencia no es inmediatamente obvia.
El acoplamiento fuerte en el caso de los campos públicos no era bueno, pero por lo menos era obvio.
Si cambiamos los campos públicos en una clase y nos olvidamos de otra, la aplicación no compilará
más y el compilador indicará el problema.
En los casos de acoplamiento implícito, el omitir un cambio necesario puede no ser detectado. Un buen
diseño de clases evitará esta forma de acoplamiento siguiendo la regla de diseño dirigido por
responsabilidades
Si nos fijamos en el diagrama de clases de una aplicación, las flechas en el diagrama son un buen
primer indicador del grado de intensidad del acoplamiento de un programa: cuantas más flechas, más
acoplamiento.
Como una aproximación a un buen diseño de clases podemos apuntar a crear diagramas con pocas
flechas.
4
6.10 Planificación por adelantado
Una característica de un buen diseñador de software es la habilidad de anticiparse a los
acontecimientos.
 ¿Qué cosas pueden cambiar?
 ¿Qué podemos asumir con seguridad que permanecerá sin cambios durante la vida del
programa?
Encapsular toda la información de la interfaz de usuario en una sola clase o en un conjunto de clases
claramente definido forma parte de un buen diseño.
6.11 Cohesión
El principio de cohesión puede aplicarse a clases y a métodos: las clases deben mostrar un alto grado
de cohesión y lo mismo ocurre con los métodos.
6.11.1 Cohesión de métodos
Un método cohesivo es responsable de una y sólo una tarea bien definida.
Es más fácil de comprender lo que hace un segmento de código y realizar modificaciones si se usan
métodos breves y bien cohesionados.
Una estructura de métodos bien elegida, todos los métodos son relativamente cortos y fáciles de
entender y sus nombres indican sus propósitos de forma bastante clara.
Estas características representan una ayuda valiosa para un programador de mantenimiento.
6.11.2 Cohesión de clases
La regla de cohesión de clases establece que cada clase debe representar una única entidad bien
definida en el dominio del problema.
Una clase cohesiva representa una única entidad bien definida.
6.11.3 Cohesión para la legibilidad
Hay varias maneras en que un diseño se ve beneficiado por la alta cohesión.
Las dos más importantes son la legibilidad y la reusabilidad.
Un programador de mantenimiento fácilmente reconocerá por dónde comenzar a leer el código si
necesita realizar un cambio en clases bien cohesionadas.
La cohesión de clases también incrementa la legibilidad de un programa.
6.11.4 Cohesión para la reusabilidad
La segunda gran ventaja de la cohesión es el alto potencial para la reutilización.
La reusabilidad es otro aspecto importante de los métodos cohesivos.
Las tareas separadas pueden reutilizarse más fácilmente y es debido al alto grado de cohesión.
6.12 Refactorización
La refactorización es la actividad de reestructurar un diseño existente para mantener un buen diseño
de clases cuando se modifica o se extiende una aplicación.
Cuando diseñamos aplicaciones, debemos tratar de planificar por adelantado, anticipar los posibles
cambios que podrían ser deseables en el futuro y crear clases altamente cohesivas y débilmente
acopladas que faciliten las modificaciones.
Está claro que no siempre podemos anticipar todas las futuras adaptaciones y que no es factible
preparar un diseño que contemple todas las posibles ampliaciones que pensamos.
5
Este es el motivo por el que resulta importante la refactorización.
Es frecuente que, durante el tiempo de vida de una aplicación, se le vaya agregando funcionalidad.
Un efecto común que se produce de manera colateral es el lento crecimiento de la longitud de los
métodos y de las clases.
Añadir código en reiteradas ocasiones suele tener como consecuencia la disminución del grado de
cohesión.
Es muy probable que si se añade más y más código a un método o a una clase, llegue un momento en
el que representará más de una tarea o una entidad claramente definida.
La refactorización consiste justamente en repensar y rediseñar las estructuras de las clases y de los
métodos.
El efecto más común es que las clases se dividan en dos o que los métodos se dividan en dos o más
métodos.
La refactorización también incluye la unión de clases o de métodos que da por resultado una sola clase
o un solo método, pero este caso es menos frecuente.
6.12.1 Refactorización y prueba
Cuando algo se modifica existe la posibilidad de que se introduzcan errores, por lo tanto, es importante
proceder cautelosamente, y antes de llevar a cabo la refactorización debemos asegurarnos de que exista
un conjunto de pruebas para la versión actual del programa.
Si las pruebas no existen, es prioritario crear algunas pruebas que se adecuen para implementar
pruebas regresivas sobre la versión rediseñada.
La refactorización debe comenzar sólo cuando existen las pruebas.
Idealmente, la refactorización debe seguir dos pasos:
 La primera etapa consiste en refactorizar para mejorar la estructura interna del código, pero sin
realizar ningún cambio en la funcionalidad de la aplicación. En otras palabras, al ejecutar el
programa debería comportarse exactamente igual que antes. Una vez que este paso está
completo, se deben ejecutar las pruebas regresivas para asegurarse de que no se hayan
introducido errores no deseados.
 La segunda etapa comenzará una vez que se ha restablecido la funcionalidad base en la versión
refactorizada. En ese momento estamos en una posición segura como para mejorar el
programa. Una vez que se ha finalizado con la refactorización, por supuesto que será necesario
ejecutar las pruebas en la nueva versión.
La implementación de varios cambios al mismo tiempo (refactorizar y agregar nuevas características)
hace que se vuelva más difícil localizar la fuente de los problemas, cuando estos ocurran.
Una buena refactorización es tanto una manera de pensar como un conjunto de habilidades técnicas.
Mientras realizamos cambios y extensiones en las aplicaciones, regularmente nos debemos preguntar
si el diseño original aún representa la mejor solución. A medida que cambia la funcionalidad, también
cambian los argumentos a favor o en contra sobre ciertos diseños. Lo que fue un buen diseño para una
aplicación simple podría dejar de serlo cuando se agregan algunas extensiones.
Reconocer estos cambios y realizar efectivamente estas modificaciones de refactorización en el
código, generalmente ahorra una gran cantidad de tiempo y de esfuerzo al final. Cuanto antes
limpiemos nuestro diseño, más trabajo ahorraremos.
Debemos estar preparados para refactorizar métodos (convertir una secuencia de sentencias del cuerpo
de un método existente en un método nuevo e independiente) y clases (tomar partes de una clase y
crear una nueva clase a partir de ella). Considerar regularmente la refactorización mantiene nuestro
diseño de clases limpio y finalmente, nos ahorra trabajo.
6
6.13 Refactorización para la independencia respecto del idioma
Si queremos que el programa sea independiente del idioma, la situación ideal sería que el texto real de
las palabras se almacene en un único lugar del código y que en todas las restantes partes se haga
referencia a ellas de manera independiente del idioma.
Una característica del lenguaje de programación que hace que esto sea posible son los tipos
enumerados o enums.
6.13.1 Tipos Enumerados
En su forma más simple, una definición de un tipo enumerado consiste en una envoltura exterior que
utiliza la palabra enum en lugar de la palabra class, y un cuerpo que es simplemente una lista de
nombres de variables que denotan el conjunto de valores que pertenece a este tipo.
Por convenio, los nombres de estas variables se escriben en mayúsculas.
Nunca creamos objetos de un tipo enumerado.
Cada nombre dentro de la definición del tipo representa una única instancia de un tipo enumerado que
ya se ha creado para usarla.
public enum <NombreEnum>{
ENUM_OPC1, ENUM_OPC2, ENUM_OPC3, ENUM_OPC4;
}
Cada opción representa una instancia, a las que nos referimos de la siguiente manera:
NombreEnum.ENUM_OPC1, NombreEnum.ENUM_OPC2, etc.
A pesar de la simplicidad de su definición, los valores del tipo enumerado son objetos propiamente
dichos, por lo tanto, no son iguales que los enteros.
Java permite que las definiciones de los tipos enumerados contengan mucho más que una lista de
valores de tipos:
public enum <NombreEnum>{
ENUM_OPC1(“valor1”),ENUM_OPC2(“valor2”),ENUM_OPC3(“valor3”),ENUM_OPC4(“valor4”);
private String valor;
NombreEnum(String valor){
this.valor = valor;
}
public String getValor(){
return this.valor;
}
}
En la declaración de cada tipo del enum se añade un parámetro.
La definición del tipo incluye un constructor que no tiene la palabra public en su encabezado.
Los constructores de los tipos enumerados nunca son públicos porque no podemos crear instancias de
ellos.
El parámetro asociado a cada valor se pasa mediante el parámetro del constructor.
La definición del tipo incluye un campo, valor.
El constructor almacena la cadena pasada como parámetro en este campo.
Cada tipo enumerado define un método values que devuelve un array que contiene todos los valores
del tipo. NombreEnum.values(.
7
Una instrucción switch selecciona una secuencia de instrucciones para su ejecución a partir de
múltiples opciones diferentes.
6.13.2 Desacoplamiento adicional de la interfaz comandos
Java permite que las definiciones de los tipos enumerados contengan mucho más que una lista de
valores de tipos.
6.14 Directrices de diseño
Antes de empezar a programar debemos plantearnos la longitud que tiene que tener un método y una
clase.
Un método es demasiado largo si hace más de una tarea lógica.
Una clase es demasiado compleja si representa más de una entidad lógica.
El tener estas pautas en mente puede mejorar significativamente su diseño de clases y permitirle
resolver problemas más complejos y escribir programas mejores y más interesantes.
6.15 Ejecutar un programa fuera de BlueJ
Para ejecutar un programa sin el entorno BlueJ necesitamos una cosa más: los métodos de clase que en
Java se conocen también como métodos estáticos.
6.15.1 Métodos de clase
Los métodos de clase que en Java se conocen también como métodos estáticos.
Hasta ahora, todos los métodos que hemos visto han sido métodos de instancia: se invocan sobre una
instancia de una clase.
Lo que distingue a los métodos de clase de los métodos de instancia es que los métodos de clase
pueden ser invocados sin tener una instancia, alcanza con tener la clase.
Los métodos de clase están relacionados conceptualmente y usan una sintaxis relacionada con las
variables de clase (la palabra clave en Java es static).
Así como las variables de clase pertenecen a la clase antes que a una instancia, lo mismo ocurre con
los métodos de clase.
Un método de clase se define agregando la palabra clave static antes del nombre del tipo en la
signatura del método:
public static int getNumeroDeDiasDeEsteMes()
{
}
Estos métodos pueden ser invocados utilizando la notación usual de punto, especificando el nombre de
la clase en que está definido seguido del punto y luego del nombre del método.
Si, por ejemplo, el método anterior está declarado en una clase de nombre Calendario, la siguiente
sentencia lo invoca:
int dias = Calendario.getNumeroDeDiasDeEstemes();
8
6.15.2 El método main
Si queremos iniciar una aplicación Java fuera del entorno BlueJ necesitamos usar un método de clase.
En BlueJ, típicamente creamos un objeto e invocamos uno de sus métodos, pero fuera de este entorno
una aplicación comienza sin que exista ningún objeto.
Las clases son las únicas cosas que tenemos inicialmente, por lo que el primer método que será
invocado debe ser un método de clase.
La definición de Java para iniciar aplicaciones es bastante simple: el usuario especifica la clase que
será iniciada y el sistema Java luego invocará un método denominado main ubicado dentro de dicha
clase.
Este método debe tener una signatura específica. Si no existe tal método en esa clase se informa un
error.
En el Apéndice E se describen los detalles de este método y los comandos necesarios para iniciar el
sistema Java fuera del entorno BlueJ.
6.15.3 Limitaciones de los métodos de clase
Dado que los métodos de clase están asociados con una clase antes que con una instancia, tienen dos
limitaciones importantes.
La primera limitación es que un método de clase no podrá acceder a ningún campo de instancia
definido en la clase.
Esto es lógico ya que los campos de instancia están asociados con objetos individuales.
En cambio, los métodos de clase tienen el acceso restringido a las variables de clase de sus propias
clases.
La segunda limitación es como la primera: un método de clase no puede invocar a un método de
instancia de la clase.
Un método de clase sólo puede invocar a otros métodos de clase definidos en su propia clase.
9
6 Diseño de Clases .............................................................................................................................. 2 6.1 Introducción ............................................................................................................................... 2 6.3 Introducción al acoplamiento y a la cohesión............................................................................ 2 6.4 Duplicación de código ............................................................................................................... 3 6.5 Cómo hacer ampliaciones .......................................................................................................... 3 6.6 Acoplamiento ............................................................................................................................. 3 6.6.1 Utilización de la encapsulación para reducir el acoplamiento ........................................... 3 6.7 Diseño dirigido por responsabilidad .......................................................................................... 4 6.8 Localidad de cambios ................................................................................................................ 4 6.9 Acoplamiento implícito ............................................................................................................. 4 6.10 Planificación por adelantado...................................................................................................... 5 6.11 Cohesión .................................................................................................................................... 5 6.11.1 Cohesión de métodos.......................................................................................................... 5 6.11.2 Cohesión de clases.............................................................................................................. 5 6.11.3 Cohesión para la legibilidad ............................................................................................... 5 6.11.4 Cohesión para la reusabilidad............................................................................................. 5 6.12 Refactorización .......................................................................................................................... 5 6.12.1 Refactorización y prueba .................................................................................................... 6 6.13 Refactorización para la independencia respecto del idioma ...................................................... 7 6.13.1 Tipos Enumerados .............................................................................................................. 7 6.13.2 Desacoplamiento adicional de la interfaz comandos ......................................................... 8 6.14 Directrices de diseño.................................................................................................................. 8 6.15 Ejecutar un programa fuera de BlueJ......................................................................................... 8 6.15.1 Métodos de clase ................................................................................................................ 8 6.15.2 El método main ................................................................................................................. 9 6.15.3 Limitaciones de los métodos de clase ................................................................................ 9 10
Capítulo
7
Objetos
conunbuen
Comportamiento
Centro Asociado Palma de Mallorca
Tutor: Antonio Rivero Cuesta
1
7 Objetosconunbuencomportamiento
7.1 Introducción
Los problemas que se presentan al escribir un programa cambiarán a lo largo del tiempo.
Los errores de sintácticos son errores en la estructura del código fuente.
Son fáciles de solucionar porque el compilador los señala y muestra algún mensaje de error.
Los programadores más experimentados, que se enfrentan con problemas más complicados,
generalmente tienen menos dificultad con la sintaxis del lenguaje y se concentran más en los errores
lógicos.
Un error lógico ocurre cuando el programa compila y se ejecuta sin errores obvios pero da resultados
incorrectos. Los problemas de lógicos son mucho más severos y difíciles de encontrar que los errores
de sintaxis.
Es esencial para un ingeniero de software competente aprender la forma de manejar la exactitud y los
caminos para reducir el número de errores existentes en una clase.
Las pruebas son la actividad cuyo objetivo es determinar si un fragmento de código, un método, una
clase o un programa, produce el comportamiento deseado.
La depuración es el intento de localizar y corregir el origen de un error.
La depuración viene a continuación de las pruebas.
Si las pruebas demostraron que se presentó un error, usamos técnicas de depuración para encontrar
exactamente dónde está ese error y corregirlo.
Puede haber una cantidad significativa de trabajo entre saber que existe un error y encontrar su causa y
solucionarlo.
La escritura de programas para su mantenibilidad es tal vez el tema más fundamental.
Se trata de escribir código de tal manera que, en primer término, se eviten los errores, y si aun así
aparecen, puedan ser encontrados lo más fácilmente posible.
Esto está fuertemente relacionado con el estilo de código y los comentarios, como también lo son los
principios de calidad del código que comentamos en temas anteriores.
El código debería ser fácil de entender, para que el programador original evite introducir errores y el
programador de mantenimiento pueda localizar los posibles errores fácilmente.
7.2 Pruebas y depuración
La prueba y la depuración son habilidades cruciales en el desarrollo de software.
Frecuentemente necesitará controlar sus programas para ver si tienen errores y luego, cuando ocurran,
localizarlos en el código.
7.3 Pruebas de unidades dentro de Bluej
El término prueba de unidades se refiere a la prueba de partes individuales de una aplicación en
contraposición con el término prueba de aplicación que es la prueba de una aplicación en su
totalidad.
Las unidades que se prueban pueden ser de tamaños diversos:
 Puede ser un grupo de clases
 Una sola clase
 Un método.
2
Debemos observar que la prueba de unidad puede tener lugar mucho antes de que una aplicación esté
completa.
Puede probarse cualquier método, una vez que esté escrito y compilado
La experimentación y prueba temprana conlleva varios beneficios:
 En primer lugar, nos dan una experiencia valiosa con un sistema que hace posible localizar
problemas tempranamente para corregirlos, a un costo mucho menor que si se hubieran
encontrado en una etapa más avanzada del desarrollo.
 En segundo término, podemos comenzar por construir una serie de casos de prueba y resultados
que pueden usarse una y otra vez a medida que el sistema crece. Cada vez que hacemos un
cambio en un sistema, estas pruebas nos permiten controlar que no hayamos introducido
errores inadvertidamente en el resto del sistema como resultado de las modificaciones.
7.3.1
Utilización de inspectores
A la hora de probar interactivamente, la utilización de inspectores de objetos suele ser muy útil.
Un componente esencial de la prueba de clases que usan estructuras de datos, es controlar que se
comporten adecuadamente tanto cuando las estructuras están vacías como cuando están llenas.
Una característica clave de una buena prueba consiste en asegurarse de controlar los límites dado que
son, con gran frecuencia, los lugares en los que las cosas funcionan mal.
7.3.2
Pruebas positivas y pruebas negativas
En una aplicación, cuando tenemos que decidir qué parte probar, generalmente distinguimos los casos
de pruebas positivas de los casos de pruebas negativas.
Una prueba positiva es la prueba de aquellos casos que esperamos que resulten exitosos.
Cuando probamos con casos positivos nos tenemos que convencer de que el código realmente funciona
como esperábamos.
Una prueba negativa es la prueba de aquellos casos que esperamos que fallen.
Cuando probamos con casos negativos esperamos que el programa maneje este error de cierta manera
especificada y controlada.
7.4 Automatización de Pruebas
Existen técnicas disponibles que nos permiten automatizar las pruebas repetitivas y así eliminar el
trabajo pesado asociado que traen aparejadas.
7.4.1
Prueba de regresión
Cuando se soluciona un error en un lugar determinado se puede, al mismo tiempo, introducir un nuevo
error.
Es deseable ejecutar pruebas de regresión cada vez que se realiza una modificación en el software.
Las pruebas de regresión consisten en ejecutar nuevamente las pruebas pasadas previamente para
asegurarse de que la nueva versión aún las supera.
Probablemente, estas pruebas son mucho más realizables cuando se las puede automatizar de alguna
manera.
Una de las formas más fáciles de automatizar las pruebas de regresión es escribir un programa que
actúa como un marco de pruebas o una batería de pruebas.
3
7.4.2
Pruebas automatizadas mediante JUnit
Las clases de prueba son una característica de BlueJ y están diseñadas para implementar pruebas de
regresión.
Se basan en el marco de trabajo para pruebas JUnit.
Utilizando las clases de prueba podemos automatizar las pruebas de regresión.
La clase de prueba contiene el código para llevar a cabo una serie de pruebas preparadas y comprobar
sus resultados.
Esto hace que repetir las mismas pruebas sea mucho más sencillo.
Las clases de prueba, en cierto sentido, son claramente diferentes de las clases ordinarias.
El código de la clase de prueba puede ser escrito por una persona, pero también puede ser generado
automáticamente por BlueJ.
Cada clase de prueba suele contener pruebas para verificar la funcionalidad de su clase de referencia.
La clase de prueba contiene tanto código fuente para ejecutar pruebas sobre una clase de referencia,
como para comprobar si las pruebas han tenido éxito o no.
7.4.3
Grabación de una prueba
BlueJ posibilita combinar la efectividad de las pruebas manuales con el poder de las pruebas
automatizadas habilitándonos para grabar las pruebas manuales y luego ejecutarlas, con el fin de
aplicar pruebas de regresión.
Una aserción es una expresión que establece una condición que esperamos que resulte verdadera. Si la
condición es falsa, decimos que falló esta aserción. Esto indica que hay un error en nuestro programa.
7.4.4
Bancos de pruebas
Un banco de pruebas es un conjunto de objetos con un estado definido que sirve como base para las
pruebas de unidades.
Un objeto o grupo de objetos que se usa en una o más pruebas se conoce como banco de pruebas o
fixture.
En BlueJ se usa Test Fixture to Object Bench, para crear automáticamente los objetos de prueba a
partir del código o Object Bench lo Test Fixture, para añadir al código los objetos de prueba.
Una vez que hemos asociado un banco de pruebas con una clase de prueba, grabar las pruebas resulta
bastante sencillo.
Cada vez que creemos un nuevo método de prueba, los objetos del banco de pruebas aparecerán
automáticamente en el banco de objetos, ya no habrá necesidad de crear nuevos objetos de prueba
manualmente cada vez.
La automatización de pruebas es un concepto poderoso porque hace más probable que las pruebas se
escriban en primer lugar y más probable que se ejecuten y reejecuten a medida que el programa se
desarrolle.
Podría formarse el hábito de comenzar por escribir pruebas de unidad tempranamente en el desarrollo
de un proyecto y mantenerlas actualizadas a medida que el proyecto avance.
4
7 Objetos con un buen comportamiento .............................................................................................. 2 7.1 Introducción ............................................................................................................................... 2 7.2 Pruebas y depuración ................................................................................................................. 2 7.3 Pruebas de unidades dentro de Bluej ......................................................................................... 2 7.3.1 Utilización de inspectores .................................................................................................. 3 7.3.2 Pruebas positivas y pruebas negativas................................................................................ 3 7.4 Automatización de Pruebas ....................................................................................................... 3 7.4.1 Prueba de regresión ............................................................................................................ 3 7.4.2 Pruebas automatizadas mediante JUnit .............................................................................. 4 7.4.3 Grabación de una prueba .................................................................................................... 4 7.4.4 Bancos de pruebas .............................................................................................................. 4 5
Capítulo
8
Mejoradela
EstructuraMediante
Herencia
Centro Asociado Palma de Mallorca
Tutor: Antonio Rivero Cuesta
1
8 Mejoradelaestructuramedianteherencia
Los conceptos principales que usaremos para diseñar programas mejor estructurados son herencia y
polimorfismo.
Ambos conceptos son fundamentales en orientación a objetos y aparecen de distintas formas.
8.2 Utilización de la Herencia
La herencia nos permite definir una clase como una ampliación de otra.
Una superclase o clase padre, es una clase que es extendida por otra clase, o que otras clases heredan.
Una subclase, clase hija, es una clase que extiende o amplia a otra clase. Hereda todos los campos y
los métodos de la superclase.
La herencia es un mecanismo que nos ofrece una solución al problema de duplicación de código.
La característica esencial de esta técnica es que necesitamos describir las características comunes sólo
una vez.
La herencia también se denomina relación «es-un».
La razón de esta nomenclatura radica en que la subclase es una especialización de la superclase.
La herencia nos permite crear dos clases que son bastante similares evitando la necesidad de escribir
dos veces la parte que es idéntica.
8.3 Jerarquías de herencia
Más de una subclase puede heredar de la misma superclase y una subclase puede convertirse en la
superclase de otras subclases.
En consecuencia, las clases forman una jerarquía de herencia.
Las clases que están vinculadas mediante una relación de herencia forman una jerarquía de herencia.
La herencia es una técnica de abstracción que nos permite categorizar las clases de objetos bajo cierto
criterio y nos ayuda a especificar las características de estas clases.
8.4 Herencia en Java
La palabra clave extends define la relación de herencia.
La frase «extends <SuperClase>» especifica que esta clase es una subclase de la clase
SuperClase.
public class <Nombre SubClase> extends <Nombre SuperClase>
{…}
La subclase define sólo aquellos campos que son únicos para los objetos de su tipo.
Los campos de la superclase se heredan y no necesitan ser incluidos en el código de la subclase.
8.4.1
Herencia y derechos de acceso
Los miembros definidos como públicos, ya sea en la superclase o en la subclase, serán accesibles para
los objetos de otras clases, pero los miembros definidos como privados serán inaccesibles.
La regla de privacidad también se aplica entre una subclase y su superclase, una subclase no puede
acceder a los miembros privados de su superclase.
Si un método de una subclase necesita acceder o modificar campos privados de su superclase, entonces
la superclase necesitará ofrecer los métodos de selectores y/o mutadores apropiados.
2
Una subclase puede invocar a cualquier método público de su superclase como si fuera propio, no se
necesita ninguna variable.
8.4.2
Herencia e inicialización
Cuando creamos un objeto, el constructor de dicho objeto se encarga de inicializar todos los campos
del objeto en algún estado razonable.
En primer lugar, la superclase tendrá un constructor aun cuando no tengamos intención de crear, de
manera directa, una instancia de la superclase.
Este constructor recibe los parámetros necesarios para inicializar los campos de una instancia y
contiene el código para llevar a cabo esta inicialización.
El constructor de la subclase recibe los parámetros necesarios para inicializar tanto los campos propios
como los de la superclase.
El constructor de la subclase contendrá el siguiente código:
super(<lista de parámetros>);
La palabra clave super es, en realidad, una llamada al constructor de la superclase.
El efecto de esta llamada es que se ejecuta el constructor de la supercase, formando parte de la
ejecución del constructor de la subclase.
Para que esta operación funcione, los parámetros necesarios para la inicialización de los campos de la
superclase se pasan a su constructor como parámetros en la llamada a super.
Constructor de superclase. El constructor de una subclase debe tener siempre como primera
sentencia una invocación al constructor de su superclase. Si el código no incluye esta llamada, Java
intentará insertarla automáticamente. La inserción automática de la llamada a la superclase sólo
funciona si la superclase tiene un constructor sin parámetros; en el caso contrario, Java informa un
error.
En general, es una buena idea la de incluir siempre en los constructores llamadas explícitas a la
superclase, aun cuando sea una llamada que el compilador puede generar automáticamente.
Consideramos que esta inclusión forma parte de un buen estilo de programación, ya que evita la
posibilidad de una mala interpretación y de confusión en el caso de que un lector no esté advertido de
la generación automática de código.
La herencia nos permite reutilizar en un nuevo contexto clases que fueron escritas previamente.
El efecto de la reutilización es que se necesita una cantidad menor de código nuevo cuando
introducimos elementos adicionales.
Las clases que no se piensan usar para crear instancias, pero cuyo propósito es exclusivamente servir
como superclases de otras clases se denominan clases abstractas.
3
8.6 Ventajas de la herencia (hasta ahora)
 Evita la duplicación de código. El uso de la herencia evita la necesidad de escribir copias de
código idénticas o muy similares dos veces (o con frecuencia, aún más veces).
 Reutilización de código. El código que ya existe puede ser reutilizado. Si ya existe una clase
similar a la que necesitamos, a veces podemos crear una subclase a partir de esa clase existente
y reutilizar un poco de su código en lugar de implementar todo nuevamente.
 Facilita el mantenimiento. El mantenimiento de la aplicación se facilita pues la relación entre
las clases está claramente expresada. Un cambio en un campo o en un método compartido entre
diferentes tipos de subclases se realiza una sola vez.
 Ampliabilidad. El uso de la herencia hace mucho más fácil la extensión de una aplicación.
8.7 Subtipos
Por analogía con la jerarquía de clases, los tipos forman una jerarquía de tipos. El tipo que se define
mediante la definición de una subclase es un subtipo del tipo de su superclase.
Hasta ahora, hemos interpretado el requerimiento de que los tipos de los parámetros “deben coincidir”
como equivalente a decir que “deben ser del mismo tipo”: por ejemplo, que el nombre del tipo de un
parámetro actual debe ser el mismo que el nombre del tipo del correspondiente parámetro formal.
En realidad, esta es sólo una parte de la verdad porque los objetos de las subclases pueden usarse en
cualquier lugar que se requiera el tipo de su superclase.
8.7.1
Subclases y subtipos
Las clases definen tipos. Las clases pueden tener subclases, por lo tanto, los tipos definidos por las
clases pueden tener subtipos.
8.7.2
Subtipos y asignaciones
Variables y subtipos. Las variables pueden contener objetos del tipo declarado o de cualquier subtipo
del tipo declarado.
Una variable puede contener objetos del tipo declarado o de cualquier subtipo del tipo declarado.
El tipo de una variable declara qué es lo que puede almacenar.
La declaración de una variable de tipo <superclase> determina que esta variable puede referenciar
instancias de tipo superclase. Pero como una <subclase> también tiene el subtipo <superclase>, es
perfectamente legal almacenar una <subclase> en una variable que está pensada para almacenar
<superclase>.
Este principio se conoce como sustitución, Se pueden usar objetos de subtipos en cualquier lugar en el
que se espera un objeto de un supertipo, ya que el objeto de la subclase es un caso especial de la
superclase.
Sin embargo hay que tener en cuenta que a una variable de tipo <subclase> no se le puede asignar un
tipo <superclase>, ni una <subclase> diferente.
8.7.3
Subtipos y paso de parámetros
El paso de parámetros, es decir, asignar un parámetro real a un parámetro formal se comporta
exactamente de la misma manera que la asignación ordinaria a una variable.
Este es el motivo por el que podemos pasar un objeto de tipo <subclase> al método que tiene un
parámetro de tipo <superclase>.
4
8.7.4
Variables polimórficas
En Java, las variables que contienen objetos son variables polimórficas.
El término polimórfico se refiere al hecho de que una misma variable puede contener objetos de
diferentes tipos del tipo declarado o de cualquier subtipo del tipo declarado.
El polimorfismo aparece en los lenguajes orientados a objetos en numerosos contextos, las variables
polimórficas constituyen justamente un primer ejemplo.
La herencia evita la duplicación de código no sólo en las clases servidoras sino también en las clases
clientes de aquellas.
8.7.5
Casting o proyección de tipos
Algunas veces, la regla de que no puede asignarse un supertipo a un subtipo es más restrictiva de lo
necesario:
Vehicle v;
Car c = new Car();
v = c; / / es correcta
c = v; / / es un error
Las instrucciones anteriores no compilarán.
Obtendremos un error de compilación en la última línea porque no está permitida la asignación de una
variable Vehicle en una variable Car.
Sin embargo, si recorremos estas sentencias secuencialmente, sabemos que esta asignación podría
realmente permitirse.
Podemos ver que la variable v en realidad contiene un objeto de tipo Car, de modo que su asignación a
la variable c debiera ser correcta.
El compilador no es tan inteligente, traduce el código línea por línea, de modo que analiza la última
línea aislada de las restantes, sin saber qué es lo que realmente se almacena en la variable v.
Este problema se denomina pérdida de tipo.
El tipo del objeto v realmente es un Coche, pero el compilador no lo sabe.
Podemos resolver este problema diciendo explícitamente al sistema, que la variable v contiene un
objeto Car, y lo hacemos utilizando el operador de enmascaramiento de tipos:
c = (Car) v; / / correcto
El operador de cast consiste en el nombre de un tipo (en este caso, Car) escrito entre paréntesis, que
precede a una variable o a una expresión.
Al usar esta operación, el compilador creerá que el objeto es un Car y no informará ningún error.
Sin embargo, en tiempo de ejecución, el sistema Java verificará si realmente es un Car.
Si fuimos cuidadosos, todo estará bien; si el objeto almacenado en v es de otro tipo, el sistema indicará
un error en tiempo de ejecución (denominado ClassCastException) y el programa se detendrá.
El casting solo se puede hacer con variables que constituyen una relación subtipo/supertipo, sino el
compilador produce un error.
El casting debiera evitarse siempre que sea posible, porque puede llevar a errores en tiempo de
ejecución y esto es algo que claramente no queremos.
El compilador no puede ayudarnos a asegurar la corrección de este caso.
En la práctica, raramente se necesita del casting en un programa orientado a objetos bien estructurado.
5
En la mayoría de los casos, cuando se use un casting en el código, debiera reestructurarse el código
para evitar el enmascaramiento, y se terminará con un programa mejor diseñado.
8.8 La clase Object
Todas aquellas clases que no tienen una superclase explícita tienen como su superclase a la clase
Object.
Object es una clase de la biblioteca estándar de Java que sirve como superclase para todos los objetos.
El compilador de Java inserta automáticamente la superclase Object en todas las clases que no tengan
una declaración explícita extends por lo que jamás es necesario hacer esto manualmente.
Todas las clases (con la única excepción de la clase Object en sí misma) heredan de Object, ya sea
directa o indirectamente.
El que todos los objetos tengan una superclase en común tiene dos propósitos.
Primero, podemos declarar variables polimórficas de tipo Object que pueden contener cualquier
objeto.
En segundo lugar, la clase Object puede definir algunos métodos que están automáticamente
disponibles para cada objeto existente.
8.9 Autoboxing y clases envoltorio
Los tipos primitivos tales como int, boolean y char están separados de los tipos objeto.
Sus valores no son instancias de clases y no derivan de la clase Object.
Debido a esto, no son suptipos de Object y normalmente, no es posible ubicarlos dentro de una
colección.
Este es un inconveniente pues existen situaciones en las que quisiéramos crear, por ejemplo, una lista
de valores int o un conjunto de valores char. ¿Qué podemos hacer?
La solución de Java para este problema son las clases envoltorio.
Cada tipo simple o primitivo tiene su correspondiente clase envoltorio que representa el mismo tipo
pero que, en realidad, es un tipo objeto.
Por ejemplo, la clase envoltorio para el tipo simple int es la clase de nombre Integer.
El almacenamiento de valores primitivos en un objeto colección se lleva a cabo aún más fácilmente
mediante una característica del compilador conocida como autoboxing.
En cualquier lugar en el que se use un valor de un tipo primitivo en un contexto que requiere un tipo
objeto, el compilador automáticamente envuelve al valor de tipo primitivo en un objeto con el
envoltorio adecuado.
Esto quiere decir que los valores de tipos primitivos se pueden agregar directamente a una colección.
La operación inversa, unboxing, también se lleva a cabo automáticamente, es decir asignar a una
variable primitiva un objeto de la clase envoltorio correspondiente.
El proceso de autoboxing se aplica en cualquier lugar en el que se pase como parámetro un tipo
primitivo a un método que espera un tipo envoltorio, y cuando un valor primitivo se almacena en una
variable de su correspondiente tipo envoltorio.
De manera similar, el proceso de unboxing se aplica cuando un valor de tipo envoltorio se pasa como
parámetro a un método que espera un valor de tipo primitivo, y cuando se almacena en una variable de
tipo primitivo.
6
8 Mejora de la estructura mediante herencia ....................................................................................... 2 8.2 Utilización de la Herencia ......................................................................................................... 2 8.3 Jerarquías de herencia ................................................................................................................ 2 8.4 Herencia en Java ........................................................................................................................ 2 8.4.1 Herencia y derechos de acceso ........................................................................................... 2 8.4.2 Herencia e inicialización .................................................................................................... 3 8.6 Ventajas de la herencia (hasta ahora) ........................................................................................ 4 8.7 Subtipos ..................................................................................................................................... 4 8.7.1 Subclases y subtipos ........................................................................................................... 4 8.7.2 Subtipos y asignaciones...................................................................................................... 4 8.7.3 Subtipos y paso de parámetros ........................................................................................... 4 8.7.4 Variables polimórficas ....................................................................................................... 5 8.7.5 Casting o proyección de tipos ............................................................................................ 5 8.8 La clase Object ....................................................................................................................... 6 8.9 Autoboxing y clases envoltorio ................................................................................................. 6 7
Capítulo
9
Mássobre
laHerencia
Centro Asociado Palma de Mallorca
Tutor: Antonio Rivero Cuesta
1
9 Mássobreherencia
9.2 Tipo estático y tipo dinámico
Denominamos tipo estático al tipo declarado de una variable en el código fuente, la representación
estática del programa.
Denominamos tipo dinámico al tipo del objeto almacenado en una variable porque depende de su
asignación en tiempo de ejecución, el comportamiento dinámico del programa.
Si con una variable invocamos un método que el tipo dinámico tiene, pero el estático no, el compilador
informa de un error porque cuando controla los tipos usa el tipo estático.
El tipo dinámico se conoce, frecuentemente, sólo en tiempo de ejecución por lo que el compilador no
tiene otra opción más que usar el tipo estático cuando quiere hacer alguna verificación de tipos en
tiempo de compilación.
9.3 Sustitución de métodos
Sobreescritura (redefinición). Una subclase puede sobrescribir la implementación de un método.
Para hacerlo la subclase declara un método con la misma signatura que la superclase pero con un
cuerpo diferente.
El método que sobrescribe tiene precedencia cuando se invoca sobre objetos de la subclase.
9.4 Búsqueda dinámica del método
El control de tipos que realiza el compilador es sobre el tipo estático, pero en tiempo de ejecución los
métodos que se ejecutan son los que corresponden al tipo dinámico.
Veamos con más detalle cómo se invocan los métodos.
Este procedimiento se conoce como búsqueda de métodos, asociación de métodos o despacho de
métodos.
Suponga que tenemos un objeto de clase Photopost almacenado en una variable v1 cuyo tipo
declarado es Photopost. La clase Photopost tiene un método display y no tiene declarada
ninguna superclase.
Esta es una situación muy simple que no involucra herencia ni polimorfismo.
Luego, ejecutamos la instrucción:
v1. display();
El método display se invoca siguiendo los siguientes pasos:
1. Se accede a la variable v1.
2. Se localiza el objeto almacenado en esa variable (siguiendo la referencia).
3. Se encuentra la clase del objeto (siguiendo la referencia "instancia de").
4. Se localiza la implementación del método imprimir en la clase y se ejecuta.
A continuación, vemos la búsqueda de un método cuando hay herencia.
El escenario es similar al anterior, pero esta vez la clase Photopost tiene una superclase, Post, y el
método display está definido sólo en la superclase.
Ejecutamos la misma instrucción.
2
La invocación al método comienza de manera similar: se ejecutan nuevamente los pasos l al 3 del
escenario anterior pero luego continúa de manera diferente:
4. No se encuentra ningún método imprimir en la clase display en la clase Photopost.
5. Puesto que no se encontró ningún método adecuado, se busca en la superclase un método que
coincida. Si no se encuentra ningún método en la superclase, se busca en la siguiente
superclase (si es que existe). Esta búsqueda continúa hacia arriba por toda la jerarquía de
herencia de la clase hasta Object o hasta que se encuentre definitivamente un método. Hay que
tener en cuenta que, en tiempo de ejecución, debe encontrarse definitivamente un método que
coincida, de lo contrario la clase no compila.
6. En nuestro ejemplo, el método display es encontrado en la clase Post y se ejecuta.
Este escenario ilustra la manera en que los objetos heredan los métodos.
Cualquier método que se encuentre en la superclase puede ser invocado sobre un objeto de la subclase
y será correctamente encontrado y ejecutado.
Ahora llegamos al escenario más interesante: la búsqueda de métodos con una variable polimórfica y
un método sobrescrito. El escenario nuevamente es similar al anterior pero existen dos cambios:
 El tipo declarado de la variable v1 ahora es Post, no Photopost.
 El método display está definido en la clase Post y redefinido (o sobrescrito) en la clase
Photopost.
Los pasos que se siguen para la ejecución del método son exactamente los mismos pasos 1 al 4 del
primer escenario.
Es importante hacer algunas observaciones:
 No se usa ninguna regla especial para la búsqueda del método en los casos en los que el tipo
dinámico no sea igual al tipo estático. El comportamiento que observamos es un resultado de
las reglas generales.
 El método que se encuentra primero y que se ejecuta está determinado por el tipo dinámico, no
por el tipo estático. En otras palabras, el hecho de que el tipo declarado de la variable v1 ahora
es Post no tiene ningún efecto. La instancia con la que estamos trabajando es de la clase
Photopost, y esto es todo lo que cuenta.
 Los métodos sobrescritos en las subclases tienen precedencia sobre los métodos de las
superclases. Dado que la búsqueda de método comienza en la clase dinámica de la instancia (al
final de la jerarquía de herencia) la última redefinición de un método es la que se encuentra
primero y la que se ejecuta.
 Cuando un método está sobrescrito, sólo se ejecuta la última versión (la más baja en la
jerarquía de herencia). Las versiones del mismo método en cualquier superclase no se ejecutan
automáticamente.
9.5 Llamada a super en métodos
Para llamar al método sobrescrito de la superclase desde la subclase escribiremos:
public void <NombreMetodo>(){
super.<NombreMetodo>();
…}
3
Hay tres detalles importantes:
 Al contrario que las llamadas a super en los constructores, el nombre del método de la
superclase está explícitamente establecido. Una llamada a super en un método siempre tiene la
forma:
super.<nombre-del-método>(<parámetros>)
 Nuevamente, y en contra de la regla de las llamadas a super en los constructores, la llamada a
super en los métodos puede ocurrir en cualquier lugar dentro de dicho método. No tiene por
qué ocurrir en la primera instrucción.
 Al contrario que en las llamadas a super en los constructores, no se genera automáticamente
ninguna llamada a super y tampoco se requiere ninguna llamada a super, es completamente
opcional. De modo que el comportamiento por defecto presenta el efecto de un método de una
subclase ocultando completamente (sobrescribiendo) la versión de la superclase del mismo
método contenida en la superclase.
En ausencia del mecanismo de sustitución de métodos, los miembros no privados de una superclase
son directamente accesibles desde sus subclases sin necesidad de ninguna sintaxis especial.
Solo es necesario hacer una llamada super cuando haga falta acceder a la versión existente en la
superclase de un método sustituido.
9.6 Polimorfismo de métodos
Método polimórfico. Las llamadas a métodos en Java son polimórficas. El mismo método puede
invocar en diferentes momentos diferentes métodos dependiendo del tipo dinámico de la variable
usada para hacer la invocación.
9.7 Métodos de Object: toString
La superclase universal Object implementa algunos métodos que, por tanto, forman parte de todos los
objetos. El más interesante de estos métodos es toString.
El propósito del método toString es crear una representación de un objeto en forma de cadena de
caracteres.
Esto es útil para cualquier objeto que pueda ser representado textualmente en la interfaz de usuario
pero también es de ayuda para todos los otros objetos; por ejemplo, los objetos pueden ser fácilmente
impresos con fines de depuración de un programa.
En Object el valor de retorno de toString muestra simplemente el nombre de la clase del objeto y
un número que representa la dirección de memoria donde el objeto está almacenado
Todo objeto en Java tiene un método toString que puede usarse para devolver un String de su
representación. Normalmente, para que este método resulte útil los objetos deben sustituirlo por una
implementación propia.
Los métodos System.out.print y System.out.println son especiales con respecto a esto:
si el parámetro de uno de estos métodos no es un objeto String, el método invoca automáticamente
al método toString de dicho objeto.
4
9.8 Igualdad entre objetos: equals y hashCode
La clase Object define dos métodos, equals y hashCode, que están estrechamente relacionados
con la tarea de determinar la similitud de objetos.
En ocasiones, lo que deseamos saber es si dos variables diferentes están haciendo referencia al mismo
objeto.
Esto es exactamente lo que sucede cuando se pasa una variable de objeto como parámetro de un
método, sólo hay un único objeto, pero tanto la variable original como la variable del parámetro hacen
referencia a él.
Lo mismo sucede cuando se asigna una variable de objeto a otra.
Estas situaciones dan lugar a lo que se conoce con el nombre de igualdad de referencias.
La igualdad de referencia se comprueba utilizando el operador ==.
La igualdad de referencia no tiene en cuenta en absoluto el contenido de los objetos a los que hace
referencia, sino que se limita a comprobar si hay un único objeto al que están haciendo referencia dos
variables distintas o dos objetos distintos.
Definimos a continuación igualdad de contenidos.
Lo que una comprobación de la igualdad de contenidos pregunta es si dos objetos son iguales
internamente, es decir, si los estados internos de los dos objetos coinciden.
La forma de comprobar la igualdad de contenidos entre dos objetos consiste en verificar si los valores
de sus dos conjuntos de campos son iguales.
Como el parámetro del método equals es de tipo Object, esa comprobación solo tiene sentido si
estamos comprobando campos del mismo tipo.
9.9 Acceso protegido
Los lenguajes orientados a objetos frecuentemente definen un nivel de acceso que está a medias entre
la restricción completa del acceso privado y la total disponibilidad del acceso público.
En Java este nivel de acceso se denomina acceso protegido.
En Java este nivel de acceso se denomina acceso protegido y es provisto por la palabra clave
protected como alternativa entre public y private.
La declaración de un campo o un método como protegido permite su acceso directo desde las
subclases directas o indirectas.
El acceso protegido permite acceder a los campos o a los métodos dentro de una misma clase y desde
todas las subclases, pero no desde las clases restantes.
Aunque el acceso protegido puede aplicarse a cualquier miembro de una clase, suele reservarse para
los métodos y los constructores.
No es frecuente aplicarlo a los campos porque debilitaría la encapsulación.
Siempre que sea posible, los campos mutables de las superclases deberían permanecer privados.
Sin embargo, existen casos válidos ocasionales en los que es deseable el acceso directo desde una
subclase.
La herencia representa una forma mucho más cerrada de acoplamiento que una relación normal de
cliente.
La herencia vincula las clases de manera muy cercana y la modificación de la superclase puede romper
fácilmente la subclase. Este punto debiera tenerse en consideración cuando se diseñan las clases y sus
relaciones.
5
9.10 El operador instanceof
Hay ocasiones en las que necesitamos averiguar el tipo dinámico de un objeto, en lugar de limitarnos a
tratar con un supertipo compartido.
Para estos casos Java proporciona el operador instanceof.
Este operador comprueba si un objeto determinado, es directa o indirectamente, una instancia de una
determinada clase.
La utilización del operador instanceof suele ir seguida inmediatamente por un cast de la
referencia a objeto, para transformarla en el tipo identificado. de la referencia a objeto, para
transformarla en el tipo identificado.
Modificadores de acceso java: public, private, protected
Visibilidad
Desde la misma clase
Desde cualquier clase del mismo
paquete
Desde una subclase del mismo
paquete
Desde una subclase fuera del
mismo paquete
Desde cualquier clase fuera del
paquete
public
protected
default
private
SI
SI
SI
SI
SI
SI
SI
NO
SI
SI
SI
NO
SI
SI
Herencia
NO
NO
SI
NO
NO
NO
6
9 Más sobre herencia ........................................................................................................................... 2 9.2 Tipo estático y tipo dinámico .................................................................................................... 2 9.3 Sustitución de métodos .............................................................................................................. 2 9.4 Búsqueda dinámica del método ................................................................................................. 2 9.5 Llamada a super en métodos .................................................................................................. 3 9.6 Polimorfismo de métodos .......................................................................................................... 4 9.7 Métodos de Object: toString ............................................................................................ 4 9.8 Igualdad entre objetos: equals y hashCode ........................................................................ 5 9.9 Acceso protegido ....................................................................................................................... 5 9.10 El operador instanceof........................................................................................................ 6 7
Capítulo
12
Tratamiento
deErrores
Centro Asociado Palma de Mallorca
Tutor: Antonio Rivero Cuesta
1
12 TratamientodeErrores
Los errores lógicos de los programas son más difíciles de detectar que los errores sintácticos porque el
compilador no los detecta.
Los errores lógicos surgen por diversos motivos y en algunas situaciones pueden estar encubiertos:
 La solución de un problema puede estar implementada incorrectamente.
 Se puede haber solicitado a un objeto que haga algo que es incapaz de hacer.

Se puede haber usado un objeto de maneras tales que no coinciden con las anticipadas por el
diseñador de la clase, dejando al objeto en un estado inapropiado o inconsistente.
Además, aun cuando un programa se pruebe exhaustivamente puede fallar debido a circunstancias que
están más allá del control del programador.
Veremos cómo anticiparse y responder a las posibles situaciones de error que pueden surgir durante la
ejecución de un programa.
Veremos algunas sugerencias sobre la manera de informar de los errores cuando éstos ocurren.
También veremos una breve introducción sobre los procesos de entrada y salida de texto como una de
las situaciones en la que pueden aparecer fácilmente errores durante el tratamiento de los archivos.
12.2 Programación defensiva
12.2.1 Interacción cliente-servidor
Los implementadores pueden adoptar como mínimo dos puntos de vista posibles al diseñar e
implementar una clase servidor:
 Pueden asumir que los objetos cliente sabrán lo que están haciendo y requerirán servicios sólo
de una manera coherente y bien definida.
 Pueden asumir que el servidor operará en un ambiente esencialmente hostil, en el que se deben
tomar todas las medidas posibles para prevenir que los objetos cliente usen el servidor
incorrectamente.
Estas visiones representan claramente dos extremos opuestos.
En la práctica, la mayoría de las situaciones asumirán posiciones intermedias.
Estos diferentes puntos de vista proporcionan una base muy útil para discutir asuntos del estilo:
 ¿Cuántas comprobaciones de las solicitudes del cliente deben realizar los métodos del servidor?
 ¿Cómo debe informar el servidor, de los errores producidos a sus clientes?
 ¿Cómo puede un cliente anticipar un fallo en una solicitud al servidor?
 ¿Cómo puede tratar un cliente el fallo de una solicitud?
No es posible invocar un método sobre el valor null y el resultado de esta invocación es un error en
tiempo de ejecución.
BlueJ informa esta situación como un NullPointerException y resalta la sentencia que lo
produjo.
2
12.2.2 Validar argumentos
Un objeto servidor es más vulnerable cuando su constructor y sus métodos reciben los valores de los
argumentos a través de sus parámetros.
Los valores que se pasan a un constructor se utilizan para establecer el estado inicial de un objeto.
Los valores que se pasan a un método se usarán para influir sobre el efecto general de la llamada al
método y pueden cambiar el estado del objeto y el resultado que el método devuelve.
Por lo tanto, es vital que un servidor sepa si puede confiar en que los valores de los argumentos son
válidos o si necesita verificar su validez por sí mismo.
12.3 Generación de informes de error de servidor
En lugar de simplemente programar alrededor del problema en el servidor y dejar el problema
localizado allí, es una buena práctica hacer que el servidor realice algún esfuerzo para indicar que ha
surgido un problema, ya sea del propio cliente o de un usuario humano o del programador.
En este sentido, existe la posibilidad de que funcione bien un cliente escrito incorrectamente.
¿Cuál es la mejor manera de que un servidor informe de los problemas cuando éstos ocurren?
No hay una sola respuesta a esta pregunta y generalmente, la respuesta más adecuada dependerá del
contexto particular en el que se use el objeto servidor.
En las siguientes secciones exploraremos un conjunto de opciones para informar errores mediante un
servidor.
12.3.1 Notificación al usuario
La manera más obvia en que un objeto puede tratar de responder cuando detecta algo erróneo es
intentar notificar al usuario de la aplicación de alguna forma.
Las principales opciones son imprimir un mensaje de error usando System.out o System.err o
mostrar una ventana de mensaje de error.
Los dos problemas principales que tiene este abordaje son los siguientes:
 Asumen que la aplicación será usada por un usuario humano que verá el mensaje de error. Hay
muchas aplicaciones que corren de manera completamente independiente de un usuario
humano, en las que un mensaje de error o una ventana de error será completamente pasada por
alto. La computadora en la que se ejecuta la aplicación podría no tener ningún dispositivo
visual conectado para mostrar estos mensajes de error.
 Aun cuando exista un humano que pueda ver el mensaje de error, es raro que dicho usuario esté
en posición de hacer algo con respecto al problema. Imagine a un usuario de un cajero
automático enfrentado a un NullPointerException . Solamente en aquellos casos en los
que la acción directa del usuario conduzca al problema puede estar capacitado para tomar
alguna medida correctiva adecuada.
Los programas que imprimen mensajes de error inapropiados tienden más a confundir al. Por lo tanto,
excepto en un muy limitado conjunto de circunstancias, la notificación al usuario no es, en general,
una solución al problema del informe de errores.
3
12.3.2 Notificación al objeto cliente
Un enfoque radicalmente diferente al que abordamos hasta ahora consiste en que el servidor ofrezca
alguna indicación al objeto cliente de que algo anduvo mal.
Hay dos maneras de hacer esto:
 Un servidor puede usar el valor de retorno de un método para devolver una bandera que indique
si fue exitoso o si ocurrió un fallo en la llamada a dicho método.
 Un servidor puede lanzar una excepción desde el método servidor si algo anda mal. Esto
introduce una nueva característica de Java que se encuentra también en otros lenguajes de
programación.
Ambas técnicas tienen el beneficio de asegurar que el programador del cliente tenga en cuenta que
puede fallar una llamada a un método sobre otro objeto. Sin embargo, sólo la decisión de lanzar una
excepción evita activamente que el programador del cliente ignore las consecuencias del fallo del
método.
Cuando un método servidor ya tenga un tipo de retorno distinto de void, para evitar efectivamente
que se retorne un valor de diagnóstico de tipo boolean, todavía existe alguna forma de indicar que
ha ocurrido un error mediante el tipo de retorno.
Es común que los métodos que retoman referencias a objetos utilicen el valor null para indicar un
fallo o un error.
En los métodos que retornan valores de tipos primitivos, se suele devolver algún valor fuera de los
límites válidos que cumple un rol similar: por ejemplo, el método indexOf de la clase String
devuelve un valor negativo para indicar que falló en encontrar el carácter buscado.
Claramente, este enfoque no se puede usar en aquellos lugares en los que todos los valores del tipo de
retorno ya tienen significados válidos para el cliente.
En tales casos, generalmente será necesario pasar a la técnica alternativa de lanzar una excepción que,
de hecho, ofrece importantes ventajas.
Para ayudar a apreciar estas ventajas, es valioso considerar dos cuestiones asociadas al uso de los
valores de retorno como indicadores de fallo o de error:
 Desafortunadamente, no hay manera de requerir al cliente que controle el valor de retorno en
relación a sus propiedades de diagnóstico. En consecuencia, un cliente podría fácilmente actuar
como si nada hubiera ocurrido y luego terminar con un NullPointerException, o peor todavía,
podría usar el valor de retorno de diagnóstico como si fuera un valor de retorno normal,
creando un error lógico difícil de diagnosticar.
 En algunos casos, podríamos usar el valor de diagnóstico con dos propósitos muy diferentes.
Un propósito es notificar al cliente si su petición fue exitosa o no. El otro es indicar que hubo
algún error en su solicitud, como por ejemplo, que se pasó un valor incorrecto como
argumento. En muchos casos, una solicitud no exitosa no representa un error lógico de
programación sino que se hizo una solicitud incorrecta. Debemos esperar respuestas muy
diferentes de un cliente en estos dos casos. No existe una manera satisfactoria y general de
resolver este conflicto usando simplemente valores de retorno.
Otro problema es que pasa con los errores en los constructores.
4
12.4 Principios del lanzamiento de excepciones
El lanzamiento de una excepción es la manera más efectiva que tiene un objeto servidor para indicar
que es incapaz de completar la solicitud del cliente.
Una de las mayores ventajas que tiene esta técnica es que usa un valor especial de retorno que hace
casi imposible que un cliente ignore el hecho de que se ha lanzado una excepción y continúe
indiferente.
El fracaso del cliente al manejar una excepción dará por resultado que la aplicación termine
inmediatamente.
Además, el mecanismo de la excepción es independiente del valor de retorno de un método y se puede
usar en todos los métodos, sin importar su tipo de retorno.
12.4.1 Lanzar una excepción
Una excepción es un objeto que representa los detalles de un fallo de un programa. Se lanza una
excepción para indicar que ha ocurrido un fallo.
Se lanza una excepción usando una instrucción throw dentro de un método.
El lanzamiento de una excepción tiene dos etapas:
Primero se crea un objeto Excepction utilizando la palabra clave new (en este caso un objeto
IllegalArgumentException) y luego se lanza el objeto Exception usando la palabra clave
throw.
Estas dos etapas se combinan casi invariablemente en una única sentencia:
throw new TipoDeExcepcion (" cadena opcional de diagnóstico");
Cuando se crea un objeto excepción, se puede pasar una cadena de diagnóstico a su constructor.
Esta cadena estará disponible para el receptor de la excepción mediante el método de acceso
getMessage y toString del objeto excepción.
Si esa excepción no se trata, la cadena se muestra también al usuario y esto conduce a la terminación
de programa.
Se puede expandir la documentación de un método para que incluya los detalles de cualquier
excepción que lance mediante la etiqueta @throws del documentador de java (javadoc).
12.4.2 Excepciones comprobadas y no comprobadas
Un objeto excepción es siempre una instancia de una clase de una jerarquía de herencia especial.
Podemos crear nuevos tipos de excepciones creando subclases en esta jerarquía.
Hablando estrictamente, las clases de excepciones siempre son subclases de la clase Throwable que
está definida en el paquete java.lang.
Normalmente se definen y usan las clases de excepciones como subclases de la clase Exception,
también definida en java.lang.
La clase Exception es una de las dos subclases directas de Throwable; la otra es Error.
Las subclases de Error se reservan, generalmente, para los errores en tiempo de ejecución antes que
para los errores sobre los que el programador tiene control.
5
El paquete java.lang define varias clases de excepciones que se ven comúnmente como por
ejemplo:
 NullPointerException
 IndexOutOfBoundsException
 ClassCastException.
Java divide las clases de excepciones en dos categorías:
 Excepciones comprobadas.
 Excepciones no comprobadas.
Todas las subclases de la clase estándar de Java RunTimeException son excepciones no
comprobadas, todas las restantes subclases de Exception son excepciones comprobadas.
La diferencia es ésta:
 Las excepciones comprobadas están pensadas para aquellos casos en los que el cliente debe
esperar que una operación falle (por ejemplo: cuando grabamos en un disco, sabemos que el
disco puede estar lleno). En estos casos, el cliente está obligado a comprobar si la operación fue
exitosa.
 Las excepciones no comprobadas están pensadas para aquellos casos que no deben fallar en
una operación normal; generalmente indican un error en el programa.
Saber qué categoría de excepción conviene lanzar en una circunstancia en particular no es una ciencia
exacta pero podemos ofrecer las siguientes sugerencias:
 Una regla a priori que se puede aplicar es usar excepciones no comprobadas en las situaciones
que podrían producir un fallo del programa, ya que se sospecha de la existencia de un error
lógico en el programa que le impedirá continuar funcionando. Se desprende que las
excepciones comprobadas deben usarse cuando ocurrió un problema pero existe alguna
posibilidad de que el cliente efectúe alguna recuperación. Un problema con esta política es que
asume que el servidor es suficientemente consciente del contexto en el que se está usando
como para ser capaz de determinar si es probable que la recuperación del cliente sea posible.
 Otra regla a priori es usar excepciones no comprobadas en aquellas situaciones que pueden ser
razonablemente evitadas. Por ejemplo, el uso de un índice no válido para acceder a un array es
el resultado de un error lógico de programación que es completamente evitable y el hecho de
que la excepción ArraylndexOutOfBoundsExcepction no es comprobada encaja con
este modelo. Se desprende que las excepciones comprobadas deben usarse para situaciones de
fallos que están bajo el control del programador como por ejemplo, que un disco se llene
cuando se intenta grabar un archivo.
Las reglas formales de Java que gobiernan el uso de las excepciones son significativamente diferentes
para las excepciones comprobadas y para las no comprobadas.
En términos simples, las reglas aseguran que un objeto cliente que llama a un método que puede
disparar una excepción comprobada puede contener tanto código para anticipar la posibilidad de un
problema como código para intentar manejar el problema cuando éste ocurra.
6
12.4.3 El efecto de una excepción
¿Qué ocurre cuando se lanza una excepción?
Hay dos efectos a considerar:
 El efecto sobre el método en que se ha descubierto el problema y que ha lanzado la excepción.
 El efecto sobre aquel que ha invocado el método problemático.
Cuando se lanza una excepción, la ejecución del método que la disparó termina inmediatamente, no
continúa hasta el final del cuerpo del método.
Una consecuencia particular de esto es que no se requiere un método con un tipo de retorno distinto de
void para ejecutar una sentencia return en la ruta en que se lanza una excepción.
Esto es razonable porque el lanzamiento de una excepción es una indicación de la incapacidad del
método disparador para continuar con la ejecución normal, que incluye la imposibilidad de retornar un
resultado válido.
La ausencia de una instrucción de retorno en la ruta de ejecución termina generando una excepción es
aceptable.
En su lugar, el compilador indicará un error si se han escrito sentencias a continuación de la sentencia
throw porque podrían no ejecutarse nunca.
El efecto de una excepción en el sitio del programa que invocó al método es un poco más complejo.
En particular, el efecto completo depende de si se ha escrito o no código para capturar la excepción.
Lo que realmente ocurre a continuación de una excepción depende de si se captura o no.
Si no se captura la excepción, el programa simplemente terminará con la indicación de que se ha
lanzado una Exception sin capturar.
12.4.4 Utilización de excepciones no comprobadas
Las excepciones no comprobadas son un tipo de excepción cuyo uso no requiere ninguna
comprobación por parte del compilador.
Las excepciones no comprobadas son las más fáciles de usar desde el punto de vista del
programador, porque el compilador impone muy pocas reglas para su uso.
Este es el sentido de "no comprobadas": el compilador no aplica ningún control especial sobre el
método en el que se lanza una excepción no comprobada, ni tampoco en el lugar desde donde se
invocó dicho método.
Una clase Exception es no comprobada si es una subclase de la clase RuntimeException
definida en el paquete java.lang.
Hay muy poco para agregar sobre cómo lanzar una excepción no comprobada: simplemente usar una
sentencia throw.
Si seguimos también la convención de que las excepciones no comprobadas deben usarse en aquellas
situaciones en las que esperamos que el resultado sea la terminación del programa, es decir, que no se
va a capturar la excepción, entonces tampoco hay más para discutir sobre lo que debe hacer el método
invocador puesto que no hará nada y dejará que el programa falle.
Sin embargo, si existe la necesidad de capturar una excepción no comprobada, entonces se puede
escribir un manejador de dicha excepción, exactamente de la misma manera que para una excepción
comprobada.
Una excepción no comprobada, que se usa comúnmente es IlegalArgumentException, es lanzada por
un constructor o un método para indicar que los valores de sus argumentos no son los adecuados.
7
Es valioso tener un método que realice una serie de comprobaciones de validez de sus parámetros
antes de proceder con el propósito principal del método.
Esto hace menos probable que un método ejecute parte de sus acciones antes de lanzar una excepción
debida a valores incorrectos en sus argumentos.
Una razón particular para evitar esta situación es que la modificación parcial de un objeto
probablemente lo deje en un estado inconsistente para su futuro uso.
Si una operación falla por alguna razón, idealmente, el objeto deberá quedar en el estado en que estaba
antes de que se intentara realizar la operación.
12.4.5 Como impedir la creación de un objeto
Un uso importante de las excepciones es impedir que se creen objetos cuando no se los puede preparar
con un estado inicial válido.
Generalmente, este será el resultado del paso al constructor de valores de parámetro inapropiados.
El proceso de lanzamiento de una excepción desde un constructor es exactamente el mismo que el
lanzamiento desde un método.
Una excepción que se lanza desde un constructor tiene el mismo efecto sobre el cliente que una
excepción que se lanza desde un método.
En consecuencia, el intento de crear un objeto con parámetros no válidos que provoquen que se lance
una excepción fallará completamente; no dará por resultado que se almacene un valor null en la
variable a la que se trataba de asignar el objeto.
12.5 Manejo de excepciones
Los principios del lanzamiento de excepciones se aplican tanto para las excepciones comprobadas
como para las no comprobadas, pero las reglas particulares de Java indican que el manejo de una
excepción se convierte en un requerimiento sólo en el caso de excepciones comprobadas.
Una clase de excepción comprobada es una subclase de Exception pero no de
RuntimeException.
Existen varias reglas que se deben seguir cuando se usan excepciones comprobadas porque el
compilador obliga a tener controles tanto en los métodos que lanzan una excepción comprobada como
en el invocador de dicho método.
12.5.1 Excepciones comprobadas: la cláusula throws
Las excepciones comprobadas son un tipo de excepción cuyo uso requiere controles adicionales del
compilador. En particular las excepciones comprobadas en Java requieren el uso de cláusulas throws
y de sentencias try.
El primer requerimiento del compilador es que un método que lanza una excepción comprobada debe
declarar que lo hace mediante una cláusula throws añadida a la cabecera del método.
Por ejemplo, un método que lanza una IOException comprobada del paquete java.io debe tener
el siguiente encabezado:
public void grabarEnArchivo (String archivoDestino) throws IOException
Si bien se permite el uso de la cláusula throws para las excepciones no comprobadas, el compilador
no lo requiere.
Recomendamos que se use una cláusula throws solamente para enumerar las excepciones
comprobadas que lanza un método.
8
Es importante distinguir entre la cláusula throws en el encabezado de un método y la etiqueta que se
utiliza en el comentario que precede al método javadoc @throws.
La última es completamente opcional para ambos tipos de excepción.
12.5.2 Anticipando las excepciones: la instrucción try
El segundo requisito es que el invocador de un método que lanza una excepción comprobada debe
proveer un tratamiento para dicha excepción.
Esto generalmente implica escribir una rutina de tratamiento de excepciones bajo la forma de una
instrucción try.
Esta sentencia introduce dos nuevas palabras clave de Java, try y catch, que marcan un bloque try
y un bloque catch respectivamente.
try {
Aquí se protege una o más instrucciones.
}
catch (Exception e) {
Aquí se informa y se recupera de la excepción
}
El código de un programa que protege las instrucciones en las que podrían lanzar una excepción se
denomina rutina de tratamiento de excepción. El código proporciona información y/o código para
recuperarse del error.
En un bloque try puede incluirse cualquier número de excepciones, colocaremos allí no solo la
instrucción que puede fallar sino también todas aquellas otras instrucciones que estén relacionadas con
ella de alguna manera.
El bloque try representa una secuencia de acciones que queremos tratar como una sola unidad lógica,
reconociendo que puede fallar en algún punto.
El bloque catch intentará entonces tratar con la situación que se ha producido o informar acerca del
problema, si es que se genera alguna excepción como consecuencia de la ejecución de cualquiera de
las instrucciones contenidas dentro del bloque try asociado.
Para comprender cómo funciona rutina de tratamiento de excepción es esencial comprender que una
excepción impide que continúe en el llamante el flujo normal de control.
Una excepción interrumpe la ejecución de las instrucciones del llamante, por lo que cualquier
instrucción que esté inmediatamente a continuación de la instrucción que produjo el problema no se
ejecutará.
La pregunta que surge entonces es, ¿En qué punto se reanuda la ejecución en el llamante?
La instrucción try proporciona la respuesta: si se genera una excepción debido a una instrucción
invocada dentro del bloque try entonces la ejecución se reanuda en el correspondiente bloque
catch.
Las instrucciones ubicadas dentro de un bloque try se conocen como instrucciones protegidas.
Si no surge ninguna excepción durante la ejecución de las sentencias protegidas, entonces se saltará al
bloque catch cuando se llegue al final del bloque try.
La ejecución continuará con cualquier instrucción que esté a continuación de la instrucción
try/catch completa.
El bloque catch indica el tipo de excepción que tiene designado tratar dentro de un par de paréntesis
inmediatamente a continuación de la palabra catch.
Así como el nombre del tipo de la excepción, también incluye un nombre de variable que
tradicionalmente, es e o ex que se puede usar para hacer referencia al objeto Exception generado.
9
Una referencia a este objeto puede ser muy útil para proporcionar la información que se podrá usar
para recuperarse del problema, o bien a la hora de informar de que ese problema se ha producido.
Una vez que se completó el bloque catch, el control no retorna a la instrucción que causó la
excepción.
12.5.3 Generación y captura de múltiples excepciones
Algunas veces, un método lanza más de un tipo de excepción para indicar diferentes tipos de
problemas.
Cuando se trate de excepciones comprobadas deben enumerarse todas en la cláusula throws del
método, separadas por comas.
Por ejemplo:
public void procesar ( ) throws EOException, FileNotFoundException
Una rutina de tratamiento de excepciones debe capturar todas las excepciones comprobadas que se
lanzan desde sus sentencias protegidas, de modo que una sentencia try puede contener varios bloques
catch.
Cuando se lanza una excepción mediante una llamada a método dentro de un bloque try, los bloques
catch se evalúan en el orden en que están escritos hasta que se encuentra una coincidencia en el tipo
de excepción.
Una vez que se llega al final de un único bloque catch, la ejecución continúa debajo del último
bloque catch.
Si se desea, se puede usar polimorfismo para evitar la escritura de varios bloques catch.
Sin embargo, esto puede tener como contrapartida no ser capaces de llevar a cabo acciones de
recuperación específicas para cada tipo de excepción.
Del proceso natural de coincidencias se desprende que es importante el orden de los bloques catch
en una única sentencia try y que un bloque catch para un tipo de excepción en particular no puede
estar debajo de uno de sus supertipos.
El bloque del supertipo anterior siempre encontrará coincidencia antes que el bloque del subtipo que se
controla.
12.5.5 Propagar una excepción
Hasta ahora, hemos sugerido que una excepción debe ser capturada y tratada en la primera oportunidad
disponible.
Es decir, una excepción lanzada en un método process debe ser capturada y tratada en el método
que haya llamado a process.
En la realidad, este no es estrictamente el caso ya que Java permite que una excepción se propague
desde el método receptor hasta su invocador y posiblemente, más allá.
Un método propaga una excepción simplemente al no incluir una rutina de tratamiento de excepciones
para proteger la instrucción que pueda lanzarla.
Sin embargo, para una excepción comprobada, el compilador requiere que el método propagador
incluya una cláusula throws aun cuando no lance en sí mismo una excepción.
Si la excepción es no comprobada, la cláusula throws es opcional y preferimos omitirla.
La propagación es común en los lugares en que el método invocador es incapaz de tomar una medida
de recuperación o bien, no necesita ninguna, pero esto podría ser posible o necesario dentro de
llamadas de nivel más alto.
10
12.5.6 La cláusula finally
Una sentencia try puede incluir un tercer componente que es opcional: la cláusula final1y, que se
omite con frecuencia.
La cláusula finally se proporciona para instrucciones que se deben ejecutar tanto si se lanza una
excepción para las sentencias protegidas como si no.
Si el control alcanza el final del bloque try entonces se saltan los bloques cacth y se ejecuta la
cláusula finally.
Si se genera una excepción a dentro del bloque try, entonces se ejecuta el bloque catch apropiado y
luego se sigue con la ejecución de la cláusula finally.
try {
//Bloque de sentencias que podrían generar una excepción.
} catch (clase_de_excepcion_1 e){
//sentencias que se ejecutan si se ha producido una
excepción de la clase clase_de_excepcion_1.
} catch (clase_de_excepcion_2 e){
//sentencias que se ejecutan si se ha producido una
excepción de la clase clase_de_excepcion_2.
} catch (Exception e){
//sentencias que se ejecutan si se ha producido una
excepción no capturada anteriormente.
} finally {
//Bloque de sentencias que se ejecutan siempre.
}
Se ejecuta una cláusula finally aunque se ejecute una sentencia return en los bloques try o
catch.
Si se genera una excepción en el bloque try pero no se captura, entonces también se ejecuta la
cláusula finally.
En el último caso, la excepción no capturada podría ser una excepción no comprobada que no requiere
un bloque catch, por ejemplo.
Sin embargo, también podría ser una excepción comprobada que no sea tratada mediante un bloque
catch pero que se propaga desde ese método, para ser tratada a un nivel superior dentro de la pila de
llamadas.
En tal caso, la cláusula finally aún podría ser ejecutada.
Es posible omitir los bloques catch en una instrucción try que disponga de un bloque try y una
cláusula finally.
12.6 Definir nuevas clases de excepción
Cuando las clases estándares de excepciones no describen satisfactoriamente la naturaleza del
problema, se pueden definir nuevas clases más descriptivas usando el mecanismo de herencia.
Las nuevas clases de excepciones comprobadas pueden definirse como subclases de una clase de
excepción comprobada existente (tal como Exception) y las nuevas excepciones no comprobadas
debieran ser subclases de la jerarquía RuntimeException.
Todas las clases de excepción existentes soportan la inclusión de una cadena de diagnóstico que se
pasa al constructor.
Sin embargo, una de las principales razones para definir nuevas clases de excepción es la inclusión de
más información dentro del objeto Exception para brindar el diagnóstico de error y de recuperación de
errores.
11
El principio de incluir información que podría colaborar en la recuperación del error debe tenerse en
cuenta particularmente cuando se definen nuevas clases de excepción comprobadas.
La definición de los parámetros formales del constructor de una excepción ayudará a asegurar que la
información de diagnóstico esté disponible.
Además, cuando la recuperación no sea posible o no se intente, asegura que se sobrescriba el método
toString de la excepción de modo que incluya la información adecuada y de esta manera, ayudará a
diagnosticar el motivo del error.
12
12 Tratamiento de Errores ..................................................................................................................... 2 12.2 Programación defensiva............................................................................................................. 2 12.2.1 Interacción cliente-servidor ................................................................................................ 2 12.2.2 Validar argumentos ............................................................................................................ 3 12.3 Generación de informes de error de servidor ............................................................................ 3 12.3.1 Notificación al usuario ....................................................................................................... 3 12.3.2 Notificación al objeto cliente ............................................................................................. 4 12.4 Principios del lanzamiento de excepciones ............................................................................... 5 12.4.1 Lanzar una excepción ......................................................................................................... 5 12.4.2 Excepciones comprobadas y no comprobadas ................................................................... 5 12.4.3 El efecto de una excepción ................................................................................................. 7 12.4.4 Utilización de excepciones no comprobadas...................................................................... 7 12.4.5 Como impedir la creación de un objeto.............................................................................. 8 12.5 Manejo de excepciones .............................................................................................................. 8 12.5.1 Excepciones comprobadas: la cláusula throws ............................................................... 8 12.5.2 Anticipando las excepciones: la instrucción try ................................................................. 9 12.5.3 Generación y captura de múltiples excepciones............................................................... 10 12.5.5 Propagar una excepción.................................................................................................... 10 12.5.6 La cláusula finally ...................................................................................................... 11 12.6 Definir nuevas clases de excepción ......................................................................................... 11 13
Apéndice
B
Tiposde
datosenJava
Centro Asociado Palma de Mallorca
Tutor: Antonio Rivero Cuesta
1
ApéndiceB‐TiposdedatosenJava
Java reconoce dos categorías de tipos:
 Tipos primitivos.
 Tipos objeto.
Los tipos primitivos se almacenan directamente en las variables y tienen valores semánticos (se copian
los valores cuando se asignan a otra variable).
Los tipos objeto se almacenan mediante referencias al objeto (no se almacena el objeto propiamente
dicho); cuando se asignan a otra variable sólo se copia la referencia, no el objeto.
B.1 Tipos primitivos
La siguiente tabla enumera los tipos primitivos de Java:
Tipo
byte
Descripción
Entero de 1 byte de tamaño (8 bit)
Long
1 byte
Rango
−128 a 127
short
Entero corto (16 bit)
2 bytes
−32768 a 32767
int
Entero (32 bit)
4 bytes
−231 a 231−1
long
Entero largo (64 bit)
8 bytes
−263 a 263−1
float
Real Coma Flotante Simple Precisión 32 bytes ± 1,4 · 10-38 a ±3,4·1038
double
Real Coma Flotante Doble Precisión
64 bytes ± 4,9·10-324 a ±1,8·10308
char
Un solo carácter (16 bit)
2 bytes
0 a 65.535
1 bit
true o false
boolean Un valor lógico (verdadero o falso)
Notas:
 Un número que no contiene un punto decimal se interpreta generalmente como un int, pero se
convierte automáticamente a los tipos short, byte o long cuando se le asigna (si el valor encaja).
Se puede declarar un literal como long añadiendo una ' L' al final del número (también se puede
utilizar la letra '1' (L minúscula) pero debería evitarse ya que se puede confundir fácilmente con
el uno).
 Un número con un punto decimal se considera de tipo double. Se puede especificar un literal
como un float añadiendo una 'F' o 'f' al final del número.
 Un carácter se puede escribir como un carácter Unicode encerrándolo entre comillas simples o
como un valor Unicode de cuatro dígitos precedidos por '\u'.
 Los dos literales booleanos son true y false.
No existen métodos asociados con los tipos primitivos.
Sin embargo, cuando se usa un tipo primitivo en un contexto que requiere un tipo objeto se puede usar
el proceso de autoboxing para convertir un valor primitivo en su correspondiente objeto.
2
B.2 Conversión de tipos primitivos
En ocasiones, es necesario convertir un valor de un tipo primitivo a un valor de otro tipo primitivo.
Normalmente suele ser un valor de un tipo con un cierto rango de valores a otro con un rango más
pequeño.
Este proceso se denomina cast.
La conversión de tipos implica casi siempre pérdida de información.
Por ejemplo el paso de un tipo de coma flotante a tipo entero.
No es posible convertir un valor de tipo boolean en ningún otro tipo con un cast, o viceversa.
El operador cast consta del nombre de un tipo primitivo escrito entre paréntesis delante de una variable
o expresión:
int val = (int)mean;
si mean es una variable de tipo double con valor 3.9, entonces la instrucción anterior almacenaría el
valor entero 3 en la variable val.
B.3 Tipos objeto
Los tipos objeto se almacenan mediante referencias al objeto (no se almacena el objeto propiamente
dicho); cuando se asignan a otra variable sólo se copia la referencia, no el objeto.
Todos los tipos que no aparecen en la sección Tipos primitivos son tipos objeto. Esto incluye los tipos
clase e interfaz de la biblioteca estándar de Java (como por ejemplo, String) y los tipos definidos por el
usuario.
Una variable de tipo objeto contiene una referencia (o un «puntero») a un objeto.
Las asignaciones y los pasajes de parámetros utilizan referencias semánticas (es decir, se copia la
referencia, no el objeto).
Después de asignar una variable a otra, ambas variables hacen referencia al mismo objeto.
Se dice que las dos variables son alias del mismo objeto.
Las clases son las plantillas de los objetos: definen los campos y los métodos que poseerá cada
instancia.
Los arrays se comportan como tipos objeto; también utilizan referencias semánticas. No hay
definición de clase para las matrices.
3
B.4 Clases envoltorio
En Java, cada tipo primitivo tiene su correspondiente clase envoltorio que representa el mismo tipo
pero que en realidad, es un tipo objeto.
Estas clases hacen posible que se usen valores de tipos primitivos en los lugares en que se requieren
tipos objeto mediante un proceso conocido como autoboxing.
La siguiente tabla enumera los tipos primitivos y sus correspondientes clases envoltorio del paquete
java.lang.
Los nombres de las clases envoltorio coinciden con los nombres de los tipos primitivos, pero con su
primera letra en mayúscula.
Tipo de datos simple
byte
Tipo de envoltorio
Byte
Clase equivalente
java.lang.Byte
short
Short
java.lang.Short
int
Int
java.lang.Integer
long
Long
java.lang.Long
float
Float
java.lang.Float
double
Double
java.lang.Double
char
Char
java.lang.Character
boolean
Boolean
java.lang.Boolean
Siempre que se use un valor de un tipo primitivo en un contexto que requiera un tipo objeto, el
compilador utiliza la propiedad de autoboxing para encapsular automáticamente al valor de tipo
primitivo en un objeto envoltorio equivalente.
La operación inversa autounboxing también se lleva a cabo automáticamente cuando se utiliza un
objeto envoltorio en un contexto que requiere un valor del tipo primitivo correspondiente.
B.5 Cast de tipo de objeto.
Puesto que un objeto puede pertenecer a una jerarquía de herencia de tipos, en ocasiones es necesario
convertir una referencia de objeto en una referencia a un subtipo situado más abajo en la jerarquía de
herencia.
Este proceso se denomina casting o downcasting.
El operador cast está compuesto por el nombre de una clase o tipo de interfaz escrito entre paréntesis
delante de una variable o expresión:
Car c = (Car)veh;
Si el tipo declarado estático de la variable veh es Vehicle y Car es una subclase de Vehicle,
entonces esta instrucción podrá compilarse correctamente.
En tiempo de ejecución se hace otra comprobación independiente, para garantizar que el objeto al que
hace referencia veh sea realmente un Car y no una instancia de un subtipo distinto.
El cast entre tipo de objetos es completamente distinto de la conversión entre tipos primitivos.
El cast entre tipos de objetos no implica ninguna modificación del objeto implicado.
Se trata simplemente de una forma de obtener acceso a una información de tipo que ya cierta para ese
objeto, es decir, que forma parte de su tipo dinámico completo.
4
Apéndice B - Tipos de datos en Java ....................................................................................................... 2 B.1 Tipos primitivos ......................................................................................................................... 2 B.2 Conversión de tipos primitivos .................................................................................................. 3 B.3 Tipos objeto ............................................................................................................................... 3 B.4 Clases envoltorio ....................................................................................................................... 4 B.5 Cast de tipo de objeto. ............................................................................................................... 4 5
Apéndice
C
Operadores
Centro Asociado Palma de Mallorca
Tutor: Antonio Rivero Cuesta
1
ApéndiceC‐Operadores
C.1 Expresiones aritméticas
Java dispone de una cantidad considerable de operadores para expresiones aritméticas y lógicas.
La tabla C.1 muestra todo aquello que se clasifica como un operador, incluyendo la conversión de
tipos (casting) y el pasaje de parámetros.
Los principales operadores aritméticos son:
Operador
+
Formato
op1 + op2
Descripción
Suma aritmética de dos operandos
-
op1 - op2
Resta aritmética de dos operandos
-
-op1
*
op1 * op2
Multiplicación de dos operandos

op1 / op2
División entera de dos operandos
%
op1 op2
Resto de la división entera o módulo
++
−−
++op1
op1++
--op1
op1--
Cambio de signo
Incremento unitario
Decremento unitario
Tanto en la división como en el módulo, los resultados de las operaciones dependen de si sus
operandos son enteros o si son valores de punto flotante.
Entre dos valores enteros, la división retiene el resultado entero y descarta cualquier resto; pero entre
dos valores de punto flotante, el resultado es un valor de punto flotante.
Cuando en una operación aparecen más operadores, se deben usar las reglas de precedencia para
indicar el orden de su aplicación.
En la Tabla C.1, los operadores se presentan por nivel de precedencia, de mayor a menor.
En la primera fila aparecen los operadores de nivel de precedencia más alto.
Por ejemplo, podemos ver que la multiplicación, la división y el módulo preceden a la suma y a la
resta.
Los operadores binarios que tienen el mismo nivel de precedencia se evalúan de izquierda a derecha.
Los operadores unarios que tienen el mismo nivel de precedencia se evalúan de derecha a izquierda.
Se pueden usar paréntesis cuando se necesite alterar el orden de evaluación.
Los principales operadores unarios son: −, !, ++, −−, [] Y NEW.
Observe que algunos operadores aparecen en las dos primeras filas de la Tabla C.l.
Los que aparecen en la primera fila admiten un solo operando a su izquierda.
Los que están en la segunda fila admiten un solo operando a su derecha.
2
Precedencia de operadores en Java
Operadores
Precedencia
[ ] . ++ -- (parametros)
++ -- + - ! ~
new cast
Multiplicativos
* / %
Aditivos
+ De movimiento (shift)
<< >> >>>
Relacionales
< > <= >= instanceof
De igualdad
== !=
AND a nivel de bit (bitwise AND) &
OR exclusivo a nivel de bit
^
OR inclusivo a nivel de bit
|
AND lógico
&&
OR lógico
||
Ternarios
?:
De asignación
= += -= *= /= %= &= ^= |= <<= >>= >>>=
C.2 Expresiones booleanas
En las ex presiones lógicas, se usan los operadores para combinar operandos y producir un único valor
lógico, ya sea verdadero o falso (true o fa/s e). Las expresiones lógicas generalmente se encuentran en
las condiciones de las sentencias ife/se y en las de los ciclos.
Los operadores relacionales o de comparación combinan generalmente un par de operandos
aritméticos, aunque también se utilizan para evaluar la igualdad y la desigualdad de referencias a
objetos.
Los operadores relacionales de Java son:
Operador
Formato
>
op1 > op2
<
op1 < op2
>=
op1 >= op2
<=
op1<= op2
==
op1 == op2
!=
op1 != op2
Devuelve true
Devuelve true
Devuelve true
Devuelve true
Devuelve true
Devuelve true
Descripción
(cierto) si op1 es mayor que op2
(cierto) si op1 es menor que op2
(cierto) si op1 es mayor o igual que op2
(cierto) si op1 es menor o igualque op2
(cierto) si op1 es igual a op2
(cierto) si op1 es distinto de op2
Los operadores lógicos binarios combinan dos expresiones lógicas para producir otro valor lógico.
Los operadores son:
Operador
Formato
Descripción
&&
op1 && op2 Y lógico. Devuelve true si son ciertos op1 y op2
op1 || op2 O lógico. Devuelve true si son ciertos op1 o op2

!
!op1
Negación lógica. Devuelve true si es false op1.
Y además,
!
not
Que toma una expresión lógica y cambia su valor de verdadero a falso y viceversa.
3
C.3 Operadores de cortocircuito
Los operadores && y || se llaman operadores en cortocircuito porque si no se cumple la condición de
un término no se evalúa el resto de la operación.
C.4 Operadores de asignación
Operador
Formato
Equivalencia
+=
op1 += op2
op1 = op1 + op2
-=
op1 -= op2
op1 = op1 − op2
*=
op1 *= op2
op1 = op1 * op2
/=
op1 /= op2
op1 = op1 / op2
%=
op1 op2
op1 = op1 % op2
&=
op1 op2
op1 = op1 & op2
|=
op1 op2
op1 = op1 | op2
^=
op1 ^op2
op1 = op1 ^ op2
>>=
op1 >>op2
op1 = op1 >> op2
<<=
op1 op2
op1 = op1 << op2
>>>=
op1 >>>op2
op1 = op1 >>> op2
C.5 Secuencias de escape
Secuencia
Significado
\’
Comillas simples
\”
Dobles comillas
\\
Contrabarra
\b
Retroceso
\n
Línea siguiente
\f
Form feed
\r
Retorno de carro
\t
Tabulador
\a
Alarma
\xxx
Carácter en octal
\0
Carácter nulo
\uxxxx
Carácter en hexadecimal Unicode
4
Apéndice C - Operadores ......................................................................................................................... 2 C.1 Expresiones aritméticas ............................................................................................................. 2 C.2 Expresiones booleanas ............................................................................................................... 3 C.3 Operadores de cortocircuito ....................................................................................................... 4 C.4 Operadores de asignación .......................................................................................................... 4 C.5 Secuencias de escape ................................................................................................................. 4 5
Apéndice
D
Estructurasde
ControlenJava
Centro Asociado Palma de Mallorca
Tutor: Antonio Rivero Cuesta
1
ApéndiceD‐EstructurasdeControlenJava
D.1 Estructuras de Control
Las estructuras de control afectan al orden en que se ejecutan las instrucciones.
Existen dos categorías principales:
 Estructuras de selección.
 Estructuras de repetición.
Una estructura de selección proporciona un punto de decisión en el que se realiza una elección para
seguir una ruta a través del cuerpo de un método o constructor en lugar de otra ruta.
Una estructura de repetición ofrece la opción de repetir instrucciones, un número definido o indefinido
de veces.
La repetición de un número definido de veces lo hacemos con el bucle for y el for-each.
La repetición indefinida lo hacemos con el bucle while y do.
D.2 Estructuras de selección
D.2.1 if
La sentencia if permite en un programa tomar la decisión sobre la ejecución o no de una acción o de
un grupo de acciones, mediante la evaluación de una expresión lógica o booleana.
La acción o grupo de acciones se ejecutan cuando la condición es cierta.
En caso contrario no se ejecutan y se saltan.
if (condición){
instrucciones
}
D.2.2 Sentencia if - else
Esta clase de sentencia if ofrece dos alternativas a seguir, basadas en la comprobación de la
condición.
La palabra reservada else separa las instrucciones utilizadas para ejecutar cada alternativa.
Si la evaluación de la condición es verdadera, se ejecuta la instrucción 1 o secuencia de instrucciones l,
mientras que si la evaluación es falsa se ejecuta la instrucción 2 o secuencia de instrucciones 2.
if (condición){
instrucciones 1
}
else {
instrucciones 2
}
2
D.2.3 switch
Cuando se tienen muchas alternativas posibles a elegir, el uso de sentencias if, else-if puede resultar
bastante complicado, siendo en general más adecuado en estos casos el empleo de la instrucción
switch.
La sintaxis de una instrucción switch es la siguiente:
switch(expresión){
case valor1:
instrucciones;
break;
case valor2:
case valor3:
instrucciones;
break;
………
default:
instrucciones;
break;
}
La expresión, que es obligatorio que esté entre paréntesis, tiene que evaluarse a un entero, un carácter,
un enumerado o un booleano.
A continuación, en cada case aparece un valor que únicamente puede ser una expresión constante, es
decir, una expresión cuyo valor se puede conocer antes de empezar a ejecutar el programa del mismo
tipo que la expresión del switch.
Después de cada case se puede poner una única sentencia o un conjunto de ellas.
Los valores asociados en cada case se comparan en el orden en que están escritos.
Cuando se quiere interrumpir la ejecución de sentencias se utiliza la sentencia break que hace que el
control del programa termine el switch y continúe ejecutando la sentencia que se encuentre después
de esta estructura.
Si no coincide el valor de ningún case con el resultado de la expresión, se ejecuta la parte default.
Si ningún valor de los case coincide con el resultado de la expresión y la parte default no existe,
ya que es opcional, no se ejecuta nada de la estructura switch.
3
D.3 Estructuras de repetición
D.3.1 While
El bucle while ejecuta una sentencia o bloque de sentencias mientras se cumple una determinada
condición.
La condición tiene que estar obligatoriamente entre paréntesis.
La condición es una expresión lógica.
Si la condición vale true, se ejecutan las sentencias que componen el bucle.
Cuando concluye la ejecución de las instrucciones del bucle se vuelve a evaluar la condición.
De nuevo, si la condición es cierta se vuelven a ejecutar las instrucciones del bucle.
En algún momento la condición valdrá false, en cuyo caso finaliza la ejecución del bucle y el
programa continúa ejecutándose por la sentencia que se encuentre a continuación de la estructura
while.
Un problema frecuente en programación se produce cuando aparecen bucles infinitos.
Un bucle infinito es aquel que nunca termina.
Los bucles while infinitos se producen debido a que la condición que se comprueba nunca se hace
falsa, de modo que el bucle while ejecuta repetidamente sus sentencias una y otra vez.
while (condición){
instrucciones
}
D.3.2 do-while
La sentencia do-while es similar a la sentencia while, excepto que la condición se comprueba
después de que el bloque de sentencias se ejecute.
La sentencia o sentencias se ejecutan y, a continuación, se evalúa la condición.
Si la condición se evalúa a un valor verdadero, las sentencias se ejecutan de nuevo.
Este proceso se repite hasta que expresión se evalúa a un valor falso, en cuyo momento se sale de la
sentencia do-while.
Dado que el test condicional se realiza al final del bucle la sentencia o bloque de sentencias se ejecuta
al menos una vez.
do{
instrucciones
}while (condición);
4
D.3.3 for
Tiene dos formas diferentes.
El Bucle for-each, se utiliza exclusivamente para iterar a través de los elementos de una colección.
A la variable del bucle se le asigna el valor de los elementos sucesivos de la colección en cada
iteración del bucle.
for (declaración-de-variables : colección){
instrucciones
}
El bucle for está diseñado para ejecutar una secuencia de sentencias un número fijo de veces.
La sintaxis de la sentencia for es:
for (inicialización ; condición ; incremento){
instrucciones
}
Las sentencias podrán ser cero, una única sentencia o un bloque, y serán lo que se repita durante el
proceso del bucle.
La inicialización fija los valores iniciales de la variable o variables de control antes de que el bucle
for se procese y ejecute solo una vez.
Si se desea inicializar más de un valor, se puede utilizar un operador especial de los bucles for en
Java, el operador coma, para pegar sentencias.
Cuando no se tiene que inicializar, se omite este apartado; sin embargo, nunca se debe omitir el punto
y coma que actúa como separador.
La condición de terminación se comprueba antes de cada iteración del bucle y éste se repite mientras
que dicha condición se evalúe a un valor verdadero.
Si se omite no se realiza ninguna prueba y se ejecuta siempre la sentencia for.
El incremento se ejecuta después de que se ejecuten las sentencias y antes de que se realice la siguiente
prueba de la condición de terminación.
Normalmente esta parte se utiliza para incrementar o decrementar el valor de más variables de control
y, al igual que en la inicialización, se puede usar en ella el operador coma para pegar sentencias.
Cuando no se tienen valores a incrementar se puede suprimir este apartado.
En esencia, el bucle for comprueba si la condición de terminación es verdadera
Si la condición es Verdadera, se ejecutan las sentencias del interior del bucle, y si la condición es falsa,
se saltan todas las sentencias del interior del bucle, es decir, no se ejecutan.
Cuando la condición es verdadera, el bucle ejecuta una iteración, todas sus sentencias, y a continuación
la variable de control del bucle se incrementa.
5
D.4 Excepciones
El lanzamiento y la captura de excepciones proporciona otro par de construcciones que alteran el flujo
del control.
try {
//Bloque de sentencias que podrían generar una excepción.
} catch (clase_de_excepcion_1 e){
//sentencias que se ejecutan si se ha producido una
excepción de la clase clase_de_excepcion_1.
} catch (clase_de_excepcion_2 e){
//sentencias que se ejecutan si se ha producido una
excepción de la clase clase_de_excepcion_2.
} catch (Exception e){
//sentencias que se ejecutan si se ha producido una
excepción no capturada anteriormente.
} finally {
//Bloque de sentencias que se ejecutan siempre.
}
Una sentencia de excepción puede tener cualquier número de cláusulas catch que son evaluadas en el
orden en que aparecen y se ejecuta sólo la primera cláusula que coincida.
Una cláusula coincide si el tipo dinámico del objeto excepción que ha sido lanzado es compatible en la
asignación con el tipo de excepción declarado en la cláusula catch.
La cláusula flnally es opcional.
Se pueden tratar varias excepciones en la misma clausula catch escribiendo la lista de tipos de
excepción, separados por el símbolo "|".
try {
...
var.doSomething();
...
}
catch (EOFException | FileNotFoundException e){
...
}
6
La gestión automática de recursos, try con recursos, refleja el hecho de que las instrucciones try se
utilizan a menudo para proteger instrucciones que utilizan recursos que hay que cerrar una vez que se
haya terminado de utilizarlos, independientemente de si ese uso ha tenido éxito o ha fallado.
La cabecera de la instrucción try se amplía para incluir la apertura del recurso, que a menudo es un
archivo, y el recurso será cerrado automáticamente al final de la instrucción try.
try (FileWriter writer = new FileWriter(filename)){
...
Utilizar el escritor...
...
}
catch (IOException e)¨
...
}
7
Apéndice D - Estructuras de Control en Java........................................................................................... 2 D.1 Estructuras de Control ............................................................................................................... 2 D.2 Estructuras de selección ............................................................................................................. 2 D.2.1 if.......................................................................................................................................... 2 D.2.2 Sentencia if - else ............................................................................................................... 2 D.2.3 switch.................................................................................................................................. 3 D.3 Estructuras de repetición............................................................................................................ 4 D.3.1 While .................................................................................................................................. 4 D.3.2 do-while .............................................................................................................................. 4 D.3.3 for ....................................................................................................................................... 5 D.4 Excepciones ............................................................................................................................... 6 8
Apéndice
E
Ejecuciónde
JavasinBlueJ
Centro Asociado Palma de Mallorca
Tutor: Antonio Rivero Cuesta
1
ApéndiceE‐EjecucióndeJavasinBlueJ
A lo largo de este libro hemos usado BlueJ para desarrollar y ejecutar nuestras aplicaciones Java. Hay
una buena razón para esto: BlueJ nos ofrece algunas herramientas para que resulten más fáciles
algunas de las tareas de desarrollo. En particular, nos permite ejecutar fácilmente métodos individuales
de clases y de objetos, lo que resulta muy útil si queremos probar rápidamente un fragmento de código.
Dividimos la discusión sobre cómo trabajar fuera del entorno BlueJ en dos categorías: ejecutar una
aplicación y desarrollarla fuera del entorno BlueJ.
E.1 Ejecución sin BlueJ
Normalmente, cuando se entregan las aplicaciones a los usuarios finales, son ejecutadas de diferentes
maneras. Las aplicaciones tienen un solo punto de comienzo que define el lugar en que empieza la
ejecución cuando un usuario inicia la aplicación.
El mecanismo exacto que se usa para iniciar una aplicación depende del sistema operativo;
generalmente, se hace doble c1ic sobre el icono de la aplicación o se ingresa el nombre de la misma en
una línea de comando. El sistema operativo necesita saber qué método o qué clase debe invocar para
ejecutar el programa completo.
En Java, este problema se resuelve usando un convenio. Cuando se inicia un programa Java, el nombre
de la clase se especifica como un parámetro del comando de inicio y el nombre del método es siempre
el mismo, el nombre de este método es «main». Por ejemplo, considere el siguiente comando
ingresado en una línea de comando, como si fuera un comando de Windows o de una terminal Unix:
java Juego
El comando java inicia la máquina virtual de Java, que forma parte del kit de desarrollo de Java (JDK)
y que debe estar instalado en su sistema. Juego es el nombre de la clase que queremos iniciar.
Entonces Java buscará un método en la clase Juego cuya signatura coincida exactamente con la
siguiente:
public static void main (String [] args)
El método debe ser público para que pueda ser invocado desde el exterior de la clase.
Debe ser static porque no existe ningún objeto cuando se inicia el programa; inicialmente, tenemos
sólo clases, motivo por el cual sólo podemos invocar métodos estáticos.
Este método estático crea el primer objeto. El tipo de retorno es void ya que este método no devuelve
ningún valor. Aunque el nombre «main» fue seleccionado arbitrariamente por los desarrolladores de
Java, es fijo: el método debe tener siempre este nombre. (La elección de «main» como nombre del
método inicial en realidad proviene del lenguaje C, del que Java hereda gran parte de su sintaxis.)
El parámetro es una matriz de String, que permite a los usuarios pasar argumentos adicionales. En
nuestro ejemplo, el valor del parámetro args será un arreglo de longitud cero. Sin embargo, la línea
de comandos que inicia el programa puede definir argumentos:
java Juego 2 Fred
En esta línea de comando, cada palabra ubicada a continuación del nombre de la clase será leída como
un String independiente y pasado al método main como un elemento del arreglo de String. En
este caso, el arreglo args contendrá dos elementos que son las cadenas «2» y «Fred». Los parámetros
en la línea de comandos no son muy usados en Java.
2
En teoría, el cuerpo del método main puede contener el número de sentencias que se deseen. Sin
embargo, un buen estilo indica que el método main debiera mantenerse lo más corto posible;
específicamente, no debiera contener nada que forme parte de la lógica de la aplicación.
En general, el método main debe hacer exactamente lo que se hizo interactivamente para iniciar la
misma aplicación en BlueJ. Por ejemplo, si para iniciar la aplicación en BlueJ se creó un objeto de la
clase Juego y se invocó el método de nombre start, en el método main de la clase Juego deberían
agregarse las siguientes sentencias:
public static void main(String[]args)
{
Juego juego new Juego();
juego.start();
}
Ahora, al ejecutar el método main se imitará la invocación interactiva del juego.
Los proyectos Java se guardan generalmente en un directorio independiente para cada uno y todas las
clases del proyecto se ubican dentro de este directorio. Cuando se ejecute el comando para iniciar Java
y ejecutar su aplicación, se debe asegurar de que el directorio del proyecto sea el directorio activo en la
terminal de comandos, lo que asegura que se encontrarán las clases que se usan.
Si no puede encontrar una clase específica, la máquina virtual de Java generará un mensaje de error
similar a este:
Exception in thread "main" java.lang.NoClassDefFoundError: Juego
Si ve un mensaje como éste, asegúrese de que escribió correctamente el nombre de la clase y de que el
directorio actual realmente contenga esta clase. La clase se guarda en un archivo de extensión ". class":
por ejemplo, el código de la clase Juego se almacena en un archivo de nombre Juego.class.
Si encuentra la clase pero ésta no contiene un método main o el método main no posee la signatura
correcta verá un mensaje similar a este:
Exception in thread "main " java.lang.NoSuchMethodError: main
En este caso, asegúrese de que la clase que quiere ejecutar tenga el método main correcto.
E.2 Crear archivos ejecutables .jar
Los proyectos Java se almacenan como una colección de archivos en un directorio (o carpeta). A
continuación, hablaremos brevemente sobre los diferentes tipos de archivo.
Generalmente, para distribuir aplicaciones a otros usuarios es más fácil si toda la aplicación se guarda
en un único archivo; el mecanismo de Java que realiza esto tiene el formato de archivo Java (.jar).
Todos los archivos de una aplicación se pueden reunir en un único archivo y aun así podrán ser
ejecutados. Si está familiarizado con el formato de compresión «zip», sería interesante saber que, de
hecho, el formato es el mismo. Los archivos jar pueden abrirse mediante programas zip y viceversa.
Para crear un archivo .jar ejecutable es necesario especificar la clase principal en algún lugar.
Recuerde: el método que se ejecuta siempre es el main, pero necesitamos especificar la clase que lo
contiene. Esta especificación se hace incluyendo un archivo de texto en el archivo .jar el archivo
explícito con la información necesaria. Afortunadamente, BlueJ se ocupa por su propia cuenta de esta
tarea.
3
Para crear un archivo ejecutable .jar en BlueJ use la función Project - Create Jar File y especifique la
clase que contiene el método main en la caja de diálogo que aparece. Debe escribir un método main
exactamente igual al descrito anteriormente.
Para ver detalles sobre esta función, lea el Tutorial de BlueJ al que puede acceder mediante el menú
Help-Tutorial de BlueJ o bien visitando el sitio web de BlueJ.
Una vez que se creó el archivo ejecutable .jar, se puede ejecutar haciendo doble c1ic sobre él. La
computadora que ejecuta este archivo jar debe tener instalado el JDK (Java Development Kit) o el JRE
(Java Runtime Environment) y con él deben estar asociados los archivos .jar.
E.3 Desarrollo sin BlueJ
Si no quiere solamente ejecutar programas, sino que también quiere desarrollarlos fuera del entorno
BlueJ, necesitará editar y compilar las clases. El código de una clase se almacena en un archivo de
extensión «. java»; por ejemplo, la clase Juego se almacena en un archivo de nombre Juego.java.
Los archivos fuente pueden editarse con cualquier editor de textos. Existen muchos editores de textos
libres o muy baratos.
Algunos, como el Notepad o el WordPad se distribuyen con Windows, pero si en realidad quiere usar
un editor para hacer algo más que una prueba rápida, querrá obtener uno mejor. Sin embargo, sea
cuidadoso con los procesadores de texto: generalmente los procesadores de texto no graban en formato
de texto plano y Java no podrá leerlos.
Los archivos fuente pueden compilarse desde una línea de comando usando el compilador Java que se
incluye en el JDK y que se invoca mediante el comando javac. Para compilar un archivo fuente de
nombre Juego.java use el comando javac Juego.java.
Este comando compilará la clase Juego y cualquier otra clase que dependa de ella.
Creará un archivo denominado Juego.class que contiene el código que puede ser ejecutado
mediante la máquina virtual de Java. Para ejecutar este archivo use el comando java Juego
Observe que este comando no incluye la extensión del archivo .class.
4
Apéndice
F
Depurador
Centro Asociado Palma de Mallorca
Tutor: Antonio Rivero Cuesta
1
ApéndiceF‐UtilizacióndelDepurador
El depurador de BlueJ proporciona un conjunto de funcionalidades básicas de depuración
intencionalmente simplificadas y que son genuinamente útiles tanto para la depuración de programas
como para alcanzar mayor comprensión del comportamiento de la ejecución de un programa.
Se puede acceder a la ventana del depurador seleccionando el elemento Show Debugger del menú
View o presionando el botón derecho del ratón sobre el indicador de trabajo y seleccionando Show
Debugger desde el menú contextual.
La Figura F.l muestra la ventana del depurador.
Figura F.l
La ventana del depurador tiene cinco zonas de visualización y cinco botones de control.
Las zonas de visualización y los botones se activan solamente cuando un programa alcanza un punto
de interrupción o se para por alguna otra razón.
Las siguientes secciones describen cómo establecer puntos de interrupción para controlar la ejecución
de un programa y el propósito de cada una de las zonas.
2
F.1 Puntos de interrupción
Un punto de interrupción es una bandera que se asocia con una línea de código (Figura F.2).
Cuando se alcanza un punto de interrupción durante la ejecución de un programa, se activan las zonas
de visualización y los controles del depurador permitiendo inspeccionar el estado del programa y
controlar la ejecución a partir de allí.
Figura F.2
Los puntos de interrupción se establecen en la ventana del editor, ya sea presionando el botón
izquierdo del ratón en la zona de puntos de interrupción situada a la izquierda del código o bien
ubicando el cursor en la línea de código en la que debiera estar el punto de interrupción y
seleccionando la opción Set/Clear Breakpoinl del menú Tools del editor.
Se pueden eliminar los puntos de interrupción mediante el proceso inverso.
Sólo se pueden fijar puntos de interrupción en el código de las clases que hayan sido previamente
compiladas.
F.2 Los botones de control
La Figura F.3 muestra los botones de control que se activan ante un punto de interrupción.
Figura F.3
F.2.1 Halt
El botón Hall está activo cuando el programa se está ejecutando, para permitir que la ejecución se
pueda interrumpir, de ser necesario. Si la ejecución se detiene, el depurador mostrará el estado del
programa como si hubiera alcanzado un punto de interrupción
F.2.2 Step
El botón Step ejecuta la sentencia actual. La ejecución se detendrá nuevamente cuando se complete
dicha sentencia. Si la sentencia involucra una llamada a método, se completa la llamada al método
antes de que la ejecución se detenga nuevamente (a menos que el método invocado tenga otro punto de
interrupción explícito).
3
F.2.3 Step Into
El botón Step Into ejecuta la sentencia actual. Si esta sentencia es una llamada a un método entonces la
ejecución se introducirá en ese método y se detendrá nuevamente en la primer sentencia del mismo.
F.2.4 Continue
El botón Continue continúa la ejecución del programa hasta que se alcance el siguiente punto de
interrupción, se interrumpa la ejecución mediante el botón Halt o se complete la ejecución
normalmente.
F.2.5 Terminate
El botón Terminate finaliza agresivamente la ejecución del programa actual de manera tal que no puede
ser detenida nuevamente. Si se desea simplemente interrumpir la ejecución para examinar el estado
actual del programa es preferible utilizar la operación Halt.
F.3 Las áreas de visualización de variables
La Figura F.4 muestra las tres zonas activas en las que se muestran las variables cuando se encuentra
un punto de interrupción, en un ejemplo tomado de la simulación predadorpresa trabajada en el
Capítulo 10. Las variables estáticas se muestran en la zona superior, las variables de instancia en la del
medio y las variables locales en la zona inferior.
Figura F.4
Cuando se alcanza un punto de interrupción, la ejecución se detendrá en una sentencia de un objeto
arbitrario dentro del programa actual. La zona de variables estáticas (Static variables) muestra los
valores de las variables estáticas definidas en la clase de dicho objeto. La zona de variables de
instancia (Instance variables) muestra las variables de instancia de dicho objeto en particular. Ambas
zonas también incluyen las variables heredadas de las superclases.
La zona de variables locales (Local variables) muestra los valores de las variables locales y de los
parámetros del método o del constructor que se está ejecutando actualmente. Las variables locales
aparecerán en esta zona sólo una vez que hayan sido inicializadas ya que solamente comienzan a
existir en la máquina virtual de Java a partir de ese momento.
4
F.4 El área de Call Sequence
La Figura F.5 muestra la zona Call Sequence que contiene una secuencia de cuatro métodos de
profundidad. Los métodos aparecen en la secuencia en el formato Class.méthod,
independientemente de si son métodos estáticos o métodos de instancia.
Los constructores aparecen en la secuencia como Clase.<init>.
Figura F.5
La secuencia de llamadas opera como una pila: el método que aparece en la parte superior de la
secuencia es donde reside actualmente el flujo de la ejecución. Las zonas que muestran a las variables
reflejan los detalles del método o del constructor que esté resaltado en ese momento en la secuencia de
llamadas. Al seleccionar una línea diferente de la secuencia de llamadas se actualizarán los contenidos
de las otras zonas.
F.5 El área de visualización Threads
Esta zona está fuera del alcance de este libro y no será tratada.
5
Apéndice F - Utilización del Depurador .................................................................................................. 2 F.1 Puntos de interrupción ............................................................................................................... 3 F.2 Los botones de control ............................................................................................................... 3 F.2.1 Halt ..................................................................................................................................... 3 F.2.2 Step ..................................................................................................................................... 3 F.2.3 Step Into ............................................................................................................................. 4 F.2.4 Continue ............................................................................................................................. 4 F.2.5 Terminate............................................................................................................................ 4 F.3 Las áreas de visualización de variables ..................................................................................... 4 F.4 El área de Call Sequence ........................................................................................................... 5 F.5 El área de visualización Threads ............................................................................................... 5 6
Apéndice
I
Javadoc
Centro Asociado Palma de Mallorca
Tutor: Antonio Rivero Cuesta
1
ApéndiceI‐Javadoc
La escritura de buena documentación de las definiciones de las clases y de las interfaces es un
complemento importante para obtener código de buena calidad. La documentación le permite al
programador comunicar sus intenciones a los lectores humanos en un lenguaje natural de alto nivel, en
lugar de forzarlos a leer código de nivel relativamente bajo. La documentación de los elementos
públicos de una clase o de una interfaz tienen un valor especial, pues los programadores pueden usarla
sin tener que conocer los detalles de su implementación.
En todos los proyectos de ejemplo de este libro hemos usado un estilo particular de comentarios que es
reconocido por la herramienta de documentación javadoc que se distribuye como parte del kit de
desarrollo (JDK) de Java de Sun Microsystem. Esta herramienta automatiza la generación de
documentación de clases en formato HTML con un estilo consistente. El API de Java ha sido
documentado usando esta misma herramienta y se aprecia su valor cuando se usa la biblioteca de
clases.
En este apéndice hacemos un breve resumen de los principales elementos de los comentarios de
documentación que deberá introducir habitualmente en su propio código fuente.
I.1
Comentarios de documentación
Los elementos de una clase que se documentarán son la definición de la clase, sus campos,
constructores y métodos. Desde el punto de vista de un usuario, lo más importante de una clase es que
tenga documentación sobre ella y sobre sus constructores y métodos públicos. Tendemos a no
proporcionar comentarios del estilo de javadoc para los campos aunque recordamos que forman parte
del detalle del nivel de implementación y no es algo que verán los usuarios.
Los comentarios de documentación comienzan siempre con los tres caracteres "/**" y terminan con el
par de caracteres "*/".
Entre estos símbolos, un comentario contendrá una descripción principal seguida por una sección de
etiqueta, aunque ambas partes son opcionales.
I.1.1
La descripción principal
La descripción principal de una clase debiera consistir en una descripción del objetivo general de la
clase. El Código I.I muestra parte de una típica descripción principal, tomada de la clase Juego del
proyecto world-of-zuul. Observe que la descripción incluye detalles sobre cómo usar esta clase para
iniciar el juego.
/**
* Esta es la clase principal de la aplicación "World of Zuul"
* "World of Zuul" es un juego de aventuras muy sencillo, basado en texto.
* Los usuarios pueden caminar por algún escenario, y eso es todo lo
* que hace el juego. ¡Podría ampliarse para que resulte más interesante!
* Para jugar, cree una instancia de esta clase e invoque el método "jugar"
*/
2
La descripción principal de un método debiera ser bastante general, sin introducir demasiados detalles
sobre su implementación. En realidad, la descripción principal de un método generalmente consiste en
una sola oración, como por ejemplo
/ **
* Crea un nuevo pasajero con distintas ubicaciones de
* salida y de destino.
*/
Las ideas esencia les debieran presentarse en la primera sentencia de la descripción principal de una
clase, de una interfaz o de un método ya que es lo que se usa a modo de resumen independiente en la
parte superior de la documentación generada.
Javadoc también soporta el uso de etiquetas HTML en sus comentarios.
I.1.2
La sección de marcadores
A continuación de la descripción principal aparece la sección de marcadores.
Javadoc reconoce alrededor de 20 marcadores distintos pero sólo trataremos las más importantes
(Tabla I.l).
Los marcadores pueden usarse de dos maneras:
 Marcadores de bloques.
 Marcadores incrustados.
Sólo hablaremos de los marcadores de bloques pues son los que se usan con mayor frecuencia.
Para ver más detalles sobre las etiquetas de una sola línea y sobre las restantes etiquetas, puede recurrir
a la sección Javadoc de la documentación Tools and Utilities que forma parte del Java JDK.
Etiqueta
Texto asociado
@author
nombre(s) del autor(es)
@param
nombre de parámetro y descripción
@return
descripción del valor de retorno
@see
referencia cruzada
@throws
tipo de excepción que se lanza y las circunstancias en las que se hace
@version
descripción de la versión
Los marcadores @author y @version se encuentran regularmente en los comentarios de una clase y de
una interfaz y no pueden usarse en los comentarios de métodos, constructores o campos.
Ambos marcadores pueden estar seguidos de cualquier texto y no se requiere ningún formato especial
para ninguna de ellos.
Ejemplos:
@author Hakcer T. LargeBrain
@version 2004.12.31
3
Los marcadores @param y @throws se usan en métodos y en constructores, mientras que @return se
usa sólo en métodos.
Algunos ejemplos:
@param limite El valor máximo permitido.
@return Un número aleatorio en el rango 1 a limite (inclusive).
@throws IllegalLimitException Si el límite es menor que 1.
La etiqueta @see adopta varias formas diferentes y puede usarse en cualquier comentario de
documentación.
Proporciona un camino de referencia cruzada hacia un comentario de otra clase, método o cualquier
otra forma de documentación.
Se agrega una sección See Also al elemento que está siendo comentado.
Algunos ejemplos típicos:
@see "The Java Language Specification, by Joy et al"
@see <a href=http://www.bluej .org/>The BlueJ web site </a>
@see #estaVivo
@see java.util.ArrayList#add
El primer marcador simplemente encierra un texto en forma de cadena sin un hipervínculo.
El segundo es un hipervínculo hacia el documento especificado.
El tercero es un vínculo a la documentación del método estaVivo de la misma clase.
El cuarto vincula la documentación del método add de la clase ArrayList del paquete java.util.
I.2
Soporte de BlueJ para javadoc
Si un proyecto ha sido comentado usando el estilo de Javadoc, BlueJ ofrece utilidades para generar la
documentación HTML completa.
En la ventana principal, seleccione el elemento Tools/Project Documentation del menú y se generará la
documentación (si es necesario) y se mostrará en la ventana de un navegador.
Dentro del editor de BlueJ, se puede pasar de la vista del código fuente de una clase a la vista de su
documentación cambiando la opción Source Code a Documentation en la parte derecha de la ventana
del menú Tools del editor.
Esta opción ofrece una vista previa y rápida de la documentación pero no contendrá referencias a la
documentación de las superclases o de las clases que se usan.
4
Apéndice I - Javadoc ................................................................................................................................ 2 I.1 Comentarios de documentación ................................................................................................. 2 I.1.1 La descripción principal ..................................................................................................... 2 I.1.2 La sección de marcadores................................................................................................... 3 I.2 Soporte de BlueJ para javadoc ................................................................................................... 4 5
Apéndice
J
Estilo de
Programación
Centro Asociado Palma de Mallorca
Tutor: Antonio Rivero Cuesta
1
Apéndice J - Estilo de Programación
J.1 Nombres
J.1.1 Use nombres significativos
Use nombres descriptivos para todos los identificadores (nombres de clases, de variables, de métodos).
Evite ambigüedades.
Evite abreviaturas.
Están formados por letras y dígitos.
No pueden empezar por un dígito.
No pueden contener ninguno de los caracteres especiales vistos en una entrada anterior.
Los caracteres especiales y signos de puntuación siguientes:
+-*/=%&#!?^“‘~\|<>()[]{}:;.,
No puede ser una palabra reservada de Java. Las palabras reservadas en Java.
Los métodos de modificación deben comenzar con el prefijo "set": setAlgo( .. ).
Los métodos de acceso deben comenzar con el prefijo "get": getAlgo( .. ).
Los métodos de acceso con valores de retorno booleanos generalmente comienzan con el prefijo "es":
esAlgo(..); por ejemplo, esVacio( ).
J.1.2 Los nombres de las clases comienzan con una letra mayúscula.
J.1.3 Los nombres de las clases son sustantivos en singular.
J.1.4 Los nombres de los métodos y de las variables comienzan con letras minúsculas.
Tanto los nombres de las clases, como los de los métodos y los de las variables, emplean letras
mayúsculas entre medio para aumentar la legibilidad de los identificadores que lo componen; por
ejemplo: numeroDeElementos.
J.1.5 Las constantes se escriben en MAYÚSCULAS
Ocasionalmente se utiliza el símbolo de subrayado en el nombre de una constante para diferenciar los
identificadores que lo componen: TAMANIO_MAXIMO.
J.2 Esquema
J.2.1 Un nivel de indentación es de cuatro espacios
J.2.2 Todas las sentencias de un bloque se indentan un nivel
2
J.2.3 Las llaves de las clases y de los métodos se ubican solas en una línea
Las llaves que encierran el bloque de código de la clase y las de los bloques de código de los métodos
se escriben en una sola línea y con el mismo nivel de indentación. Por ejemplo:
public int getEdad(){
sentencias
}
J.2.4 Para los restantes bloques de código, las llaves se abren al final de una línea
En todos los bloques de código restantes, la llave se abre al final de la línea que contiene la palabra
clave que define al bloque. La llave se cierra en una línea independiente, alineada con la palabra clave
que define dicho bloque. Por ejemplo:
while(condición){
sentencias
}
if(condición){
sentencias
}
else{
sentencias
}
J.2.5 Use siempre llaves en las estructuras de control.
Se usan llaves en las sentencias if y en los ciclos aun cuando el cuerpo esté compuesto por una única
sentencia.
J.2.6 Use un espacio antes de la llave de apertura de un bloque de una estructura de control.
J.2.7 Use un espacio antes y después de un operador.
J.2.8 Use una línea en blanco entre los métodos (y los constructores).
Use líneas en blanco para separar bloques lógicos de código; es decir, use líneas en blanco por lo
menos entre métodos, pero también entre las partes lógicas dentro de un mismo método.
J.3 Documentación
J.3.1 Cada clase tiene un comentario de clase en su parte superior
El comentario de clase contiene como mínimo
• Una descripción general de la clase
• El nombre del autor (o autores)
• Un número de versión
3
Cada persona que ha contribuido en la clase debe ser nombrada como un autor o debe ser acreditada
apropiadamente de otra manera.
Un número de versión puede ser simplemente un número o algún otro formato. Lo más importante es
que el lector pueda reconocer si dos versiones no son iguales y determinar cuál es la más reciente.
J.3.2 Cada método tiene un comentario
J.3.3 Los comentarios son legibles para javadoc
Los comentarios de la clase y de los métodos deben ser reconocidos por javadoc; en otras palabras:
deben comenzar con el símbolo de comentario «/**».
J.3.4 Comente el código sólo donde sea necesario
Se deben incluir comentarios en el código en los lugares en que no resulte obvio o sea difícil de
comprender (y preferentemente, el código debe ser obvio o fácil de entender, siempre que sea posible)
y donde ayude a la comprensión de un método. No comente sentencias obvias, ¡asuma que el lector
comprende Java!
J.4 Restricciones de uso del lenguaje
J.4.1 Orden de las declaraciones: campos, constructores, métodos.
Los elementos de una definición de clase aparecen (si se presentan) en el siguiente orden: sentencias
de paquete, sentencias de importación, comentario de clase, encabezado de la clase, definición de
campos, constructores, métodos.
J.4.2 Los campos no deben ser públicos (con excepción de los campos final).
J.4.3 Use siempre modificadores de acceso.
Especifique todos los campos y los métodos como privados, públicos o protegidos.
Nunca use el acceso por defecto (package private).
J.4.4 Importe las clases individualmente
Es preferible que las sentencias de importación nombren explícitamente cada clase que se quiere
importar y no al paquete completo. Por ejemplo:
import java.util.ArrayList;
import java.util.HashSet;
Es mejor que:
import java.util.*;
J.4.5 Incluya siempre un constructor (aun cuando su cuerpo quede vacío).
J.4.6 Incluya siempre una l/amada al constructor de una superclase.
En los constructores de las subclases no deje que se realice la inserción automática de una llamada a
una superclase; incluya explícitamente la invocación super( ... ), aun cuando funcione bien sin hacerlo.
4
J.4.7 Inicialice todos los campos en el constructor.
J.5 Modismos del código
J.5.1 Use iteradores en las colecciones
Para iterar o recorrer una colección, use un ciclo for-each. Cuando la colección debe ser modificada
durante una iteración, use un Iterator en lugar de un índice entero.
5