Está en la página 1de 307

INTRODUCCIÓN AL DESARROLLO

Amparo López Gaona


E ste libro está dirigido a aquellas personas que quieren aprender a
programar; el objetivo es introducirlos al mundo de la programación
orientada a objetos utilizando el lenguaje Java. En el libro se ilustra el proceso DE PROGRAMAS CON
de programación, desarrollando programas alrededor de estudios de casos y
utilizando una metodología de programación que incluye la etapa de diseño a
fin de que el lector tenga más herramientas para desarrollar sus propios
programas. El diseño de programas es un tema por sí solo, por lo que se
JAVA
presenta una versión simplificada, pero suficiente. El resultado es una obra Tercera edición
donde todos los programas están completos, ampliamente explicados y
documentados para su mayor comprensión.
A lo largo del libro se van introduciendo los elementos necesarios del
lenguaje Java, según sean requeridos para el problema concreto que se está
resolviendo. Al final, el lector tendrá tanto una metodología para el desarrollo Amparo López Gaona
de programas orientados a objetos, como los conocimientos del lenguaje que

Temas de computacion
INTRODUCCIÓN AL DESARROLLO DE PROGRAMAS CON JAVA
le permiten la implementación de sus programas.

´
Esta tercera edición mantiene la mayoría de los problemas presentados en
las ediciones anteriores y se ha enriquecido con más ejemplos en casi todos
los capítulos con una ligera modificación para introducir otras instrucciones
de Java. Un capítulo está dedicado al manejo de errores a través de
excepciones, aunque se insiste en la importancia de los programas robustos.

Imágen de portada:
La cocina (fragmento)
Pablo Picasso
París, 1948
ISBN: 978-607-02-4241-0

9 786070 242410

plasta Pantone 208


AMPARO LÓPEZ GAONA

INTRODUCCIÓN
AL DESARROLLO
DE PROGRAMAS
CON JAVA

FACULTAD DE CIENCIAS
2013
López Gaona, Amparo
Introducción al desarrollo de programas con Java / Amparo López
Gaona. -- 3ª edición. -- México : UNAM, Facultad de Ciencias, 2013.
xii, 306 páginas : ilustraciones ; 23 cm. -- (Temas de computación)

Incluye índice
Bibliografía: páginas 283-284
ISBN 978-607-02-4241-0

1. Java (Lenguaje de programación de computadora). 2. Lenguajes de


programación orientados a objetos. 3. Programación orientada a objetos
(Ciencia de la computación). I. Universidad Nacional Autónoma de
México. Facultad de Ciencias. II. título. III. Serie.

005.133-scdd21 Biblioteca Nacional de México

Introducción al desarrollo de programas con Java


1º edición, 2007
2º edición, 2011
3º edición, 10 de marzo de 2013
1À reimpresión, 2017

© D.R. 2013. Universidad Nacional Autónoma de México.


Facultad de Ciencias.
Ciudad Universitaria. Delegación Coyoacán,
C. P. 04510, México, Distrito Federal.
editoriales@ciencias.unam.mx

ISBN: 978-607-02-4241-0

Diseño de portada: Laura Uribe.

Prohibida la reproducción parcial o total de la obra por cualquier medio,


sin la autorización por escrito del titular de los derechos patrimoniales.1Àedición,2007

Impreso y hecho en México.


A Andrea y Salvador
Agradecimientos

Agradezco a los que me ayudaron a tener esta tercera edición. A las personas que compraron
ejemplares de las ediciones anteriores, a las que lo recomendaron y a las que me hicieron
llegar sugerencias. A los alumnos que con su participación en clase me motivaron a desarrollar
nuevos ejemplos. A Salvador y a Gerardo por utilizar el libro en sus cursos y por compartir
conmigo su experiencia.
A Salvador por su apoyo incondicional en este y todos los proyectos que emprendo. Un
agradecimiento especial a Andrea por su lindo corazón y la “fotografı́a” que aparece en esta
página y que “tomó” en 1999.
Índice general

Introducción ix

1 Proceso de programación 1
1.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 Definición del problema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3 Diseño . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.3.1 Metodologı́a de diseño . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.4 Implementación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.5 Depuración . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.6 Mantenimiento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.7 Documentación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.8 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

2 Creación y uso de datos primitivos 17


2.1 Identificadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.2 Declaración de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.3 Uso de datos (operadores) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.3.1 Operador de asignación . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.3.2 Operadores aritméticos . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.3.3 Operador de asignación compuesto . . . . . . . . . . . . . . . . . . . 25
2.3.4 Operadores de relación . . . . . . . . . . . . . . . . . . . . . . . . . . 26
2.3.5 Operadores lógicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
2.3.6 Operador + para cadenas . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.3.7 Precedencia y asociación de operadores . . . . . . . . . . . . . . . . . 28
2.4 Conversión de tipo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
2.5 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31

3 Creación y uso de objetos 35


3.1 Creación de objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.2 Uso de objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
3.3 Eliminación de objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
3.4 Objetos para lectura y escritura . . . . . . . . . . . . . . . . . . . . . . . . . 40
3.5 Ejemplos de trabajo con objetos . . . . . . . . . . . . . . . . . . . . . . . . . 42
3.6 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58

i
ii ÍNDICE GENERAL

4 Creación y uso de clases 63


4.1 Diseño de clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
4.2 Definición de clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
4.2.1 Encabezado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
4.2.2 Atributos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
4.2.3 Métodos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
4.3 Programación de métodos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
4.3.1 Métodos modificadores . . . . . . . . . . . . . . . . . . . . . . . . . . 70
4.3.2 Métodos de acceso . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
4.3.3 Métodos calculadores . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
4.3.4 Métodos constructores . . . . . . . . . . . . . . . . . . . . . . . . . . 75
4.3.5 El método main . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
4.3.6 El método equals (igualdad entre objetos) . . . . . . . . . . . . . . . 79
4.3.7 El método toString . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
4.3.8 Métodos que devuelven objetos . . . . . . . . . . . . . . . . . . . . . 84
4.4 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94

5 Objetos como atributos 99


5.1 La clase Lı́nea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
5.2 Abstracción en el diseño de clases . . . . . . . . . . . . . . . . . . . . . . . . 106
5.3 Objetos que crean objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
5.4 Interacción de objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
5.5 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121

6 Agrupación de objetos 125


6.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
6.2 Creación y uso de arreglos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
6.3 Métodos que devuelven arreglos . . . . . . . . . . . . . . . . . . . . . . . . . 137
6.4 Arreglos como parámetros . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
6.5 Arreglos de cadenas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
6.5.1 Parámetros del método main . . . . . . . . . . . . . . . . . . . . . . . 143
6.6 Arreglos de objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
6.7 Arreglos bidimensionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
6.8 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161

7 Herencia de clases 167


7.1 Ampliación mediante herencia . . . . . . . . . . . . . . . . . . . . . . . . . . 167
7.2 Control de acceso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
7.3 Constructores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
7.4 Uso de clases derivadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
7.5 Especialización mediante herencia . . . . . . . . . . . . . . . . . . . . . . . . 175
7.6 Jerarquı́a de clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
7.7 Ventajas de la herencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
7.8 Compatibilidad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
7.9 Generalización mediante herencia . . . . . . . . . . . . . . . . . . . . . . . . 186
7.10 La clase Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
7.11 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194
ÍNDICE GENERAL iii

8 La clase Exception 199


8.1 Excepciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
8.2 Jerarquı́a de excepciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
8.3 Estados de una excepción . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
8.3.1 Disparo de excepciones . . . . . . . . . . . . . . . . . . . . . . . . . . 202
8.3.2 Manejo de excepciones . . . . . . . . . . . . . . . . . . . . . . . . . . 204
8.3.3 Fin de la excepción . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207
8.4 Creación de excepciones propias . . . . . . . . . . . . . . . . . . . . . . . . . 208
8.5 Propagación de excepciones . . . . . . . . . . . . . . . . . . . . . . . . . . . 213
8.6 Recuperación de excepciones . . . . . . . . . . . . . . . . . . . . . . . . . . . 215
8.7 Ventajas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216
8.8 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216

9 Clases abstractas e interfaces 221


9.1 Clases abstractas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221
9.2 Jerarquı́a de clases abstractas . . . . . . . . . . . . . . . . . . . . . . . . . . 228
9.3 Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235
9.4 Referencias a interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240
9.5 Implementación de más de una interfaz . . . . . . . . . . . . . . . . . . . . . 242
9.6 Definición de grupos de constantes . . . . . . . . . . . . . . . . . . . . . . . 244
9.7 Interfaces definidas en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . 244
9.8 Clases abstractas vs. interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . 250
9.9 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250

10 Serialización de objetos 255


10.1 Objetos serializables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255
10.2 Serialización . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256
10.3 Deserialización . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
10.4 Serialización y agregación . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259
10.5 Serialización y herencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 260
10.6 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268

A Normas de estilo en Java 271

B El programa javadoc 275

C Archivos de texto 279

Bibliografı́a 283

Índice alfabético 285


Índice de figuras

1.1 Proceso de programación. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2


1.2 Diseño orientado a objetos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.3 Cooperación entre objetos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.4 Proceso de compilación/ejecución de un programa en Java. . . . . . . . . . . . . 11

2.1 Variables al momento de su declaración. . . . . . . . . . . . . . . . . . . . . . . 20


2.2 Variable con valor inicial en su declaración. . . . . . . . . . . . . . . . . . . . . 20
2.3 Uso del operador de asignación. . . . . . . . . . . . . . . . . . . . . . . . . . . 23
2.4 Uso del operador de asignación múltiple. . . . . . . . . . . . . . . . . . . . . . . 23

3.1 Objeto y referencia a él. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36


3.2 Variable para dato de tipo primitivo y variable para referencia. . . . . . . . . . . 36
3.3 Creación de un objeto en Java. . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
3.4 Varias referencias a un objeto. . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
3.5 Asignación de valor nulo a una referencia. . . . . . . . . . . . . . . . . . . . . . 38
3.6 Envı́o y recepción de mensajes. . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
3.7 Envı́o y recepción de mensajes con parámetros. . . . . . . . . . . . . . . . . . . 39
3.8 Recepción de mensajes que devuelven un valor. . . . . . . . . . . . . . . . . . . 40
3.9 Preguntas para el diagnóstico de causa de fiebre. . . . . . . . . . . . . . . . . . 62

4.1 Método visto como caja negra. . . . . . . . . . . . . . . . . . . . . . . . . . . . 68


4.2 Varios objetos de la clase Punto. . . . . . . . . . . . . . . . . . . . . . . . . . . 78
4.3 Correspondencia de parámetros. . . . . . . . . . . . . . . . . . . . . . . . . . . 79

5.1 Relación de agregación entre objetos. . . . . . . . . . . . . . . . . . . . . . . . 100


5.2 Elementos de una máquina expendedora de boletos. . . . . . . . . . . . . . . . . 108
5.3 Diferentes abstracciones del mismo objeto. . . . . . . . . . . . . . . . . . . . . . 109
5.4 Relación de agregación en la máquina expendedora de boletos. . . . . . . . . . . 115

6.1 Arreglo de 10 elementos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128


6.2 Creación de un arreglo de Alumnos. . . . . . . . . . . . . . . . . . . . . . . . . 147
6.3 Búsqueda binaria. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151

v
vi ÍNDICE DE FIGURAS

6.4 Arreglo de 10*5 elementos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155


6.5 Anotaciones para el boliche. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164

7.1 Niveles de acceso de los elementos de la clase A. . . . . . . . . . . . . . . . . . . 172


7.2 Jerarquı́a de clases con raı́z Cuenta. . . . . . . . . . . . . . . . . . . . . . . . . 179
7.3 Crecimiento de la jerarquı́a de la clase Cuenta. . . . . . . . . . . . . . . . . . . 180
7.4 Jerarquı́a de clases de artı́culos para cafeterı́as. . . . . . . . . . . . . . . . . . . 188

8.1 Jerarquı́a de clases a partir de Exception. . . . . . . . . . . . . . . . . . . . . . 201


8.2 Propagación de excepciones. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214

9.1 Jerarquı́a de empleados. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229


9.2 Jerarquı́a de figuras geométricas. . . . . . . . . . . . . . . . . . . . . . . . . . . 236
9.3 Cı́rculo heredando de dos clases. . . . . . . . . . . . . . . . . . . . . . . . . . . 242

10.1 Jerarquı́a de clases de obras impresas. . . . . . . . . . . . . . . . . . . . . . . . 261

B.1 Documentación de la clase CuentaDeCrédito (tercera parte). . . . . . . . . . . . 276


B.2 Documentación de la clase CuentaDeCrédito (cuarta parte). . . . . . . . . . . . 277
Índice de tablas

2.1 Palabras reservadas de Java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18


2.2 Tipos primitivos de Java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.3 Operadores aritméticos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.4 Operadores unarios. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.5 Operadores de relación. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
2.6 Operadores lógicos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.7 Tablas de verdad. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.8 Precedencia de operadores. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
2.9 Conversión implı́cita de operadores. . . . . . . . . . . . . . . . . . . . . . . . . 30

3.1 Algunos métodos de la clase Scanner. . . . . . . . . . . . . . . . . . . . . . . . 41


3.2 Métodos de la clase String . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

4.1 Visibilidad en la clase. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68

7.1 Atributos y métodos de una clase y su subclase . . . . . . . . . . . . . . . . . . 171


7.2 Atributos y métodos de la clase CuentaDeCredito. . . . . . . . . . . . . . . . . 175

vii
Introducción

Aprender a programar es una tarea difı́cil debido, entre otras cosas, a que no existe un
procedimiento para ello. Para aprender a programar es necesario escribir programas, no
basta con leer o entender programas ya escritos, es necesario enfrentar el reto de programar,
tener tropiezos en el camino y aprender de ellos. Para lograrlo se debe aprender a analizar
un problema, descomponerlo en sus partes y esbozar una solución. Una vez que se tiene el
esbozo de solución se puede proceder a escribir en un lenguaje de programación los pasos
que se deben seguir para llegar a la solución del problema. En el caso de la programación
orientada a objetos estos pasos deben contener instrucciones que impliquen la interacción de
objetos a través de mensajes.
Este libro tiene como objetivo introducir al lector al mundo de la programación orientada a
objetos utilizando el lenguaje Java. El libro está escrito para principiantes en programación.
La mayorı́a de los libros acerca del tema se centran en explicar los aspectos sintácticos y
semánticos de las construcciones en Java a través de porciones de programas o pequeños
programas para ilustrar la construcción en turno, obviando la etapa de diseño. Por su parte,
el tema de diseño es suficientemente amplio para escribir un libro, además, para que se note
la utilidad del diseño, los libros del tema están enfocados al desarrollo de grandes programas
denominados sistemas y los autores asumen conocimiento del lenguaje de programación.
En este libro se muestra el proceso de programación, no se limita a mostrar los programas
ya terminados. De acuerdo con mi experiencia en la enseñanza de programación considero
más apropiado este enfoque para lograr el aprendizaje de tal proceso. Todos los capı́tulos,
excepto los dos primeros, se desarrollan alrededor de casos de estudio. Para ello se utiliza
una metodologı́a de programación, que incluye la etapa de diseño de los programas pa-
ra que el lector tenga más herramientas para desarrollar sus propios programas. Como se
mencionó anteriormente, el tema de diseño de programas requiere de mucho tiempo para
estudiarlo a fondo, por lo que se ha optado por presentar una versión simplificada de tal
proceso pero suficiente para el propósito del libro.
Este libro no es un manual de Java, sin embargo contiene lo básico para desarrollar progra-
mas que creen objetos e interactúen mediante el intercambio de mensajes, y en caso necesario
desarrollar las clases para generar dichos objetos. La forma en que se introducen, en esta
obra, los conceptos de la programación orientada a objetos es mostrando y explicando su
necesidad, luego especificando cómo se trabaja ese concepto en Java, y finalmente desarro-

ix
x INTRODUCCIÓN

llando un programa que lo utilice: en algunos casos pueden surgir soluciones alternativas,
las cuales se analizan y se explican sus ventajas o desventajas. El resultado es un libro don-
de todos los programas están completos, ampliamente explicados y documentados para su
mayor comprensión.
Otro aspecto que se resalta a lo largo del libro es la importancia de desarrollar programas
robustos, es decir, programas que estén preparados para trabajar aun en situaciones anóma-
las, sin importar qué tan incorrectos o poco plausibles sean los datos de entrada. Todos los
programas desarrollados en este libro son robustos.
En esta segunda edición el diseño de los problemas planteados es más detallado que en la
primera edición. Se introdujo una clase Error para que el programador principiante maneje
los errores que pueda tener su programa al momento de ejecutarlo. También se dedica un
capı́tulo completo al manejo de errores a través de excepciones. Se profundizó en la presen-
tación del tema de interfaces. Se tiene un capı́tulo detallado para el tema de serialización de
objetos. Para la lectura de datos proporcionados por el usuario se emplea la clase Scanner
incluida a partir de Java 5.0.
El libro ha sido dividido en los siguientes capı́tulos:

Capı́tulo 1. Proceso de programación. En este capı́tulo se detalla cada una de las activi-
dades necesarias para escribir programas que funcionen adecuadamente como solución
a un problema particular. Estas actividades incluyen la definición del problema, el di-
seño de la solución, ası́ como la codificación, la depuración, las pruebas, la validación,
la documentación y el mantenimiento de la solución.

Capı́tulo 2. Creación y uso de datos primitivos. Se describe la forma de crear y utilizar


datos de los tipos predefinidos por Java, denominados primitivos, en expresiones que
devuelven un valor. Se describe la forma en que Java evalúa las expresiones de acuerdo
con la prioridad y reglas de asociación de los operadores involucrados, ası́ como la
forma de trabajar con expresiones que contengan elementos de diferente tipo, siempre
y cuando sean compatibles.

Capı́tulo 3. Creación y uso de objetos. Se muestra cómo trabajar con objetos, ya sea de
clases definidas en los paquetes de Java o bien de clases definidas por el programador. El
trabajo con objetos incluye crearlos, interactuar con ellos a través del envı́o y recepción
de mensajes, compararlos, eliminarlos e imprimirlos, entre otras operaciones.

Capı́tulo 4. Creación y uso de clases. En este capı́tulo se muestra la forma en que el progra-
mador puede crear sus propias clases. En toda clase se debe definir tanto la estructura
como el comportamiento que tendrán sus objetos. La parte estructural de los objetos
se define mediante la declaración de datos; éstos pueden ser de cualquier tipo definido
por el lenguaje. El comportamiento de los objetos se modela mediante métodos, y es
sólo mediante éstos que se puede asignar, alterar y conocer el estado de un objeto.
xi

Capı́tulo 5. Objetos como atributos. En este capı́tulo se abordan problemas que permiten
ilustrar los conceptos de modularización, abstracción y agregación. Los dos primeros
facilitan el diseño de programas grandes y el tercero consiste en incluir objetos en la
estructura de objetos de otra clase.

Capı́tulo 6. Agrupación de objetos. Se presenta la forma de agrupar objetos de la misma


clase, y en general datos del mismo tipo, en un objeto denominado arreglo. Una vez
creada la agrupación se analiza la forma de trabajar con cada elemento de ella o con
todos como unidad.

Capı́tulo 7. Herencia de clases. Se muestra el concepto de herencia, con el que se pueden


crear nuevas clases por combinación, extensión y/o especialización de clases existentes.
También se introduce el concepto de polimorfismo, el cual permite determinar en el
momento de ejecución a qué clase enviar el mensaje.

Capı́tulo 8. La clase Exception. Se describe la forma de crear programas robustos utili-


zando el mecanismo de excepciones, para forzar al usuario a enfrentarse a los errores.
Se describe qué sucede después de que ocurre un error, cómo manejarlo, dónde hacerlo
y cómo puede el programa recuperarse de tal error.

Capı́tulo 9. Clases abstractas e interfaces. En este capı́tulo se expone la forma de crear


clases tan generales que representan un concepto abstracto por lo que no es posible
definir la implementación de todos sus métodos. También se presenta el concepto de
interfaz. Las interfaces son el caso extremo de las clases abstractas, pues sólo contienen
especificaciones del comportamiento deseado para las clases que las implementen.

Capı́tulo 10. Serialización de objetos. En este capı́tulo se presenta la forma de lograr que
los objetos que se crean en un programa no se destruyan al terminar la ejecución del
mismo, es decir que sigan existiendo independientemente de que el programa haya
terminado su ejecución.

Apéndice A. Normas de estilo de Java. Este apéndice contiene las principales guı́as de
estilo para organizar y dar formato al código fuente de los programas en Java.

Apéndice B. El programa javadoc. Se describen las marcas permitidas dentro de los co-
mentarios para javadoc y utilizadas en este libro, además del resultado de este pro-
grama al tomar como entrada uno de los programas desarrollados en el libro.

Apéndice C. Archivos de texto. Se muestra la forma de crear archivos de texto, ası́ como
de recuperar la información almacenada en ellos.

Los programas de ejemplo desarrollados a lo largo del libro se encuentran en la dirección:


http://www.fciencias.unam.mx/~alg/programacion
Capı́tulo 1

Proceso de programación

El proceso de programación consta de las actividades necesarias para escribir programas


que funcionen adecuadamente como solución a un problema particular. Estas actividades
incluyen la definición del problema, el diseño de la solución, ası́ como la codificación, la
depuración, las pruebas, la validación, la documentación y el mantenimiento de la solución.
En este capı́tulo se detalla cada una de las actividades necesarias para programar.

1.1 Introducción
El proceso de programar requiere de varias actividades, de manera similar al proceso que
generalmente sigue un estudiante al elaborar algún reporte escolar. Para la elaboración de un
reporte es preciso empezar por definir el tema especı́fico sobre el que se hará el reporte, do-
cumentarse acerca del tema, preparar un guión, escribir un primer borrador, leerlo, revisarlo,
modificarlo y repetir estos tres pasos hasta estar satisfecho con el trabajo. En el caso de la
programación, se debe empezar por especificar lo más claramente posible el problema que
se quiere resolver; una vez especificado qué se desea hacer se procede a diseñar cómo se va a
resolver el problema; el diseño se traduce a un lenguaje de programación (esto es lo que se
conoce como programa); se depura el programa, es decir, se buscan y corrigen errores; una
vez que el programa ya no tiene errores se verifica que éste resuelva el problema planteado;
el mantenimiento es la tarea de hacer modificaciones a un programa con el propósito de que
haga más tareas de las que se tenı́an originalmente o se hagan de manera diferente (figura
1.1).
El proceso de programación es un proceso iterativo, pues cualquiera de los pasos puede
llevar a regresar y afinar lo obtenido en pasos anteriores; por ejemplo, al probar el programa
puede que sea inevitable volver a analizar el problema, diseñar, implementar y probar la
solución tantas veces como sea necesario. El proceso de programación también es incremental
porque se puede trabajar en una solución a una versión simplificada del problema y en etapas
posteriores incorporar más funcionalidad de manera que se va acercando a la solución del

1
2 CAPÍTULO 1. PROCESO DE PROGRAMACIÓN

Especificación
(Definición)

DOCUMENTACION
Diseño

Implementación

Pruebas
(Depuración)

Mantenimiento

Figura 1.1 Proceso de programación.

problema original. Como actividad paralela a estas está la de documentación, cuyo objetivo
es dejar constancia de qué se hizo, cómo se hizo y por qué se hizo ası́. Cada uno de estos
pasos es importante: con el análisis del problema se determina qué hacer; con el diseño cómo
hacerlo; con la implementación se hace; con las pruebas se revisa que la solución sea adecuada
y con el mantenimiento se adapta la solución a nuevos requerimientos.

1.2 Definición del problema


En la definición del problema se especifica qué es lo que debe hacer el programa. Este primer
paso puede parecer trivial aunque no lo es. La comprensión exacta de lo que se necesita hacer
es requisito indispensable para crear una solución funcional. En ocasiones los programadores
ignoran esta fase obvia y comienzan a escribir un programa sin tener claro el problema a
resolver.
En esta etapa se determina el objetivo del programa y las caracterı́sticas del mismo, para
ello se debe empezar por especificar para qué se requiere el programa, ası́ como las carac-
terı́sticas que lo hacen útil para el usuario final.
El grupo de personas (o una sola) que requiere el programa es el encargado de propor-
cionar un conjunto inicial de requerimientos; estos requerimientos suelen ser incompletos o
ambiguos. El programador debe trabajar para refinar los requerimientos hasta que exista un
acuerdo entre quien solicita el programa y quien define la solución, de tal forma que no se
tengan ambigüedades.
1.3 DISEÑO 3

Existen técnicas para llevar a cabo la tarea de definición del problema, sin embargo están
más allá del alcance de este libro. En este texto el desarrollo de programas empieza a partir de
una especificación del problema sin justificar las razones por las que se tiene tal planteamiento
del problema que se desea resolver.

1.3 Diseño
En el diseño se indica una forma de satisfacer, mediante un programa, los requerimientos
establecidos en la etapa anterior. El diseño de un programa es un proceso al que muchas veces
no se le da importancia y de ahı́ que en las etapas posteriores se tengan muchos problemas.
En el diseño es necesario identificar los principales componentes de la solución y la relación
entre ellos. Hacer un programa sin un diseño previo es equivalente a hacer un reporte escolar
sin un ı́ndice o bosquejo de los puntos que se quieren tratar; el reporte puede hacerse pero
no se tiene delimitado su alcance, además es posible que no tenga coherencia y si se desea
incluir un nuevo subtema no sea fácil saber en dónde incluirlo o cómo relacionarlo con lo ya
escrito. Pueden evitarse problemas posteriores con un buen diseño.
No necesariamente el primer diseño es el definitivo, ası́ que es común considerar y explorar
diversas alternativas debido principalmente a que en esta etapa los cambios no son tan
complicados.
Para ilustrar el proceso de diseño e introducir conceptos básicos importantes utilizados en
este texto, se presenta el siguiente problema.
Ejemplo 1.1. El equipo de fútbol de una escuela va a enfrentarse en la final de un torneo al
equipo de otra escuela; el partido se realizará a las 12 del dı́a en pleno verano. El entrenador
del equipo, anticipando las necesidades de los jugadores, solicita a un alumno que se encargue
de llevar agua de limón.
El alumno responsable de llevar el agua encuentra dos opciones para cumplir con su tarea:

1. Preparar el agua en base a la receta de su mamá. Él sabe que en la receta se encuentra
la especificación detallada de los pasos a seguir para preparar agua de limón.

2. Acudir a un especialista, es decir, a una persona que se dedica profesionalmente a


preparar agua de limón.

Diseño de solución (1): Usar una receta.


La receta especifica detalladamente los pasos que se deben seguir para resolver el problema.
Se puede suponer que la receta es la que se presenta a continuación.

Agua de limón
Ingredientes:
1L agua
5 limones
4 CAPÍTULO 1. PROCESO DE PROGRAMACIÓN

1/2 taza de azúcar

Procedimiento:
1. Lavar los limones.
2. Disolver el azúcar en el agua.
3. Probar que esté dulce, en caso necesario agregar más azúcar.
4. Agregar el jugo de los limones.

En el diseño de esta solución se especifica detallada y ordenadamente los pasos a seguir


para resolver el problema. Es importante seguir los pasos en el orden establecido porque
de otra forma podrı́a no obtenerse el resultado esperado; por ejemplo, el agua podrı́a no
endulzarse.
Este es el enfoque tradicional de diseñar: cada paso permite acercarse a la solución final.
En muchas ocasiones lo que es claro para una persona no lo es para otra, si un paso no es
suficientemente claro se debe detallar aún más. En el ejemplo anterior, quizá una persona
con nula experiencia en la preparación de agua de limón requiera que se detalle más el cuarto
paso. Una forma de detallarlo serı́a la siguiente:

4.1. Partir los limones por la mitad.


4.2. Exprimir los limones de tal forma que el jugo caiga sobre el agua.
4.3. Revolver el agua para incorporar el jugo.

Un algoritmo es la especificación de los datos y la descripción de los pasos que deben


seguirse para resolver un problema. En estos pasos está implı́cito el orden en que se deben
ejecutar; cada paso está definido sin ambigüedad y al ejecutarse en el orden especificado se
obtiene siempre el mismo resultado.
En ocasiones se piensa en los algoritmos como el equivalente a las recetas de cocina, sólo
que no son exactamente lo mismo puesto que en la descripción de las recetas los datos y las
instrucciones pueden ser ambiguas. Por ejemplo, “probar que esté dulce” no es lo mismo para
todas las personas. Por eso, cuando se cocina, aunque más de una persona siga la misma
receta el resultado no siempre es el mismo, aquı́ se depende del sazón del cocinero y su
capacidad interpretativa de las instrucciones. Al seguir un algoritmo el resultado debe ser
siempre el mismo, independientemente de quién lo siga, por eso se pide que las instrucciones
se definan sin ambigüedad. El lenguaje natural permite la introducción de ambigüedades,
por eso es común que los algoritmos no se expresen en lenguaje natural;, para expresarlos
muchas veces se escriben como programas en algún lenguaje de programación o bien en
pseudocódigo.
Diseño de solución (2): Acudir a un especialista.
En este caso, se requiere una persona que sepa preparar el agua de limón y el joven se
despreocupa de los detalles de la preparación. Suponer que para resolver el problema el joven
decide buscar a una persona que ofrezca el servicio que se necesita y se encuentra con el Sr.
Limón, un juguero. Entonces el joven le hace su petición (figura 1.2) que en ocasiones requiere
1.3 DISEÑO 5

incluir información adicional necesaria para llevar a cabo la acción; por ejemplo, cantidad de
litros de agua. La persona receptora al aceptar la petición está aceptando la responsabilidad
de realizar la acción indicada. En este caso, es responsabilidad del Sr. Limón satisfacer la
petición y para ello seguirá su propio método para preparar agua de limón. El cliente no
necesita conocer este método.

Dame agua de limón

Juan Sr. Limón


(Alumno) (Juguero)

Figura 1.2 Diseño orientado a objetos.

En el ejemplo se habla en términos de personas que realizan acciones, en el contexto


de la programación orientada a objetos se habla de objetos que responden a mensajes
recibidos (las peticiones) mediante la ejecución de métodos. Un objeto es cualquier cosa
con significado para el problema que se trata de resolver.
Desde el punto de vista de la programación, los objetos tienen tres tipos de componentes:
estructura, comportamiento e identidad. La estructura está formada por los atributos que
describen al objeto. El comportamiento está definido por el conjunto de tareas que es capaz
de realizar el objeto. La identidad es el componente que permite distinguir un objeto de otros
que tienen la misma estructura y comportamiento. El conjunto de valores asociados a cada
atributo de la estructura de un objeto en un momento dado se denomina estado. Durante
el tiempo de vida de un objeto lo único que puede cambiar es su estado.
Volviendo a la solución del problema, se contactó con el Sr. Limón, aunque no se le cono-
ciera personalmente, porque los jugueros son la clase de personas que venden jugos y aguas.
Conceptualmente, una clase es un molde a partir del cual se pueden crear objetos con la
estructura y el comportamiento definidos en ella. La identidad de cada objeto es única y no
depende de la clase. De una clase se pueden crear varios objetos, todos con igual estructura
y comportamiento pero cada uno con su propio estado y una identidad independiente de la
clase. Todos los objetos son ejemplares o instancias de una clase.
Como otro ejemplo de clase se presenta la clase pelota cuya estructura está formada por
color, tamaño y material; su comportamiento por los métodos botar, desinflar e inflar. Un
objeto de esta clase puede ser la pelota grande, roja, de plástico que tiene, como identidad,
marcado el nombre del dueño. En este caso, el estado del objeto está formado por los valores
concretos de la pelota descrita antes.
Para los usuarios de un objeto lo único importante de éste es la clase a la que pertenece y
los mensajes que puede recibir. No importa ni la estructura ni los métodos que emplea para
satisfacer las peticiones o mensajes que recibe.
6 CAPÍTULO 1. PROCESO DE PROGRAMACIÓN

Al acudir a un especialista para resolver un problema es posible que el objeto receptor


requiera delegar parte del trabajo. Por ejemplo, el Sr. Limón puede ser parte de una empresa
grande, en la que trabajen varias personas, cada una realizando un trabajo especializado:
alguien está encargado de lavar y cortar los limones, otra persona se encarga de exprimirlos y
el Sr. Limón se encarga de preparar el agua (figura 1.3). Esto es un ejemplo de colaboración
en la solución de un problema; de manera similar se realiza la colaboración entre objetos
para resolver un problema particular. Sin embargo, para el emisor del mensaje original es
irrelevante si sucede esto o no.
Dame agua de limón

(Cliente) (Dependiente) (Juguero) (Ayudante)

Figura 1.3 Cooperación entre objetos.

Recapitulando, en el diseño de un programa es necesario escribir un algoritmo. En el


caso del diseño orientado a objetos es necesario determinar los objetos que participan en
la solución del problema. Luego, de acuerdo con su comportamiento, determinar en cuáles
pasos de la solución participan y cómo lo hacen, incluyendo la forma de colaborar entre ellos.
Esta forma de enfrentar los problemas y resolverlos funciona muy bien si existen los objetos
que se necesitan para un problema particular, pero si no existen o los que existen no tienen
toda la funcionalidad requerida se deben diseñar las clases para esos objetos.
En el ejemplo de la solución al problema del alumno encargado de llevar el agua de limón,
se determinó que la clase necesaria para la solución es la de los jugueros, donde cada juguero
tiene la responsabilidad de saber preparar agua de limón y su colaboración con otros objetos
está limitada a atender la petición del usuario y tal vez a cobrarle.

1.3.1 Metodologı́a de diseño


Existen diversas metodologı́as para elaborar un diseño a partir del análisis de la especificación
del problema. Por tratarse de un texto introductorio a la programación orientada a objetos,
se presenta una metodologı́a de diseño simplificada pero adecuada para problemas sencillos.
El primer paso consiste en descubrir las clases que se requieren en la solución del problema,
para ello se asocian los sustantivos de la definición del problema con objetos/clases. Podrı́a
pensarse que cada sustantivo corresponde a una clase pero no siempre es ası́. Algunos sus-
tantivos pueden ser atributos de un objeto y también pueden faltar clases en la descripción
del problema.
El siguiente paso consiste en asignar/determinar responsabilidades a cada clase; éstas
son las acciones que realiza cada clase. De ahı́ que generalmente se usan los verbos de la
1.3 DISEÑO 7

descripción del problema para determinar las responsabilidades. En un diseño preliminar


basta con considerar las responsabilidades principales, no es necesario determinar todas.
El siguiente paso consiste en determinar la colaboración entre objetos, para ello es conve-
niente definir escenarios que ejemplifiquen diversas actividades requeridas del programa. Un
escenario es la descripción detallada del comportamiento esperado del programa en térmi-
nos de colaboración entre objetos e interacción con el usuario. Finalmente, estos escenarios
son algoritmos.
Con los escenarios es posible determinar responsabilidades de las clases que no se tienen
en la primera aproximación del diseño. También puede suceder que sea necesario incluir
una clase que coordine el trabajo que hacen otras clases. Al hacer un diseño es posible
que algunas clases ya existan, aún si no corresponden completamente a las necesidades
del problema pueden servir como base para nuevas clases. A continuación se muestra la
metodologı́a aplicada a un problema.
Ejemplo 1.2. Un pequeño empresario requiere un programa para mantener, en un sitio web,
un directorio telefónico con información de las personas con las que tiene contacto, con la
finalidad de agilizar la localización de la información de cada una.
Para cada persona o contacto requiere la siguiente información: nombre, teléfono de contac-
to, nombre de la empresa en que trabaja, puesto que ocupa y dirección de correo electrónico.
La funcionalidad esperada de este programa es permitir agregar un nuevo contacto al
directorio, editar la información de un contacto particular (excepto el nombre), borrar a un
contacto del directorio, realizar búsquedas por nombre y obtener la información del contacto,
ası́ como realizar búsquedas inversas, es decir, dado un número telefónico obtener toda la
información registrada del contacto.

1. Encontrar los objetos principales.


Los sustantivos encontrados en la descripción del problema son: empresario, progra-
ma, sitio web, directorio telefónico, información, personas, contacto, nombre, teléfono,
empresa, puesto y dirección de correo electrónico.
Analizando los sustantivos se puede notar que “empresario” es el usuario del programa,
y éste normalmente no se traduce a clase, al igual que el sustantivo “programa”. “Sitio
web” es el lugar en donde se pretende alojar el programa, pero no tiene funcionalidad
que se deba programar en este problema, ası́ que tampoco es una clase.
Directorio telefónico y contacto/persona sı́ son candidatos a clases pues se puede dedu-
cir que el programa requiere de un directorio telefónico y que éste manejará contactos.
Los sustantivos: nombre del contacto, teléfono del contacto, nombre de la empresa en
que trabaja, puesto que ocupa y dirección de correo electrónico son atributos para los
objetos de la clase Contacto.

2. Encontrar el comportamiento deseado (responsabilidades) de las clases.


8 CAPÍTULO 1. PROCESO DE PROGRAMACIÓN

Para este paso se empieza por utilizar los verbos de la descripción del problema. Para
el caso concreto del directorio se tienen, en los primeros dos párrafos, los siguientes:
requerir, mantener sitio web, tener contacto, agilizar; que no se asocian con ninguna
clase.
En el tercer párrafo se encuentran verbos que indican claramente la responsabilidad del
directorio: agregar un nuevo contacto, editar la información de un contacto particular
(excepto el nombre), borrar a un contacto, realizar búsquedas por nombre y por número
telefónico.
Finalmente, aunque en la descripción del problema no se especifica, en el programa el
contacto tiene la responsabilidad de mantener sus datos y proporcionarlos al directorio
cuando se lo soliciten.

3. Determinar colaboración entre objetos mediante escenarios.


Al empezar a escribir el primer escenario, se tiene el problema de decidir quién es el
encargado de dar la bienvenida al usuario e interactuar con él. Ası́ que se tiene la
necesidad de introducir una clase intermediaria que se llamará Coordinador.

Escenario: Inicio del programa.


(a) El Coordinador da la bienvenida al programa.
(b) El Coordinador muestra un menú con las opciones de trabajo con el directorio.
(c) El usuario elige una de ellas.
(d) El Coordinador valida la opción elegida.
(e) Si la opción es válida, se ejecuta el método correspondiente del directorio.
(f) Si la opción es inválida, se envı́a un mensaje al usuario y se vuelve al paso
(b).
(g) Si la opción elegida es “terminar”, el programa termina. En otro caso se vuelve
al paso (b).
Escenario: Alta de contacto
(a) El usuario desea dar de alta un contacto.
(b) El Coordinador solicita al usuario el nombre del nuevo contacto.
(c) Si el nuevo contacto no está en el directorio, el Coordinador solicita los datos
necesarios para el nuevo contacto.
(d) El usuario proporciona los datos del nuevo contacto.
(e) El Coordinador envı́a al directorio el mensaje para dar de alta al nuevo con-
tacto.
1.4 IMPLEMENTACIÓN 9

Desde luego que el programa consta de más escenarios, pero para apreciar la interacción
entre los diferentes objetos involucrados en el problema bastan estos dos como ejemplo.
También sirvieron para poner al descubierto la necesidad de una clase Coordinador que
tiene como función coordinar el trabajo del directorio y la interacción con el usuario.

Muchos programadores consideran esta etapa inútil y tediosa pues lo que quieren es empe-
zar inmediatamente a programar, sobre todo cuando sienten que la sencillez de los problemas
permite una solución trivial. Programar sin pasar por la etapa de diseño es muy peligroso,
pues puede propiciar que los programas no resuelvan correctamente el problema, por lo que
es importante desarrollar este hábito.
Al igual que la escritura de un reporte o ensayo, el diseño de un programa se debe revisar
varias veces. Después de que el diseño se ha desarrollado y refinado ya se puede pasar a la
etapa de codificación o implementación.

1.4 Implementación
Una vez que se tiene el diseño de la solución se procede a traducirlo a un lenguaje de
programación. Esta tarea se conoce como codificación o implementación. Muchos pro-
gramadores se centran únicamente en esta etapa aunque, como puede apreciarse, el proceso
de programar es mucho más complejo y creativo.
En este texto la codificación se hará en Java siguiendo prácticas de programación que
contribuyen al desarrollo de programas funcionales, legibles y entendibles. Es recomendable
acostumbrarse desde el principio a escribir programas que sean fácilmente entendibles por
otras personas. No hay una fórmula para ello, pero los siguientes lineamientos pueden ayudar.

1. Los programas deben tener una estructura clara. Se debe facilitar la ubicación del
código en caso de que sea necesario modificar algo; al hacerlo se debe tener cuidado de
que las modificaciones no alteren el código que funciona correctamente.

2. El código debe estar organizado y presentado de manera que sea fácil su lectura. De
esta forma se facilita la identificación de las diferentes instrucciones del lenguaje y por
lo tanto la lectura del código y la corrección de errores.

3. El código debe estar documentado. Como se indica en la sección 1.7, esto facilita la
comprensión del significado de cada porción de código.

En este texto se utiliza Java debido a que es un lenguaje de programación que permite
trabajar con los conceptos de la programación orientada a objetos, como son: objeto, clase,
mensaje, método, herencia, agregación, etcétera. El lenguaje Java facilita la escritura de
código seguro, debido principalmente a que no permite el manejo de apuntadores como lo
hace su antecesor C++, asigna y libera memoria de manera automática mediante un recolector
10 CAPÍTULO 1. PROCESO DE PROGRAMACIÓN

de basura que toma los objetos no referenciados para poder reutilizar esa memoria y es muy
estricto con el tipo de sus componentes (datos, métodos, etcétera), los cuales son verificados
en tiempo de compilación.
Los programas escritos en Java son independientes del tipo de computadora que se utilice
para trabajar. Tradicionalmente, los programas escritos en algún lenguaje de alto nivel se
tienen que compilar para cada tipo de computadora, en el caso de Java sólo es necesario
compilar cada programa una vez, el código resultante es ejecutado por un programa cono-
cido como máquina virtual (JVM, por las siglas en inglés de Java Virtual Machine); esta
JVM tiene un lenguaje propio denominado bytecode; para poder ejecutar programas en Java
primero deben ser traducidos a bytecode y entonces la JVM se encarga de ejecutarlos. En
cada computadora que se desee ejecutar programas escritos en Java debe estar instalada la
máquina virtual, y debido a que la máquina virtual no depende de ningún procesador ni
sistema operativo particular, se dice que Java es completamente portable.
Para crear los archivos que contienen el programa con la solución se utiliza un editor de
textos; cada archivo debe contener alguna de las clases que fue preciso crear para la solución
del problema. El archivo puede tener el nombre que se desee, las buenas prácticas de progra-
mación recomiendan que el nombre sea descriptivo de lo que contiene; la única restricción es
que termine con .java, es decir, debe tener extensión java. Por ejemplo, Directorio.java
podrı́a ser el nombre de un archivo con un programa en Java que implemente la clase que
maneja el directorio telefónico que se diseñó en la sección anterior.
Para compilar un programa en Java es necesario teclear la palabra javac seguida del nom-
bre del archivo con extensión java. Por ejemplo, para compilar el archivo Directorio.java
es necesario escribir la instrucción javac Directorio.java.
El compilador realiza la traducción del programa en Java a un programa en bytecode, es
decir, la traducción del lenguaje de alto nivel Java al lenguaje de la máquina virtual. Esta
traducción se almacena en un archivo con el nombre de la clase compilada y extensión .class
En el ejemplo que se presenta se generarı́a un archivo Directorio.class.
El archivo Directorio.class debe ser interpretado por la máquina virtual vı́a la instruc-
ción java Directorio, si es que la clase Directorio contiene el método main.1 Una vez
que se tiene el archivo interpretable éste puede ejecutarse en cualquier plataforma que tenga
la máquina virtual (figura 1.4).
Java incluye gran variedad de bibliotecas, una biblioteca es un conjunto de clases e
interfaces que se presentan compiladas al programador; por ejemplo, se tiene la biblioteca
java.lang donde están las clases para trabajar con funciones matemáticas y trigonométricas,
cadenas de caracteres, etcétera. También proporciona una biblioteca para las funciones de
entrada y salida, denominada java.io. Otras bibliotecas, o paquetes como se denominan
en Java, son: java.awt para desarrollo de interfaces gráficas, java.sql para trabajo con
bases de datos, etcétera. Existen muchas más, unas incluidas en Java y otras diseñadas por
1
Este método se trata a detalle en el capı́tulo 3.
1.5 DEPURACIÓN 11

javac Directorio.java
Compilador

Directorio.java

java Directorio
JVM

Directorio.class 01010111
01010111
0101111
Linux
Windows Mac OS

Figura 1.4 Proceso de compilación/ejecución de un programa en Java.

programadores para algún propósito particular. Java es extensible en el sentido de que es


posible extender las clases existentes mediante el uso de herencia y también mediante la
creación de bibliotecas propias.

1.5 Depuración
El siguiente paso en el desarrollo de un programa es la depuración que consiste en verificar
que el algoritmo y el programa sean adecuados. No importa qué tan bonito esté el programa,
si no produce los resultados deseados simplemente no sirve.
Depurar implica descubrir, localizar y corregir todos los errores que causen que un progra-
ma produzca resultados incorrectos o que no produzca ningún resultado. Esta tarea puede
consumir mucho tiempo, debido, principalmente, a que encontrar errores está directamente
relacionado con la claridad de la estructura de dicho programa.
Para depurar un programa es necesario compilarlo. Un compilador es un programa que
tiene dos funciones: verificar que el programa siga las reglas de sintaxis del lenguaje de
programación y traducir el programa original a uno equivalente en un lenguaje entendible
12 CAPÍTULO 1. PROCESO DE PROGRAMACIÓN

por la computadora. En la sección anterior se explicó el proceso de compilación/ejecución


de un programa en Java sin mencionar los posibles errores. Los lenguajes de programación
tienen un conjunto de reglas que especifican la sintaxis correcta de cada instrucción y de
todo programa.
Cada vez que un programa viola alguna de las reglas de sintaxis del lenguaje de progra-
mación (en este caso Java), el compilador lo indica mediante mensajes (especificando cuál
es error y dónde ocurrió)2 para que el programador pueda corregir tales violaciones y vuelva
a compilar el programa hasta quedar libre de este tipo de errores, conocidos como errores
de sintaxis. Estos errores son los más comunes y fáciles de detectar y corregir.
Una vez que el programa no tiene errores de sintaxis es posible que aún no pueda ejecutarse
debido a que no se encuentran todas las clases que se requieren, probablemente porque se
encuentren en bibliotecas o en otros archivos, tal vez en algún otro directorio. Estos errores
se conocen como errores de ligado y también son fáciles de detectar, pues el programador
recibe un mensaje indicando la falta cometida.
En la etapa de depuración cada componente del programa se diseña y prueba por separado
y luego se integran todos los componentes; este paso puede no ser tan sencillo como parece.
Una vez que el programa puede ser ejecutado es posible que produzca resultados incorrectos,
debidos a un mal diseño o una codificación incorrecta. El compilador verifica la sintaxis de
los programas, sin embargo, no puede detectar errores en la lógica de los mismos. Por
ejemplo, no puede determinar si al hacer un promedio en lugar de dividir entre el número de
datos se multiplica por tal número. La instrucción podrı́a ser sintácticamente correcta, pero
no es la operación adecuada para el resultado que se espera.
Estos errores son difı́ciles de localizar y de corregir puesto que no se genera ningún mensaje
indicando dónde ocurrió tal error, lo único que se tiene es un resultado incorrecto. Puede
ayudar a detectar tales errores ponerse en el papel de la computadora y sentarse con papel
y lápiz a hacer un seguimiento del programa instrucción por instrucción (esto se conoce
como prueba de escritorio). En programas de gran tamaño ésta es tarea difı́cil de realizar
adecuadamente. Otra forma de localizar los errores en la lógica de los programas es haciendo
uso de las instrucciones para escritura que proporciona el lenguaje e imprimir cualquier valor
intermedio que pueda ayudar a localizarlos. A fin de facilitar esta tarea, se debe tratar de
aislar un error en una porción especı́fica de un programa, para ello es recomendable imprimir
el valor de cada variable importante al inicio y al final de tal porción, de esta manera se
determina si las variables tienen los valores que corresponden al comportamiento esperado,
con lo que se puede determinar si ahı́ se tiene un error o no. Es necesario continuar esta
operación hasta que se aı́sle el problema en un área muy reducida y ası́ poder corregirlo.
Una vez corregido el error es recomendable quitar las instrucciones para el seguimiento del
programa.

2
No siempre es preciso el mensaje de error. En ocasiones un error puede producir muchos mensajes.
1.6 MANTENIMIENTO 13

Una forma común de detectar errores en la lógica de los programas es probar el programa
con datos muestra para los cuales los resultados correctos ya se conocen con anticipación.
Este proceso debe seguirse varias veces con diferentes conjuntos de valores. Se deben crear
casos de prueba que pasen por todas las partes del programa, para ello se requiere de un
conocimiento de las distintas trayectorias lógicas que existen en él. También se deben incluir
conjuntos de datos para casos especiales o poco usados, y conjuntos de datos para casos que
no tienen significado alguno y violan el planteamiento del problema.
En ocasiones un programa ya compilado puede producir errores que impiden que la ejecu-
ción del mismo termine adecuadamente. Estos errores se conocen como errores de ejecu-
ción, y al igual que con los de compilación generan mensajes para que el programador pueda
saber en dónde ocurrió tal error, aunque faltarı́a saber por qué se llegó a ese estado. Por
ejemplo, al realizar una división se tiene que el valor del divisor es cero, ası́ que es imposible
que se realice tal división.
Es importante programar “a la defensiva”, es decir, el programa debe estar preparado para
cualquier situación anómala. Se dice que un programa es robusto si produce una salida
significativa para cualquier conjunto de datos de entrada, sin importar qué tan incorrectos
o poco plausibles sean. Esta es una propiedad altamente deseable, puesto que permite al
programa mantener el control, producir mensajes con sentido y, en caso de ser posible,
mantenerse en ejecución. Por eso es conveniente escribir programas que contemplen todas
las posibles situaciones. La presencia de un error de ejecución generalmente indica que se
tiene un programa pobremente escrito.
Una vez que se determina y corrige un error es conveniente volver a probar con los casos
de prueba previos para asegurar que al corregir un error no se produjeron otros.

1.6 Mantenimiento
En los programas y trabajos escolares la tarea termina en el paso anterior, pero en la vida real
no es ası́. La etapa de mantenimiento consiste en supervisar la operación de un programa,
corregir cualquier error encontrado durante su uso continuo o efectuar modificaciones al
programa con el propósito de que realice más tareas o de manera diferente a las que se
tenı́an contempladas originalmente. El mantenimiento puede ser:

• Correctivo. Este tipo de mantenimiento se realiza cuando se detectan errores debidos


a cambios en la versión del software utilizado (que no es lo mismo que depuración), o
cuando se encuentran mejores algoritmos para alguna parte del programa.

• Adaptativo. En este caso, se efectúan cambios al programa para incorporar aspectos


no considerados en un principio. Los cambios pueden ser: agregar nuevas opciones de
trabajo a un programa, cambiar el formato de algunos datos (por ejemplo, cuando la
14 CAPÍTULO 1. PROCESO DE PROGRAMACIÓN

cantidad de dı́gitos de los números telefónicos cambia), o incorporar el procesamiento


de nuevos datos (cuando se acaba de crear la CURP), por ejemplo.

1.7 Documentación
La documentación es un proceso continuo que inicia una vez formulada la descripción del
problema a resolver y continúa durante el diseño de la solución, el desarrollo de algoritmos,
la codificación, la depuración, etcétera. La documentación es parte inherente del programa,
por tanto no tiene sentido hacerla una vez terminado el programa, su propósito es dejar
constancia de qué es lo que se hizo, cómo y por qué se hizo de la forma en que está el
programa, con la finalidad de facilitar su comprensión para futuras modificaciones al mismo.
La documentación se presenta a diferentes niveles:

• Documentación para el usuario. Ésta describe la interacción del programa desde el


punto de vista de los usuarios, es decir, se explica cómo usarlo. Se cubren aspectos tales
como el propósito del programa, el alcance del mismo, la forma de iniciar el programa,
los datos que requiere, la salida del mismo. También se puede ofrecer una explicación
de los mensajes que el programa puede producir, etcétera. En este documento, que
generalmente es un manual de uso, no es necesario incluir el funcionamiento interno
del programa.

• Documentación técnica. Esta documentación está dirigida a los programadores o di-


señadores que pueden tener necesidad de modificar el programa. En este documento se
registran las principales decisiones tomadas durante el diseño y por tanto debe hacerse
al mismo tiempo que se toman estas decisiones, antes de que se olviden las razones que
llevaron a tomarlas.

• Documentación de programas. Ésta es parte de la documentación técnica y está dirigida


a los programadores. Esta documentación se debe incluir al principio de cada clase,
como parte del archivo mismo, e incluye información general de clase: el objetivo de la
clase, autor, fecha, versión, si requiere de otra clase, etcétera. Antes de cada método se
debe incluir un mensaje indicando cuál es el propósito del mismo, los parámetros que
requiere, el valor que devuelve, los casos de excepción que se contemplan, etcétera.

Es importante registrar en la documentación todos los cambios que tenga el programa, ya


sea en diseño o en el mantenimiento. Una buena documentación es indispensable para que
el programa pueda ser utilizado de la mejor manera posible, para mantenerlo e incluso para
fines didácticos cuando se analizan programas ya hechos.
Como puede verse, el desarrollo de programas no es un proceso lineal sino más bien ite-
rativo y la documentación es una actividad paralela a todo el desarrollo. En el desarrollo
1.8 EJERCICIOS 15

de programas grandes, denominados sistemas, estas actividades pueden ser muy comple-
jas y cada una puede ser realizada por personas distintas, ya que este tipo de desarrollos
generalmente es realizado por equipos de trabajo.

1.8 Ejercicios
1. ¿Qué ventajas tiene hacer un diseño antes de codificar?

2. ¿Cuáles son los tres pasos en la metodologı́a de diseño?

3. ¿Qué es una clase?

4. ¿Cuál es la relación entre clase y objeto?

5. Dar tres ejemplos de clases y un par de objetos de cada clase.

6. ¿La estructura y el estado de dos objetos de la misma clase son siempre iguales? ¿Por
qué?

7. ¿Cuál es la importancia del bytecode de Java?

8. ¿Qué significa que un programa sea portable?

9. ¿Cuál es el objetivo de la JVM?

10. ¿Cómo se ejecuta un programa originalmente escrito en Java?

11. ¿Qué es un paquete en Java?

12. ¿Qué tipo de errores se pueden tener en un programa?

13. ¿Cómo se pueden detectar los errores en la lógica del programa?

14. Especificar los tipos de mantenimiento que existen.

15. ¿Cuál es el propósito de la documentación y en qué momento se hace?

16. Definir estructura y comportamiento para objetos de las siguientes clases: alumno,
automóvil, cuenta bancaria, teléfono celular, computadora.

17. Utilizando la metodologı́a presentada escribir un algoritmo para el funcionamiento de


una caja de una máquina de refrescos.
16 CAPÍTULO 1. PROCESO DE PROGRAMACIÓN

18. Una casa de cambio requiere un programa para llevar control de sus transacciones
diarias y ası́ saber, entre otras cosas, cuánto en billetes y en monedas de cada divisa
(dolares, dolares canadienses, pesos, euros, libras, coronas, etc.) tener cada dı́a para
cubrir la demanda de los clientes.
El primer paso de la metodologı́a de diseño consiste en encontrar los objetos requeridos
en la solución del problema. Supón que en este caso se requieren objetos de la clase
Moneda, lo que debes hacer es definir, de acuerdo al segundo paso de la metodologı́a,
la estructura de dicha clase.

19. Se requiere hacer un programa para jugar a los dados con la computadora. Las reglas
del juego son: se tiran dos dados y se suma el valor de la cara superior de cada uno.
Si la suma es 7 u 11 el jugador gana. Si la suma es 2, 3 o 12 el jugador pierde. Si
la suma es 4, 5, 6, 8, 9 o 10, ésta se convierte en los puntos del jugador quien, para
ganar, debe volver a tirar los dados si en esta tirada la suma de puntos es 7, o bien
la suma de esta tirada más lo acumulado da un total de 11 puntos gana, en otro caso
pierde. Encuentra una clase de objetos que se necesite para este programa y define su
estructura y comportamiento.

20. Escribir el escenario principal, el algoritmo, para el problema anterior.


Capı́tulo 2

Creación y uso de datos primitivos

Los datos que intervienen en la solución de un problema pueden ser: datos de algún tipo
definido por Java; objetos de clases definidas en Java y que pertenecen a bibliotecas (paque-
tes), ya sea proporcionadas por el lenguaje o definidas por otros programadores, y objetos
de clases definidas por el mismo programador. En este capı́tulo se describe la forma de crear
y utilizar datos de los tipos predefinidos por Java, primitivos, en expresiones que devuelven
un valor. Se describe la forma en que Java evalúa las expresiones de acuerdo con la priori-
dad y reglas de asociación de los operadores involucrados, ası́ como la forma trabajar con
expresiones que contengan elementos de diferente tipo.

2.1 Identificadores
En un programa en Java es preciso asignar un identificador (o nombre) a cada elemento que se
defina, sea éste clase, atributo, método, objeto, dato, etcétera. Un identificador se construye
como una sucesión de caracteres alfanuméricos (que pueden ir con acento o diéresis) que inicia
con letra o guión bajo ( ), aunque en general empiezan con letra. Ejemplos: rectángulo,
no, x25. En Java, son distintos los identificadores edad y Edad debido a la distinción que
a~
se hace entre mayúsculas y minúsculas.1
Es recomendable que se elijan nombres apropiados para los identificadores, pues de esta
manera ayudan a que el código sea comprensible para cualquier programador, incluyendo el
que escribe el programa. Es más claro tener un identificador cuyo nombre sea distancia a
uno x para representar un dato relacionado con el concepto distancia. Un identificador puede
estar formado por varias palabras; si es el caso, no se deben incluir espacios en blanco y a par-
tir de la segunda palabra cada una empieza con mayúscula. Por ejemplo: dineroRecibido,
promedioAnual, autoMásVeloz. Por convención, los únicos identificadores que empiezan
1
A fin de que todo mundo pueda ejecutar sus programas sin depender de la configuración del sistema en
que lo haga, en este texto no se usan acentos ni eñes en los identificadores empleados en los programas.

17
18 CAPÍTULO 2. CREACIÓN Y USO DE DATOS PRIMITIVOS

con mayúscula son los nombres de las clases y de las interfaces. Por ejemplo: Automóvil,
FiguraGeométrica.
Existe un conjunto de palabras que no pueden ser usadas como identificadores porque
tienen un significado especial para Java, cada una de éstas se denomina palabra reservada.
El conjunto de palabras reservadas se muestra en la tabla 2.1 y el significado de cada una
se explicará a lo largo del texto.

abstract else interface static


boolean extends long super
break false main switch
byte final native synchronized
case finally new this
catch float null throw
char for package throws
class if private true
continue implements protected try
default import public void
do instanceof return volatile
double int short while
Tabla 2.1 Palabras reservadas de Java.

2.2 Declaración de datos


Los tipos de datos definidos en Java, y denominados primitivos, son: tipo numérico (en-
teros o reales), tipo carácter y tipo Booleano. En la tabla 2.2 se listan los distintos tipos
primitivos que proporciona Java; en la primera columna está la palabra reservada empleada
para este tipo, y en la descripción se tiene el tipo de dato y el espacio que se requiere para
su almacenamiento. Por ejemplo, si se tiene un dato de tipo int ocupará 32 bits indepen-
dientemente de cual sea su valor. Con base en este espacio se puede determinar el rango de
valores permitidos, por ejemplo, para un dato numérico entero es [2n−1 , 2n − 1], donde n es el
número de bits que se tienen para ese tipo de dato. Se tiene esa gama para tipos numéricos
debido a que al realizar programas grandes es indispensable optimizar el uso de recursos y
esto permite tener números cuya representación ocupa poco espacio en memoria y permiten
expresar un rango de valores pequeño, hasta uno grande, o bien, en el caso de números no
enteros permite tener números con varios dı́gitos decimales.
Además de los datos de tipo primitivo, en Java se tiene a los objetos como el tipo de
datos más importante. Para poder manipular los objetos se tiene a las referencias. Una
2.2 DECLARACIÓN DE DATOS 19

Tipo Descripción
byte Entero de 8 bits
short Entero de 16 bits
int Entero de 32 bits
long Entero de 64 bits
float Real en 32 bits
con 7 dı́gitos en la parte decimal
double Real en 64 bits
con 15 dı́gitos en la parte decimal
char Carácter en 16-bits (Unicode)
boolean Booleano
Tabla 2.2 Tipos primitivos de Java.

referencia contiene la dirección en memoria, donde se encuentra el objeto con el que se


quiere trabajar, no contiene el objeto en sı́. Es equivalente a la ficha de colocación de un
libro en una biblioteca, con ella sólo se tiene la ubicación del libro pero no el libro. No
existe una palabra reservada para las referencias. En el siguiente capı́tulo se explica cómo
utilizarlas.
En todo programa es necesario especificar de antemano los datos con los que se va a
trabajar, para que el compilador reserve espacio para cada uno de ellos y para que se puedan
manipular en el programa. Esta especificación se conoce como declaración.
Si en un problema se tiene un dato que se sabe puede cambiar de valor, es recomendable
almacenarlo en una variable; por ejemplo, se puede declarar una variable para contener la
edad de una persona. Un dato constante es aquel cuyo valor permanece fijo durante toda
su vida, por ejemplo la fecha de nacimiento de una persona.
Las variables se declaran escribiendo el tipo del dato seguido de su identificador y al final
un punto y coma. Si hay más de una variable del mismo tipo se puede escribir el tipo de ellas
seguido del identificador de cada una de tales variables, separados por comas y al final un
punto y coma. Esto puede ser en la misma lı́nea o en diferentes lı́neas, la declaración termina
con un punto y coma. A continuación se presentan ejemplos de declaración de variables:

int edad;
double altura;
char letra;
boolean terminó;

El identificador para cada variable debe ser único para evitar problemas al momento de tratar
de trabajar con él, en caso de no ser único el compilador lo indica mediante un mensaje de
error. Todos los datos, incluyendo los objetos, deben estar declarados antes de su uso.
20 CAPÍTULO 2. CREACIÓN Y USO DE DATOS PRIMITIVOS

El contenido de cada variable es desconocido al momento de la declaración (figura 2.1)2 .

edad peso salario altura


? ? ? ?

Figura 2.1 Variables al momento de su declaración.

En ocasiones es conveniente definir un valor inicial para una variable en el momento de su


declaración. Java lo permite de la siguiente forma: nombreDeTipo identificador = valor;. Por
ejemplo, double altura = 0.48; con lo cual la variable altura ya no tiene un valor des-
conocido e inservible, tiene el valor asignado por el programador (figura 2.2). Es importante
tomar en cuenta que en todo momento una variable sólo tiene un valor, el más reciente que
se le haya asignado, no se lleva registro de los valores anteriores. Si se desea guardar un valor
anterior, se debe crear otra variable para tal fin y es responsabilidad del programador llevar
el registro del valor anterior.

altura
0.48

Figura 2.2 Variable con valor inicial en su declaración.

La declaración de constantes es similar a la declaración de variables excepto que el tipo de


la misma se precede de la palabra reservada final. Por ejemplo:

final int IVA = 15;

Con lo cual se está definiendo una constante denominada IVA cuyo valor es 15. Es obliga-
torio asignar un valor a la constante antes de poder usarla, aunque no necesariamente en
la declaración. Si se intenta cambiar el valor de una constante el compilador lo reporta,
garantizando ası́ la integridad de la constante. Es costumbre especificar el nombre de cada
constante sólo con mayúsculas3 para poder distinguirlas fácilmente dentro de un programa.
Además de facilitar la legibilidad (es más fácil saber que se trata del cálculo del IVA, si se
tiene una constante con ese nombre que ver el número 15) las constantes también ayudan en
cuestiones de consistencia, por ejemplo si el IVA cambia de 15% al 16%, en lugar de buscar
en todos los sitios en donde aparece el número 15 y reemplazarlo (con posibles errores), sólo
se requiere cambiar el valor en el sitio en donde se define la constante.
2
Aunque todas las cajas se dibujan del mismo tamaño, en memoria pueden ser de diferente tamaño.
3
Si la constante lleva más de una palabra se separan con guión bajo.
2.2 DECLARACIÓN DE DATOS 21

Es posible tener constantes sin que se hayan declarado previamente, por lo tanto no tienen
asociado un identificador. Estas constantes se conocen como literales debido a que su nombre
es la representación literal de su valor. Las literales pueden ser de cualquiera de los siguientes
tipos:

Entero. Número entero con o sin signo. Ejemplos: 8, 25, -78654. Se debe cuidar que no
empiecen con el número cero pues esto representa cantidades enteras en base 8 que son
diferente de los de base 10 a los que usualmente se está acostumbrado, por ejemplo, 023
es distinto de 23. En los números expresados en base 8 no se pueden usar los dı́gitos 8
ni 9.
Si se desea tener una literal de tipo long se debe terminar el valor de ésta con una
letra ele minúscula o bien mayúscula. Es recomendable usar la letra ele mayúscula (L)
para evitar errores al confundir la minúscula con el número 1. Por ejemplo, 45678954L
es una literal de tipo long.

Real. Números con o sin signo y con punto decimal. Ejemplo: 34567.534. Estos números
también pueden expresarse en notación cientı́fica, el ejemplo anterior se puede escribir
como: 34567534e-3. No se deben poner comas ni espacios para separar las cantidades.
Java maneja estas literales como datos de tipo double. Si se desea que se consideren
de tipo float se debe terminar con una letra f o bien F. Por ejemplo, 47.5F es una
literal de tipo float.

Carácter. Cualquier sı́mbolo escrito entre apóstrofos. Ejemplos: ’a’, ’4’, ’ ’, etcétera.
Aquı́ surge un pequeño problema, ¿cómo definir la literal que es un apóstrofo? La
respuesta es: empleando el carácter de escape que es la diagonal invertida (\), este
carácter de escape sirve no sólo para el apóstrofo sino para los caracteres no imprimi-
bles. Ejemplos: el apóstrofo ’\’’; la comilla ’\"’; el salto de lı́nea ’\n’; el tabulador
’\t’; el carácter de escape ’\\’, etcétera.
El lenguaje Java utiliza código Unicode para representar internamente los caracteres de
cualquier alfabeto, en particular se pueden tener letras acentuadas y la eñe del español.
Las literales de tipo carácter Unicode se pueden escribir como un número de cuatro
dı́gitos que representa el valor hexadecimal de ese carácter en el código Unicode, estos
dı́gitos deben ir precedidos de \u y todo entre apóstrofos. Por ejemplo, ’\u03A6’ es
código de la letra π del alfabeto griego.
Si se desea tener más de un carácter, entonces se deben colocar comillas en lugar de
apostrofes y se dice que se tiene una cadena de caracteres. Ejemplo: "Soy una cadena
con e~nes, comillas, \" y salto de lı́nea \n"

Booleano. Los valores lógicos verdadero y falso, denotados en Java como: true y false,
respectivamente.
22 CAPÍTULO 2. CREACIÓN Y USO DE DATOS PRIMITIVOS

Referencia. En este tipo de dato sólo hay una literal, null. El uso de referencias se explica
con mayor amplitud en el siguiente capı́tulo, aquı́ se incluye null por tratarse de una
literal.

2.3 Uso de datos (operadores)


El tipo de dato determina los posibles valores que puede tener un dato, ası́ como las opera-
ciones que se pueden hacer con él. Debido a que los datos de tipo primitivo no son objetos
no existen métodos asociados a ellos; la única forma de trabajar con estos datos es mediante
los operadores que existen para ellos.
Un operador y sus operandos constituyen una expresión. El propósito de una expresión
es calcular un valor (el resultado de la operación). El resultado de una expresión se puede
utilizar como operando de otras operaciones (si el tipo de valor devuelto es compatible). De
acuerdo al operador que se utilice, la expresión puede ser una expresión aritmética, lógica
o de asignación. Los operandos pueden ser literales, constantes, variables o resultados de
llamadas a métodos.

2.3.1 Operador de asignación


El operador de asignación se utiliza para modificar el valor de cualquier variable, este ope-
rador se expresa con el signo igual (=), éste se puede utilizar con cualquiera de los tipos de
datos descritos en las secciones previas. Del lado izquierdo del operador de asignación debe
estar el identificador de una variable o de una constante y del lado derecho una expresión.
El resultado de la evaluación de la expresión es el valor que se asigna al dato especificado en
el lado izquierdo del operador de asignación.
A continuación se muestran algunos ejemplos, en ellos se incluye al final de cada instrucción
un comentario que especifica la acción realizada; una forma de especificar un comentario es
empezar con dos diagonales (//) y lo que va de ahı́ al final de la lı́nea es el comentario.
Los comentarios son ignorados por el compilador, pues no son parte funcional del algo-
ritmo, su objetivo es facilitar la comprensión del código, por tanto deben ser descriptivos,
no especificar cosas obvias. Los comentarios presentados en este capı́tulo sı́ especifican cosas
obvias, porque se están utilizando para explicar el significado de cada lı́nea. En el siguiente
capı́tulo se expondrá más sobre el uso de los comentarios.

int a = 4, b = 3, c, d; //Declaración de 4 variables. Dos con valor inicial


// y dos sin valor inicial.
c = 8; // Asignación del valor 8 a la variable c
d = b; // Asignación del valor 3 a la variable d
c = a*a + b*b; // Asignación del valor 25 a la variable c
2.3 USO DE DATOS (OPERADORES) 23

El valor de una variable cambia sólo si ésta aparece del lado izquierdo del operador de
asignación. Cuando está del lado derecho sólo se toma una copia de su valor para realizar
la operación en la que aparece la variable.4 En la figura 2.3 se tiene en cada renglón el valor
de las variables después de la ejecución de cada lı́nea del código del ejemplo anterior. Notar
que en el tercer y cuarto renglones no se modifica el valor de las variables a ni b.

a b c d
4 3 ? ?
4 3 8 ?
4 3 8 3
4 3 25 3

Figura 2.3 Uso del operador de asignación.

Java no permite asignar un valor a una variable si no es del mismo tipo de ella, por lo
tanto, la expresión del lado derecho y el tipo de la variable (o constante) del lado izquierdo
deben ser compatibles.
En ocasiones se desea asignar el mismo valor a diferentes variables, en ese caso se puede
hacer uso del concepto de asignación múltiple, que consiste en tener las variables sepa-
radas por el operador de asignación y al final la expresión que dará valor a las variables.
A continuación se presenta un ejemplo, el resultado de las instrucciones puede verse en la
figura 2.4.

int a= 8, b = 3, c = -8, d = 25;

a = b = c = 0;
a = b = c = d *2;

a b c d
8 3 −8 25
0 0 0 25
50 50 50 25

Figura 2.4 Uso del operador de asignación múltiple.

4
La excepción a esta regla es cuando en el lado derecho de la variable se usa el operador de autoincremento
o autodecremento, como se verá en la sección 2.3.2.
24 CAPÍTULO 2. CREACIÓN Y USO DE DATOS PRIMITIVOS

En el caso de asignación múltiple no se permite que en medio aparezcan expresiones. Por


ejemplo, a = b+1 = 25; es una instrucción sintáctica y semánticamente incorrecta, puesto
que el valor 25 no puede asignarse a la expresión b+1 ni a ninguna otra.

2.3.2 Operadores aritméticos


Con los datos numéricos es posible realizar las operaciones aritméticas básicas utilizando los
operadores representados en la tabla 2.3. Estos operadores requieren de dos operandos para
trabajar, de ahı́ que se diga que son operadores binarios.

Operador Descripción
+ Suma
- Resta
* Multiplicación
/ División
% Residuo de la división
Tabla 2.3 Operadores aritméticos.

Estas operaciones están definidas de tal forma que si se tiene algún operando que no sea
de tipo entero el resultado será de tipo real, en caso contrario será un entero. Ejemplos:

int divisor = 5;
int dividendoEntero = 39;
double dividendoReal = 39.0;

dividendoEntero / divisor; // devuelve 7


dividendoReal / divisor; // devuelve 7.8
dividendoEntero % 4; // devuelve 3

Además de los operadores binarios presentados con anterioridad se tienen los operadores
unarios de la tabla 2.4.

Operador Descripción
+ Más unario
- Menos unario
++ Autoincremento
-- Autodecremento
Tabla 2.4 Operadores unarios.
2.3 USO DE DATOS (OPERADORES) 25

El efecto de la aplicación del operador de menos unario (o de negación) a un operando es


cambiar el signo del valor resultante. Ejemplos:
final int FIN = -1;
int resultado = -(a * b/ c);
int resultadoPositivo = -resultado;

El operador unario + no produce ningún cambio en el signo del valor resultante ası́ que
rara vez se utiliza.
El operador de autoincremento se utiliza para incrementar en una unidad el valor de la
variable a la que se aplica. Ejemplos:
int veces = 15;
veces ++; // Después de esta instrucción el valor de veces es 16

Es posible colocar el operador de autoincremento antes o después de la variable que se desea


incrementar. Si aparece del lado derecho de la variable se conoce como post-incremento, en
otro caso se conoce como pre-incremento.
Si la instrucción que se desea evaluar consiste sólo en incrementar la variable, el resultado
de ésta es el mismo, independientemente de donde aparezca el operador. Sin embargo, si
una variable con autoincremento es un operando dentro de una expresión aritmética más
compleja, el resultado depende del lugar en donde aparezca el operador de autoincremento.
Si está del lado derecho, se toma el valor original de la variable para evaluar la expresión
y después se incrementa la variable. Si aparece del lado izquierdo, primero se incrementa
la variable y luego se toma ese valor incrementado para continuar con la evaluación de la
expresión. Ejemplos:
int veces = 6;
++veces + 4; // El valor de la expresión es 11 y el de veces 7.

veces = 6;
veces++ + 4; // El valor de la expresión es 10 y el de veces 7.

Si se aplica el operador de autoincremento sobre variables de tipo carácter se obtiene el


siguiente carácter según el orden lexicográfico definido en el código Unicode (véase sección
2.3.4). Todo lo explicado en esta sección para el operador de autoincremento es válido tam-
bién para el operador de autodecremento, el cual decrementa en una unidad la variable
correspondiente.

2.3.3 Operador de asignación compuesto


Frecuentemente se desea actualizar el valor de una variable a partir del valor que ya posee,
en estos casos en el lado izquierdo y en el derecho de la asignación se tiene la misma variable;
por ejemplo, la expresión a = a + 5; suma cinco unidades al valor que tenga la variable a.
26 CAPÍTULO 2. CREACIÓN Y USO DE DATOS PRIMITIVOS

Es posible abreviar estas expresiones (variable = variable operador expresión) utilizando el


operador de asignación compuesto como sigue: variable operador = expresión. Ejemplos:

int i = 90, j = 5;
i /= 45; // Equivalente a i = i / 45;
i *= j + 5; // Equivalente a i = i * (j + 5);

2.3.4 Operadores de relación


Los operadores de relación trabajan con datos de cualquier tipo y devuelven como resultado
un valor Booleano (true, false), de acuerdo con la relación de orden entre los valores
comparados. Los operadores de relación se presentan en la tabla 2.5.

Operador Descripción
< Menor que
<= Menor o igual que
== Igual que
>= Mayor o igual que
> Mayor que
!= Diferente de
Tabla 2.5 Operadores de relación.

Aquı́ es conveniente aclarar que se debe ser cuidadoso al hacer una comparación de igualdad
o desigualdad con números reales por el aspecto de precisión de la representación de éstos
en la computadora. Por ejemplo, 1.0/3 no es igual a .333
Es normal que conozcamos la relación de orden entre dos números, pero ¿qué significa
entre caracteres? Esta relación está dada por el orden en que aparecen en la tabla del código
Unicode correspondiente, si se trata de letras, el orden es el alfabético y son menores las
mayúsculas a las minúsculas, y antes de las letras se encuentran los caracteres numéricos.
Pero entre estos tres grupos están los otros caracteres.

2.3.5 Operadores lógicos


Los operadores lógicos trabajan con datos o expresiones Booleanas y devuelven un valor
Booleano (true, false). Los operadores lógicos se presentan en la tabla 2.6. Los primeros
dos son operadores binarios, el otro es un operador unario.
El resultado de la aplicación de los operadores lógicos se presentan en las llamadas tablas
de verdad que contienen todas las posibles combinaciones de valores involucrados en una
operación lógica (tabla 2.7).
2.3 USO DE DATOS (OPERADORES) 27

Operador Descripción
&& Conjunción
|| Disyunción
! Negación
Tabla 2.6 Operadores lógicos.

&& true false || true false !


true true false true true true false true
false false false false true false true false
Tabla 2.7 Tablas de verdad.

Para determinar el resultado de una expresión lógica o condicional no siempre es necesario


evaluar todos los operandos de ésta, en cuanto se tiene la certeza del resultado se suspende la
evaluación del otro operando. Este concepto se conoce como evaluación con cortocircuito,
y se basa en las siguientes reglas:

• La evaluación del && se detiene cuando encuentra un valor falso, porque en ese momento
se sabe sin lugar a dudas que el resultado será false, independientemente del valor
del siguiente operando.

• La evaluación del || se detiene al encontrar un valor verdadero, porque en ese momento


se sabe que, independientemente del valor del segundo operando, el resultado será true.

El trabajar de esta manera permite optimizar la ejecución del código y además ayuda a
evitar errores muy comunes como se verá en los siguientes capı́tulos.

2.3.6 Operador + para cadenas


No existe un tipo de datos primitivo para manejo de cadenas de caracteres, sin embargo
es común trabajar con cadenas. Para concatenar cadenas de caracteres, es decir, pegar una
después de la otra, se puede utilizar el operador binario de suma +. Por ejemplo:

"Anita "+"lava "+"la "+"tina" // Produce "Anita lava la tina"

Este operador se puede aplicar para concatenar cadenas con el valor de datos de cualquier
tipo. Por ejemplo, la cadena "La calificación es "+ calificación da como resultado
La calificación es 10 si la variable calificación tiene valor 10. La justificación de este
comportamiento se verá en el capı́tulo 7. Ejemplo:
28 CAPÍTULO 2. CREACIÓN Y USO DE DATOS PRIMITIVOS

int a = -8, b = 25;

"a = "+ a + ", b = "+ b +" y la suma es " + (a+b)


// Produce a = -8, b = 25 y la suma es 17

En la cadena anterior, si al final no se ponen los paréntesis, el resultado cambia: a = -8,


b = 25 y la suma es -825 porque concatenarı́a el valor de a (-8) con el de b (25).

2.3.7 Precedencia y asociación de operadores


Cuando se evalúan expresiones sencillas como a + b + c, no existe duda de cuál será el
resultado obtenido. Sin embargo, cuando se mezclan operadores las cosas se pueden compli-
car, pues el resultado puede depender del orden en que se realicen las operaciones, situación
totalmente inaceptable debido a que se espera que el resultado de una expresión siempre sea
el mismo. Para apreciar este problema se presenta el siguiente fragmento de programa:

int a = 6, b = 3, c = 3, d;
int d = a + b /c;

Después de la segunda lı́nea, la variable d puede tener como valor un 3 si el resultado de la


suma se divide entre el valor de c, o bien puede tener 7 si primero se hace la división b/c y
al resultado se suma a. Para evitar esta ambigüedad, se tiene el concepto de precedencia
de operadores, con el cual se determina el orden de evaluación de las operaciones dentro
de una expresión.
En la tabla 2.8 se presentan los operadores de mayor a menor precedencia. En una expresión
se realizan primero las operaciones que tienen mayor prioridad, es decir, las que estén más
arriba en la tabla.
De acuerdo con la tabla, el resultado de la operación inicial de esta sección es 7, si se desea
que sea 3 se debe escribir (a+b)/c, porque el operador con mayor precedencia es el de
agrupación representado por los paréntesis.
¿Qué pasa cuando los operadores tienen igual precedencia y la operación no es conmuta-
tiva? Por ejemplo, si se tiene que evaluar la expresión a * b % c, ¿qué operación se hace
primero, la multiplicación o el residuo? Para saber la respuesta se usa la última columna
de la tabla 2.8 que indica el orden de ejecución en operadores de igual precedencia (o aso-
ciatividad), si es de izquierda a derecha o viceversa. Debido a que la asociatividad de los
operadores de multiplicación y residuo es de izquierda a derecha primero se hace la multi-
plicación y luego el módulo o residuo. En todo caso, si se tiene duda, es recomendable poner
entre paréntesis las operaciones para evitar ambigüedades.
2.4 CONVERSIÓN DE TIPO 29

Operador Descripción Asoc


() [] . paréntesis y punto I
++ -- Incremento y decremento (Post) I
++ -- Incremento y decremento (Pre) D
! Negación D
- + Menos y más unarios D
new Operador para crear objetos D
(tipo) Conversión explı́cita de tipo D
* /% Multiplicación, división, residuo I
+ - Suma, resta I
instanceof <, <=, >, >= Operadores de relación I
== != Operadores de igualdad I
&& Conjunción I
|| Disyunción I
?: Operador condicional D
= op = Asignación D
Tabla 2.8 Precedencia de operadores.

2.4 Conversión de tipo


Es frecuente tener expresiones que involucran operandos de diferente tipo. Algunos tipos de
datos son compatibles con otros pero no siempre es el caso, por ejemplo, los enteros son
compatibles con los caracteres pero no con otros tipos de datos, como los reales. Cuando los
tipos no son compatibles no se puede evaluar la expresión, por lo que es necesario convertirlos
a algún tipo compatible para todos. La conversión del tipo de un dato se puede hacer de
manera implı́cita o bien explı́cita.
La conversión implı́cita ocurre al trabajar con datos de tipos compatibles y el tipo de
la expresión tiene un rango mayor de valores que el tipo de la variable a la que se asignará el
valor. De acuerdo con la tabla 2.2, al tener una expresión que incluya datos de tipo entero y
datos de tipo real el resultado será un número real. Los booleanos no se incluyen en esta lista
porque no es posible convertirlos a ningún otro tipo ni de otro tipo convertir a booleanos.
La tabla 2.9 muestra el tipo de resultado en caso de que se definan las variables como
sigue:

char c; double d; float f; int i;


long l; short s;

Al hacer una asignación no se permite que un tipo de mayor prioridad se asigne a uno de
menor prioridad, porque se puede perder información. Por tanto:
30 CAPÍTULO 2. CREACIÓN Y USO DE DATOS PRIMITIVOS

Expresión Tipo
c - s / i int
c + 3 int
c + 5.0 double
f * 7 - i float
7 * s * l long
d + s double
Tabla 2.9 Conversión implı́cita de operadores.

int i;
float f;

i = 3.8; //Incorrecto porque un número real tiene mayor prioridad


//que un número entero y se perderı́a la parte fraccionaria.
f = 4.5; //No se permite porque el double tiene más prioridad que el float.
f = 4.5f; //Válido porque ambos son del mismo tipo.
f = 25; //Válido porque un entero tiene menor prioridad que uno real.

Si la conversión implı́cita no es factible y aun ası́ se requiere trabajar con datos de distinto
tipo, se debe usar la conversión explı́cita (el término en inglés es cast), indicando el tipo
del resultado entre paréntesis antes del nombre de la variable. Por ejemplo, si se tiene la
expresión int i = 2 + 3.5; causará un error, pues el tipo del lado derecho es incompatible
con el lado izquierdo. Para almacenar un valor real en una variable de tipo entero se puede
hacer int i = (int) 3.8; en este caso el valor que se almacena es la parte entera de la
expresión.
Es importante notar que es posible perder precisión, como en el siguiente ejemplo.
long largo = 9998887654321L;
int entero = (int)largo; //Trunca a un valor de 32 bits (longitud de
// datos enteros) que es igual a 20378923

Por tanto, es importante hacer con cuidado las asignaciones entre elementos de distinto
tipo para evitar pérdida de información y trabajar, quizá sin notarlo, con datos incorrectos.
La conversión explı́cita es útil cuando es necesario tratar, de manera temporal, un valor
como si fuera de otro tipo. Ejemplo, al dividir dos enteros si se desea obtener la división no
entera, podrı́a hacerse lo siguiente:
double resultado;
int total, nDatos;

resultado = (double) total/ nDatos;


2.5 EJERCICIOS 31

Si no se hubiera hecho la conversión explı́cita se habrı́a realizado la división entera, y no


es lo deseado en este caso particular.

2.5 Ejercicios
1. ¿Cuántos tipos de datos hay para representar números enteros? ¿En qué difieren?

2. ¿Cuál es el objetivo de una declaración?

3. Si se tiene la declaración final int UNO = 1; ¿es posible tener la siguiente instrucción
int negativo = -UNO;?

4. Indicar tres formas distintas de incrementar en 1 el valor de una variable.

5. ¿Es correcta la siguiente lı́nea de código? ¿Por qué?


int posición // Indica la posición del objeto ;

6. ¿Existe alguna diferencia entre tener bancario y "bancario"?

7. ¿Cuáles de las siguientes cadenas no pueden ser identificadores en Java? ¿Por qué?

a) java.awt.Graphics f) void
b) rayos-x g) Void
c) _123 h) 2dı́as
d) valor calculado i) segundaBase
e) valorCalculado j) x

8. ¿ Es verdadero o falso que en Java 1/3 es distinto de 1.0/3.0? ¿Por qué?

9. ¿Cuándo es verdadera la siguiente ecuación (a/b)*b + (a %b) == a?

10. Dadas las siguientes declaraciones:

int x, y;
float z =3.1313f;
double w= 3.1212;
boolean verdad = true;
long l = 45L, m;

evaluar las siguientes expresiones siempre y cuando sea posible. En caso de no ser
posible la evaluación justificar la respuesta.
32 CAPÍTULO 2. CREACIÓN Y USO DE DATOS PRIMITIVOS

a) x = 6; f) verdad = 1;
b) y = 1000; g) z = 3.1416;
c) y = 2.33333; h) m = x * 250;
d) 25++; i) (x+y)++;
e) w = 175,000; j) y = l;

11. Volver a escribir las siguientes expresiones usando asignación combinada, si es posible.

a) x = x + 3*y;
b) x = -b + x + 87;
c) x = x * y -10;
d) x = (3-y) * x;
e) x = (3-y) / x;

12. Escribir el valor que tiene la variable complejo después de ejecutar cada instrucción
del siguiente código.

final int FACTOR = 10;


int a = 1;
int b = 2;
int complejo = a + b * 3 - a * b + 3;

13. Escribir el valor que tienen las variables c y d después de ejecutar cada instrucción del
siguiente código.

final int FACTOR = 10;


int a = 4;
int b = 6;
int c = (a + b) * FACTOR;
int d = a + b * FACTOR;

14. Si se tiene una variable real val que tiene su valor entre 0 y 10 y se desea calcular
un valor proporcional entre 0 y 100. Es decir, si val = 7 se espera que el valor de la
variable calculado sea 70, ¿cuál de las dos instrucciones siguientes es la correcta?

double calculado = (val /10) * 100;


double calculado = (val /100) * 10;

15. Escribir expresiones en Java para las siguientes expresiones matemáticas.

(a) a3 (a + 1)(a − 7)
1
(b) 1+x2
2.5 EJERCICIOS 33

1 ×t2
(c) q = ( td−k ) + t2

16. Definir constantes o variables para los siguientes datos.

(a) El lado de un cuadrado de longitud 4.5 metros.


(b) Cien metros.
(c) El número de llantas de una bicicleta.
(d) Un número primo.
(e) La raı́z cuadrada de 2.
(f) Una interrogación.
(g) El área de un cuadrado.

17. Si el costo de la gasolina es de $7.50 por litro, escribir una expresión que defina el valor
de la variable real total como el total a pagar por n litros de gasolina.

18. Escribir expresiones lógicas para expresar que:

(a) a es mayor que b y que c.


(b) a y b son ambas negativas.
(c) ni a ni b son negativas.
(d) a es igual a b pero diferente de c.
(e) tanto a como b y c son iguales.
Capı́tulo 3

Creación y uso de objetos

Al escribir un programa en Java es común que se tenga la necesidad de crear varios objetos.
Éstos interactuarán entre sı́ mediante el envı́o y recepción de mensajes. Una vez que un
objeto termina el trabajo para el cual fue creado se recicla la memoria utilizada por él, y de
esta manera puede ser usada por otros objetos. La creación, uso y eliminación de un objeto
se conoce como ciclo de vida de un objeto. En este capı́tulo se muestra cómo trabajar con
objetos, ya sea de clases definidas en los paquetes de Java o bien de clases definidas por el
programador. El trabajo con objetos incluye crearlos, interactuar con ellos a través del envı́o
y recepción de mensajes, compararlos, eliminarlos e imprimirlos, entre otras operaciones.

3.1 Creación de objetos


El primer paso para trabajar con un objeto en Java es su creación. Para crear un objeto
se requiere utilizar el operador new seguido del nombre de la clase a la que pertenecerá tal
objeto.1 Por ejemplo, si se sabe que existe la clase Automovil y se desea crear un objeto de
ella, se debe escribir la instrucción new Automovil() .
La aplicación del operador new crea un objeto, reservando en memoria el espacio necesario
para mantener su estado, y devuelve la referencia al objeto creado. Una referencia es un
tipo de dato usado para trabajar con objetos. El valor que se almacena en una variable o
constante de tipo referencia no es el objeto en sı́, es la dirección o referencia al objeto; de
ahı́ que, en las figuras, las referencias se representan con flechas. La confusión puede ser
debida a que es común referirse al objeto con el nombre de la referencia. La creación de un
objeto puede pensarse de manera análoga al hecho de inflar un globo con gas y atarle un
cordón (figura 3.1). La referencia es el cordón atado al globo no es el globo.

1
Recordar que todo objeto pertenece a una clase.

35
36 CAPÍTULO 3. CREACIÓN Y USO DE OBJETOS

... Objeto de la clase Automovil

Referencia al objeto

Figura 3.1 Objeto y referencia a él.

En la figura 3.2 se muestra la diferencia entre un tipo primitivo y una referencia. En la parte
izquierda se tiene un dato de tipo primitivo, en este caso un número entero, denominado
edad cuyo valor es 35. En la parte derecha de la figura se tiene un dato de tipo referencia
denominado saludo cuyo valor es la dirección (flecha) a un objeto.

edad 35 saludo

HOLA

Figura 3.2 Variable para dato de tipo primitivo y variable para referencia.

Si la referencia devuelta por el operador new no se asigna a ninguna variable, el objeto sólo
se crea y no es posible darle un uso posterior. (El globo se infla pero no se fija a nada ası́ que
vuela y nadie puede jugar con él). Por esta razón, antes de crear un objeto, es necesario
declarar una variable para almacenar la referencia a él. La declaración de una referencia es
similar a cualquier dato primitivo: se especifica el tipo de dato seguido de un identificador y
posiblemente un valor inicial. La diferencia está en que el tipo de dato debe ser el nombre de
una clase. Por ejemplo, Automovil miAuto; (parte superior de la figura 3.3), con lo cual se
asigna espacio para una referencia a un objeto de la clase Automovil y sin valor conocido.
En la analogı́a, la variable de tipo referencia es la mano del niño que sostiene el cordón del
globo, con la restricción de que en esa mano puede haber máximo un cordón.
Ejemplo 3.1. Código para la creación de un objeto de la clase Automovil en Java.
Automovil miAuto; // Declaración de una referencia
... // Código
miAuto = new Automovil(); // Creación del objeto
... // Más código

Al crear un objeto se hace con el estado inicial definido en la clase. Por ejemplo, con la
instrucción new Automovil(); se crea un automóvil estándar de 4 puertas como valores
predefinidos. (Ver parte media de la figura 3.3).
3.1 CREACIÓN DE OBJETOS 37

En ocasiones, es preciso proporcionar valores para crear este estado inicial. Estos valores se
especifican entre los paréntesis que están después del nombre de la clase. Por ejemplo, si se
desea un automóvil con 2 puertas y automático se debe hacer new Automovil(2,false);.
El último paso en la creación de un objeto, es la asignación de la referencia del objeto
creado a la variable declarada para ese fin, como se muestra en la parte baja de la figura 3.3.
Al igual que con los datos primitivos, es posible asignar un valor a una referencia en el
momento de su declaración, de esta manera se pueden tener los tres pasos anteriores en una
sola instrucción; por ejemplo: Automovil miAuto = new Automovil();.

Automovil miAuto; Declaración de referencia


?
miAuto
Creación del objeto
Automovil miAuto; ?
miAuto
new Automovil(); puertas 4
estandar t
Asignación de referencia
miAuto puertas 4
miAuto = new Automovil(); estandar t

Figura 3.3 Creación de un objeto en Java.

Una referencia al igual que cualquier variable sólo tiene un valor a la vez. Sin embargo, un
objeto puede tener varias referencias, esto es el equivalente a que alguien tenga su nombre
y varios apodos, o bien, en la analogı́a con los globos, a un globo con varios cordones. Por
ejemplo, si se tienen las siguientes instrucciones:

Automovil miAuto = new Automovil();


Automovil chiquito = miAuto;
Automovil rebelde = miAuto;

se tiene el efecto gráfico mostrado en la figura 3.4. En las dos últimas instrucciones no se
crean objetos, debido a que no se usa el operador new, sólo se crean referencias al objeto
existente. En este caso se dice que chiquito y rebelde son alias de miAuto.
La palabra null está reservada por Java para asignar a las variables de tipo referencia
cuando se desea especificar que no están ligadas a ningún objeto. Por ejemplo, la instrucción
miAuto = null; indica que no se tiene asignada la referencia a ningún automóvil en la
variable llamada miAuto, ya sea porque no se ha creado ninguno o bien porque ya no se
desea usar más el objeto referenciado. (Véase figura 3.5). En caso de que se asigne el valor
38 CAPÍTULO 3. CREACIÓN Y USO DE OBJETOS

miAuto puertas 4
estandar t
chiquito

rebelde

Figura 3.4 Varias referencias a un objeto.

(miAuto = null;)
miAuto
puertas 4
chiquito estandar t
rebelde

Figura 3.5 Asignación de valor nulo a una referencia.

null a la única referencia a un objeto éste ya no puede accesarse más y se deja a disposición
del recolector de basura.
En resumen, el valor de una referencia puede ser el valor de otra variable (o constante) de
tipo referencia, el resultado de la creación de un objeto o bien el valor constante null.

3.2 Uso de objetos


Una vez creado un objeto se le pueden enviar mensajes, ya sea para conocer su estado,
modificar su estado, realizar algún cálculo, etcétera. Cada mensaje enviado se implementa
mediante la llamada a un método haciendo uso de la notación punto, cuya sintaxis es
la siguiente: referenciaDelObjeto.nombreDelMétodo(listaDeParamétros)
La notación punto consta del nombre o identificador de la referencia al objeto que aten-
derá el mensaje, un punto, el nombre del método y la lista de parámetros para esa llamada
en particular. La lista de parámetros es una lista de identificadores o literales, cada uno
separado por comas. Siempre deben escribirse los paréntesis independientemente de que el
método requiera, o no, parámetros.
La respuesta a un mensaje depende de la interacción entre los métodos definidos en la
clase del objeto que recibió el mensaje y del valor asignado en el mensaje a cada uno de
sus parámetros. En algunos casos, la respuesta a un mensaje implica cambiar el estado de
un objeto. Por ejemplo, si se tiene un objeto llamado miAuto de la clase Automóvil y se le
envı́a el mensaje frenar (mediante la sintaxis miAuto.frenar();) la velocidad del automóvil
cambia a cero (figura 3.6).
3.2 USO DE OBJETOS 39

a) Envío de mensaje b) Recepción del mensaje


miAuto.frenar() miAuto.frenar()

jaime miAuto jaime miAuto


velocidad
.... 100 velocidad
.... 0

Figura 3.6 Envı́o y recepción de mensajes.

Por otra parte, hay mensajes que requieren información adicional. Por ejemplo, si se desea
disminuir la velocidad de un auto es necesario indicar cuánto, para ello en la llamada al
método se debe especificar este valor entre los paréntesis; para el ejemplo del auto se podrı́a
escribir miAuto.ajustarVelocidad(80) (figura 3.7).

a) Envío de mensaje b) Recepción del mensaje


miAuto.ajustarVelocidad(80) miAuto.ajustarVelocidad(80)

jaime miAuto jaime miAuto


velocidad
.... 100 velocidad
.... 80

Figura 3.7 Envı́o y recepción de mensajes con parámetros.

En ocasiones se espera recibir un valor como una respuesta a un mensaje, por ejemplo,
si interesa conocer la velocidad del auto. (Figura 3.8). Notar que el estado del objeto no
cambia, sólo envı́a una copia del valor. En el caso que la respuesta a un mensaje sea un
valor, es conveniente que éste se almacene en alguna variable para su uso posterior. Ejemplo:
velocidadActual = miAuto.obtenerVelocidad();
La ejecución de los programas es secuencial, por lo tanto, una vez que se envı́a un mensaje a
un objeto, el objeto emisor debe esperar a que termine de ejecutarse el método que responde
a dicho mensaje antes de realizar cualquier otra tarea.
Para poder llamar a un método es necesario conocer su signatura o firma, pues ésta
proporciona la información necesaria para poder hacerlo. La firma de un método consta
del nombre del método y entre paréntesis una lista con los parámetros que requiere para
trabajar. Esta lista indica el tipo y posiblemente el nombre de cada parámetro, el orden de
los parámetros está implı́cito en la lista.
El tipo de valor que devuelve el método no forma parte de la firma, sin embargo en este
capı́tulo se incluye, porque es conveniente almacenar el resultado de un método en una
40 CAPÍTULO 3. CREACIÓN Y USO DE OBJETOS

a) Envío de mensaje b) Recepción del mensaje


miAuto.obtenerVelocidad() miAuto.obtenerVelocidad()

jaime miAuto jaime miAuto


velocidad
.... 100 velocidad
.... 100

100

Figura 3.8 Recepción de mensajes que devuelven un valor.

variable (o constante) del tipo de valor que se especifica en el resultado para poder trabajar
con ese valor más adelante. Ejemplos de firmas:
char lanzar(); // Devuelve un carácter y no requiere parámetros.
String obtenerNombre(); // Devuelve una cadena y no requiere parámetros.
void asignarNombre(String); // No devuelve nada y requiere una cadena.
double distancia (Punto); // Devuelve un doble y requiere un Punto.

3.3 Eliminación de objetos


Java permite al programador crear todos los objetos que necesite sin preocuparse de saber
si el sistema de cómputo tiene suficientes recursos o no, la máquina virtual se encarga de
destruirlos cuando ya no se necesitan. La JVM también se hace cargo de recuperar la memoria
utilizada por los objetos que han sido eliminados, esto lo hace mediante la utilización de un
recolector de basura, el cual es activado automáticamente.
En tanto que para crear un objeto se debe utilizar el operador new, la eliminación de objetos
es implı́cita, se realiza cuando ya no hay más referencias a él. En general, el programador
no tiene que ocuparse de la eliminación de los objetos, pero si se piensa que se tienen varios
objetos sin referencia, se puede llamar al método gc de la clase System quizá porque se
requiera disponer de todos los recursos del sistema en ese momento.

3.4 Objetos para lectura y escritura


Una tarea frecuente en todo programa es la interacción con el usuario, ya sea para solicitar
datos, mostrar mensajes, o los resultados del mismo. En esta sección se presentan dos objetos
ampliamente utilizados para la interacción con el usuario.
En Java se tiene el objeto predefinido System.out para que enviar, al usuario, informa-
ción generada por el programa. Este objeto responde a los métodos println y print para
3.4 OBJETOS PARA LECTURA Y ESCRITURA 41

mostrar información en el monitor. Ambos métodos reciben como parámetro una cadena y
la despliegan en el monitor al ejecutarse. La diferencia entre estos métodos radica en que el
método println una vez que muestra su cadena en el monitor pone un salto de lı́nea para
que la siguiente cadena desplegada empiece en una nueva lı́nea y el método print no hace
el salto de lı́nea. Por ser un objeto predefinido no es necesario crearlo mediante el operador
new, basta con usarlo en los lugares que se requiera enviar información al usuario.
Ejemplos:

System.out.println("Calculando el valor solicitado. Por favor, espera ....");


System.out.println("Tengo "+ anios + " de edad.");

En el segundo ejemplo se utiliza el operador + para concatenar cadenas de caracteres, y


ası́ formar la cadena que se envı́a como parámetro.
Solicitar datos al usuario es tarea cotidiana en los programas, para ello Java proporciona,
en el paquete java.util, la clase Scanner, la cual tiene métodos para interactuar con el
usuario de los programas, por lo tanto tiene métodos para leer, del teclado, datos de cada
tipo primitivo de Java.
Al crear un objeto de la clase Scanner se debe incluir como parámetro el objeto System.in
para indicar que los datos leı́dos serán proporcionados a través del teclado. Por ejemplo,
Scanner lector = new Scanner(System.in);.
Los métodos de la clase Scanner tienen por nombre nexttipo, donde el sufijo tipo correspon-
de al tipo primitivo que se desea leer. Como es la segunda palabra del nombre de un método
debe empezar con mayúscula. Por ejemplo, para leer un número entero se tiene el método
nextInt. También incluye el método nextLine que se utiliza para leer cadenas de caracteres.
Las firmas de los métodos utilizados en este texto se presentan en la tabla 3.1. En la dirección
http://docs.oracle.com/javase/1.5.0/docs/api/java/util/Scanner.html se encuen-
tra la documentación completa de la clase Scanner.

Sintaxis Descripción del resultado


boolean nextBoolean() Devuelve el booleano leı́do del teclado.
byte nextByte() Devuelve el byte leı́do del teclado.
double nextDouble() Devuelve el número real leı́do del teclado.
int nextInt() Devuelve el número entero leı́do del teclado.
long nextLong() Devuelve el número entero grande leı́do del teclado.
float nextFloat() Devuelve el número real grande leı́do del teclado.
short nextShort() Devuelve el número entero pequeño leı́do del teclado.
String nextLine() Devuelve la cadena leı́da del teclado.
Tabla 3.1 Algunos métodos de la clase Scanner.
42 CAPÍTULO 3. CREACIÓN Y USO DE OBJETOS

Si se desea usar métodos de clases definidas en cualquier paquete de Java se deben llamar
con la sintaxis: NombreDePaquete.NombreDeClase.NombreDeMétodo(parámetros). Una forma
abreviada de hacerlo es incluir, al principio del archivo (fuera de la clase) la instrucción
import NombreDePaquete.NombreDeClase; Ejemplo: import java.util.Scanner; y luego
ya se puede usar el método como si estuviera definido en esa clase, es decir, sólo basta
especificar su nombre y parámetros. Cabe destacar que la instrucción import no copia el
código, sólo avisa al compilador en donde buscar.

3.5 Ejemplos de trabajo con objetos


Los objetos se comunican entre sı́ mediante llamadas a métodos. Ası́ que un programa en
Java debe verse como una serie de objetos llamando a métodos. En esta sección se presentan
programas cuyo objetivo es ilustrar el trabajo con objetos en Java, de tal forma que se asume
que las clases que se usan en estos ejemplos existen.
Ejemplo 3.2. Diseño de un programa que muestra en la pantalla un texto de felicitación.

1. Encontrar los objetos principales.


Los sustantivos son: programa, pantalla y texto de felicitación. El sustantivo “pro-
grama” no se convierte en clase, en general se convierte en el método principal del
programa, cuya programación será el resultado del escenario especificado en el tercer
paso de esta metodologı́a.
Pantalla no se va a programar pues es en donde se verá el resultado del programa.
Felicitación es el resultado del programa.

2. Determinar el comportamiento deseado.


En este caso, como no hay objetos en la definición del problema, no es posible asociarles
comportamiento.

3. Definir escenarios.
Para ayudar al diseño del programa, sobre todo en este caso en que la descripción es
tan breve, se define el escenario del programa.

(a) El usuario ejecuta el programa.


(b) “Algún objeto” muestra el mensaje de felicitación.

En este caso se ha descubierto que se requiere un objeto capaz de escribir un mensaje


de felicitación.
3.5 EJEMPLOS DE TRABAJO CON OBJETOS 43

Todo programa en Java está formado por clases, aquı́ se muestra cómo construir una clase
que solamente contiene el método main, que es en donde inicia la ejecución de todo programa.
Ejemplo 3.3. Implementación del programa para mostrar un mensaje de felicitación.
/**
* Programa para que envı́e un mensaje de felicitación
* Objetivo Mostrar el uso del objeto System.out
* @author Amparo López Gaona
* @version 3a edición
*/
public class Felicitacion { // 1
public static void main (String [] pps) { // 2

System.out.println("¡Felicidades! Has escrito tu primer programa en Java");// 3


} // 4
} // 5
Los comentarios forman parte de la documentación de los programas, su objetivo es fa-
cilitar las tareas de depuración y mantenimiento de los programas, por lo tanto, deben ser
informativos de lo que se está codificando y cómo se está haciendo en términos generales, no
deben especificar cosas obvias. Los comentarios no aportan nada funcional a los algoritmos,
únicamente sirven para facilitar su comprensión, por tanto son ignorados por el compilador.
Es recomendable que todo programa en Java empiece con un comentario en el que se
describa el objetivo del programa, el nombre del programador y la fecha de realización.
Este comentario abarca varias lı́neas, ası́ que se utiliza un comentario que empieza con
/** y termina con */ además se recomienda incluir algunas instrucciones que empiezan
con arroba (@) como son: @author seguida del nombre del programador. Como no existe una
instrucción para especificar la fecha, en este texto se coloca en la instrucción @version. Estos
comentarios son útiles para generar la documentación del programa en páginas en HTML2 de
manera automática. Para generar la documentación se emplea el programa javadoc seguido
del nombre del archivo fuente. En el apéndice B se tiene un ejemplo de su utilización y el
resultado obtenido.
Para facilitar la explicación del programa, se numeraron las lı́neas con un comentario al
final de cada una de ellas.
En la lı́nea 1 se define una clase llamada Felicitacion. La forma de definirla es mediante
las palabras reservadas public class seguidas de un identificador para el nombre de la
clase. En Java siempre se tienen que construir clases, por lo menos una que utilice objetos
de clases definidas por otros, como en este ejemplo.
En la lı́nea 2 se define el método main, que es en donde inicia la ejecución del programa.
Esta lı́nea tiene que escribirse exactamente como aparece en el ejemplo. En el siguiente
capı́tulo se explica el significado de cada palabra que aparece en esta lı́nea.
2
Para poder ser leı́das desde cualquier navegador de Internet.
44 CAPÍTULO 3. CREACIÓN Y USO DE OBJETOS

En la lı́nea 3, se envı́a el mensaje println al objeto System.out. En este caso el método


println recibe la cadena "¡Felicidades! Has escrito tu primer programa en Java".
En la lı́nea 4 está la llave que termina el método main y en la lı́nea 5 está la llave que
termina la clase Felicitacion.
Para compilar este programa se debe dar la instrucción javac Felicitacion.java. De-
bido a que la clase Felicitacion contiene al método main, el nombre del archivo debe ser
igual al nombre de la clase y terminar con .java. En caso de encontrar errores se deben
corregir y volver a compilar el programa, repitiendo estos pasos hasta que no haya errores.
Entre los errores comunes que se cometen al empezar a programar está omitir el sı́mbolo
de punto y coma con que se termina cada instrucción, otro error frecuente es no cerrar las
comillas o bien cerrarlas en una lı́nea diferente de la que se abrieron.
Una vez libre de errores de sintaxis se puede ejecutar el programa con la instrucción
java Felicitacion. Al hacerlo se hace una llamada implı́cita al método main de la clase
Felicitacion. El resultado de esta llamada es mostrar en la pantalla de la computadora lo
siguiente:
¡Felicidades! Has escrito tu primer programa en Java
Recordar que las comillas se utilizan para especificar que se tiene una literal y no son parte
de la cadena.
Ejemplo 3.4. El programa 3.3 podrı́a haberse escrito sin alinear como se muestra en seguida:

/**
* Programa sencillo para que envı́e un mensaje de felicitación.
* Objetivo Mostrar un programa sin alineación.
* @author Amparo López Gaona
* @version 3a edición
*/
public class Felicitacion{public static void main(String[] pps){System.out.println
("¡Felicidades! Has escrito tu primer programa en Java");}}

Este programa funciona igual que el programa 3.3, sólo es más compacto, con lo cual se
ahorra espacio en su almacenamiento; sin embargo, es mucho más difı́cil de leer y en su caso
de corregir o modificar, de ahı́ la importancia de la alineación de instrucciones.
Ejemplo 3.5. El siguiente programa desplegará la felicitación en dos lı́neas.
/**
* Programa sencillo para que envı́e un mensaje de felicitación.
* Objetivo Mostrar cómo se crean y escriben mensajes en más de una lı́nea.
* @author Amparo López Gaona
* @version 3a edición
*/
public class FelicitacionEn2Lineas { // 1
public static void main (String [] pps) { // 2
3.5 EJEMPLOS DE TRABAJO CON OBJETOS 45

System.out.println("¡Felicidades!"); // 3
System.out.println("\t Has escrito tu primer programa en Java"); // 4
} // 5
} // 6

En el segundo mensaje, la cadena empieza con \t, que es el carácter para el tabulador de
espacios en blanco. El resultado de este programa es:
¡Felicidades!
Has escrito tu primer programa en Java

Si las lı́neas 3 y 4 se cambiaran por las siguientes lı́neas:


System.out.print("¡Felicidades!");
System.out.println("\t Has escrito tu primer programa en Java");

El mensaje se mostrarı́a en una sola lı́nea debido a que la primera instrucción no baja de
lı́nea, ası́ que el segundo mensaje empezará en donde acabe el primero, es decir, después del
signo de admiración.
Las lı́neas 3 y 4 se podrı́an escribir en una sola como sigue:
System.out.println("¡Felicidades!\n\t Has escrito tu primer programa en Java");

En caso de que el mensaje sea tan grande que la cadena no quepa en una sola lı́nea es
necesario dividirlo, para ello se deben cerrar las comillas y continuar en la siguiente lı́nea
especificando una concatenación de cadenas, como sigue:
System.out.println("¡Felicidades!\n\t Has escrito tu primer programa en Java y"
+ " esto es un gran paso en tu carrera como programador.");

Ejemplo 3.6. Programa que felicita al usuario del programa incluyendo su nombre en la
felicitación.
Se desea que cada vez que se ejecute este programa, éste le pregunte a la persona su nombre
y luego la felicite.

1. Encontrar los objetos principales.


Sustantivos: usuario, nombre y felicitación.
Al igual que programa, la palabra usuario es común en la descripción de problemas y
no se toma en cuenta como candidato a objeto.
2. Determinar el comportamiento deseado.
En este caso, como no hay objetos en la definición del problema, no es posible asociarles
comportamiento.
46 CAPÍTULO 3. CREACIÓN Y USO DE OBJETOS

3. Definir escenario.

(a) Solicitar el nombre del usuario.


(b) Almacenarlo en un objeto de la clase String.
(c) Felicitar al usuario incluyendo su nombre en la felicitación.

Este problema es muy parecido al problema anterior, excepto que ahora se requiere
interactuar con el usuario para solicitarle su nombre y leerlo, ası́ que además del objeto
para enviar el mensaje se requiere un objeto para poder leer del teclado. Finalmente,
se requiere un objeto para almacenar el nombre de la persona que ejecuta el programa.

A continuación se muestra la implementación del algoritmo.

import java.util.Scanner; // 0
/**
* Programa para enviar una felicitación personalizada
* Objetivo Mostrar cómo se crean y usan objetos de distintas clases
* @author Amparo López Gaona
* @version 3a edición
*/
public class FelicitacionPersonalizada {
public static void main (String [] pps) {
Scanner in = new Scanner(System.in); // 1
String nombre; // 2

System.out.println("Dame tu nombre "); // 3


nombre = in.nextLine(); // 4
System.out.print("¡Felicidades "+ nombre+ "! "); // 5
System.out.println("Has escrito tu primer programa en Java"); // 6
}
}

En la lı́nea 1 de este programa se crea un objeto de la clase Scanner. En este caso, y


a lo largo de todo el libro, se le pasará como parámetro al momento de crear el lector el
objeto System.in. Para poder crearlo, se debe avisar al compilador que se va a utilizar el
paquete en donde está esta clase, por lo cual es indispensable empezar el programa con esta
instrucción: import java.util.Scanner; que se muestra en la lı́nea cero.
En la lı́nea 2 se crea una referencia a un objeto de la clase String que contendrá el
nombre del usuario de programa. Esta clase la proporciona Java en el paquete java.lang.
No es necesario incluir explı́citamente este paquete.
Notar que entre las declaraciones y las instrucciones se deja una lı́nea en blanco, esto es
para facilitar la lectura.
3.5 EJEMPLOS DE TRABAJO CON OBJETOS 47

En la lı́nea 3 se pide al usuario que proporcione su nombre. Si no se incluyera esta ins-


trucción, el usuario no sabrı́a qué hacer y la ejecución del programa serı́a detenida, pues se
tratarı́a de realizar la instrucción de lectura, pero como el usuario no sabe que debe teclear
algo se queda en espera de algún resultado.
En la lı́nea 4 se lee el nombre del usuario mediante la llamada al método nextLine de la
clase Scanner, el resultado de este método (que es la referencia a una cadena) se almacena
en la variable nombre. Recordar que es importante almacenar el resultado de la llamada
a un método en una variable de tipo correspondiente para poder hacer uso de ese valor
posteriormente.
Al ejecutar la lı́nea 5 se imprime la felicitación seguida del nombre del usuario. Para ello
se utiliza el operador + con cadenas de caracteres, como se vio en la sección 2.3.6.
A continuación un ejemplo de ejecución de este programa:

Dame tu nombre
Andrea
¡Felicidades Andrea! Has escrito tu primer programa en Java

En este programa se puede apreciar cómo es posible tener varios objetos y estos objetos
pueden ser de clases diferentes.
La clase String tiene métodos para comparar cadenas, buscar subcadenas, extraer subca-
denas, crear una copia de una cadena, convertirla a minúsculas o mayúsculas, entre otros.
Algunos de ellos se muestran en la tabla 3.2.

Sintaxis Descripción del resultado


charAt(int) Devuelve el carácter de la posición indicada.
equals(String) Devuelve true si la cadena pasada como parámetro
es igual a la cadena que llama al método. Devuelve
false en caso contrario.
indexOf(String) Devuelve la posición en donde empieza la subcadena
pasada como parámetro.
length() Devuelve la cantidad de caracteres que tiene la cadena.
substring(int, int) Devuelve la cadena formada por los caracteres que
están desde la posición indicada en el primer parámetro
hasta uno antes de la indicada en el segundo parámetro.
toLowerCase() Devuelve la cadena en minúsculas.
toUpperCase() Devuelve la cadena en mayúsculas.
trim() Devuelve la cadena sin espacios en blanco al final.
Tabla 3.2 Métodos de la clase String
.
48 CAPÍTULO 3. CREACIÓN Y USO DE OBJETOS

Para la descripción completa de todos los métodos de esta clase consultar la dirección
http://download.oracle.com/javase/1.5.0/docs/api/java/lang/String.html
Ejemplo 3.7. Programa para conversión de nombres. El programa debe leer del teclado
el nombre completo de una persona separando con una coma el nombre de los apellidos y
mostrarlo en el formato: apellido materno seguido del apellido paterno y del nombre.
Por ejemplo, si el programa lee Marı́a del Pilar, Casas Sandoval debe devolver Sandoval
Casas, Marı́a del Pilar.

1. Encontrar los objetos principales.


Sustantivos: programa, teclado, nombre completo, persona, coma, nombre, apellidos,
formato, apellido materno, apellido paterno.
En este caso, como en los dos anteriores, sólo se tienen objetos para interactuar con el
usuario y cadenas para leer y manipular los nombres.

2. Definir escenario.

(a) El programa solicita al usuario un nombre completo en un formato especial.


(b) El usuario teclea el nombre completo separando con una coma el nombre de los
apellidos.
(c) El programa lee el nombre tecleado y lo almacena en un objeto de la clase String.
(d) El programa extrae cada elemento del nombre.
(e) El programa acomoda los elementos del nombre de acuerdo al formato deseado.
(f) El programa muestra el nombre en el nuevo formato.

A continuación la implementación del algoritmo.


import java.util.Scanner;
/**
* Programa para convertir nombres de persona de un formato a otro
* Objetivo Trabajar con objetos de la clase String
* @author Amparo López Gaona
* @version 3a edición
*/
public class ConvertidorNombres {
public static void main (String [] pps) {
Scanner in = new Scanner(System.in);
String nombreCompleto = new String();
String nombre, aPaterno, aMaterno;

System.out.println("Dame el nombre completo de una persona.");


3.5 EJEMPLOS DE TRABAJO CON OBJETOS 49

System.out.println("Separando el nombre de los apellidos con una coma");


nombreCompleto = in.nextLine();
// Elimina blancos al final de la lı́nea
nombreCompleto = nombreCompleto.trim();

int posición = nombreCompleto.indexOf(","); //Busca la coma


nombre = nombreCompleto.substring(0, posición); //Extrae el nombre
nombreCompleto = nombreCompleto.substring(posición+2,nombreCompleto.length());

posición = nombreCompleto.indexOf(" "); //Busca el primer blanco de la lı́nea


aPaterno = nombreCompleto.substring(0, posición); //Extrae el apellido paterno

nombreCompleto = nombreCompleto.substring(posición+1);
aMaterno = nombreCompleto; //Extrae el apellido materno

String nombreNuevo = aMaterno + " " + aPaterno + " " + nombre;

System.out.println("("+nombreNuevo+ ") en el formato solicitado.");


}
}

Los dos primeros elementos se extraen de la cadena desde la posición inicial hasta donde
se encuentra el delimitador, que en el primer caso es una coma y en el segundo un espacio
en blanco.
Ejemplo 3.8. Programa para jugar a los volados con la computadora. El juego de volados
es un juego entre dos personas, una de ellas lanza una moneda al aire y la otra trata de
adivinar la cara que estará visible al caer la moneda al suelo. La cara puede ser “águila” o
bien “sol”. Si acierta gana y si no es el caso pierde. En este juego el lugar de la persona que
lanza la moneda será tomado por la computadora.

1. Encontrar los objetos principales.


Sustantivos: juego, persona, jugador, moneda, aire, cara, suelo, computadora.
Juego, es lo que se quiere programar. Persona y jugador son sinónimos y se refieren
al usuario del programa. Computadora es con quién se va a jugar. Aire y suelo están
relacionados con el juego fı́sicamente, no se van a programar como clases.
Cara es atributo de la moneda. Tanto águila como sol son valores de la cara de la
moneda.
Ası́ que el único objeto que se requiere es una moneda y aunque no se especifica,
implı́citamente en la definición del problema están objetos para interactuar con el
usuario.
50 CAPÍTULO 3. CREACIÓN Y USO DE OBJETOS

2. Determinar el comportamiento deseado.


Verbos: lanzar la moneda, cara de la moneda que estará visible, caer la moneda. Estos
son métodos de la clase Moneda como se puede deducir de la definición del problema.

3. Definir escenario.

(a) El programa da al usuario la bienvenida y le solicita predicción (águila o sol).


(b) El programa lee la predicción del usuario.
(c) El programa lanza la moneda.
(d) El programa muestra la cara visible de la moneda.
(e) El programa determina el ganador.

Se puede asumir que ya existe la clase Moneda con un método lanzar que devuelve la
cadena aguila, si cae águila, y sol en otro caso.
El algoritmo descrito en el escenario, se presenta en las siguientes lı́neas de programa.

import java.util.Scanner;
/**
* Programa para que el usuario juegue volados con la computadora
* Objetivo Mostrar cómo se crean y usan objetos de distintas clases
* @author Amparo López Gaona
* @version 3a edición
*/
public class Volados {
static public void main (String [] pps) {
Moneda centenario = new Moneda(); //Declaración de variables
Scanner in = new Scanner(System.in);
String nombre, pidio, cayo;

System.out.print("Dame tu nombre "); // Solicita nombre del jugador


nombre = in.nextLine();
// Solicita predicción
System.out.print(nombre+ ", ¿qué pides: aguila o sol? ");
pidio = in.nextLine(); // Lee predicción
pidio = pidio.toLowerCase(); // La pasa a minúsculas
pidio = pidio.trim();
cayo = centenario.lanzar(); // Lanza la moneda
System.out.println("Cayó " + cayo); // Muestra lo que cayó
}
}
3.5 EJEMPLOS DE TRABAJO CON OBJETOS 51

En las primeras lı́neas del método main se crean los objetos que se requerirán en el progra-
ma. Estos son: un objeto de la clase Moneda, es el objeto con el que se jugará; un objeto de
la clase Scanner con el propósito de que permita al programa enterarse de la predicción del
usuario, en cuanto a la cara que mostrará la moneda al caer; un objeto de la clase String
para almacenar el nombre del jugador; dos variables más de tipo String, una para saber
con qué cara pensó el usuario que caerı́a la moneda y la otra para almacenar el valor con el
que realmente cayó.
Se solicita al jugador su nombre; se lee este nombre y se almacena en la cadena referenciada
por la variable nombre. Se imprime el nombre del jugador seguido de la solicitud para que
especifique lo que elige. Por ejemplo, Andrea, ¿qué pides: aguila o sol? El jugador
debe escribir una cadena con su petición.
Se lee la selección del usuario, enviando el mensaje nextLine al objeto in de la clase
Scanner. Este método devuelve la lı́nea (cadena) tecleada por el usuario. La respuesta al
mensaje se almacena en la variable pidio convertida a minúsculas y sin espacios en blanco
después de la última letra.
Se simula el lanzamiento al aire de la moneda, mediante el mensaje lanzar al objeto
centenario de la clase Moneda. Este método no requiere argumentos y devuelve una cadena;
ésta puede ser aguila si cae águila o bien sol si cae sol. El resultado de este método se
almacena en la variable cayo.
Se muestra lo que cayó, mediante la instrucción System.out.println que recibe como
parámetro la cadena "Cayó "+ cayo.
La explicación del programa, en los párrafos anteriores, es prácticamente la misma que se
puede leer en los comentarios del mismo. Es un ejemplo de la importancia de los comentarios
adecuados.
Si el programa termina aquı́, el usuario puede saber si ganó o perdió de acuerdo al resultado
del programa. Sin embargo, para evitar trampas se solicitó que el programa determine el
ganador. Para poder hacerlo se requiere de una instrucción que permita verificar si el valor
solicitado y el que cayó son iguales o no. En el capı́tulo anterior se presentaron las expresiones
condicionales, y lo que se requiere en este punto es una expresión que compare pidio con
cayo y de acuerdo al resultado de esta comparación ejecute cierta acción. Es decir, se requiere
una instrucción condicional.
La instrucción condicional if tiene la siguiente sintaxis: empieza con la palabra reservada
if seguida de una expresión booleana, denominada condición, entre paréntesis; luego un
bloque de instrucciones que se ejecutan cuando la evaluación de la condición es verdadera.

if (condición)
bloque

Un bloque es un conjunto de instrucciones agrupadas con un par de llaves ({}) con el


propósito de ser tratadas como unidad. Dentro de un bloque puede haber otros bloques. La
52 CAPÍTULO 3. CREACIÓN Y USO DE OBJETOS

convención de Java especifica que la llave de inicio del bloque se pone al final de la instrucción
que lo contendrá, en este caso la instrucción if. Las instrucciones dentro del bloque llevan
un nuevo margen de alineación y la llave que cierra el bloque va alineada al mismo nivel que
la instrucción donde se abre el bloque.
Las instrucciones contenidas en el bloque de la instrucción if se ejecutarán siempre y
cuando el resultado de la evaluación de la condición sea true, en caso contrario se ignoran.
Ejemplo:

if (visitas > 5) {
palomitasGratis();
}
verPelicula();

Sólo si el número de visitas es mayor que 5 se obtienen palomitas gratis e independiente-


mente de esto se ve una pelı́cula.
Otra forma de la instrucción if es la siguiente:

if (condición)
bloque1
else
bloque2

La instrucción condicional if ejecuta sólo uno de los dos bloques de instrucciones incluidos
en ella de acuerdo al resultado de la evaluación de la condición, si la condición devuelve
el valor true se ejecuta sólo el bloque1 y en caso de devolver false se ejecuta sólo el
bloque2; nunca se ejecutan ambos bloques. En ambos casos de instrucción if, la ejecución
del programa continúa después de la instrucción if. Ejemplo:

if (calificacion >= 6) {
System.out.println("Aprobado");
} else {
System.out.println("No aprobado");
}
asistirAClase();
hacerTareas();
...

si la calificación es mayor o igual a 6 el alumno está aprobado, en otro caso está reprobado.
Sólo puede estar en una de esas dos situaciones e independiente de ello debe asistir a clases
y hacer sus tareas.
Ası́ para que el programa que juega volados determine y muestre si el jugador ganó o perdió,
antes de terminar el programa anterior se debe incluir la siguiente instrucción condicional:
3.5 EJEMPLOS DE TRABAJO CON OBJETOS 53

if (pidio.equals(cayo)) {
System.out.println(nombre + ", ganaste :) ");
} else {
System.out.println(nombre + ", perdiste :(");
}

El significado de la instrucción es que si el valor de la variable pidio es igual al de la variable


cayo el jugador acertó en su predicción y por lo tanto ganó, que es el mensaje que se envı́a,
en caso contrario perdió. Es decir, sólo puede ganar o perder pero no ambas cosas y se
determina cuál de estas situaciones se presenta de acuerdo con la evaluación de la condición.
Para comparar dos cadenas no se utiliza el operador == visto en el capı́tulo anterior, se
utiliza el método equals, como se especificó en la tabla 3.2. En el siguiente capı́tulo se explica
la razón.
En el programa del ejemplo 3.8, ¿qué sucede si el usuario proporciona una cadena distinta
de aguila y de sol? El programa funcionará, en este caso con un valor que nunca será igual
al resultado del volado; este es un comportamiento incorrecto. En el capı́tulo 1 se resaltó la
importancia de desarrollar programas robustos (programas que hacen lo que se desea
que hagan y funcionen a prueba de errores), el programa anterior no lo es porque no se
está asegurando que el usuario teclee sólo las cadenas permitidas, él puede teclear cualquiera.
Para evitar que el programa trabaje con cualquier cadena es necesario verificar el dato leı́do,
y en caso de que no sea un valor que se encuentre dentro del conjunto de valores permitidos
avisar al usuario del problema y terminar. Para programar esto se requiere de una instrucción
condicional como la siguiente:

if (pidio.equals("aguila") || pidio.equals("sol")) {
// Jugar
} else {
//Enviar mensaje indicando el error
}

En la condición de la instrucción if anterior se asegura que se puede jugar sólo en caso


de que el usuario haya tecleado una cadena correcta independientemente de si lo hace con
mayúsculas o minúsculas debido a que antes ésta se convirtió a minúsculas.
Ejemplo 3.9. Programa completo y robusto para jugar volados con la computadora.

import java.util.Scanner;
/**
* Programa para que el usuario juegue volados con la computadora
* Objetivo Mostrar cómo se crean y usan objetos de distintas clases
* y el uso de la instrucción condicional if.
* @author Amparo López Gaona
* @version 3a edición
54 CAPÍTULO 3. CREACIÓN Y USO DE OBJETOS

*/
public class Volados {
static public void main (String [] pps) {
Moneda centenario = new Moneda(); //Declaración de variables
Scanner in = new Scanner(System.in);
String nombre, pidio, cayo;

System.out.print("Dame tu nombre "); // Solicita nombre del jugador


nombre = in.nextLine();
// Solicita predicción
System.out.print(nombre+ ", ¿qué pides: aguila o sol? ");
pidio = in.nextLine(); // Lee predicción
pidio = pidio.toLowerCase(); // La pasa a minúsculas
pidio = pidio.trim();

if (pidio.equals("aguila") || pidio.equals("sol")) { //Elección válida. Juega


cayo = centenario.lanzar(); // Lanza la moneda
System.out.println("Cayó " + cayo); // Muestra lo que cayó

if (pidio.equals(cayo)) // Determina el ganador


System.out.println(nombre + ", ganaste :) ");
else
System.out.println(nombre + ", perdiste :(");
} else // Elección incorrecta.
System.out.println("Sólo puedes pedir aguila o sol. Ası́ no juego.");
}
}

En este programa se puede observar que en el bloque de la instrucción if puede haber


instrucciones de cualquier tipo incluso otra(s) instrucción condicional. La forma de funcionar
en este ejemplo es la siguiente: si el usuario no tecleó una cadena correcta se envı́a el mensa-
je de error Sólo puedes pedir aguila o sol. Ası́ no juego. En otro caso, se lanza la
moneda y se determina al ganador.
Ejemplo 3.10. Programa que determina entre tres jugadores quién tiene el número mayor
al lanzar un dado.
En este juego se requieren tres jugadores, cada uno lanza el dado y gana el jugador cuya
cara superior del dado haya tenido el valor más alto. Si los tres valores son iguales gana el
primer jugador.

1. Encontrar los objetos principales.


Sustantivos: jugador, número mayor, dado, valor.
3.5 EJEMPLOS DE TRABAJO CON OBJETOS 55

Jugador es el usuario del programa; número mayor es valor que calculará el programa.
Dado es el objeto principal pues tiene estructura y comportamiento que se utilizará en
el programa. Valor es atributo del dado.

2. Determinar el comportamiento deseado (responsabilidades).


Para el dado el comportamiento esperado es poder lanzarlo y que muestre el valor de
su cara superior.

3. Definir escenario.

(a) El programa solicita el nombre a cada jugador.


(b) El programa recaba los nombres.
(c) Cada jugador lanza el dado.
(d) El programa determina el valor más alto.
(e) El programa avisa quién es el ganador.

/*
* Programa que determina el mayor de tres valores de un dado, al ser lanzado
* cada vez por un jugador distinto
* Objetivo: ilustrar el uso de condicionales
* @author Amparo López Gaona
* @version 3a edición
*/
import java.util.Scanner;

public class MayorValor {


public static void main (String [] pps) {
Dado miDado = new Dado();
Scanner in = new Scanner(System.in);
int mayor;
int valor1, valor2, valor3; //Valor del dado en cada tirada
String nombre1, nombre2, nombre3; //Nombre de los jugadores

//Pedir el nombre de cada jugador


System.out.println("Dame el nombre del primer jugador");
nombre1 = in.nextLine();
System.out.println("Dame el nombre del segundo jugador");
nombre2 = in.nextLine();
System.out.println("Dame el nombre del tercer jugador");
nombre3 = in.nextLine();
//Cada jugador lanza el dado
56 CAPÍTULO 3. CREACIÓN Y USO DE OBJETOS

valor1 = miDado.lanzar();
valor2 = miDado.lanzar();
valor3 = miDado.lanzar();
// Avisa qué valor le cayó a cada jugador
System.out.println(nombre1 + " tienes un "+valor1);
System.out.println(nombre2 + " tienes un "+valor2);
System.out.println(nombre3 + " tienes un "+valor3);

// Determina el valor mayor


...
// Avisa cuál es el valor mayor
System.out.println("\n el mayor valor es "+ mayor);
}
}

Primero se declaran las variables necesarias en este programa: un dado, un objeto lector,
variables para almacenar el nombre de cada jugador y otras para almacenar el valor de cada
tirada. Una vez solicitado el nombre de cada jugador se lanza el dado y se almacena el valor
de la cara superior en una variable entera distinta.
Para determinar el valor mayor lo primero que se piensa es utilizar una condición como
la siguiente: valor1 > valor2 > valor3, pero esto no es posible porque la expresión se
evalúa de izquierda a derecha comparando primero valor1 > valor2, el resultado de esta
comparación es un valor booleano (true o false), que no puede luego ser comparado con el
valor de la variable valor3; se estarı́a preguntando false > valor3.
Existen varias formas para determinar el valor mayor entre tres valores. Una forma consiste
en determinar el mayor entre el valor1 y el valor2 y luego encontrar el mayor entre el valor3
y el mayor de los dos anteriores, como se muestra a continuación:
if (valor1 > valor2) {
mayor = valor1;
} else {
mayor = valor2;
}
if (valor3 > mayor) {
mayor = valor3;
}

En este caso, con la primera instrucción if se determina el mayor entre las variables valor1
y valor2 y se almacena en la variable mayor, luego se compara éste con el valor de la tercera
variable (valor3). Si se cumple la condición de la segunda instrucción if la tercera variable
tiene el valor mayor y se almacena éste en la variable mayor, y si no se cumple la condición,
no se hace nada porque antes ya se habı́a almacenado el valor mayor en la variable mayor
que no se modifica.
3.5 EJEMPLOS DE TRABAJO CON OBJETOS 57

Otra forma de determinar el valor más alto es utilizar el siguiente código:


if (valor1 > valor2 && valor1 > valor3) {
mayor = valor1;
} else {
if (valor2 > valor1 && valor2 > valor3) {
mayor = valor2;
} else {
mayor = valor3;
}
}

con lo cual se compara el valor de la primera tirada con el de las otras dos para determinar si
es la mayor, pero se hace en dos pasos: primero se compara el valor de la primera tirada con
el de la segunda. Si es mayor, luego se compara ese mismo valor con el de la tercera tirada.
Esta última comparación no se efectúa si la primera tirada tiene valor menor que la segunda,
debido a la propiedad de cortocircuito mencionada en el capı́tulo anterior. Si la evaluación
de la condición anterior arroja el valor true se sabe que el primer jugador tiene el valor más
alto y termina esta instrucción.
Si no es el caso, entonces es necesario comparar el valor de la tirada del segundo jugador
con las otras dos, si es mayor entonces ésta es la mayor y si no, la mayor es la tercera. En
este ejemplo se tiene que la instrucción de la cláusula else de la instrucción condicional if,
es a su vez otra instrucción condicional if. Cuando alguno de los bloques de la instrucción
if es a su vez otra instrucción if se tiene anidamiento de instrucciones condicionales if,
con lo cual se permite tener una condición después de haber tomado una decisión mediante
otra condición.
Si en lugar de tener el código como se mostró anteriormente se tuviera el siguiente, en el
que se elimina el último else:
if (valor1 > valor2 && valor1 > valor3) {
mayor = valor1;
} else {
if (valor2 > valor1 && valor2 > valor3) {
mayor = valor2;
}
mayor = valor3;
}

siempre se almacenarı́a en mayor el valor que obtuvo el tercer jugador, cosa que es incorrecta
desde el punto de vista lógico, no desde el sintáctico. Es decir, se tendrı́a un programa que
compila, pero que arroja un resultado incorrecto en la mayorı́a de los casos. Como puede
apreciarse, la alineación de las instrucciones es solamente para facilitar la legibilidad del
código no para influir en la ejecución del mismo.
58 CAPÍTULO 3. CREACIÓN Y USO DE OBJETOS

Ejemplo 3.11. Ahora se desea saber no sólo el valor sino cuál jugador lo obtuvo. El cambio
en el programa es conservar el nombre del jugador, esto se logra declarando una variable de
tipo String llamada ganador y modificando las condicionales como sigue:

if (valor1 > valor2) {


mayor = valor1;
ganador = nombre1;
} else {
mayor = valor2;
ganador = nombre2;
}
if (mayor < valor3) {
mayor = valor3;
ganador = nombre3;
}

y al avisar cuál es el valor mayor se incluye el nombre del ganador como sigue:

System.out.println("El ganador es "+ ganador + " con un valor de "+ mayor);

3.6 Ejercicios
1. Describir en qué difiere una variable para un tipo primitivo de una variable para refe-
rencias.

2. Describir en qué difiere un tipo primitivo de una clase.

3. Responder a las siguientes preguntas con verdadero o falso:

(a) ¿Es posible que con una variable se haga referencia a más de un objeto a la vez?
(b) ¿Es posible que con una variable se haga referencia a más de un objeto en dife-
rentes momentos?
(c) ¿Es posible crear objetos sin utilizar el operador new?
(d) ¿Es posible eliminar objetos sin utilizar la instrucción System.gc()?
4. En cada una de las siguientes columnas, que representan una porción de código, espe-
cificar cuántos objetos hay, además de cuántas y cuáles referencias tiene cada uno.

Objeto t, s; Objeto t, s; Objeto t, s;


t = new Objeto(); t = new Objeto(); t = new Objeto();
s = t; s = t; s = new Objeto();
t = null;
3.6 EJERCICIOS 59

5. ¿Es correcto el siguiente código para intercambiar dos valores? ¿Por qué?

public class Duda{


public static void main (String[] pps) {
int x = 25, y = 18;
// Intercambia x con y
y = x;
x = y;
System.out.println("Ahora x = " + x + " y = " + y);
}
}

6. Dada la siguiente instrucción:

if ((valorNuevo >= 0) && (valorNuevo < lı́mite)) {


valor = valorNuevo;
}

(a) Indicar cuál es el rango de valores válidos para asignar el valor de valorNuevo a
la variable valor.
(b) Indicar el rango de valores si se sustituye el operador >= de la condición por el
operador >.
(c) Indicar el rango de valores permitidos si se sustituye el operador && de la condición
por el operador ||.

7. ¿Qué imprime el siguiente programa?

class UsoDeCadenas{
public static void main (String [] pps) {
String frase = "Una mosca parada en la pared";

System.out.println("La frase :\"" + frase + "\" tiene "+ frase.length()


+ " letras");
System.out.println("Extraje la palabra :" + frase.substring(4,10));

String otra =frase.toUpperCase().replace(’E’,’A’).replace(’I’,’A’);


otra = otra.replace(’O’,’A’).replace(’U’,’A’);
System.out.println("En mayúsculas y con A: "+otra);

otra = otra.replace(’A’,’O’).toLowerCase();
System.out.println("Ahora en minúsculas y con o: "+otra);
60 CAPÍTULO 3. CREACIÓN Y USO DE OBJETOS

String larga =frase.concat(" roja");


System.out.println(larga + "\n"+ frase);
}
}

8. Escribir un programa para rotular sobres de cartas dirigidas a James Bond, con la
siguiente dirección:

J. Bond
"Agente 007"
Apt. #645 N.Y.
USA.

9. Escribir un programa que modele un psiquiatra. El programa debe preguntar al usuario,


que tomará el papel de paciente, cuál es su problema. Con la respuesta debe preguntar
por qué y después de su respuesta decir “Muy interesante, hablaremos de ello con más
detalle en la siguiente sesión”. Por ejemplo:

¿Cómo se encuentra?
Estoy deprimido.
¿Por qué dice ’estoy deprimido’?
No dije que usted estuviera deprimido; dije que yo estoy deprimido.
Muy interesante, hablaremos de ello con más detalle en la siguiente
sesión.

10. Escribir un programa para obtener el RFC de una persona. El RFC se obtiene tomando
las dos primeras letras del apellido paterno, la inicial del apellido materno y la inicial
del nombre, seguido de los dos dı́gitos finales del año de nacimiento, los dos dı́gitos del
mes de nacimiento y dos dı́gitos para el dı́a de nacimiento. Por ejemplo, si la persona
se llama Andrea López López y nació el 14-04-1992, su RFC es lola920414.

11. Escribir un programa para que jueguen disparejos entre tres jugadores; uno de ellos es
la computadora. El juego consiste en que cada jugador lanza su moneda al aire y gana
el jugador cuya cara visible de la moneda sea distinta de la cara visible de cada una
de las otras dos monedas.

12. Escribir un programa que juegue dados con las siguientes reglas: se tiran dos dados
y se suma el valor de la cara superior de cada uno. Si la suma es 7 u 11 el jugador
gana. Si la suma es 2, 3 o 12 el jugador pierde. Si la suma es 4, 5, 6, 8, 9 o 10, ésta se
convierte en los puntos del jugador quien, para ganar, debe volver a tirar los dados y
que la suma de esta tirada sea igual a la anterior o bien igual a 7, en otro caso pierde.
3.6 EJERCICIOS 61

13. En el programa del ejemplo 3.10 hacer los cambios necesarios para que se determine
no sólo el ganador sino también quién queda en segundo lugar y quién en tercero.

14. Escribir un programa que utilice la clase Random de Java para generar un número entre
1 y 10 y pida al usuario lo adivine en tres intentos. En cada intento, la computadora
debe indicar al usuario si el número es mayor o menor que el buscado.

15. Escribir un programa que juegue “piedra, papel o tijeras”. Es un juego entre la compu-
tadora y el usuario, cada uno elige uno de los tres objetos y si uno elige papel y otro
piedra, gana el que elige papel. Si uno elige piedra y otro tijera gana el que elige piedra.
Si uno elige tijeras y otro papel gana el que elige tijeras. Si ambos eligen lo mismo hay
un empate.

16. Escribir un programa que ayude al estudiante de medicina a diagnosticar la causa


de fiebre de acuerdo al flujo mostrado en la figura 3.9 en la página siguiente. Tomar
en cuenta que no es real, es sólo un ejercicio. Una vez que el estudiante pregunta al
paciente su nombre, edad y peso, registra esta información; con ayuda del programa
va haciendo las preguntas al paciente hasta llegar a un diagnóstico.
62 CAPÍTULO 3. CREACIÓN Y USO DE OBJETOS

no
¿Tiene fiebre? Información insuficiente


sí ¿Dificultad para respirar sí Posible neumonía o
¿Tos? infección en las vias
o tos con flemas? aéreas
no no

¿Dolor de cabeza? ¿Dolor de cabeza? Posible infección viral

sí no

¿Nauseas, vómito, no ¿Dolor de huesos o de sí


articulaciones? Posible infección viral
somnolencia o
confusión? no
sí sí Información insuficiente
no ¿Comezón?
Posible no
meningitis
sí Posible infección en
¿Dolor de garganta?
la garganta
¿Diarrea? no

sí ¿Dolor en la espalda, sí Posible infección renal


arriba de la cintura
Posible infección con resfriado?
en el tracto digestivo no

¿Dolor al orinar u sí Posible infección


orina frecuente? en las vías urinarias
no

¿Estuvo todo el día Posible insolación
expuesto al sol?
no

Posible infección
viral

Figura 3.9 Preguntas para el diagnóstico de causa de fiebre.


Capı́tulo 4

Creación y uso de clases

En toda clase se debe definir tanto la estructura como el comportamiento que tendrán sus
objetos. La parte estructural de los objetos se define mediante la declaración de datos, éstos
pueden ser de cualquier tipo definido por el lenguaje. El comportamiento de los objetos se
modela mediante métodos, y es sólo mediante éstos que se puede asignar, alterar y conocer
el estado de un objeto. En este capı́tulo se muestra la forma en que el programador puede
crear sus propias clases.

4.1 Diseño de clases


Con el propósito de ilustrar tanto la metodologı́a de desarrollo de programas presentada en
el capı́tulo 1, como la forma de programar clases en Java se plantea el siguiente problema:
Se necesita un programa para ayudar a los alumnos de nivel superior a comprender concep-
tos básicos de geometrı́a analı́tica. Los conceptos con los que se trabajará son punto, lı́nea
y triángulo. Un punto se define como una pareja ordenada de números. Un punto puede
crearse, desplazarse, calcular su distancia con respecto a otro punto, determinar si está ali-
neado con respecto a otros dos puntos, determinar si es igual a otro punto, e imprimirse
como pareja ordenada de puntos separados por comas y entre paréntesis. Para la lı́nea recta
se requieren dos puntos cualquiera y lo que se desea hacer con ella es crearla, encontrar su
ecuación, calcular su pendiente, su ordenada al origen, dadas dos rectas determinar si son
paralelas, si son la misma, si son perpendiculares, su punto de intersección y dada una recta
y un punto determinar si éste está en la recta. Para el triángulo, definido como tres puntos no
alineados, se requiere determinar su perı́metro, área y tipo (equilátero, escaleno e isósceles).

1. Encontrar los objetos principales.


Sustantivos: programa, alumno, concepto, geometrı́a, punto, lı́nea, triángulo.
Como es costumbre, programa no se considera clase, alumno es sinónimo de usuario

63
64 CAPÍTULO 4. CREACIÓN Y USO DE CLASES

y concepto es una palabra que engloba a punto, lı́nea y triángulo. Por lo tanto, los
objetos principales son punto, lı́nea y triángulo.
De la descripción también se puede definir la estructura de los objetos principales. Por
ejemplo, un punto está definido como una pareja ordenada de números.
2. Determinar el comportamiento deseado para los objetos principales.
De los verbos y la definición del problema se determina el siguiente comportamiento
para los objetos principales.

punto:
Crear.
Desplazar.
Calcular distancia con respecto a otro punto.
Determinar si está alineado con otros dos puntos.
Determinar si es igual a otro punto.
Imprimirlo en formato (x,y).

lı́nea:
Crear.
Encontrar su ecuación.
Calcular su pendiente.
Calcular su ordenada al origen.
Determinar si un punto pertenece a la recta.
Determinar si dos rectas son paralelas.
Determinar si dos rectas son la misma.
Determinar si dos rectas son perpendiculares.
Calcular el punto de intersección de dos rectas.

triángulo:
Crear.
Calcular su perı́metro.
Calcular su área.
Determinar si es equilátero.
Determinar si es isósceles .
Determinar si es escaleno.
Determinar si es igual a otro.

De la definición del problema se tiene que la estructura del punto son dos números. La
estructura de la lı́nea dos puntos y la estructura del triángulo son tres puntos.
4.2 DEFINICIÓN DE CLASES 65

3. Definir escenarios.
A manera de ejemplo se presentan dos escenarios para este programa.

Escenario: Inicio del programa


(a) El programa da la bienvenida al usuario y le muestra un menú con los dife-
rentes temas que puede trabajar.
(b) El programa solicita al usuario el tema sobre el que se desea trabajar.
(c) El usuario indica el tema deseado.
(d) El programa envı́a un mensaje para trabajar con el tema indicado.
Escenario: Trabajar con puntos.
(a) El programa solicita al usuario las coordenadas de un punto.
(b) El usuario proporciona las coordenadas de un punto.
(c) El programa crea un punto.
(d) El programa muestra al usuario un menú con las diferentes operaciones que
puede hacer con un punto
(e) Dependiendo del método elegido por el usuario, el programa solicita más
datos.
(f) El programa muestra el resultado de la operación elegida.

Del diseño se pasa a la implementación, ası́ que en las siguientes secciones se especifica cómo
hacerlo en Java.

4.2 Definición de clases


En cada clase se define la estructura y el comportamiento que tendrán sus objetos. Para
programar una clase en Java se requiere de un encabezado y un cuerpo. El encabezado
incluye, entre otra información, el nombre de la clase. El cuerpo contiene atributos y métodos,
éstos pueden estar definidos en cualquier orden, sin embargo, por cuestión de orden y de estilo
primero se definen los atributos y luego los métodos.

4.2.1 Encabezado
El encabezado de una clase consta, como mı́nimo, de la visibilidad de la clase, la palabra
reservada class y el nombre de la clase; en ese orden. La visibilidad puede ser pública
(public) o bien privada (package). En caso de omitir este calificador se asume que no es
pública. Normalmente el nombre de la clase se escribe en singular e iniciando con mayúscula.
El encabezado puede incluir otras especificaciones que se verán en los siguiente capı́tulos.
Ejemplo 4.1. Encabezado de la clase Punto y los espacios para los atributos y los métodos.
66 CAPÍTULO 4. CREACIÓN Y USO DE CLASES

public class Punto { // Encabezado


... // Atributos
... // Métodos
}

4.2.2 Atributos
La estructura de los objetos de la clase se define declarando sus atributos como datos de
cualquiera de los tipos definidos por el lenguaje, incluyendo referencias a otros objetos. Una
vez definida la estructura para los objetos de una clase, ésta no puede alterarse. Sin embargo,
el estado de cada objeto puede cambiar todas las veces que sea necesario.
Las variables utilizadas para definir los atributos en una clase se conocen como variables
de instancia debido a que cada objeto o instancia de esta clase las tendrá. Cada atributo
definido en una clase tiene los componentes que se mencionan a continuación. Éstos deben
escribirse en el orden descrito.

• Visibilidad. La visibilidad especifica a cuáles atributos se puede tener acceso desde el


cliente que usará el objeto.
Desde el punto de vista del usuario, o cliente, de un objeto sólo interesa el compor-
tamiento y la identidad del objeto, su estructura es privada y no se puede acceder a
ella. A lo que se tiene acceso es al estado del objeto, pero sólo a través de los métodos
proporcionados para ello. La encapsulación consiste en organizar los elementos de
un objeto de manera que algunos sean accesibles desde el exterior y otros privados. La
ventaja de la encapsulación es que el usuario de la clase desconoce la representación
del objeto y si ésta cambia, en principio, no le afecta.
La visibilidad de un atributo se expresa mediante una de las siguientes palabras re-
servadas: public, private o protected. En general, todo atributo debe ser privado,
es decir, se debe utilizar la palabra private para especificar su visibilidad, de otra
forma serı́a público (public), lo que significa que puede ser visto y manipulado fuera
del objeto, situación no deseable porque se pierden las ventajas de la encapsulación.
La excepción de esta regla se aplica para atributos cuyo valor es constante, estos pue-
den ser públicos debido a que no es posible modificarlos una vez asignado su valor. El
calificador protected se verá en el capı́tulo 7, correspondiente al tema de herencia.

• Calificador static (o ninguno). En caso de utilizar el calificador static significa que


es un atributo compartido por todos los objetos de la clase, es decir, se trata de un
atributo de la clase, no de los objetos. Al modificar el valor de un atributo estático en
un objeto se modifica para todos los objetos de esa clase. En caso de no tener calificador
se trata de un atributo que tendrá cada objeto de la clase.
4.2 DEFINICIÓN DE CLASES 67

• Calificador final(o ninguno). Si el atributo tiene el calificador final se está definiendo


un atributo constante. En caso de no tener calificador se trata de un atributo cuyo valor
puede variar.

• Tipo. Nombre de algún tipo primitivo o nombre de clase.

• Nombre. Identificador del atributo, por convención el identificador para el atributo


debe empezar con minúscula.

• Opcionalmente un valor inicial. El valor inicial puede asignarse en la definición del


atributo o en cualquier otro momento, probablemente a través de un método creado
con ese propósito. No debe asumirse que el compilador asigna un valor inicial válido a
los elementos de la estructura.

La declaración de cada atributo debe terminar con un punto y coma.


Ejemplo 4.2. En la clase Punto, cada objeto se representará con una pareja ordenada de
números, por tanto, la estructura estará definida por dos variables de tipo real x,y que
determinan la posición del punto en el plano cartesiano.

public class Punto {


// Atributos
private double x; // Coordenada x
private double y; // Coordenada y
... // Métodos
}

Con esta definición, los atributos x y y sólo son accesibles directamente desde cualquier
método dentro de la clase; para hacerlo desde otra clase se deben utilizar los métodos apro-
piados.

4.2.3 Métodos
El comportamiento de los objetos de una clase se programa mediante la creación de métodos.
Un método es el conjunto de instrucciones (programación del algoritmo) que se deben
realizar al llamar a ejecutar tal método, es decir, al enviar el mensaje correspondiente.
Un método es visto por el objeto cliente como una caja negra con nombre y una función
especı́fica. El cliente desconoce la forma en que se trabaja en la caja para conseguir el
resultado esperado, es decir, desconoce la implementación del método (figura 4.1).
Los componentes de un método son encabezado y cuerpo. El encabezado está formado de:

• Visibilidad. La visibilidad especifica a cuáles métodos se puede tener acceso desde un


objeto cliente.
68 CAPÍTULO 4. CREACIÓN Y USO DE CLASES

Llamada al método Valor esperado

Figura 4.1 Método visto como caja negra.

La visibilidad de los métodos se especifica mediante las palabras reservadas: public,


private o protected. Si no se especifica nada se asume que es pública. Un método
privado es un auxiliar en el trabajo de algún otro método de la clase y no puede ser
usado por los clientes. Los métodos protegidos serán explicados en el capı́tulo 7.
La visibilidad de los atributos y métodos está directamente relacionada con la encap-
sulación, como se muestra en la tabla 4.1.

public private
Atributo Viola la encapsulación Se tiene encapsulación
Método Proporciona servicio a Auxiliar a otros
clientes métodos en la clase

Tabla 4.1 Visibilidad en la clase.

• Tipo del valor que devuelve. El tipo es el nombre de una clase, un tipo primitivo o
bien void, en caso de no devolver nada.
• Nombre. El nombre del método es un identificador como cualquier otro, de preferencia
un nombre que describa la función del método y por convención empieza con una letra
minúscula.
• Elementos necesarios para realizar esta tarea. Estos elementos se especifican como
una lista y se llaman parámetros. Los parámetros formales especifican el tipo de
cada valor que requiere el método para trabajar. Se especifican entre paréntesis, como
una lista de parejas separadas por comas. Cada pareja incluye el tipo y el nombre de
cada parámetro. Los paréntesis son parte de la sintaxis, ası́ que deben estar presentes
aunque el método no requiera parámetros. Por ejemplo, una lista de parámetros puede
ser: (int base, int altura).
Existen diferencias entre la declaración de una variable y la de un parámetro. En los
parámetros no se especifica la visibilidad, cada parámetro se precede de su tipo, aunque
sea el mismo, la definición no termina con punto y coma y no se puede definir valor
4.2 DEFINICIÓN DE CLASES 69

inicial a los parámetros. El nombre de un método y su lista de parámetros formales se


conoce como firma del método.
El dato con el que se llama a ejecutar el método se conoce como parámetro real o bien
como parámetro actual. Al llamar a ejecución un método, el valor del parámetro real
se asigna como valor inicial del parámetro formal y termina la relación entre ambos
parámetros; es decir, si en el cuerpo del método se modifica el valor del parámetro
formal no cambia el valor del parámetro real; esto se conoce como paso de parámetros
por valor. Debido a este funcionamiento, el parámetro real puede ser una variable,
una literal o una expresión; en este último caso se evalúa la expresión y su valor se
asigna al parámetro formal.
Cuando se llama a un método ya no es necesario especificar el tipo de los parámetros,
pues en la definición del método se hace y se espera que se asigne un dato de tipo
compatible con el definido. El compilador se encarga de verificar este aspecto en caso
de no ser compatibles los tipos envı́a un mensaje de error.

El último componente de un método es el cuerpo, éste se programa como un bloque con las
instrucciones que implementan la tarea que debe realizar el método. El cuerpo del método es
privado en el sentido que no se conoce fuera de la clase que lo implementa. El hecho de que
el cuerpo sea privado, permite cambios en la implementación sin que afecte a las aplicaciones
que utilizan ese método; esta es otra ventaja de la encapsulación.
Es importante que cada método implemente una y sólo una tarea, esto facilita su lectura,
revisión y posibles modificaciones. En la programación de un método se puede utilizar:

1. Cualquier atributo de la estructura del objeto de la clase (variable de instancia) inde-


pendientemente de su visibilidad. Para ello, basta con escribir el nombre de tal atributo
y usarlo.
2. Parámetros y variables locales. El área dentro del programa en la cual se puede tener
acceso a un dato se denomina alcance. De esta manera, el alcance de los datos decla-
rados en un bloque es dentro de él y los bloques internos al mismo. Los datos definidos
dentro de un bloque se denominan variables o constantes locales, según sea el caso.
En adelante se habla sólo de variables locales, aunque también pueden ser constantes
locales.
No puede asumirse ningún valor inicial para las variables locales, el valor almacenado
en ellas se utiliza para que el método cumpla su tarea. Tanto la variable como su valor
se pierde una vez que termina la ejecución del bloque en que se declaró.
Los parámetros formales están disponibles sólo dentro del cuerpo del método en que
se declaran, por lo que se dice que el alcance de un parámetro es el método donde
está declarado. Los parámetros son como variables locales, sólo que éstos cuentan con
un valor inicial dado al momento de llamar al método.
70 CAPÍTULO 4. CREACIÓN Y USO DE CLASES

El alcance de las variables de instancia es toda la clase en la que se definen, por eso
pueden utilizarse en cualquier método de la clase.
Relacionado con el alcance de una variable está su tiempo de vida. Una vez que termina
la ejecución de un método, el parámetro formal y su valor se pierde. El tiempo de vida
de una variable de instancia es el mismo que el del objeto al que pertenece.

3. Datos obtenidos como respuesta a mensajes enviados a objetos de su misma clase o de


otras clases.

4. Atributos definidos como públicos o estáticos en otras clases.

4.3 Programación de métodos


En esta sección se muestra y explica la programación de los diversos métodos requeridos
para la clase Punto, de acuerdo con lo especificado en la primera sección de este capı́tulo y
tomando en cuenta los elementos mencionados en las secciones anteriores.
Aunque no es parte de la sintaxis de un método, es buena práctica de programación incluir
un comentario antes del encabezado. En este comentario se especifica la tarea que se realiza,
cuáles son los parámetros que utiliza y cuál es el valor que devuelve, si es que lo hace. Para
la documentación generada con javadoc es recomendable que en cada método se especifique
como parte de los comentarios cada parámetro con @param seguido del nombre del mismo,
y para el valor que devuelve el método es recomendable usar @return seguido del tipo de
valor que devuelve.

4.3.1 Métodos modificadores


Los métodos modificadores tienen el propósito de modificar el valor de los atributos
privados de los objetos, el estado del objeto antes de llamar a un método modificador debe
ser diferente una vez que se ejecuta el método.
Los métodos modificadores, en su forma más simple, reciben como parámetro el nuevo
valor para el atributo correspondiente, por tanto, el tipo del parámetro debe coincidir con el
tipo del atributo, o bien ser de tipo compatible de acuerdo con lo especificado en la sección
2.4. Estos métodos no devuelven valor alguno, su objetivo es mantener la integridad de los
objetos de la clase al ser la única forma en que un objeto cliente puede modificar el valor
de los atributos de cualquier otro objeto. El nombre de estos métodos suele empezar con la
palabra en inglés set, seguida del nombre del atributo iniciado con letra mayúscula. Para
facilitar la lectura de los programas en este texto el nombre de cada método modificador
empieza con la palabra asignar.
Ejemplo 4.3. Programación de los métodos modificadores de cada atributo de la clase
Punto.
4.3 PROGRAMACIÓN DE MÉTODOS 71

/**
* Método para asignar valor a la coordenada x del punto.
* @param nuevaX - nuevo valor para la coordenada x.
*/
public void asignarX(double nuevaX) {
x = nuevaX;
}
/**
* Método para asignar valor a la coordenada y del punto.
* @param nuevaY - nuevo valor para la coordenada y.
*/
public void asignarY(double nuevaY) {
y = nuevaY;
}

En el método asignarX se asigna el valor del parámetro nuevaX a la variable de instancia


x definida como parte de la estructura de cada objeto de la clase Punto. De manera similar
se asigna valor a la variable de instancia y con el método asignarY.
Ejemplo 4.4. Programación del método asignarPunto que permite asignar al punto que
llama a este método nuevos valores. En este caso, se modifica el estado completo de un
objeto, no sólo el valor de un atributo.
/**
* Método para asignar nuevos valores a un punto
* @param nuevaX - coordenada x del punto
* @param nuevaY - coordenada y del punto
*/
public void asignarPunto(double nuevaX, double nuevaY) {
asignarX(nuevaX);
asignarY(nuevaY);
}

En el método anterior se hace uso de los métodos asignar previamente mostrados, aunque
también podrı́a haberse escrito como sigue, debido a que x y y son variables de instancia.
/**
* Método para asignar nuevos valores a un punto
* @param nuevaX - coordenada x del punto
* @param nuevaY - coordenada y del punto
*/
public void asignarPunto(double nuevaX, double nuevaY) {
x = nuevaX;
y = nuevaY;
}
72 CAPÍTULO 4. CREACIÓN Y USO DE CLASES

Otra forma de programar este método es que tome un punto como parámetro, como se
muestra:

/**
* Método para asignar el valor del punto p al punto con que se llama el método
* @param p - punto a asignar
*/
public void asignarPunto(Punto p) {
x = p.obtenerX();
y = p.obtenerY();
}

El método asignarPunto que toma dos enteros y el que toma un punto como parámetros
pueden ser parte de la misma clase permitiendo ambas opciones al usuario de la misma.
El tener varios métodos con el mismo nombre en una clase se conoce como sobrecarga
de métodos; para distinguirlos es necesario que la firma de éstos sea distinta, es decir, que
la cantidad, tipo u orden de parámetros que recibe cada método sea diferente. En caso de
que haya más de un método con igual firma el compilador lo detecta y se genera un mensaje
de error.
Ejemplo 4.5. Programación del método desplazar. Requiere los valores que se sumarán a
las coordenadas del punto.

/**
* Método para desplazar un punto
* @param deltaX - desplazamiento en el eje de las Xs
* @param deltaY - desplazamiento en el eje de las Ys
*/
public void desplazar (int deltaX, int deltaY) {
x += deltaX;
y += deltaY;
}

En el método desplazar se suma un número entero con uno real y el resultado se deja en
un real, lo cual está permitido.

4.3.2 Métodos de acceso


Los métodos de acceso o inspectores son el medio de conocer el valor de los atributos privados
de los objetos. No reciben parámetros, pues su objetivo es obtener el valor de un atributo y
por la misma razón el valor que devuelven es del tipo definido en el atributo. Generalmente su
nombre empieza con la palabra en inglés get seguido del nombre del atributo, sin embargo,
en este texto el nombre de los métodos de acceso empieza con la palabra obtener.
4.3 PROGRAMACIÓN DE MÉTODOS 73

Si se especifica que un método va a devolver un valor, se debe usar en el cuerpo del mismo
la instrucción return, pues con ella se devuelve el valor y se termina la ejecución del método.
Una buena práctica de programación establece que se debe considerar un return aun en caso
de falla. La instrucción return va seguida de una expresión cuyo valor es el que devolverá el
método que la contiene.
En el comentario que describe el funcionamiento del método se debe especificar el resultado
con una expresión @return.
Ejemplo 4.6. Programación de métodos de acceso para la clase Punto.

/** Método para obtener la coordenada x del punto


* @return double - la coordenada x del punto
*/
public double obtenerX () {
return x;
}
/** Método para obtener la coordenada y del punto
* @return double - la coordenada y del punto
*/
public double obtenerY () {
return y;
}

4.3.3 Métodos calculadores


Estos métodos se emplean para implementar cualquier comportamiento deseado de los ob-
jetos. Pueden recibir y/o devolver valores y trabajan con el estado del objeto para calcular
un valor.
Ejemplo 4.7. Método para determinar la distancia entre dos puntos. Para ello se debe aplicar
la fórmula: (x2 − x1 )2 + (y2 − y1 )2 , es decir, se debe tomar el cuadrado de la diferencia entre
las x, sumarlo al cuadrado de la diferencia entre las y y, finalmente, obtener la raı́z cuadrada
de esta suma.

/**
* Método para calcular la distancia entre dos puntos usando la fórmula
* ((x2-x1)^2 + (y2-y1)^2)^1/2
* @param p - punto respecto al que se quiere determinar la distancia.
* @return double - distancia entre los dos puntos.
*/
public double distancia (Punto p) {
return Math.sqrt((this.x - p.obtenerX())*(this.x - p.obtenerX()) +
(this.y - p.obtenerY())*(this.y - p.obtenerY()));
}
74 CAPÍTULO 4. CREACIÓN Y USO DE CLASES

Al escribir un método se debe tener presente que los métodos se llaman con un objeto,
sobre cuyo estado se está trabajando. Ası́ que los dos puntos necesarios para calcular la
distancia son: el punto con el que se llama al método (this) y el parámetro que recibe este
método.
El cálculo de la diferencia entre las x de los dos puntos se efectúa con la instrucción
this.x - p.obtenerX(), que significa tomar el valor del atributo x del objeto con el que se
llama al método y restarle el valor del atributo x del objeto p que se recibe como parámetro.
Otra forma de hacerlo es escribiendo p.x, es decir, el atributo x del objeto p. La notación
punto permite acceder no sólo a métodos (como se vio en el capı́tulo anterior) sino también
a atributos. En el caso de estar escribiendo los métodos de una clase (como en este caso) no
se viola el principio de encapsulación al usar la notación punto en p.x, aunque el atributo
sea privado, pues se está haciendo dentro de la definición de la clase Punto.
De manera análoga se puede explicar la expresión this.y - p.obtenerY(), como la dife-
rencia entre las variables y del objeto con que se llama al método distancia y la del objeto
p. Cada diferencia se multiplica por sı́ misma para obtener el cuadrado de ella. Math.sqrt
significa usar el método sqrt para calcular la raı́z cuadrada, de la clase Math incluida en el
paquete java.lang del lenguaje Java. Este método requiere un número real como paráme-
tro. El paquete java.lang se incluye automáticamente en todo programa Java, ası́ que no
es necesario utilizar la instrucción import.
Ejemplo 4.8. Método para determinar si tres puntos están alineados. Para saber si tres
puntos están alineados se utiliza la fórmula para la ecuación de una recta que pasa por los
puntos (x1 , y1 ) y (x2 , y2 ) que es y − y1 = ((y2 − y1 )/(x2 − x1 ))(x − x1 ). Con el fin de evitar
posibles divisiones entre cero, tarea no permitida, se hizo una manipulación algebraica a la
ecuación para determinar si los puntos están alineados.
Los tres puntos con los que trabaja este método son: el objeto implı́cito (this) más los dos
que recibe como parámetros. El método devuelve un valor booleano (true en caso de estar
alineados y false en otro caso). Si un método devuelve un valor booleano se recomienda
que el nombre del método empiece con es, o esta. Por ejemplo, estanAlineados.
/**
* Método para determinar si tres puntos están alineados
* @param p1 - Punto que con p2 y el que llama verifica si están alineados
* @param p2 - Punto que con p1 y el que llama verifica si están alineados
* @return true si están alineados, false en otro caso
*/
public boolean estanAlineados (Punto p1, Punto p2) {
return (y - p1.y)*(p2.x - p1.x) == (p2.y - p1.y)*(x - p1.x);
}
En este método puede observarse que no se incluye una instrucción condicional para cal-
cular el valor que debe devolver, sólo se especifica la expresión booleana que debe evaluar y
devuelve el resultado de la evaluación.
4.3 PROGRAMACIÓN DE MÉTODOS 75

4.3.4 Métodos constructores


Un constructor es un método cuyo objetivo es asignar valor inicial a cada atributo de un
objeto recién creado, con lo cual se garantiza que el objeto se crea con un estado válido.
Generalmente, en el diseño de un programa no se especifican los constructores, sin embargo
es obligatorio tenerlos.
Un constructor se distingue de cualquier otro método porque:

• Tiene el mismo nombre que la clase a la que pertenece.

• No tiene especificado valor de regreso, ni siquiera void.

• La única forma de llamarlo es usando el operador new.

Es común que las clases tengan más de un constructor, con lo cual se permiten diferentes
posibilidades de creación de un objeto. De ahı́ que generalmente los métodos constructores
estén sobrecargados.
Ejemplo 4.9. Constructor sin parámetros para la clase de los puntos.

/**
* Constructor de un punto, crea un punto con coordenadas (0,0)
*/
public Punto () {
x = y = 0.0;
}

Este constructor sin parámetros es denominado constructor por omisión.1 Si se tiene


un constructor por omisión, éste se llama cuando se quiere asumir un estado inicial prede-
terminado. De otra forma, se asigna el estado inicial a partir de los parámetros.
Ejemplo 4.10. Constructor de un punto a partir de dos números que representan las coor-
denadas del nuevo punto.
/**
* Constructor de un punto a partir de las coordenadas del nuevo punto.
* @param xIni - coordenada x del nuevo punto
* @param yIni - coordenada y del nuevo punto
*/
public Punto (double xIni, double yIni) {
x = xIni;
y = yIni;
}
1
En ciertas traducciones escriben “defecto” por el término “default”, hablando ası́ de constructor por
defecto.
76 CAPÍTULO 4. CREACIÓN Y USO DE CLASES

En muchas ocasiones se necesita distinguir entre los parámetros de un constructor y su


correspondiente variable de instancia; para hacerlo se utiliza la palabra reservada this que
permite a un objeto hacer referencia a sı́ mismo. Por ejemplo, el constructor anterior podrı́a
escribirse como sigue:

/**
* Constructor de un punto a partir de las coordenadas del nuevo punto
* @param x - coordenada x del nuevo punto
* @param y - coordenada y del nuevo punto
*/
public Punto (double x, double y) {
this.x = x;
this.y = y;
}

Otra forma de definir los valores iniciales de un objeto es con base en otro objeto de su
misma clase. Un constructor que toma como parámetro un objeto de su misma clase se
conoce como constructor de copia. Se llama ası́ porque con él se crea un nuevo objeto
cuyo estado inicial es una copia del estado del objeto pasado como parámetro.
Ejemplo 4.11. Constructor de un punto a partir de otro.

/**
* Constructor de un punto a partir de otro punto
* @param p - punto a partir del cual se creará el nuevo
*/
public Punto (Punto p) {
x = p.obtenerX();
y = p.obtenerY();
}

Suponiendo que ya se ha creado el objeto fin de la clase Punto, con la instrucción Punto
alguno = new Punto(fin); se crea un nuevo objeto, denominado alguno, cuyo estado ini-
cial es el mismo que el del objeto fin. En este caso, se trata de un nuevo objeto no de un
alias.
Para llamar a un constructor se debe usar el operador new, sin embargo, si desde un
constructor se llama a otro constructor de la misma clase se usa la palabra this en lugar
de new y debe ser lo primero que haga el constructor. Por ejemplo, el constructor de copia
podrı́a escribirse como sigue:

public Punto (Punto p) {


this(p.x, p.y);
}
4.3 PROGRAMACIÓN DE MÉTODOS 77

con lo cual se está llamando al constructor que toma dos valores reales, el primero es la
coordenada x del punto p y el segundo la coordenada y del punto p.
Con los constructores programados se pueden crear puntos en diferentes modalidades, como
se muestra en la siguiente sección.

4.3.5 El método main


En Java se tiene el método main, el cual es indispensable, pues es ahı́ en donde empieza la
ejecución del programa, ası́ que lo común es que en este método esté expresado el algoritmo
para resolver el problema original. La sintaxis del encabezado del método main es la siguiente:
public static void main(String [] pps) {
.... //Algoritmo
}

se trata de un método público (public), no relacionado con objetos sino con la clase
(static), que no devuelve valor alguno (void), se llama main y recibe un parámetro pps
(su tipo se verá en el siguiente capı́tulo). El parámetro del método main no es una palabra
reservada, ası́ que puede llamarse de otra forma.
Un método estático, como main, no requiere un objeto para ser llamado, pues es un método
de la clase, no de los objetos. Como no opera en el contexto de un objeto particular no puede
referenciar variables de instancia, el tratar de hacerlo ocasiona que el compilador genere un
mensaje de error. Lo que sı́ puede hacer el método main es referenciar variables estáticas,
porque éstas existen con independencia de los objetos. Por tanto, en el main se puede accesar
sólo a variables estáticas, variables locales y métodos estáticos.
Cuando se escriben clases, es importante escribir programas para probar que los métodos
no tienen errores de lógica, es decir, se debe verificar que los métodos en cada clase funcionen
adecuadamente. Lo común es escribir una clase que contenga el método main.
En caso de que algún método no funcione como se espera, éste debe corregirse. No es tan
fácil determinar la ubicación de los errores de lógica como los errores de sintaxis, sin embargo,
una forma de hacerlo es colocando instrucciones de impresión en los métodos disfuncionales,
en lugares estratégicos para cercar el error, y una vez encontrado corregirlo y eliminar las
instrucciones de impresión agregadas para encontrar el error. Esta es una forma de hacer la
tarea conocida como depuración.
Ejemplo 4.12. Programa para probar los métodos de la clase Punto, incluyendo los cons-
tructores.

public class PruebaPunto {


public static void main (String[] pps) {
Punto inicio = new Punto(4,-30);
Punto fin = new Punto(10,10);
Punto otro = new Punto();
78 CAPÍTULO 4. CREACIÓN Y USO DE CLASES

System.out.println("La distancia es "+inicio.distancia(fin));


System.out.print("Los puntos ");
boolean estan = inicio.estanAlineados(fin, otro);
if (estan) {
System.out.print("sı́ ");
} else {
System.out.print("no ");
}
System.out.println("están alineados");
}
}

En este programa se crean tres objetos de la clase Punto cuya referencia se almacena en
las variables locales inicio, fin y otro, respectivamente. Estas variables por ser locales
sólo pueden ser utilizadas en el método donde se definen, es decir, en el cuerpo del método
main. Cada objeto creado tiene su propia estructura y comportamiento como se muestra en
la figura 4.2.

OBJETOS
CLASE
x 4 inicio
Punto
y −30
int x;
int y; fin
x 10
Métodos y 10

x 0 otro
y 0

Figura 4.2 Varios objetos de la clase Punto.

Cada objeto tiene sus propias variables de instancia y para modificar su estado se debe
hacer uso de los métodos apropiados. Para crear cada objeto se utiliza el operador new. Si el
constructor tiene parámetros, los parámetros reales deben proporcionarse al usar el operador
new. Por ejemplo, si se desea crear el punto con coordenadas (4,-30) se utiliza la instrucción:
new Punto(4,-30) y se llama al constructor cuya firma es public Punto(double xIni,
double yIni). Al igual que en todos los métodos, los parámetros reales se asignan a los
formales en orden de aparición, es decir, el valor 4 se asigna al parámetro xIni y el valor -30
al parámetro yIni, que son los valores que finalmente se asignan a las variables de instancia.
El tipo del parámetro real debe ser consistente con el tipo especificado en su respectivo
parámetro formal (figura 4.3).
4.3 PROGRAMACIÓN DE MÉTODOS 79

Llamada al método: new Punto (4 , −30);

Declaración del método: Punto (int xIni, int yIni) {


x = xIni;
y = yIni;
}

Figura 4.3 Correspondencia de parámetros.

En esta versión del programa se trabaja con los puntos (4,-30), (10,10) y (0,0), respecti-
vamente. Se calcula la distancia entre los dos primeros y finalmente se verifica que los tres
puntos estén alineados. Quizá esto parezca no tener sentido, pero se debe recordar que el
objetivo de este programa es probar los métodos de la clase Punto. El programa muestra los
siguientes mensajes:
La distancia es 40.44749683231337
Los puntos no están alineados

Con los constructores también se permite crear objetos anónimos, como en el siguiente
caso:
inicio.distancia(new Punto(5,5));

el punto (5,5) no está asignado a ninguna referencia, solamente se crea para poder calcular
su distancia con respecto al punto inicial.

4.3.6 El método equals (igualdad entre objetos)


En ocasiones, los problemas tienen soluciones muy sencillas para casos particulares, por
ejemplo, la distancia entre un punto y él mismo es cero. Si se sabe esto, se puede evitar
hacer el cálculo de la distancia cuando los puntos sean el mismo, es decir, es conveniente
que antes de calcular la distancia se verifique que los puntos son distintos. Quizá se piense
escribir una instrucción como la siguiente:
if (inicio == fin) {
System.out.println("La distancia es 0");
}

Esta comparación, que en apariencia es la solución al problema planteado, no es correcta


debido a que se están comparando las referencias a los objetos, no el estado de cada objeto.
El operador == verifica si el contenido de las variables de referencia es el mismo, es decir,
verifica si ambas variables son referencia al mismo objeto, o dicho en términos más formales
si ambas referencias son alias del mismo objeto.
Ejemplo 4.13. Programa para mostrar la diferencia entre referencia y objeto.
80 CAPÍTULO 4. CREACIÓN Y USO DE CLASES

/**
* Programa que ilustra la diferencia entre referencia y objeto
* @author Amparo López Gaona
* @version 3a edición
*/
public class Diferencia {
public static void main (String [] pps) {
Punto p1 = new Punto(10,20);
Punto p2 = new Punto(20,20);
Punto p3 = new Punto(10,20);
Punto p4 = p1;

if (p1 == p1) {
System.out.println("p1 es igual a p1");
}
if (p1 == p2) {
System.out.println("p1 es igual a p2");
}
if (p1 == p3) {
System.out.println("p1 es igual a p3");
}
if (p1 == p4) {
System.out.println("p1 es igual a p4");
}
}
}

El resultado de este programa es el siguiente:

p1 es igual a p1
p1 es igual a p4

Aunque el objeto referenciado por p1 tenga el mismo estado que el objeto referenciado por
p3 no son el mismo objeto, por lo tanto la expresión p1 == p3 da como resultado falso.
Para evitar este problema se debe crear en la clase Punto un método para comparar
puntos con base en su estado, no en términos de sus referencias. Por ser una tarea común,
es recomendable incluir, en cada clase que se programe, un método para determinar cuando
dos objetos son iguales con la firma public boolean equals (Object).
Ejemplo 4.14. Método para comparar dos puntos de acuerdo a su estado.

/**
* Método para determinar si dos puntos son iguales
* @param pto - punto contra el cual se va a comparar
4.3 PROGRAMACIÓN DE MÉTODOS 81

* @return boolean - true si son iguales y false en otro caso


*/
public boolean equals (Object pto) {
Punto punto = (Punto)pto;
return x == punto.obtenerX() && y == punto.obtenerY();
}

Este es un método que recibe un objeto de la clase Object (véase capı́tulo 7) y devuelve
true sólo si las coordenadas x de ambos puntos tienen el mismo valor y las coordenadas y
de ambos puntos tienen el mismo valor; en otro caso devuelve false. La primera instrucción
del método equals consiste en tratar ese objeto de la clase Object como uno de la clase
Punto, mediante una conversión explı́cita (Véase 2.4). Después aparece una expresión que
devuelve true si la coordenada x de ambos puntos y la coordenada y coinciden, en otro caso
devuelve false. Notar que no se utiliza una instrucción if, se devuelve el resultado de la
evaluación de una expresión lógica.
Al tener este método se puede determinar si dos puntos son iguales con sólo llamar al
método, como se muestra en el siguiente ejemplo:

if (inicio.equals(fin)) {
System.out.println("La distancia es 0");
}

Es importante notar que la condición de la instrucción if no es la expresión booleana


inicio.equals(fin) == true, que también es válida. La forma en que se presentó es la
equivalente a preguntar si el valor de la expresión es verdadera.
Ejemplo 4.15. Programa para ilustrar el uso del método equals.

/**
* Programa que ilustra el uso del método equals
* @author Amparo López Gaona
* @version 3a edición
*/
public class Diferencia {
public static void main (String [] pps) {
Punto p1 = new Punto(10,20);
Punto p2 = new Punto(20,20);
Punto p3 = new Punto(10,20);
Punto p4 = p1;

if (p1.equals(p1)) {
System.out.println("p1 es igual a p1");
}
if (p1.equals(p2)) {
82 CAPÍTULO 4. CREACIÓN Y USO DE CLASES

System.out.println("p1 es igual a p2");


}
if (p1.equals(p3)) {
System.out.println("p1 es igual a p3");
}
if (p1.equals(p4)) {
System.out.println("p1 es igual a p4");
}
}
}

El resultado de este programa es el siguiente:

p1 es igual a p1
p1 es igual a p3
p1 es igual a p4

Recordar que en el capı́tulo anterior para comparar dos cadenas se utilizó el método equals
y no el operador de comparación. Ahora se puede comprender la razón de ello.

4.3.7 El método toString


Hasta ahora, los programas de prueba de la clase Punto no muestran el valor de los puntos
con los que se está trabajando, sólo dan resultados tales como la distancia entre dos puntos, si
tres puntos están alineados o bien si son iguales o no. Para poder mostrar las coordenadas del
punto con el que se está trabajando se tendrı́a que incluir una instrucción como la siguiente:

System.out.println("Tengo el punto (" + inicio.obtenerX() + "," +


inicio.obtenerY() + ")");

Esta es una forma de hacerlo. Sin embargo, resulta incómodo y es propenso a errores que
cada vez que se desee escribir un punto se deba especificar cada detalle del mismo. Una forma
más sencilla de mostrar las coordenadas del punto es crear, en la clase Punto, un método
con la firma public String toString() como el siguiente:

/**
* Método para convertir un Punto a cadena de caracteres
* @return String -- el punto en formato de cadena
*/
public String toString() {
return "(" + x + "," + y + ")";
}
4.3 PROGRAMACIÓN DE MÉTODOS 83

Ahora cada vez que que se requiera un punto con sus coordenadas en un paréntesis y
separadas por una coma, sólo se debe escribir la referencia del mismo, con lo cual automáti-
camente se llama al método toString.
Ejemplo 4.16. El siguiente ejemplo prueba los métodos de la clase Punto y en los mensajes
de salida muestra los puntos utilizados.
/** Programa para probar la clase Punto
* @author Amparo López Gaona
* @version 3a edición
*/
public class PruebaPunto {
public static void main (String[] pps) {
Punto inicio = new Punto(4,-30);
Punto fin = new Punto(10,10);
Punto otro = new Punto();

System.out.print("Los puntos " + inicio + "," + fin + " y " + otro);


if (inicio.estanAlineados(fin, otro)) {
System.out.print(" sı́ ");
} else {
System.out.print(" no ");
}
System.out.println("están alineados.");
}
}

Ahora el resultado es:


Los puntos (4,-30), (10,10) y (0,0) no están alineados.

con lo cual es más fácil comprobar que el resultado es correcto.


Ejemplo 4.17. A continuación se presenta la clase Punto completa, con los atributos y
todos los métodos que se desarrollaron en esta sección, agrupados según su función. De los
métodos, sólo se escriben sus firmas, se omite el cuerpo porque éste aparece en las secciones
anteriores.

public class Punto {


// Atributos
private double x;
private double y;
// Constructores
public Punto () { ... }
public Punto (double xIni, double yIni) { ... }
public Punto (Punto p) { ... }
84 CAPÍTULO 4. CREACIÓN Y USO DE CLASES

// Inspectores o de acceso
public double obtenerX () { ... }
public double obtenerY () { ... }
// Modificadores
public void asignarX(double nuevaX) { ... }
public void asignarY(double nuevaY) { ... }
public void asignarPunto(double nuevaX, double y1) { ... }
public void asignarPunto(Punto p) { ... }
public void desplazar (int deltaX, int deltaY) { ... }
// Calculadores
public boolean estanAlineados (Punto p1, Punto p2) { ... }
public double distancia (Punto p) { ... }
public boolean esIgual (Punto p) { ... }
// Especiales
public boolean equals (Object p) { ... }
public String toString() { ... }
}

4.3.8 Métodos que devuelven objetos


Con base en lo expuesto hasta el momento, ya se pueden desarrollar clases que ayuden a
resolver muchos problemas. En esta sección se presenta un nuevo problema y una solución,
a fin de continuar practicando el desarrollo de programas e introducir otros aspectos de
programación, por ejemplo, el que un método devuelva un objeto o cómo establecer una
relación de orden entre dos objetos.
Considerar que se tiene el siguiente problema: en el área de cuidados intensivos de un
hospital se quiere automatizar la generación de horarios en los que se deben suministrar los
medicamentos a los pacientes debido a que se requiere mucha precisión en esta tarea. Para
generar los horarios se debe saber para cada paciente, y de acuerdo al intervalo de minutos
especificado por el médico, a qué horas debe tomar sus medicamentos. El programa debe
generar un listado, para cada paciente, con su horario de medicación.

1. Encontrar los objetos principales.


Sustantivos: programa, hospital, horario, medicamento, paciente, tarea, médico, listado
y hora. De estos sustantivos, hospital es circunstancial, horario es el resultado esperado
y medicamento es información relacionada con el paciente; tarea es lo mismo que
suministrar medicamento; listado es el resultado del programa. Por lo tanto, paciente
y hora son los objetos principales.

2. Determinar el comportamiento deseado.


Verbos: automatizar, suministrar medicamentos. De aquı́ no es posible determinar el
4.3 PROGRAMACIÓN DE MÉTODOS 85

comportamiento, pero con sentido común y la experiencia en programación se puede


deducir el siguiente comportamiento para la clase de las horas.

Hora:
Crear una hora inicial.
Asignar valor a los atributos de una hora.
Obtener el valor de los atributos de una hora.
Sumar una hora con una cantidad de minutos.
Imprimir una hora en un formato hh:mm:ss.
Obtener la diferencia entre dos horas.
Determinar la relación entre dos horas.

Los primeros métodos son los obligatorios para cada clase (constructores, modificadores
y de acceso), el cuarto método surge de la descripción del problema. Los últimos dos
métodos se agregaron para tener una definición más completa y general de una clase
Hora, ya que podrı́an utilizarse en otras aplicaciones.
Al crear una clase se debe pensar en satisfacer los requerimientos del problema que se
está resolviendo; también es conveniente pensar en futuros usos de tal clase e incluir
más métodos y/o en hacerlos lo más generales posible.
3. Definir escenario de medicación de un paciente.

(a) El programa solicita el nombre del paciente.


(b) El usuario proporciona el nombre del paciente.
(c) El programa solicita el nombre del medicamento o tratamiento. Por ejemplo:
cambiar suero, limpiar herida, etcétera.
(d) El usuario proporciona el nombre del medicamento o tratamiento.
(e) El programa solicita cada hora inicial del tratamiento.
(f) El usuario proporciona la hora inicial del tratamiento.
(g) El programa valida la hora inicial del tratamiento.
(h) El programa solicita el intervalo de tratamiento en minutos.
(i) El programa valida el intervalo de tratamiento en minutos.
(j) El programa calcula las horas exactas en que se debe aplicarse el tratamiento a
lo largo del dı́a e imprime el horario.

Ejemplo 4.18. La estructura de las horas consta de tres números enteros: uno para las
horas, otro para los minutos y otro más para los segundos.
86 CAPÍTULO 4. CREACIÓN Y USO DE CLASES

/**
* Clase para trabajo con horas divididas en horas, minutos y segundos
* @author Amparo López Gaona
* @version 3a edición
*/
public class Hora {
private int horas;
private int minutos;
private int segundos;

Ejemplo 4.19. Métodos constructores de la clase Hora.

/** Constructor de una Hora a partir de la hora, los minutos y los segundos
* @param h - valor para las horas
* @param m - valor para los minutos
* @param s - valor para los segundos
*/
public Hora(int h, int m, int s) {
asignarHoras (h);
asignarMinutos(m);
asignarSegundos(s);
}
/**
* Constructor por omisión. Construye la hora 00:00:00
*/
public Hora() {
this(0,0,0);
}
/**
* Constructor una Hora a partir de otra
* @param h - Hora que se copiará
*/
public Hora(Hora h) {
this(h.horas, h.minutos, h.segundos);
}

En los últimos constructores se utiliza la instrucción this para llamar al constructor que
toma tres números enteros; cada uno es un atributo del objeto que se le pasa como parámetro.
El constructor que recibe tres enteros llama a los métodos internos para asignar valor a cada
atributo. Notar que por ser métodos internos no se requiere preceder la llamada con la
referencia a un objeto.
Ejemplo 4.20. Métodos inspectores o de acceso para la clase Hora.
4.3 PROGRAMACIÓN DE MÉTODOS 87

/**
* Método para obtener el número de horas
* @return int - las horas del objeto Hora
*/
public int obtenerHoras() {
return horas;
}
/**
* Método para obtener los minutos en una hora
* @return int - los minutos del objeto Hora
*/
public int obtenerMinutos() {
return minutos;
}
/**
* Método para obtener los segundos en una hora
* @return int - los segundos del objeto Hora
*/
public int obtenerSegundos() {
return segundos;
}

Ejemplo 4.21. Métodos modificadores.

/**
* Método para asignar la hora
* @param h - las horas para el objeto Hora
*/
public void asignarHoras(int h){
if (h >= 0 && h < 24){
horas = h;
} else {
new Error("El valor " + h + " no es apropiado para las horas");
}
}
/**
* Método para asignar los minutos de una hora
* @param h - los minutos para el objeto Hora
*/
public void asignarMinutos(int m) {
if (m >= 0 && m < 60) {
minutos = m;
} else{
88 CAPÍTULO 4. CREACIÓN Y USO DE CLASES

new Error("El valor " + m + " no es apropiado para los minutos");


}
}
/**
* Método para asignar los segundos de una hora
* @param h - los segundos para el objeto Hora
*/
public void asignarSegundos(int s) {
if (s >= 0 && s < 60) {
segundos = s;
} else {
new Error("El valor " + s + " no es apropiado para los segundos");
}
}

En estos métodos si el valor del parámetro está en el rango adecuado se asigna su valor a
la variable de instancia. En caso contrario se crea un objeto de la clase Error para enviar,
al usuario, el mensaje especificado y terminar la ejecución del programa que llamó a este
método. Con esto se están programando métodos robustos.
La clase Error sólo tiene un constructor que recibe una cadena, que funcionará como
mensaje de error. El constructor escribe el mensaje y termina la ejecución del programa.
Ejemplo 4.22. Métodos auxiliares.
En esta clase se incluyen dos métodos privados: uno para convertir un objeto de la clase
Hora en un entero y otro para hacer lo contrario. Que estos métodos sean privados significa
que fuera de la clase no pueden ser utilizados porque ni siquiera se sabe que existen; se
diseñaron privados porque son auxiliares para la implementación de las operaciones con
horas. Los métodos privados no se documentan con /** porque no son parte de la interfaz
de la clase, es decir, como el cliente de la clase no puede usarlos no tiene caso que sepa de su
existencia. Sı́ deben estar documentados pero con documentación “privada”, es decir, entre
/* y */.

/* Método para convertir una hora(hr:min:seg) a un número entero que representa


* los segundos transcurridos. Cada hora equivale a 3600 segs y cada
* minuto a 60 segundos
*/
private int enSegundos() {
return horas * 3600 + minutos * 60 + segundos;
}
/* Método para convertir un número entero, que representa los segundos
* transcurridos, en un objeto de la clase Hora
*/
private Hora enHoras(int n) {
4.3 PROGRAMACIÓN DE MÉTODOS 89

int hh, ss, mm;

hh = n / 3600;
n = n % 3600;
ss = n % 60;
mm = n /60;
return new Hora(hh, mm, ss);
}

El método enHoras devuelve un objeto de la clase Hora, como lo indica la segunda palabra
de su encabezado. El que un método devuelva un objeto es tan sencillo como que devuelva
cualquier otro tipo de dato. El método enHoras, después del cálculo llama al constructor de
Hora que recibe tres enteros y devuelve una Hora, esta última es el valor que devuelve el
método. En este método se presenta otra forma de uso de un objeto anónimo, al no asignar,
explı́citamente, el resultado del operador new a alguna referencia.
Ejemplo 4.23. Método para modificar un objeto de la clase Hora agregándole minutos.

/** Método que suma minutos a un objeto de la clase Hora


* @param mins - minutos que se sumarán a la Hora
*/
public void sumar(int mins) {
int segs = Math.abs(mins) * 60;
int sumaSegs = enSegundos() + segs;
Hora suma = enHoras(sumaSegs);
this.horas = suma.horas;
this.minutos = suma.minutos;
this.segundos = suma.segundos;
}

En este método primero se convierten a segundos los minutos dados en el parámetro. En


este caso no importa si la cantidad de minutos es mayor que 60, sólo se verifica que sea
positiva, con ello es posible sumar, por ejemplo, 4:30 + 78. Luego el objeto de la clase
Hora, con que se llama a este método, se convierte a un entero que representa los segundos
transcurridos. Después se suman los dos enteros y finalmente se convierte el resultado de
esta suma en un objeto de la clase Hora, el cual es almacenado en el objeto suma. De éste se
toma cada variable de instancia y se asigna al objeto con el que se llamó al método.
En la codificación de cualquier método se puede llamar a otros métodos de la misma clase,
esto se conoce como llamada a un método interno y para ello no se requiere la notación
punto, es decir, se omite la referencia al objeto. Sin embargo, para llamar a cualquier método
de otra clase es necesario tener un objeto y utilizar la notación punto.
Ejemplo 4.24. Método para modificar un objeto de la clase Hora sumándole otra hora.
90 CAPÍTULO 4. CREACIÓN Y USO DE CLASES

/**
* Método para sumar dos horas
* @param h - Hora que se sumará a la hora actual
*/
public void sumar(Hora h) {
int sumaSegs = h.enSegundos() + enSegundos();
Hora suma = enHoras(sumaSegs);

this.horas = suma.horas;
this.minutos = suma.minutos;
this.segundos = suma.segundos;
}

Las dos horas que se van a sumar se convierten a segundos y se suman, dejando el resultado
en suma. Como en el ejemplo anterior, se asigna el objeto suma al objeto con el que se llamó el
método.
Con el método sumar, en sus dos modalidades, se ilustra, nuevamente, la sobrecarga de
métodos.
Ejemplo 4.25. Método para obtener la diferencia entre dos horas. En este método se espera
que el parámetro sea menor que el objeto con el que se llama al método.

/**
* Método para restar al objeto que envı́a el mensaje la hora especificada en el parámetro
* @param h -- Hora que se restará
*/
public void restar(Hora h) {
if (comparar(h) < 0){ // Si el parámetro es mayor no se puede efectuar la resta
new Error("No puede efectuarse la resta. " +
"El sustraendo es mayor que el minuendo .");
} else {
int restaSegs = enSegundos() - h.enSegundos();
Hora resta = enHoras(restaSegs);
this.horas = resta.horas;
this.minutos = resta.minutos;
this.segundos = resta.segundos;
}
}

En este caso se comparan las dos horas para determinar si es posible efectuar la resta, es
decir, para determinar si el sustraendo es menor que minuendo. En caso negativo se envı́a
un mensaje al usuario y termina la ejecución del programa que llamó a este método. Debido
a que el mensaje es muy largo se dividió en dos partes que se concatenarán con el uso del
4.3 PROGRAMACIÓN DE MÉTODOS 91

operador de suma (+). En caso de poder efectuar la resta, se convierten ambas horas a
segundos, se efectúa la resta y se asigna el resultado al objeto que llamó al método.
Ejemplo 4.26. Método para comparar dos horas. Este método devuelve un valor entero,
si es positivo significa que el objeto con el que se llamó es mayor que el que se tiene como
parámetro, si es negativo significa que el objeto con el que se llamó es menor que el que se
tiene como parámetro y si es cero significa que son iguales.
/**
* Método para comparar dos horas, devolviendo la relación de orden entre ellas
* @param h - Hora con la que se comparará el objeto que envı́a el mensaje
* @return int - la relación de orden entre dos horas. >0, <0, =0
*/
public int comparar(Hora h) {
return enSegundos() - h.enSegundos();
}

Ejemplo 4.27. Método para determinar si dos horas son iguales o diferentes.
/**
* Método para determinar si dos horas son iguales
* @param h - hora contra la cual se va a comparar
* @return boolean - true si son iguales y false en otro caso
*/
public boolean equals (Object h) {
return comparar((Hora)h) == 0;
}

Ejemplo 4.28. Método para convertir un objeto de la clase Hora a cadena de caracteres.
/**
* Método para convertir una Hora a cadena de caracteres
* @return String -- la hora en formato de cadena
*/
public String toString() {
return horas + ":" + minutos + ":" + segundos;
}

Ejemplo 4.29. La clase Hora completa, sin presentar el cuerpo de los métodos, ni su docu-
mentación, quedarı́a como sigue:
public class Hora {
// Estructura
private int horas;
private int minutos;
private int segundos;
92 CAPÍTULO 4. CREACIÓN Y USO DE CLASES

// Métodos constructores
public Hora() { ... }
public Hora(int h, int m, int s) { ... }
public Hora(Hora h) { ... }
// Métodos inspectores
public int obtenerHora() { ... }
public int obtenerMinutos() { ... }
public int obtenerSegundos() { ... }
// Métodos modificadores
public void asignarHora(int h){ ... }
public void asignarMinutos(int m) { ... }
public void asignarSegundos(int s) { ... }
// Métodos privados
private int enSegundos() { ... }
private Hora enHoras(int n) { ... }
// Métodos que operan con horas
public void sumar(int mins) { ... }
public void sumar(Hora h) { ... }
public void restar(Hora h) { ... }
public int comparar(Hora h) { ... }
public boolean equals (Object h) { ... }
public String toString() { ... }
}

Ejemplo 4.30. Programa que resuelve el problema de generar el horario de administración


de medicamentos para pacientes de cierto hospital según el escenario presentado al inicio de
la sección.

import java.util.Scanner;
/**
* Clase para generar horarios de administración de medicamentos
* Objetivo Probar la clase Hora y mostrar el uso de instrucciones iterativas
* @author Amparo López Gaona
* @version 3a edición
*/
public class Horario {
public static void main(String[] pps) {
// Declaración de variables
String nombre, tratamiento; // Nombre del paciente y del tratamiento
Scanner in = new Scanner(System.in); // Lector
int horas, mins, segs, intervalo; // Variables para las horas
Hora horaInicial; // Hora inicial
Hora horaFinal = new Hora(23,59,59); // Hora final
4.3 PROGRAMACIÓN DE MÉTODOS 93

// Solicita y obtiene el nombre del paciente


System.out.println("Dar nombre del paciente");
nombre = in.nextLine();
// Solicita y obtiene el nombre del tratamiento
System.out.println("Dar nombre del medicamento o tratamiento");
tratamiento = in.nextLine();
// Solicita y obtiene hora inicial del tratamiento
System.out.println("Dar hora inicial del tratamiento");
System.out.print("Primero la hora ");
horas = in.nextInt();
System.out.print("Ahora los minutos ");
mins = in.nextInt();

horaInicial = new Hora(horas,mins,0);


// Solicita el intervalo de tratamiento en minutos
System.out.println("Dar el intervalo de tratamiento en minutos");
intervalo = in.nextInt();
// Calcula las horas exactas en que se debe aplicarse el
// tratamiento a lo largo del dı́a e imprime el horario.

System.out.println("El horario de aplicación de "+tratamiento+" es ");

...
}
}

La generación del horario requiere que se sume a la hora inicial el intervalo de espera,
tantas veces como sea necesario hasta llegar a la hora final. Para evitar escribir el código
repetidas veces existen en Java las llamadas instrucciones de/para iteración.
Una instrucción de iteración permite la ejecución repetida de instrucciones contenidas en un
bloque. Java proporciona tres tipos de instrucciones iterativas. Una de ellas es la instrucción
do. Esta instrucción tiene la siguiente sintaxis:

do {
instrucciones
} while (condición) ;

Con la instrucción do se ejecuta el bloque de instrucciones contenidas entre llaves (denomi-


nado cuerpo) mientras la condición evaluada al final del mismo sea verdadera. En cuanto el
resultado de la evaluación de la condición sea falsa se termina la instrucción do. Es decir, de
94 CAPÍTULO 4. CREACIÓN Y USO DE CLASES

antemano no se puede conocer el número de iteraciones que se realizarán y las instrucciones


del bloque se realizan al menos una vez. Es importante que en algún momento la condición
no se satisfaga para asegurar que la repetición termine.
Por ejemplo, si se desea escuchar una lista de reproducción de música
do {
escuchar melodı́a
pasar a la siguiente melodı́a
} while (quiero oı́r otra y hay más melodı́as)

La lista se reproducirá mientras haya melodı́as y desee seguir escuchando. Si no se incluyera


la segunda instrucción del bloque de la instrucción do siempre se seguirı́a escuchando la
misma melodı́a y no terminarı́a el bloque.
El código para la generación del horario de medicación es como sigue:

do {
System.out.println(horaInicial);
horaInicial.sumar(intervalo);
} while(horaInicial.obtenerHoras() < horaFinal.obtenerHoras());

Muestra la hora inicial, le suma el intervalo establecido y repite estas dos instrucciones
mientras la hora inicial sea menor que la final. Esta fracción de código se debe incluir en el
programa anterior en lugar de los puntos suspensivos.
Para que el programa sea robusto se debe validar la horaInicial del tratamiento y el in-
tervalo. Gracias a que la clase Hora es robusta no es necesario hacer la validación en este
programa puesto que se hace en la clase Hora.
En resumen, se ha visto la forma de crear una clase. Toda clase consta de atributos y
métodos. En cada método se tiene una sección de instrucciones que definen el algoritmo o la
forma en la que el método realiza su función. La programación de algunos métodos es sencilla,
otros métodos requieren una planeación más cuidadosa y quizá sea necesario descomponerlos
en otros métodos para crear un diseño más entendible.
Como consecuencia de la construcción de la clase Punto y la clase Hora se ha extendido el
lenguaje de programación con nuevos tipos de datos, porque ahora es posible tener datos de
cualquiera de estos dos tipos nuevos.

4.4 Ejercicios
1. ¿Cuáles son los componentes de una clase?

2. ¿Cuál es la visibilidad más frecuente de un atributo?, ¿y la de un método?

3. ¿Cuáles son los datos con los que puede trabajar un método?
4.4 EJERCICIOS 95

4. ¿Qué es un método constructor?

5. ¿Qué significa que un método esté sobrecargado?

6. ¿Cuándo se utiliza la palabra reservada this?

7. ¿Qué debe hacerse para que un método pueda devolver un objeto?

8. En la clase Punto, ¿cuál es la diferencia entre el método asignarPunto(Punto p) y el


constructor de copia?

9. ¿Cuál es el resultado de la ejecución del siguiente programa?

public class PruebaHora {


public static void main(String[] pps) {
Hora h1 = new Hora(10,10,25);
Hora h2 = new Hora(4,50,50);

System.out.println("La nueva hora es "+ h1 + h2);


}
}

10. Agregar a la clase Hora un método para sumar dos horas y dejar el resultado en una
hora que no sea la que llama al método.

11. Escribir una clase Trio que tenga tres números enteros y los siguientes métodos.

(a) Un constructor con tres datos.


(b) Un constructor por omisión.
(c) Un constructor de copia.
(d) Un método que devuelva true si dos trı́os tienen igual su coordenada x y false
en otro caso.
(e) Un método para determinar si dos trı́os son iguales.
(f) Un método que devuelva la cadena Mayor si cada coordenada del primer trı́o es
mayor que la correspondiente en el segundo trı́o, que devuelva la cadena menor si
cada coordenada del primer trı́o es menor que la correspondiente en el segundo
trı́o y que devuelva iguales si ambos trı́o son iguales (para esta parte debes usar
el método escrito en el punto anterior).
(g) Un método esLaSuma() que devuelva verdadero si cualquiera de los elementos del
trı́o es la suma de los otros dos y falso en otro caso.
96 CAPÍTULO 4. CREACIÓN Y USO DE CLASES

(h) Un método que trabaje con 3 objetos de la clase Trio y deje en el objeto que se
llama el valor más pequeño de los tres. Ejemplo:

t1 = new Trio(9,19,12);
t2 = new Trio(1, 25, 68);
t3 = new Trio(987, 6, 45);

t1.menor(t2, t3,t1) hace que t1 sea igual a (1,6,12)

(i) Un método que calcule el promedio de 2 trı́os y devuelva un nuevo trı́o con esos
valores. Ejemplo, con los trı́os del punto anterior:

t3 = t1.promedio(t2);
hace que t3 tenga el trio (5,17,40)

12. Si se tiene la clase Mensaje que puede almacenar un mensaje y desplegarlo.

public class Mensaje {


private String mensaje;

public Mensaje() { mensaje = "";}


public String obtenerMensaje() { return mensaje; }
public void asignarMensaje(String m) { mensaje = m; }
}

Definir una clase PruebaMensaje para probar la clase Mensaje, en el método main de
ella se deben crear dos objetos, asignarles un texto cualquiera y desplegarlos. En caso
necesario, escribir otros métodos.

13. Escribir un programa que sea una calculadora de números racionales. Un número ra-
cional está representado por un cociente entre dos números enteros de la forma p/q
donde p y q son números enteros y q = 0. El trabajo con racionales debe incluir:
4.4 EJERCICIOS 97

Suma:
p1 p2 p 1 q2 + p 2 q1
+ =
q1 q2 q 1 q2

Resta:
p1 p2 p 1 q2 − p 2 q 1
− =
q1 q2 q1 q 2

Multiplicación:
p1 p 2 p1 p2
∗ =
q 1 q2 q1 q 2

División:
p1 p2 p 1 q2
÷ =
q1 q2 p 2 q1

14. En una institución bancaria se requiere hacer cierto manejo de fechas para determinar,
por ejemplo, la fecha final de una inversión de dinero. Por ello el propósito de este
problema es escribir una clase para crear y manejar fechas. Esta clase debe incluir,
entre otros, métodos para:

(a) Crear una fecha inicial. Uno sin parámetros, otro que tome tres enteros (dı́a, mes,
año); y un tercero con tres datos: dos enteros (dı́a y año) y una cadena para el
nombre del mes.
(b) Asignar valor a los campos de una fecha.
(c) Obtener los campos de una fecha.
(d) Obtener la diferencia, en dı́as, entre dos fechas.
(e) Dada una fecha inicial y un número entero que representa una cantidad de dı́as,
obtener la fecha que corresponde, por ejemplo: con 3-9-2013 y 5, deberá dar 8-9-
2013. Con la misma fecha inicial y -2 se debe obtener 1-9-2013.
(f) Determinar si dos fechas son iguales.
(g) Devolver el número correspondiente a un mes a partir de que reciba una cadena
con su nombre.
(h) Dada una fecha, especificar los dı́as transcurridos en el año. Por ejemplo, 30 de
diciembre de 2013, debe devolver 364.
(i) Representar una fecha en el formato dd/mes/aa , donde dd, aa son dos dı́gitos y
mes es el nombre del mes.

15. Escribir un programa para ayudar a un cajero de una institución bancaria; el progra-
ma debe manejar cuentas bancarias de débito. El cajero deberá permitir realizar las
98 CAPÍTULO 4. CREACIÓN Y USO DE CLASES

siguientes operaciones sobre una tarjeta de débito: crear cuentas, retirar dinero (siem-
pre que haya suficiente), depositar dinero, conocer el saldo de una cuenta y cancelar
una cuenta.

16. Escribir un programa que simule el comportamiento de un robot. El robot tiene nombre,
modelo y un número de serie. Esta clase debe tener métodos para crear un robot,
mandarlo a dormir, despertar, repetir lo que se le dice, hacer operaciones aritméticas
básicas, determinar si dos robots son iguales y para mostrar sus datos. Mientras el
robot está dormido no atiende a ningún otro mensaje a menos que sea despertar.

17. Una casa de cambio recibe todos los dı́as del Banco Mundial una lista del cambio de
divisas en el mundo respecto del dólar americano. Escribir un programa que ayude al
cajero de la casa de cambio con las transacciones que realiza con los clientes. Para ello
debe poder saber, a partir de una cantidad de dólares o euros que se desea comprar,
cuántos pesos costarı́an esos dólares o euros. También si el cliente lleva una cantidad
de pesos le indique cuántos dólares o euros puede comprar. Las operaciones tienen
la restricción de que sólo se pueden vender/comprar divisas extranjeras en múltiplos
enteros de 5, es decir, no se pueden comprar 14 dolares, por ejemplo.
Capı́tulo 5

Objetos como atributos

En este capı́tulo se presentan problemas que permiten ilustrar los conceptos de modulariza-
ción, abstracción y agregación. Los dos primeros facilitan el diseño de programas grandes y
el tercero consiste en incluir objetos en la estructura de objetos de otra clase. También se
presentan algunas instrucciones que permiten la ejecución iterativa de un bloque de instruc-
ciones. Al igual que en los capı́tulos anteriores se presentan problemas que requieren de estos
conceptos para ilustrar la forma en que se usan.

5.1 La clase Lı́nea


En la sección 4.1 del capı́tulo anterior se determinó la necesidad de una clase llamada Lı́nea,
en esta sección se muestra su implementación. Se retoma el siguiente párrafo de la descripción
del problema: “Para la lı́nea recta se requieren dos puntos cualquiera y lo que se desea hacer
con ella es crearla, encontrar su ecuación, calcular su pendiente, dadas dos rectas determinar
si son paralelas, si son la misma, si son perpendiculares y su punto de intersección.”
De la definición del problema se tiene que la estructura de toda lı́nea tendrá un par de
puntos como se ilustra en la figura 5.1.
Ejemplo 5.1. Definición de la estructura de los objetos de la clase Lı́nea.

/**
* Clase para crear lı́neas rectas en el plano cartesiano
* Objetivo: ilustrar la relación de agregación entre objetos
* @author Amparo López Gaona
* @see Punto
* @version 3a edición
*/
public class Linea {
private Punto p1; //Estructura
private Punto p2;

99
100 CAPÍTULO 5. OBJETOS COMO ATRIBUTOS

//Métodos
...
}

Como se comentó al final del capı́tulo anterior, una vez programada una clase, puede
considerarse como un nuevo tipo de datos. Como consecuencia, es igualmente sencillo incluir
objetos que datos de tipos primitivos en la estructura de una clase. Como puede apreciarse
en el ejemplo, en el caso de objetos sólo es necesario incluir la referencia a ellos.
La figura 5.1 muestra gráficamente la relación entre un objeto de la clase Linea y objetos
de la clase Punto. Recordar que se tiene la referencia al objeto no el objeto, por eso los
objetos de la clase Punto se dibujan aparte del objeto contenedor. Esta relación donde un
objeto contiene a otro(s) se denomina relación de agregación o relación “tiene-un(a)” o
en inglés “has-a”. Por ejemplo una lı́nea tiene un punto p1 y tiene un punto p2.

p1
x
Línea y
Punto p1; métodos
Punto p2;
p2
Métodos x
y
métodos

Figura 5.1 Relación de agregación entre objetos.

En el comentario de inicio de la clase se incluye una lı́nea que empieza con @see para
especificar que se incluyen objetos de otra clase cuya documentación puede consultarse. En
este caso, @see Punto significa que puede consultarse la documentación de la clase Punto
para saber cuáles métodos tiene, qué parámetros requiere cada uno y el tipo de valor que
devuelve cada uno.
Del diseño de la solución al problema planteado en el capı́tulo anterior se determinó el
comportamiento de las lı́neas como sigue:

lı́nea:
Crear una lı́nea.
Encontrar su ecuación.
Calcular su pendiente.
Calcular su ordenada al origen.
Determinar si un punto pertenece a la recta.
Determinar si dos rectas son paralelas.
Determinar si dos rectas son la misma.
5.1 LA CLASE LÍNEA 101

Determinar si dos rectas son perpendiculares.


Calcular el punto de intersección de dos rectas.

En los siguientes párrafos se describe la programación de los métodos de la clase Linea.


Ejemplo 5.2. Constructor para una lı́nea tomando dos puntos.

/**
* Constructor de una lı́nea a partir de dos puntos
* @param p1Ini -- punto de origen
* @param p2Ini -- segundo punto
*/
Linea (Punto p1Ini, Punto p2Ini) {
p1 = new Punto(p1Ini);
p2 = new Punto(p2Ini);
}

Al crear un objeto de la clase Linea automáticamente se ejecuta un constructor y en éste


se deben crear los puntos que la definen, esto es importante porque en la estructura sólo se
especificó la referencia a cada punto, ahora se crean y ya se está en posibilidad de enviar
mensajes a dichos puntos. De esta forma, la creación de un objeto puede llevar a la creación
de otros. En el constructor se llama al constructor de copia de la clase Punto.
Ejemplo 5.3. Constructor por omisión para la lı́nea recta que pasa por el punto (0,0) y
por el punto (1,1).

/**
* Constructor por omisión. Crea la lı́nea que pasa por los puntos (0,0)
* y (1,1)
*/
Linea () {
this(new Punto(0,0), new Punto(1,1));
}

En el constructor se crean dos puntos anónimos para la creación de la lı́nea.


Ejemplo 5.4. Constructor de copia.

/**
* Constructor de copia
* @param recta -- Linea que se toma para crear una nueva
*/
Linea (Linea recta) {
this(recta.p1, recta.p2);
}
102 CAPÍTULO 5. OBJETOS COMO ATRIBUTOS

Este constructor toma los dos puntos que tiene la lı́nea recta para llamar al constructor
apropiado.
Se deja al lector la programación de los métodos asignar y obtener.
Ejemplo 5.5. Método para calcular la pendiente de una recta.
La pendiente de una recta se determina por la ecuación (y2 − y1 )/(x2 − x1 ) donde x2 = x1 ,
porque de otra forma se trata de una recta paralela al eje de las Y, en cuyo caso la pendiente
es ∞, este valor se representa en Java con la constante Double.POSITIVE INFINITY y se
encuentra definida en el paquete java.lang.
/**
* Método para obtener la pendiente de una recta
* @return double - La pendiente de la recta
*/
public double pendiente() {
double divisor = p2.obtenerX() - p1.obtenerX();

if (divisor != 0) {
return (p2.obtenerY() - p1.obtenerY())/divisor ;
}
return Double.POSITIVE_INFINITY; //Recta paralela al eje Y
}

Al aplicar la fórmula para calcular la pendiente de la recta se verifica que el divisor sea
diferente de cero para evitar hacer una división entre cero, que no está definida y causarı́a un
error de ejecución. En caso de que el divisor sea cero se trata de una recta paralela al eje Y y
por lo tanto su pendiente es infinita. Para calcular la pendiente se requieren las coordenadas
x y y de los dos puntos de la recta, para ello se utilizan los métodos obtenerX y obtenerY
de la clase Punto aplicados a los puntos de la recta.
Ejemplo 5.6. Método para determinar la ecuación de la recta que pasa por los puntos
(x1 , y1 ) y (x2 , y2 ). La ecuación es y = mx + b, donde m es la pendiente y b es la ordenada al
origen. Si la recta es paralela al eje de las Y la ecuación es x = cte. Si la recta es paralela al
eje de las X la ecuación es y = b.

/**
* Método para obtener la ecuación de la recta
* @return String -- La ecuación de la recta
*/
public String ecuacion() {
if (pendiente() == Double.POSITIVE_INFINITY) {
return "x = " + p1.obtenerX(); //Recta paralela al eje y
}
if (pendiente() == 0) {
return "y = " + ordenada(); //Recta paralela al eje y
5.1 LA CLASE LÍNEA 103

}
return (ordenada() >= 0) ? "y = "+ pendiente() +"x + "+ ordenada()
: "y = "+ pendiente() +"x " + ordenada();
}

Para calcular la ecuación primero se verifica si la recta es paralela al eje de las Y o de las
X, en ese caso devuelve la ecuación sin calcular la pendiente. En caso contrario, calcula la
pendiente y la ordenada al origen para finalmente devolver la ecuación de la recta.
En este caso se hace uso de los métodos internos pendiente y ordenada de la clase Linea,
debido a que son internos no se utiliza la notación punto, sólo se escribe su nombre.
Para devolver la ecuación se utiliza otra instrucción condicional, para que en caso de que
la ordenada al origen sea negativa no devuelva una cadena como y = 45x + -5.
La instrucción condicional utilizada es diferente de la usada hasta el momento, pues utiliza
el operador condicional. El operador condicional es un operador que requiere tres operandos
y que es equivalente a una instrucción condicional if con cláusula else. Entre paréntesis y
precediendo al sı́mbolo de interrogación (?) se coloca la condición que se desea evaluar. Si ésta
tiene un valor true se ejecuta la expresión que está después del sı́mbolo de interrogación. Si la
evaluación de la condición devuelve false, entonces se ejecuta la expresión que está después
del sı́mbolo de dos puntos (:).
El valor devuelto por las expresiones después del sı́mbolo de interrogación o los dos puntos,
se utiliza como valor para asignar a una variable, como valor retorno de un método o bien
como valor para imprimir. Por ejemplo, la instrucción System.out.println("El mayor
es "+ ((num1 >num2) ? num1 : num2)); imprime el mayor de dos números.
El cuerpo del método ecuacion podrı́a estar en el método toString y en ecuacion sólo
llamar a toString.
Ejemplo 5.7. Método que devuelve la ordenada al origen de una recta que es el lugar en
donde la recta cruza el eje Y, es decir, el valor que tiene la y cuando x = 0.
Para calcular la ordenada al origen se emplea la ecuación b = y1 − mx1 , donde m es la
pendiente de la recta. Si la recta es paralela al eje Y entonces nunca cruza dicho eje y por lo
tanto devuelve el valor infinito.

/**
* Método para obtener calcular la ordenada al origen de la recta dada
* @return double -- la ordenada al origen
*/
public double ordenada() {
return (p1.obtenerX() == p2.obtenerX())? Double.POSITIVE_INFINITY
: p1.obtenerY() - pendiente()*p1.obtenerX();
}

Ejemplo 5.8. Método para determinar si un punto dado está en la recta.


104 CAPÍTULO 5. OBJETOS COMO ATRIBUTOS

/**
* Método para determinar si un punto pertenece a la recta
* @param p - Punto a determinar si está en la recta
* @return boolean - true si el punto está en la recta y false en otro caso
*/
public boolean contiene(Punto p) {
return p.estanAlineados(p1,p2);
}
Este método se podrı́a haber programado siguiendo el algoritmo que dice que si la recta
es paralela al eje Y sólo es necesario verificar que la coordenada x del punto sea igual a la
de la recta. En caso de no ser paralela al eje Y se sustituye el punto en la ecuación de la
recta y si se cumple la igualdad el punto pertenece a la recta. Pero una forma más sencilla,
y sobre todo más segura, es utilizar el método estanAlineados de la clase Punto y no hay
que programar nada más.
Ejemplo 5.9. Método para determinar si dos rectas son la misma. Para ello no basta con
comparar los puntos dados en su creación, sino que es necesario verificar que ambas tengan
la misma pendiente y la misma ordenada al origen.
/**
* Método para determinar si dos lı́neas son la misma
* @param linea1 -- Linea con la que se comparará la lı́nea original
* @return boolean -- true si son la misma lı́nea y false en otro caso.
*/
public boolean equals(Linea linea1) {
return pendiente() == linea1.pendiente() && ordenada() == linea1.ordenada();
}
Un ejemplo de la forma de llamar a este método serı́a uno.equals(dos), donde uno es la
lı́nea que llama y dos es la lı́nea que recibe como parámetro.
Ejemplo 5.10. Método para determinar si dos lı́neas son paralelas. Para ello se debe verificar
que tengan la misma pendiente.
/**
* Método para determinar si dos lı́neas son paralelas
* @param linea1 -- Linea con la que se comparará la lı́nea original
* @return boolean -- true si son paralelas y false en otro caso.
*/
public boolean esParalelaA(Linea linea1) {
return pendiente() == linea1.pendiente();
}
Ejemplo 5.11. Método para determinar si dos lı́neas son perpendiculares, para ello es
necesario verificar que la pendiente de una sea igual al negativo del inverso multiplicativo de
la otra. En caso de que una pendiente sea igual a infinito, la otra debe ser igual a cero.
5.1 LA CLASE LÍNEA 105

/**
* Método para determinar si dos lı́neas son perpendiculares
* @param linea1 -- Linea con la que se comparará la lı́nea original
* @return -- true si son paralelas y false en otro caso.
*/
public boolean esPerpendicularA(Linea linea1) {
double m = pendiente();
double m1 = linea1.pendiente();
final double INFINITO = Double.POSITIVE_INFINITY;
boolean perpendicular;

perpendicular = (m == 0 && m1 == INFINITO) || (m1 == 0 && m == INFINITO);


if (!perpendicular) {
perpendicular = m == -1 / m1;
}
return perpendicular;
}

En el cuerpo de este método se debe verificar si una de las pendientes es igual a cero y la
otra igual a infinito, si se cumple esto se tiene que son rectas perpendiculares. En caso de
que no sea ası́ se verifica que la pendiente de una sea el negativo del inverso multiplicativo
de la otra.
Ejemplo 5.12. Método para encontrar el punto de intersección entre dos rectas. En es-
te método se debe verificar primero que las lı́neas no sean paralelas, porque de serlo su
intersección es ∞.

/**
* Método para obtener el punto de intersección entre dos lı́neas
* @param linea1 - la segunda lı́nea
* @return Punto- punto de intersección
*/
public Punto interseccion(Linea linea1) {
double nuevaX, nuevaY;

if (esParalelaA(linea1)) {
nuevaX = nuevaY = Double.POSITIVE_INFINITY;
} else {
nuevaX = (ordenada()- linea1.ordenada())/(linea1.pendiente() - pendiente());
nuevaY = pendiente()*nuevaX + ordenada();
}

return new Punto(nuevaX, nuevaY);


}
106 CAPÍTULO 5. OBJETOS COMO ATRIBUTOS

En toda la sección se ha estado usando la instrucción condicional if para verificar el


cumplimiento o no de condiciones excepcionales que de otra forma pueden causar errores de
ejecución o bien resultados no deseados. Cuando en los métodos se verifica que los datos se
encuentran dentro del rango de valores permitidos se dice que los métodos son robustos.
Para compilar esta clase es necesario tener el archivo Punto.class en el mismo directorio
donde se compile el archivo con la clase Linea o bien especificar en la trayectoria de acceso
a los archivos el lugar en donde se puede encontrar; no es necesario agregar ningún código
extra en el archivo Linea.java.

5.2 Abstracción en el diseño de clases


En esta sección se presenta otro problema para ilustrar la interacción de objetos de diferentes
clases en la solución de un problema. Además de presentar los conceptos de modularización
y abstracción. El problema es el siguiente:
La empresa que administra el servicio de Transporte Colectivo Metro, con el objeto de
agilizar la compra de boletos ha decidido instalar, en cada estación, máquinas expendedoras
de boletos. Se debe desarrollar un programa que controle el funcionamiento de cada máquina.
La máquina debe interactuar con el usuario para cobrarle el importe de los boletos, expedir y
dar los boletos requeridos, en caso de necesidad dar el cambio. Además debe llevar el control
de la cantidad de dinero que tiene y de los boletos que ha expedido. Se debe considerar que
cada boleto tiene un precio fijo.
1. Encontrar los objetos principales.
Los sustantivos encontrados en la descripción del problema son: máquina para expedi-
ción de boletos, boleto, precio y dinero. De estos sustantivos se deduce que la solución
involucra objetos de dos clases: máquina y boletos. El precio es un atributo de cada
boleto.
2. Determinar el comportamiento deseado.
Las frases clave, incluyendo verbos son: la máquina debe cobrar y dar cambio; expedir
y dar los boletos requeridos; llevar control de dinero y de boletos.
De ahı́ que el comportamiento de la máquina incluya los siguientes métodos:

Máquina:
Aceptar la solicitud de compra.
Recibir dinero.
Expedir boletos.
Llevar control de dinero.
Llevar control de boletos.
5.2 ABSTRACCIÓN EN EL DISEÑO DE CLASES 107

Boleto:
Expedirse.

3. Definir escenarios.
El escenario principal es el siguiente:

(a) La máquina da la bienvenida a usuario.


(b) El usuario solicita boletos.
(c) La máquina valida la cantidad de boletos.
(d) La máquina solicita el importe de los boletos.
(e) La máquina recibe dinero, lo deposita en...

Al llegar a este punto se aprecia la necesidad de contar con un objeto encargado de


almacenar el dinero, mientras se tiene el importe total completo y de tomar dinero para
dar el cambio. En suma, se requiere de un cajero que se encargue de lo relacionado con
el dinero y de una caja para guardarlo.
Un escenario para que el cajero cobre es el siguiente:

(a) La máquina le pide al cajero que cobre.


(b) El cajero solicita al usuario el importe de los boletos.
(c) El usuario va depositando dinero en la máquina hasta cubrir el importe requerido.
(d) El cajero recibe el dinero.
(e) El cajero guarda el importe de la compra en la caja.
(f) El cajero determina si es necesario dar cambio. En ese caso toma dinero de la caja
y se lo da al usuario.

De este escenario se puede determinar el comportamiento del cajero:

Cajero:
Cobrar.
Recibir dinero del cliente.
Almacenar el importe de la compra en la caja.
Dar cambio.
Contabilizar dinero.
108 CAPÍTULO 5. OBJETOS COMO ATRIBUTOS

Para terminar el diseño, falta volver a escribir el escenario principal, y determinar la


estructura y comportamiento de la caja. Esto se deja como ejercicio al lector.

Para resolver problemas de programación es conveniente usar la técnica llamada modu-


larización, que consiste en dividir el problema completo en partes bien definidas llamadas
módulos, las cuales pueden construirse y probarse de manera independiente y cuya interac-
ción está bien definida. En la programación orientada a objetos, los módulos son las clases.
La máquina expendedora podrı́a ser programada como una sola clase pero se dificultarı́a
la programación, ası́ que se utilizará el concepto de modularización para dividir el problema
en diversas clases. De acuerdo al diseño se determinaron las clases: Boleto, Cajero, Caja
y Máquina expendedora, que trabajará como coordinador de las otras. (Figura 5.2). La
máquina expendedora de boletos contiene boletos y un cajero para encargarse de manejar el
dinero. El cajero para su tarea requiere de una caja.

MáquinaBoletos
Boletos Cajero
Caja

Figura 5.2 Elementos de una máquina expendedora de boletos.

Definir los atributos relevantes de un objeto es una tarea difı́cil y depende de la aplicación
particular que se tenga. Es necesario concentrarse en las caracterı́sticas esenciales de los
objetos, describiendo los atributos significativos y eliminando los irrelevantes. Esta tarea se
conoce como abstracción.
Un objeto puede verse de diversas formas dependiendo del observador. Por ejemplo, en el
caso de los boletos, para el diseñador gráfico son importantes caracterı́sticas tales como me-
didas, color y material de elaboración del boleto. Para un coleccionista puede ser importante
el valor del boleto, el año de expedición, la figura que tenga, etcétera. Para el programador
de software, sólo es importante poder generarlos y su precio (figura 5.3).
A continuación se muestra la programación de cada clase. Se empezará por la clase más
sencilla: la clase Boleto que tiene un único atributo, que es el precio del mismo, este valor se
asigna y fija al momento de crear cada boleto. Como comportamiento asociado a cada boleto
está la capacidad de expedirse. Por lo tanto, se incluyen dos métodos, uno llamado expedir
y el método toString para convertir un objeto de la clase Boleto a su representación en
forma de cadena de caracteres.

/**
* Clase para generar boletos para acceso al Metro
5.2 ABSTRACCIÓN EN EL DISEÑO DE CLASES 109

Boleto
precio

pasajero

Boleto
largo
ancho
material
diseñador color

Boleto
numSerie
valor
color
coleccionista año

Figura 5.3 Diferentes abstracciones del mismo objeto.

* @author Amparo López Gaona


* @version 3a edición
*/
public class Boleto {
private final double precio; // Precio del boleto
/**
* Constructor. Inicializa el precio del boleto en la cantidad indicada
* @param valor - costo del boleto
*/
public Boleto(double valor) {
precio = valor;
}
/**
* Constructor por omisión. Inicializa el precio del boleto en $3.00
*/
public Boleto() {
this (3.0);
}
/**
* Método de acceso que devuelve el precio del boleto
* @return double - precio del boleto
*/
110 CAPÍTULO 5. OBJETOS COMO ATRIBUTOS

public double obtenerPrecio() {


return precio;
}
/**
* Método para expedir un boleto
*/
public void expedir(){
System.out.println(this);
}
/**
* Método para obtener el boleto como una cadena de caracteres
*/
public String toString(){
return "+-----------------------+\n" +
"| Boleto para el metro |\n" +
"| de Metrópolis |\n" +
"+-----------------------+\n" ;
}
}

Notar que el método expedir se encarga de imprimir el boleto actual (this) utilizando,
implı́citamente, el método toString.
En los siguientes párrafos se presenta la clase Caja, que es la encargada de almacenar el
dinero. Haciendo abstracción de los elementos fı́sicos de una caja se puede establecer que la
estructura de la caja consta de dos variables de tipo real: totalAcumulado, que es el dinero
que se tiene acumulado en la caja por concepto de ventas en el dı́a, y cantidadInicial, que
es la cantidad de dinero que tiene la caja al iniciar operaciones la máquina.

/**
* Clase para simular el funcionamiento de una caja de dinero de un
* establecimiento
* @author Amparo López Gaona
* @version 3a edición
*/
public class Caja {
private double totalAcumulado; // Dinero totalAcumulado en el dı́a
private double cantidadInicial; // Dinero al abrir la caja

Ejemplo 5.13. Constructores para la clase Caja.


Se incluyen dos constructores: un constructor que crea una caja con un total acumulado y
cantidad inicial proporcionado por el creador de la caja. El otro constructor es el constructor
por omisión y éste pone en cero tanto el total acumulado como la cantidad inicial de la caja.
5.2 ABSTRACCIÓN EN EL DISEÑO DE CLASES 111

/**
* Constructor. Inicializa una Caja poniendo cero en cantidad inicial y
* dando un total acumulado inicial.
* @param inicial - cantidad de dinero con que abrirá la caja
*/
public Caja(double inicial) {
inicial = Math.abs(inicial);
totalAcumulado = inicial;
cantidadInicial = inicial;
}
/**
* Constructor por omisión.
* Inicializa una Caja dando un total acumulado y una cantidad inicial de $100.00
*/
public Caja() {
this (100);
}

Ejemplo 5.14. El método guardarDinero permite almacenar, en la caja, el dinero recibido.

/**
* Método para guarda en la caja el dinero recibido
* @param importe -- cantidad de dinero que se guardará en la caja
*/
public void guardarDinero(double importe) {
totalAcumulado += importe;
}

Ejemplo 5.15. El método retirarDinero permite tomar, de la caja, el dinero especificado.

/**
* Método para extraer de la caja la cantidad de dinero especificada
* @param importe -- cantidad de dinero que se va a extraer
*/
public void retirarDinero(double importe) {
totalAcumulado -= importe;
}

Ejemplo 5.16. El método obtenerTotalAcumulado permite conocer la cantidad de dinero


acumulada en la caja hasta el momento.

/** Método para obtener la cantidad de dinero acumulada en la caja


* @return double - cantidad acumulada
*/
112 CAPÍTULO 5. OBJETOS COMO ATRIBUTOS

public double obtenerTotalAcumulado() {


return totalAcumulado - cantidadInicial;
}

Ejemplo 5.17. El método corteDeCaja permite conocer la cantidad de dinero que hay en
la caja hasta ese momento.

/**
* Método para devolver la cantidad de dinero que hay en la caja
* @return double -- cantidad de dinero acumulada en la caja
*/
public double corteDeCaja() {
return totalAcumulado;
}

5.3 Objetos que crean objetos


En los siguientes párrafos se presenta la clase Cajero, que es la encargada de simular el
manejo de dinero en la máquina vendedora de boletos y cuyas responsabilidades se determi-
naron en el diseño. Esta clase va a tener una caja para guardar el dinero de la venta de los
boletos.

/**
* Clase para simular el funcionamiento de un cajero de un establecimiento
* @author Amparo López Gaona
* @see Caja
* @version 3a edición
*/
public class Cajero {
private Caja miCaja;

Ejemplo 5.18. El constructor del cajero construye la caja que requiere para manejar el
dinero. Al crear un objeto de la clase Cajero éste se encarga de crear un objeto de la clase
Caja.

/**
* Constructor por omisión. Crea una caja con $100
*/
public Cajero () {
this (100.00);
}
5.3 OBJETOS QUE CREAN OBJETOS 113

/**
* Constructor que recibe una cantidad inicial de dinero para empezar a funcionar
* @param dineroInicial -- cantidad inicial para el funcionamiento de la caja
*/
public Cajero (double dineroInicial) {
if (dineroInicial < 0) {
new Error("No se puede abrir una caja con dinero negativo");
}
miCaja = new Caja(dineroInicial);
}

Ejemplo 5.19. El método cobrar es el encargado de recibir el dinero del usuario, verificar
que sea suficiente, guardarlo en la caja y de ser necesario dar cambio.

/**
* Método para cobrar el importe de los boletos
* @param importe - importe que debe cobrarse por la venta de boletos
*/
public void cobrar(double importe) {
double dineroRecibido = 0; // Listo para empezar a cobrar

dineroRecibido = recibirDinero(importe);
miCaja.guardarDinero(dineroRecibido); //Guarda el dinero en la caja
double cambio = dineroRecibido - importe; //Calcula el cambio necesario
if (miCaja.obtenerTotalAcumulado() >= cambio) {
miCaja.retirarDinero(cambio);
System.out.println("\n*** Cambio regresado: " + cambio);
} else {
System.out.println("\n*** De momento no hay cambio ***");
miCaja.retirarDinero(dineroRecibido);
}
}

El método cobrar es robusto, debido a que hace todas las validaciones necesarias, primero
que el dinero sea recibido, luego que sea suficiente para pagar los boletos y finalmente que
haya dinero suficiente para el cambio en la caja.
Ejemplo 5.20. Método privado para recibir el dinero del comprador poco a poco.

/*
* Método para recibir el importe de una compra.
* @param importe -- cantidad que espera recibir
* @return double -- cantidad recibida
*/
114 CAPÍTULO 5. OBJETOS COMO ATRIBUTOS

private double recibirDinero(double importe) {


Scanner in = new Scanner(System.in);
double dineroRecibido = 0;
double saldo = importe;

do {
System.out.print("Deposita tu dinero ");
double dinero = in.nextDouble();
if (dinero > 0 ) {
dineroRecibido += dinero;
saldo = importe - dineroRecibido;
}
if (saldo > 0)
System.out.println("El importe a pagar es $" + importe + " falta $"+ saldo);
} while (dineroRecibido < importe);

return dineroRecibido;
}

En el método recibirDinero se pide dinero al usuario, se valida la cantidad introducida


por el usuario, se calcula la cantidad faltante y se muestra el acumulado al momento; estas
instrucciones se ejecutan mientras lo recibido del usuario sea menor que la cantidad requerida,
o visto de otra forma, hasta que lo recibido sea mayor o igual que lo requerido.
Ejemplo 5.21. El método corteDeCaja permite al cajero conocer la cantidad de dinero que
hay en la caja.

/**
* Método para conocer la cantidad total de dinero que hay en la caja
* @return double -- dinero depositado en la caja
*/
public double corteDeCaja() {
return miCaja.corteDeCaja();
}

Notar que se llama al método de igual nombre de la clase Caja. Está permitido que métodos
en distintas clases tengan la misma firma, cuál se ejecute depende del objeto con el que se
haga la llamada por eso aquı́ se utiliza miCaja.corteDeCaja.

5.4 Interacción de objetos


En esta sección se presenta la clase MaquinaBoletos, que es con la que realmente trabaja
el usuario que requiere boletos para el Metro. Esta clase funciona como coordinadora entre
5.4 INTERACCIÓN DE OBJETOS 115

los objetos de las diferentes clases, tiene la responsabilidad de aceptar la solicitud de compra
por parte del usuario, pedir al cajero que cobre y expedir boletos.
Ejemplo 5.22. Estructura de los objetos de la clase MaquinaBoletos.

/**
* Clase para simular el funcionamiento de una máquina expendedora de boletos
* @author Amparo López Gaona
* @see Cajero
* @version 3a edición
*/
public class MaquinaBoletos {
private int totalBoletos;
private final double precioBoleto;
private Cajero pepe;
private Scanner in;

La estructura de los objetos de la clase MaquinaBoletos consta de dos atributos de tipo


primitivo: totalBoletos, que es un entero que permite llevar el control de los boletos de la
máquina expendedora, y precioBoleto para fijar el precio de los boletos. Además contiene
dos atributos que son objetos: un cajero y un objeto para poder leer la solicitud del compra-
dor. Recordar que el cajero contiene una caja. La relación entre estos objetos se presenta en
la figura 5.4 y es una relación de agregación.

MáquinaBoletos
double precioB pepe miCaja
int totalBoletos Caja miCaja
Cajero pepe
métodos

Figura 5.4 Relación de agregación en la máquina expendedora de boletos.

Se dice que MaquinaBoletos es cliente de Cajero porque delega tareas a objetos de la


clase Cajero. Por su parte Cajero es servidor de MaquinaBoletos. Estos no son papeles
fijos, por ejemplo, Cajero a su vez puede ser cliente de otra clase, por ejemplo de Caja. El
nivel de anidamiento o contención de un objeto es ilimitado.
Ejemplo 5.23. Métodos constructores. La clase MaquinaBoletos tiene un constructor por
omisión, otro toma como parámetro el precio del boleto, otro la cantidad de boletos que
puede expedir y el último toma dos parámetros: el precio del boleto y la cantidad de éstos.

/** Constructor por omisión. En éste se especifica que la máquina cuenta con
116 CAPÍTULO 5. OBJETOS COMO ATRIBUTOS

* cien boletos y el precio de cada uno es de $3.00 y la caja abrirá con $100.00
*/
public MaquinaBoletos() {
this(100, 3.0, 100.0);
}
/**
* Constructor para cien boletos al precio especificado en la
* creación de la máquina expendedora y la caja abrirá con $100.00
* @param precio -- precio de cada boleto
*/
public MaquinaBoletos(double precio) {
this(100, precio, 100.0);
}
/**
* Constructor para la cantidad de boletos especificados en la creación
* de la máquina expendedora. Cada boleto tendrá un precio de $3.00
* y la caja abrirá con $100.00
* @param nBoletos -- cantidad de boletos que se podrá vender
*/
public MaquinaBoletos(int nBoletos) {
this(nBoletos, 3.0, 100.0);
}
/**
* Constructor de una máquina expendedora de boletos con la cantidad de
* boletos especificada y al precio especificado. La caja iniciará con
* la cantidad especificada
* @param nBoletos -- cantidad de boletos que se podrá vender
* @param precio -- precio de cada boleto
* @param dinero -- cantidad de dinero para iniciar operaciones
*/
public MaquinaBoletos(int nBoletos, double precio, double dinero) {
totalBoletos = nBoletos;
precioBoleto = precio;
pepe = new Cajero(dinero);
in = new Scanner(System.in);
}

En este último constructor se puede observar que al crear una máquina de boletos se deben
crear dos objetos más: un cajero y un lector.
Ejemplo 5.24. El método solicitarCantidadDeBoletos se encarga de interactuar con el
usuario para saber cuántos boletos desea comprar el usuario. Valida que esta cantidad sea
positiva y que haya boletos suficientes para vendérselos. Mientras la cantidad no sea positiva
sigue solicitando al usuario la cantidad de boletos deseada.
5.4 INTERACCIÓN DE OBJETOS 117

/*
* Método para solicitar al usuario la cantidad de boletos deseada
* @return int -- cantidad de boletos solicitada
*/
private int solicitarCantidadDeBoletos () {
int nBoletos = 0;

do {
System.out.println("¿Cuántos boletos quieres?");
nBoletos = in.nextInt();
} while (nBoletos <= 0);

if (totalBoletos < nBoletos) {


new Error("Lo siento no tengo esa cantidad de boletos");
}

return nBoletos;
}

Ejemplo 5.25. El método venderBoletos establece comunicación con el usuario dándole


la bienvenida, preguntándole la cantidad de boletos que desea comprar, calcula e indica el
monto de la compra, solicita sean pagados los boletos y si todo está bien le da los boletos al
usuario.

/**
* Método para realizar la venta de boletos solicitados por el usuario
*/
public void venderBoletos() {
bienvenida();
int nBoletos = solicitarCantidadDeBoletos();
double importe = nBoletos*precioBoleto;
System.out.println("El importe a pagar es "+importe);
System.out.println("Introdúcelo en la \"ranura\" ");
pepe.cobrar(importe);
expedirBoletos(nBoletos);
}

El método expedirBoletos, como su nombre indica, expedirá la cantidad de boletos espe-


cificada en su parámetro. Este es un método privado, pues sólo sirve de ayuda en la ejecución
del método venderBoletos, el usuario de la clase no sabrá de su existencia.
El método expedirBoletos crea los boletos necesarios y los imprime. Requiere realizar
este par de acciones el número de veces determinado por la cantidad de boletos solicitada,
para ello se utiliza otra forma de instrucción de iteración.
118 CAPÍTULO 5. OBJETOS COMO ATRIBUTOS

La instrucción for se denomina enumerativa porque en general trabaja con uno (o varios)
contadores. Un contador es una variable entera que, como su nombre indica, sirve para
contar, para ello es común definirla con valor inicial de cero y luego cada vez que sucede el
evento que se desea contabilizar se incrementa en 1 su valor.
La sintaxis de la instrucción for consta de un encabezado y un cuerpo. El cuerpo es
un bloque de instrucciones. El encabezado tiene tres expresiones, la de inicialización y la
de actualización son expresiones aritméticas y la condición es una expresión booleana. Se
presenta a continuación la sintaxis de la instrucción for:

for (inicialización; condición; actualización) {


instrucciones
}

Las variables de la expresión de inicialización deben involucrarse tanto en la condición


como en la expresión de actualización, que generalmente es un incremento para que tenga
sentido la instrucción.
La forma de trabajar de esta instrucción es ejecutar la expresión de inicialización una vez,
luego evaluar la condición y si devuelve true se realiza el cuerpo, seguido de la actualización.
Se repiten estos pasos desde la evaluación de la condición hasta que la condición tiene valor
false.
Ejemplo 5.26. Método expedirBoletos utilizando la instrucción for.

/*
* Método para expedir la cantidad de boletos solicitada
* @param n -- cantidad de boletos solicitada
*/
private void expedirBoletos(int nBoletos) {
for (int i = 0; i < nBoletos; i++) {
Boleto b = new Boleto(precioBoleto);
b.expedir();
}
totalBoletos -= nBoletos;
}

Primero se declara una variable local denominada i que se inicializa en cero, luego se
realizan las instrucciones de crear un boleto y expedirlo mientras el valor de la variable i sea
menor que el de la variable nBoletos pasada como parámetro, en cada iteración después de
expedir el boleto se incrementa en 1 el valor de la variable i para que después de n iteraciones
se deje de cumplir la condición y termine la iteración. Al salir de la iteración se actualiza el
total de boletos de la máquina expendedora.
Ejemplo 5.27. Método expedirBoletos utilizando la instrucción for con decrementos.
5.4 INTERACCIÓN DE OBJETOS 119

/*
* Método para expedir la cantidad de boletos solicitada
* @param n -- cantidad de boletos solicitada
*/
private void expedirBoletos(int nBoletos) {
for(int i = nBoletos; i > 0; i--) {
Boleto b = new Boleto(precioBoleto);
b.expedir();
}
totalBoletos -= nBoletos;
}

Primero se declara una variable local denominada i que se inicializa con el valor de
nBoletos, luego las instrucciones de crear un boleto e expedirlo se realizarán mientras el
valor de la variable i sea mayor que cero, en cada iteración después de expedir el boleto se
decrementa en 1 el valor de la variable i, con lo cual se asegura que en algún momento deje
de ser positivo y termine la iteración.
Cuando se conoce de antemano la cantidad de veces que se va a repetir un conjunto de
instrucciones lo más conveniente es utilizar la instrucción for.
Ejemplo 5.28. El método obtenerPrecioBoleto permite conocer el precio del boleto.

/**
* Método para obtener el precio de cada boleto
* @return double -- precio de cada boleto
*/
public double obtenerPrecioBoleto() {
return precioBoleto;
}

Ejemplo 5.29. El método obtenerTotalBoletos devuelve la cantidad de boletos en exis-


tencia.

/**
* Método para conocer la cantidad de boletos que tiene la máquina expendedora
* @return double -- total de boletos en la máquina expendedora
*/
public int obtenerTotalBoletos() {
return totalBoletos;
}

Ejemplo 5.30. El método corteDeCaja especifica la cantidad acumulada por concepto de


ventas hasta el momento de llamar a este método.
120 CAPÍTULO 5. OBJETOS COMO ATRIBUTOS

/**
* Método para conocer la cantidad de dinero acumulada en la caja
* @return double -- cantidad de dinero acumulada
*/
public double corteDeCaja() {
return pepe.corteDeCaja();
}

En el método corteDeCaja es importante no olvidar especificar la referencia al cajero


cuando se llama al método corteDeCaja de la clase Caja, porque de no hacerlo se tendrı́a
una cantidad infinita de llamadas al método de la clase MaquinaBoletos.
Ejemplo 5.31. Clase Expendedora que incluye el método main que crea una máquina de
boletos, da la bienvenida al sistema y efectúa las ventas.
En este caso, se presenta la tercera forma de instrucción de iteración, ésta se denomina:
while. La sintaxis de la instrucción while es la siguiente:

while (condición) {
instrucciones
}

y su semántica es realizar el bloque de instrucciones mientras la evaluación de la condición


sea verdadera. A diferencia de la instrucción do, primero se evalúa la condición y luego se
realiza el bloque de instrucciones, con lo cual cabe la posibilidad de que la primera evaluación
de la condición dé el valor false y por lo tanto las instrucciones del cuerpo no se ejecuten
ni siquiera una vez.
Al igual que en la iteración mediante la instrucción do es necesario tener cuidado que en
el cuerpo del while se trabaje para modificar los valores involucrados en la condición, con
el propósito de que en algún momento ya no se satisfaga ésta y no dejar el programa en un
ciclo infinito.

/** Clase para expender boletos usando una máquina


* @author Amparo López Gaona
* @version 3a edición
*/
public class Expendedora{
public static void main(String[] pps) {
MaquinaBoletos maquina = new MaquinaBoletos();
while (maquina.obtenerTotalBoletos() > 0) { // Vende boletos mientras haya
maquina.venderBoletos();
}
System.out.println("El total vendido es de "+maquina.corteDeCaja());
}}
5.5 EJERCICIOS 121

Para compilar esta clase es necesario que los archivos ejecutables de todas las clases
vistas en esta sección estén en el mismo directorio donde se compile el archivo con la
clase Expendedora o bien especificar en la trayectoria de acceso a los archivos el lugar
en donde se pueden encontrar; no es necesario agregar ningún código extra en el archivo
Expendedora.java.

5.5 Ejercicios
1. ¿Cómo se declaran objetos dentro de otros?

2. ¿Cómo se distingue la llamada a un método externo de la llamada a un método interno?

3. ¿Qué es un contador?

4. El siguiente código tiene como objetivo escribir los enteros del 1 al 10. ¿Es correcto?
¿Por qué?

int entero = 1;
while (entero <= 10) ;
entero++;
System.out.print(entero + " ");

5. El siguiente código tiene como objetivo escribir los enteros impares del 999 al 1. ¿Es
correcto? ¿Por qué?

for (int i = 999; i >=1; i+=2)


System.out.println("i = " + i);

6. Cuántas veces se lanza la moneda en el siguiente código:

Moneda laSuertuda = new Moneda();


int limite = 1;

for (int i = 0; i <= limite ; i++) {


laSuertuda.lanzar();
limite ++;
}

7. El siguiente código calcula el promedio de los números leı́dos. El fin de la lectura se da


al encontrar un -1. ¿Es correcto? ¿Por qué? En caso de estar mal corregirlo.
122 CAPÍTULO 5. OBJETOS COMO ATRIBUTOS

public class Duda{


static public void main (String[] pps) {
Scanner in = new Scanner(System.in); // Declaración de variables
int valorTerminal = -1, valor = 0, suma = 0, datos = 0;

System.out.println("Teclear valores numéricos, terminar con -1");


while (valor != valorTerminal) {
valor = in.nextInt();
suma += valor;
datos ++;
}
System.out.println("El promedio es" + (suma/datos));
}

8. En el ejercicio anterior, ¿qué pasa si se coloca la lectura antes de la instrucción while?


9. Escribir programas para que el usuario juegue volados con la computadora.
(a) Juegue exactamente 5 veces.
(b) Juegue a lo más m veces, y gana el primero que gane n veces.
(c) Juegue hasta que alguno de los dos gane tres juegos seguidos.
(d) Juegue 100 veces y determine cuántas veces seguidas cae águila.
En todos los casos debe determinar quién es el ganador.
10. Escribir la clase Triangulo.
11. Probar las clases Linea y Triangulo. Integrar estas clase con la clase Punto y formar
un sólo programa para trabajar con ellas.
12. Escribir un programa para efectuar el cobro de un estacionamiento. Al entrar al estacio-
namiento se genera un boleto. Todo boleto tiene una hora de entrada al estacionamiento
y otra de salida. Al salir del estacionamiento se calcula el tiempo que se estuvo en él y
se cobra a $3.00 la hora, con 15 minutos de tolerancia. Se puede utilizar la clase Hora
desarrollada en el capı́tulo anterior.
13. Escribir un programa para efectuar el cobro en un café Internet, para ello se necesita
la hora de inicio y de fin de uso de la computadora además del precio y cantidad de
cafés consumidos durante el tiempo de trabajo.
14. El gobierno ha solicitado al INEGI información para conocer el porcentaje de perso-
nas que trabajan en una localidad diferente de donde viven con la intensión de crear
viviendas de interés social para los trabajadores. Escribir un programa que ayude a
obtener la información requerida.
5.5 EJERCICIOS 123

15. Escribir un programa para trabajar con rectángulos. Un rectángulo se debe crear a
partir de 2 puntos. Con operaciones para crearlos, desplazarlos en forma horizontal o
vertical la cantidad de unidades que se especifique, calcular la unión de los rectángulos,
es decir, el rectángulo menor que incluya a ambos, la intersección de dos rectángulos,
dar alguna representación como cadena.
Capı́tulo 6

Agrupación de objetos

Es frecuente encontrar problemas que requieren varios objetos de la misma clase y a los que
se da un uso similar. En este capı́tulo se presenta la forma de agrupar objetos y en general
datos de cualquier tipo en un objeto denominado arreglo. Una vez creada la agrupación se
analiza la forma de trabajar con cada elemento de ella o con todos como unidad.

6.1 Introducción
Con el propósito de ilustrar la forma de agrupar objetos y trabajar con ellos de manera
individual o agrupados se presenta el siguiente problema:
En una institución educativa se requiere un programa para ayudar con el trabajo de la
sección escolar. Este trabajo consiste en guardar y proporcionar toda la información rela-
cionada con los alumnos de la institución. Por lo tanto, se necesita que el programa ayude
a conservar y obtener datos acerca de los alumnos. Los datos que debe tener, o en su caso
calcular, son los datos personales del alumno, calificaciones, promedio, la mayor calificación
obtenida, los cursos en los cuales su calificación es 10 y determinar el aprovechamiento de
un alumno con respecto al resto del grupo.
El diseño del programa, siguiendo la metodologı́a presentada en este texto, se presenta a
continuación.

1. Encontrar los objetos principales.


Sustantivos: como hasta ahora, en la descripción del problema aparecen varios sustan-
tivos pero los relevantes son: sección escolar y alumno. El alumno es el objeto principal,
pues alrededor de él está la información que se desea obtener y la sección escolar se
encarga de trabajar con esta información.

2. Determinar el comportamiento deseado.

125
126 CAPÍTULO 6. AGRUPACIÓN DE OBJETOS

Alumno:
Crear un alumno.
Obtener el valor de cada uno de los atributos.
Asignar valor a cada atributo.
Calcular el promedio de las calificaciones.
Obtener la calificación más alta.
Determinar el curso con una calificación particular.
Determinar todos los cursos con calificación igual a 10.
Determinar el aprovechamiento de un alumno con respecto al resto del grupo.

Sección escolar:
Registrar la información de los alumnos.
Proporcionar información de los alumnos.

De la descripción del problema se deduce la estructura del alumno, que debe incluir
datos personales y calificaciones. Aunque en la definición no se especifica, los datos
personales requeridos son: nombre, dirección y teléfono.
3. Definir escenarios.
El escenario principal para trabajar con la información de cada alumno es:

(a) El programa muestra un menú con las opciones.


(b) El usuario selecciona una opción.
(c) Dependiendo de la opción seleccionada, se solicita más información.
(d) Estos pasos se repiten hasta que el usuario seleccione la opción para terminar de
trabajar.

Ejemplo 6.1. Estructura de la clase Alumno. Haciendo uso del concepto de abstracción y de
acuerdo a la definición del problema. La estructura de la clase Alumno debe tener nombre,
dirección, teléfono y calificaciones. Con lo que se ha presentado hasta el momento, una posible
definición serı́a la siguiente:
public class Alumno {
private String nombre;
private String direccion;
private String telefono;

private int calif1, calif2, calif3, ..., calif15;


... // Métodos
}
6.1 INTRODUCCIÓN 127

Es decir, es necesario definir 15 variables de tipo entero para almacenar en cada una
de ellas la calificación obtenida en un curso particular. En el ejemplo se les puso como
nombre calif1, calif2, ..., calif15. Cabe mencionar que los puntos suspensivos no
están permitidos en Java, se escribieron para simplificar la escritura de este ejemplo y evitar
escribir las 15 variables. El definir estas variables de la forma en que se hizo implica un
problema para su manejo. Por ejemplo, el método para calcular el promedio del estudiante
serı́a como sigue:

public double promedio () {


return (calif1 + calif2 + calif3 + calif4 + ... + calif15) / 15;
}

además, se tendrı́an que escribir 15 métodos muy parecidos para asignar valor a cada califi-
cación y otros 15 para recuperar su valor.

public void asignarCal1(int c){ cal1 = c; }


public void asignarCal2(int c) { cal2 = c; }
...
public void asignarCal15(int c) { cal15 = c; }
public int obtenerCal1() { return cal1; }
...
public int obtenerCal15() { return cal15; }

Para facilitar la declaración y manipulación de grupos de datos del mismo tipo y relacio-
nados entre sı́ como en el ejemplo, se tiene el concepto de arreglo. Un arreglo es un objeto
que agrupa una cantidad fija y predeterminada de elementos del mismo tipo. A diferencia
de cualquier otro objeto, los arreglos no responden a métodos.
La declaración de un arreglo requiere crear una referencia a él y luego llamar al constructor
de la clase. Para crear una referencia a un arreglo se utiliza la siguiente sintaxis:
tipoDeDato [] nombreDeArreglo ;
al igual que en la declaración de cualquier variable se empieza por especificar su tipo y
se termina con un identificador, sin embargo, en este caso después del tipo deben ir unos
corchetes (paréntesis cuadrados) para indicar que se trata de un arreglo. El tipo de dato no
está restringido a ser el nombre de una clase, también puede ser un tipo de dato primitivo.
Para llamar al constructor se utiliza el operador new seguido del tipo de elementos del
arreglo, con la siguiente sintaxis:
new tipoDeDato [tamaño];
Para crear un arreglo se debe indicar, entre corchetes, la cantidad de elementos (o tamaño)
que tendrá el arreglo (éste puede estar definido por cualquier expresión que dé como resultado
un valor entero positivo). Cabe aclarar que aunque se utilice una variable para especificar el
tamaño del arreglo, éste queda fijo una vez creado.
Ejemplo 6.2. Declaración de un arreglo de diez enteros llamado enteros:
128 CAPÍTULO 6. AGRUPACIÓN DE OBJETOS

int[] enteros;
enteros = new int [10];

El significado de cada instrucción es el siguiente:

• int [] enteros; se declara una referencia denominada enteros a un arreglo de núme-


ros enteros (figura 6.1). Si se omitieran los corchetes se estarı́a declarando una sola
variable de tipo entero.

• enteros = new int [10] se crea un arreglo con diez elementos enteros y se asigna la
dirección de inicio de este arreglo a la referencia llamada enteros (figura 6.1).

int [] enteros

= new int[10]
0 1 2 3 4 5 6 7 8 9 Índices

10 elementos

Figura 6.1 Arreglo de 10 elementos.

La declaración de un arreglo puede hacerse en una sola instrucción como sigue:


int [] enteros = new int[10];
Una vez definido y creado el arreglo se puede usar cada elemento del mismo como una
variable independiente, del tipo especificado en la declaración del arreglo. Cada una de
estas variables puede aparecer en asignaciones, comparaciones, lecturas, impresiones, paso
de parámetros, etcétera. En el caso del ejemplo, las variables del arreglo son de tipo entero.
Para trabajar con cualquier dato del arreglo es necesario usar una variable o constante de
tipo entero que representa la posición del elemento dentro del arreglo; este valor se denomina
ı́ndice. Los valores válidos para un ı́ndice son enteros entre 0, incluyéndolo, y uno menos que
la cantidad de elementos (como se ve en la figura 6.1). En el ejemplo anterior para acceder
al i-ésimo elemento se usa entero[i] con 0 <= i < 10. El valor del ı́ndice puede ser el
resultado de cualquier expresión que devuelva un entero en el rango permitido. El pretender
acceder a un elemento del arreglo fuera de su rango permitido ocasiona la suspensión de la
ejecución del programa con el mensaje de error ArrayOutOfBoundsException. Este es un
ejemplo de los errores que suceden al momento de ejecución de un programa, mencionados
en la sección 1.5.
Todo arreglo tiene en su estructura un atributo público constante llamado length, en él se
almacena el tamaño del arreglo. Debido a que es público se accede a él directamente (sin un
método de por medio) para consultarse, más no para modificarse (por ser constante). Cabe
6.2 CREACIÓN Y USO DE ARREGLOS 129

aclarar que length indica la cantidad de elementos en el arreglo, pero no es un valor válido
para un ı́ndice, el máximo valor para un ı́ndice es length - 1.
Al declarar un arreglo es posible asignar valor inicial a cada uno de los elementos que lo
componen. Por ejemplo: int [] b = new int[] {0,1,1,2,3}; en cuyo caso se omite la
especificación del tamaño en el constructor debido a que éste será la cantidad de elementos
en la lista de inicialización. También es posible omitir el operador new en la declaración
de un arreglo con valor inicial, por lo tanto, la declaración int [] b = {0,1,1,2,3}; y la
primera de este párrafo producen el mismo efecto. Si luego se incluye la instrucción numElems
= b.length; el valor de la variable numElems es 5. Al usar b[3] en cualquier contexto donde
se requiera un entero se obtiene el valor almacenado en el lugar cuyo ı́ndice es 3, en este caso
el valor es 2.

6.2 Creación y uso de arreglos


La solución al problema de llevar el registro de los datos de los alumnos de cierta institu-
ción educativa requiere definir la clase Alumno, en ella se declaran como atributos nombre,
dirección, teléfono, número de registro y calificaciones. El número de registro no aparece en
la definición del problema, pero se incluye para facilitar la identificación del alumno.
Ejemplo 6.3. Estructura de la clase Alumno especificada en la definición del problema:

/**
* Clase para almacenar y trabajar con información de alumnos
* Objetivo: ilustrar el uso de arreglos
* @author Amparo López Gaona
* @version 3a edición
*/
public class Alumno {
private final String nombre;
private String direccion;
private String telefono;
private final String numRegistro;
private static int folio = 2010;
private int [] calificaciones;

Se define una cadena constante para el nombre (una vez asignado no va a cambiar), una
cadena para la dirección y otra para el teléfono. Una cadena para un número de registro. Un
arreglo de enteros para almacenar las calificaciones. Un entero llamado folio que tiene la
palabra static, con ello se está especificando que es una variable de la clase. Por lo tanto es
compartida por todos los objetos de la clase; ası́ que con ella se puede crear un número de
registro diferente y único para cada alumno. Notar que en la definición de la variable folio
se asigna un valor inicial.
130 CAPÍTULO 6. AGRUPACIÓN DE OBJETOS

Ejemplo 6.4. Constructor que asigna valor a cada uno de los atributos del alumno.
/**
* Constructor que recibe los datos personales del alumno
* @param n - Cadena que representa el nombre del alumno
* @param d - Cadena que representa la dirección del alumno
* @param t - Cadena que representa el teléfono del alumno
* @param nCalif - entero que especifica la cantidad de calificaciones del alumno
*/
public Alumno (String n, String d, String t, int nCalif) {
nombre = n.trim();
direccion = d.trim();
telefono = t.trim();
numRegistro = "UNAM-" + folio++;
calificaciones = new int[(nCalif > 0)? nCalif : 15];
}
En el constructor se asigna el número de registro al alumno, que es una cadena que tiene
el prefijo "UNAM-" seguido de un número de folio. El número de folio empieza en 2010 y
cada que se llama al constructor se incrementa en 1, esto último con el propósito de que
el siguiente alumno creado tenga el siguiente número consecutivo. El constructor recibe un
entero que especifica el número de calificaciones de ese alumno, pues no todos los alumnos
tienen el mismo grado de avance en sus estudios.
Ejemplo 6.5. El método obtenerNombre regresa el valor de la variable nombre, con lo cual
se obtiene el nombre del alumno.
/**
* Método para obtener el nombre del alumno
* @return String - nombre del alumno
*/
public String obtenerNombre() {
return nombre;
}
La programación de los métodos para obtener el valor de cada atributo de tipo cadena y
entero es tan sencilla que se omite su presentación. Se sugiere al lector que los programe.
Ejemplo 6.6. Método para obtener la cantidad de calificaciones que debe tener un alumno.
/**
* Método para obtener la cantidad de calificaciones del alumno
* @return int - cantidad de calificaciones de un alumno
*/
public int obtenerNumCalificaciones() {
return calificaciones.length;
}
6.2 CREACIÓN Y USO DE ARREGLOS 131

La cantidad de calificaciones de un alumno está determinada por el tamaño del arreglo y


se fija al crear el objeto.
Ejemplo 6.7. Método para asignar una calificación.
/** Método para registrar una calificación siempre y cuando sea mayor que cero
* @param curso -- curso al que se asigna una calificación
* @param cal -- calificación que se asigna
*/
public void asignarCalificacion(int curso, int cal) {
if (curso >= 0 && curso < calificaciones.length) {
if (cal >= 0 && cal <=10) {
calificaciones[curso] = cal ;
} else {
new Error("Calificación incorrecta!");
}
} else {
new Error("Número de curso incorrecto! ");
}
}

Si el número de curso o la calificación están fuera del rango válido no asigna calificación
alguna y se envı́a un mensaje de error indicando la situación.
Las instrucciones que pueden ir en los bloques de la instrucción condicional if no tie-
nen ninguna restricción, ası́ que en particular puede ser otra instrucción condicional. Esto
se conoce como instrucción condicional anidada.. En el método asignarCalificacion se
puede apreciar el uso de una instrucción if anidada, pues si el curso es válido se tiene otra
instrucción condicional para validar la calificación.
Ejemplo 6.8. Método para asignar todas las calificaciones del alumno.

/**
* Método para asignar calificaciones al alumno
*/
public void asignarCalificaciones() {
Scanner in = new Scanner(System.in);
int cal;
for (int i = 0; i < calificaciones.length; i++) {
do { // Valida la calificación
System.out.print("Dar la calificación para el curso "+(i+1)+" ");
cal = in.nextInt();
} while (cal < 0 || cal > 10);
asignarCalificacion(i, cal);
}
}
132 CAPÍTULO 6. AGRUPACIÓN DE OBJETOS

Se debe notar que en el método anterior se solicita la calificación i + 1 para que el usuario
vea que se le solicita la calificación del curso 1, 2, etc., y no del 0, 1,..., porque solicitar la
calificación del curso 0 podrı́a ocasionar confusión en el usuario.
Al igual que hay instrucciones condicionales anidadas, hay instrucciones iterativas anida-
das. En este caso se tiene una instrucción do dentro de una instrucción for. La forma de
trabajar, en este ejemplo, es que para cada valor de i se hace completo el ciclo con la
instrucción do, además de la asignación de la calificación.
Ejemplo 6.9. Método para calcular el promedio de un alumno.

/**
* Método para calcular el promedio de las calificaciones del alumno
* @return double - promedio de calificaciones
*/
public double promedio() {
double suma = 0;
for (int i = 0; i < calificaciones.length; i++) {
suma += calificaciones[i];
}
return suma/calificaciones.length;
}

Se suman todas las calificaciones del arreglo y luego se divide la suma entre la cantidad
de calificaciones. La instrucción for está muy ligada a los arreglos debido a que, en general,
se requiere recorrerlos completamente y el contador de esta instrucción puede usarse como
ı́ndice del arreglo. El usar la instrucción calificaciones.length permite que este método
funcione sin fijar de antemano la cantidad de calificaciones.
Ejemplo 6.10. Método para obtener la mayor calificación registrada de un alumno.

/**
* Método para calcular la calificación más alta del alumno
* @return int - la calificación mayor
*/
public int mayorCalificacion () {
int mayor = calificaciones[0];

for (int i = 1; i < calificaciones.length; i++) {


if (calificaciones[i] > mayor) {
mayor = calificaciones[i];
}
}
return mayor;
}
6.2 CREACIÓN Y USO DE ARREGLOS 133

El algoritmo para encontrar el valor mayor de un arreglo consiste en recorrer todo el


arreglo comparando el elemento de la localidad i del arreglo con el que hasta ese momento
es el mayor. Si calificaciones[i] es mayor que la variable mayor, significa que el valor
almacenado en esta variable no es el más grande, ası́ que se debe actualizar su valor; en caso
contrario, sı́ es el mayor y no hay necesidad de hacer nada.
Ejemplo 6.11. Método para determinar el primer curso en que un alumno obtiene una
calificación particular. Este método devuelve el número del curso en que se encuentra, por
primera vez, una calificación buscada. En caso de recorrer todo el arreglo y no encontrar tal
calificación devuelve -1, que no es un ı́ndice válido para arreglos y por lo tanto se usa para
indicar que no hay el curso buscado.

/**
* Método para obtener el curso con calificación dada
* @param cal - calificación buscada
* @return int - curso con calificación igual a la solicitada y -1 en
* caso que no haya curso con la calificación dada
*/
public int buscarCurso (int cal) {
int i = 0;
while ((i < calificaciones.length) && (calificaciones[i] != cal)) {
i++;
}
return (i == calificaciones.length) ? -1 : i+1;
}

En este caso no es conveniente usar una instrucción for porque no necesariamente se va a


recorrer todo el arreglo. Se utiliza una instrucción while para salir de la iteración en cuanto
se detecte el curso con la calificación que se busca. La condición de la instrucción while es
(i <calificaciones.length) && (calificaciones[i] != cal), con lo cual primero se
verifica que el ı́ndice está en el rango permitido (no se valida el lı́mite inferior, 0, porque
es el valor inicial de la i), si lo está entonces verifica que la calificación sea diferente de la
buscada. Aquı́ se está haciendo uso de la propiedad de evaluación con cortocircuito, porque
si el valor de la variable i no está en el rango permitido ya no se evalúa la otra parte de la
conjunción y por lo tanto no se accede a una posición inválida del arreglo.
Ejemplo 6.12. Clase para probar los métodos de la clase Alumno. Esta clase no tiene más
lógica que crear un objeto de la clase Alumno y llamar a cada método definido en ella.

public class PruebaAlumno {


public static void main(String [] pps) {
Alumno alumn= new Alumno("Andrea","Calle Chica 56","921404",10);
alumn.asignarCalificaciones();
System.out.println("La mayor calificación de "+alumn.obtenerNombre()+
134 CAPÍTULO 6. AGRUPACIÓN DE OBJETOS

" es "+alumn.mayorCalificacion());
System.out.println("El promedio es "+alumn.promedio());
if (alumn.buscarCurso(6) != -1)
System.out.println("El curso con seis es "+alumn.buscarCurso(6));
else
System.out.println("¡Felicidades! no tienes seis en ningún curso.");
}
}

A continuación se presenta otra posibilidad para probar los métodos de la clase Alumno y
que es muy utilizada.
Ejemplo 6.13. Clase para probar la clase Alumno con un menú de opciones.
La programación de un menú de opciones consiste en mostrar al usuario del programa
las diferentes opciones que tiene el mismo, luego leer la opción elegida por el usuario y de
acuerdo con esa selección realizar la acción deseada.

public class MenuAlumno {


static Alumno alumnoPrueba = null; //Alumno del cual se pedirá información
static Scanner in = new Scanner(System.in);

/* Método que muestra todas las opciones de un menú */


private static void menu() {
System.out.println("Menú para probar alumnos\n");
System.out.println("1. Crear un alumno");
System.out.println("2. Asignar calificaciones");
System.out.println("3. Calcular el promedio de calificaciones");
System.out.println("4. Indicar la calificación más alta");
System.out.println("5. Indicar el curso con la calificación especificada");
System.out.println("0. Fin");
System.out.println("\nElige una opción");
}

Ejemplo 6.14. Método que ejecuta la opción elegida por el usuario.


private static void realizarAccion(int opción) {
if (opción == 1) {
// Solicitar datos y crear el registro del alumno
} else if (opción == 2) {
// Asignar calificaciones
} else if (opción == 3) {
...
}
...
6.2 CREACIÓN Y USO DE ARREGLOS 135

Cuando se tiene una serie de instrucciones if anidadas y en cada una, la condición consiste
en comparar la misma variable con un valor dado; puede escribirse utilizando una instrucción
denominada switch cuya sintaxis es la siguiente:

switch ( expresión ) {
case valor1 : instrucciones
break;
case valor2 : instrucciones
break;
...
case valorn : instrucciones
break;
default: instrucciones
}

La sintaxis de la instrucción switch no define una cantidad especı́fica de cláusulas case, por
lo tanto se pueden tener tantas como se requieran. La instrucción switch evalúa la expresión
y ejecuta todas las instrucciones del caso cuyo valor coincide con el de la expresión evaluada,
hasta encontrar la instrucción break o terminar la instrucción switch. La instrucción break
puede estar en cualquier punto de la instrucción switch aunque lo recomendable es que
esté al final de cada caso. La cláusula default es opcional; en caso de incluirse se ejecutan
las instrucciones que contiene, si no hay un caso con el valor obtenido al evaluar la expresión.
Por lo tanto, el método realizarAccion puede escribirse como sigue:
// Método que realiza la opción elegida de un menú
private static void realizarAccion(int opcion) {
switch (opcion) {
case 0:
break;
case 1: // Crear un alumno
System.out.print("Dar el nombre del alumno");
String nombre = in.nextLine();
System.out.print("Dar la dirección del alumno");
String dir = in.nextLine();
...
alumnoPrueba = new Alumno(nombre, dir, tel, cursos);
break;
case 2: // Asigna calificaciones
if (alumnoPrueba == null) {
System.out.println("No se ha dado de alta un alumno");
} else {
136 CAPÍTULO 6. AGRUPACIÓN DE OBJETOS

alumnoPrueba.asignarCalificaciones();
}
break;
case 3: //Calcula el promedio
if (alumnoPrueba == null) {
System.out.println("No se ha dado de alta un alumno");
} else {
System.out.println("El promedio de "+alumnoPrueba.obtenerNombre()+
" es "+alumnoPrueba.promedio());
}
break;
case 4: // Encuentra la mayor calificación
if (alumnoPrueba == null) {
System.out.println("No se ha dado de alta un alumno");
} else {
System.out.println("La mayor calificación de "+
alumnoPrueba.obtenerNombre()+" es "+alumnoPrueba.mayorCalificacion());
}
break;
case 5: //Busca el curso con la calificación indicada
if (alumnoPrueba == null) {
System.out.println("No se ha dado de alta un alumno");
} else {
System.out.println("Dar la calificación");
int cal = in.nextInt();
int curso = alumnoPrueba.buscarCurso(cal);
if (curso != -1) {
System.out.println("El alumno "+alumnoPrueba.obtenerNombre()+"tiene "
+cal+" en el curso "+curso);
} else {
System.out.println("El alumno "+alumnoPrueba.obtenerNombre()+
" no tiene curso con "+cal+ " de calificación");
}
}
break;
default:
System.out.println("Opción incorrecta");
}
}

En cada caso, primero se valida que haya algún alumno para poder enviarle el mensaje
deseado.
Ejemplo 6.15. Método main para la clase MenuAlumno.
6.3 MÉTODOS QUE DEVUELVEN ARREGLOS 137

public static void main(String [] pps) {


int opcion;

do {
menu(); //Muestra opciones
opcion = in.nextInt(); //Lee opción elegida
realizarAccion(opcion); //Ejecuta la opción elegida
} while (opcion != 0);
}
}

El cuerpo del método main consiste en mostrar un menú de opciones, solicitar una elección
al usuario y de acuerdo con ella tomar una acción; esto, mientras la opción elegida sea
diferente de finalizar. Ası́ que esta clase también sirve como programación del escenario
definido al inicio de la sección anterior.

6.3 Métodos que devuelven arreglos


Para escribir el método que devuelve todas las materias en las que el alumno obtuvo 10
de calificación es necesario localizarlas y devolverlas, sin embargo, se sabe que los métodos
sólo pueden devolver un valor. Se podrı́a pensar que no es posible escribir tal método. Sin
embargo, si se recuerda que un arreglo es un objeto y se sabe cómo escribir métodos que
devuelvan objetos entonces para solucionar el problema se puede crear un arreglo local,
llenarlo con la información solicitada y devolverlo.
Ejemplo 6.16. Método que devuelve todos los cursos en los que el alumno tiene 10 de
calificación.
/** Método para obtener todos los cursos en los cuales la calificación es de 10
* @return int[] - arreglo con los cursos con calificación igual a 10
*/
public int[] todosLosDieces () {
int j = 0;
int [] dieces = new int[calificaciones.length + 1];

for (int i = 0; i < calificaciones.length; i++) {


if (calificaciones[i] == 10) {
dieces[++j] = i;
}
}
dieces[0] = j ; //Para indicar el número de elementos ocupados
return dieces;
}
138 CAPÍTULO 6. AGRUPACIÓN DE OBJETOS

En el método todosLosDieces se crea el arreglo dieces de tamaño mayor en una unidad


que el tamaño del arreglo de calificaciones del alumno, debido a que en el mejor de los casos
obtuvo 10 en todos sus cursos. Como este arreglo no siempre va a estar lleno (el alumno no
siempre tiene 10 en todos sus cursos) se tiene un elemento extra para indicar la cantidad de
dieces que obtuvo el alumno, que es diferente del tamaño del arreglo.
El algoritmo programado consiste en recorrer, completamente, el arreglo de calificaciones
del alumno y cada vez que encuentra un 10 almacena su posición en el arreglo dieces, a partir
de la localidad con ı́ndice 1. Al terminar el recorrido del arreglo se almacena en la primera
localidad del arreglo dieces un entero que indica la cantidad de elementos almacenados.
Finalmente se devuelve el arreglo, o dicho correctamente, se devuelve una referencia a él.
Hay que recordar que el nombre de un objeto es, en realidad, el nombre de una referencia a
él.
Ejemplo 6.17. Llamada al método que devuelve un arreglo.
Para usar este método se debe crear un arreglo en el cual se va a recibir el arreglo con
los ı́ndices de las materias en las que el alumno obtuvo 10 de calificación; a este arreglo se
llama, por ejemplo, excelentes. Para saber cuántos elementos tomar del arreglo se utiliza
excelentes[0] en lugar de excelentes.length, porque no necesariamente está lleno el
arreglo, y se empiezan a tomar las claves de los cursos a partir del elemento 1 del arreglo.
A continuación se presenta el código que muestra el uso del método todosLosDieces, éste
puede estar incluido en las clases de prueba mostradas en los ejemplos 6.12 y 6.14.

int[] excelentes = alumn.todosLosDieces();


for (int i = 1; i <= excelentes[0]; i++) {
System.out.println(excelentes[i]+" ");
}

Ejemplo 6.18. El método obtenerCalificaciones devuelve todas las calificaciones del


alumno; por lo tanto es otro ejemplo de un método que devuelve un arreglo.

/**
* Devuelve todas las calificaciones del alumno
* @return int[] -- arreglo con las calificaciones del alumno
*/
public int[] obtenerCalificaciones() {
return calificaciones;
}

6.4 Arreglos como parámetros


Los arreglos pueden usarse como parámetro de los métodos, de la misma forma en que se
usa cualquier otro tipo de dato. A continuación se presenta un ejemplo.
6.4 ARREGLOS COMO PARÁMETROS 139

Ejemplo 6.19. Método para determinar el aprovechamiento de un alumno con respecto al


resto del grupo.
/**
* Método para comparar las calificaciones del alumno con las calificaciones
* promedio
* @param promedio -- arreglo con los promedios que se compara al alumno.
* @return int -- un número positivo si el alumno está por arriba del promedio
* del grupo; uno negativo si está por debajo y 0 si está en el promedio del grupo.
*/
public int compararPromedio(int [] promedio) {
int contador = 0;

if (promedio.length != calificaciones.length)
new Error("No es posible trabajar con arreglos de distinto tama~
no");
for (int i = 0; i < calificaciones.length; i++) {
if (calificaciones[i] < promedio[i]) {
contador --;
} else if (calificaciones[i] > promedio[i]) {
contador ++;
}
}
return contador;
}
Para determinar el aprovechamiento de un alumno con respecto a un grupo se revisa
cada calificación en el arreglo del alumno con las calificaciones promedio para cada curso.
Si la calificación del alumno es mayor que la promedio incrementa el contador, si es menor
lo decrementa y si es igual lo deja sin modificar. Al final si el alumno estuvo arriba del
promedio el contador será positivo, si estuvo abajo del promedio será negativo y si todas sus
calificaciones son iguales a las del promedio el contador tendrá un cero.
Ejemplo 6.20. Llamada a un método que recibe un arreglo como parámetro.
int [] promedios = {10, 6,10,10,8,...};
...
System.out.print(alumn.obtenerNombre());
int resultado = alumn.compararPromedio(promedios);
if (resultado >0) {
System.out.println(" estás por arriba del promedio del grupo.");
} else if (resultado < 0) {
System.out.println(" estás por abajo del promedio del grupo.");
} else {
System.out.println(" estás en el promedio del grupo.");
}
140 CAPÍTULO 6. AGRUPACIÓN DE OBJETOS

En la llamada al método compararPromerio el parámetro es sólo el nombre del arreglo,


es decir, no se incluyen los corchetes.
Este código se puede incluir en la clase PruebaAlumno que se muestra en el ejemplo 6.14.
Recordar que los puntos suspensivos no son parte de Java.
Ejemplo 6.21. Método para llenar el arreglo de calificaciones del alumno a partir de otro
arreglo.

/**
* Método para asignar calificaciones al alumno
*/
public void asignarCalificaciones(int [] c) {
calificaciones = c;
}

En este método cada calificación se lee y valida fuera de él.


Con los arreglos pasa algo que a primera vista parece curioso y da la impresión de que
va en contra del paso de parámetros por valor. Al enviar como parámetro un arreglo se
permite modificarlo, esto es debido a que se está enviando la referencia al arreglo y ésta no
puede modificarse en el método, pero sı́ puede modificarse lo referenciado por ella, es decir,
el contenido del arreglo, y estas modificaciones son visibles fuera del método.
Ejemplo 6.22. Clase que contiene dos métodos: uno para modificar un arreglo y otro para
modificar un elemento del arreglo. El objetivo es ilustrar el paso de parámetros por valor.

/** Programa para ilustrar el paso de parámetros por valor


* @author Amparo López Gaona
* @version 3a edición
*/
public class Cambios{
/**
* Método que recibe un arreglo y duplica el valor de cada elemento.
* @param x - arreglo de enteros que será duplicado.
*/
private static void modificar(int [] x) {
for (int i = 0; i < x.length; i ++)
x[i] *= 2;
}
/** Método que recibe un entero y duplica el valor.
* @param x - entero que será modificado.
*/
private static void modificar(int x) {
x *= 2;
}
6.5 ARREGLOS DE CADENAS 141

public static void main (String [] pps) {


int a[] = {0,1,2,3,4,5,6}; // Definición de un arreglo de enteros.

System.out.println("El arreglo original tiene ");


for (int i = 0; i < a.length; i ++) { // Imprime el arreglo
System.out.print(" " + a[i]);
}
modificar(a); // Llama al método con el arreglo.
System.out.println("\nEl arreglo ahora tiene ");
for (int i = 0; i < a.length; i ++) { // Imprime el arreglo
System.out.print(" " + a[i]);
}
modificar(a[4]); // Llama al método con un elemento del arreglo.
System.out.println("\nEl arreglo ahora tiene ");
for (int i = 0; i < a.length; i ++) { // Imprime el arreglo
System.out.print(" " + a[i]);
}
}

La salida de este programa es:


El arreglo original tiene
0 1 2 3 4 5 6
El arreglo ahora tiene
0 2 4 6 8 10 12
El arreglo ahora tiene
0 2 4 6 8 10 12

En el ejemplo se imprime el arreglo después de llamar al método modificar con un arreglo,


en ese caso se puede apreciar que sı́ se modifica el arreglo. Luego se vuelve a llamar al método
modificar que recibe un entero, con un elemento del arreglo, dentro del método se duplica
el valor. Sin embargo, al volver a imprimirlo se puede apreciar que el arreglo queda intacto
(porque se está llamando al método que recibe un número entero).
En este ejemplo los dos métodos modificar se definen con el calificador static, debido a
que sólo se van a usar en el método main sin crear objetos.

6.5 Arreglos de cadenas


Los arreglos no están limitados a almacenar datos de tipos primitivos, en particular se puede
tener un arreglo cuyos elementos sean cadenas (Strings). La forma de declarar un arreglo
de cadenas es similar a la de cualquier otro arreglo:
String[] diasHabiles = new String[5];
142 CAPÍTULO 6. AGRUPACIÓN DE OBJETOS

La semántica varı́a un poco con respecto a los arreglos de tipos primitivos. Debido a
que String no es un tipo primitivo, se tiene un arreglo con espacio suficiente para cinco
referencias, cada una a alguna cadena, no para cinco cadenas. Si después de la declaración
anterior se tuviera el siguiente código, se tendrı́a el error mostrado en el comentario:
for (int i = 0; i < diasHabiles.length; i++)
System.out.println(diasHabiles[i].toLowerCase());
// ERROR: NullPointerException: ...

debido a que se pretende utilizar un método de la clase String sobre un objeto que no se
ha creado.
Para asignar valor inicial a un arreglo de cadenas, en su declaración, se tienen dos posibi-
lidades igual que en un arreglo de datos de cualquier tipo primitivo:
1. Declarar el arreglo sin especificar su tamaño y a continuación un bloque con las cadenas
que se almacenarán en él:

String [] diasHabiles = new String[] {"Lunes", "Martes", "Miércoles",


"Jueves", "Viernes"};

2. Asignar directamente el bloque con las cadenas sin utilizar el operador new:

String[] diasHabiles = {"Lunes", "Martes", "Miércoles", "Jueves",


"Viernes"};

Ejemplo 6.23. Impresión del nombre de las materias en que un alumno obtuvo 10 de
calificación.
La clase Alumno tiene un método para obtener las materias en las cuales el alumno tiene 10
de calificación, sólo que ese método devuelve un arreglo de enteros, indicando, por ejemplo,
que en los cursos 1, 3 y 4 obtuvo la calificación mencionada. Ahora se presenta el código para
extraer esa información e imprimir el nombre del curso. Esto no se hace en la clase Alumno
sino en la aplicación particular que puede ser el main o bien en otro método para dejar la
clase independiente del nombre de los cursos.
public static void main(String [] pps) {
final String [] materia = {"Álgebra", "Cálculo","Programación",
"Estructuras de Datos", "Bases de Datos"};

Alumno alumn= new Alumno("Andrea","Calle Chica 56","921404", materia.length);


...
int[] excelentes = alumn.todosLosDieces();
for (int i = 1; i<=excelentes[0]; i++)
System.out.println("Obtuvo 10 en "+materia[excelentes[i]]);
}
6.5 ARREGLOS DE CADENAS 143

En esta ocasión, el entero que se utiliza como ı́ndice para el arreglo materia es el valor
almacenado en excelentes[i] que se sabe es un valor entero en el rango adecuado. Si el
alumno no tiene cursos con calificación igual a 10 no ejecuta la instrucción for, porque en
excelentes[0] habrá un cero, y entonces la condición que se evalúa antes de entrar al ciclo
es 1 <= 0 que nunca es válida.

6.5.1 Parámetros del método main


Hasta ahora se ha utilizado el método main sin explicar el significado de su parámetro. El
método main tiene como parámetro un arreglo de cadenas que se llena al llamar al método. El
método main se llama implı́citamente al ejecutar el programa, es decir, al teclear, por ejem-
plo, java PruebaAlumno, y por tanto también ahı́ es donde se le proporcionan las cadenas
que se almacenan en el arreglo especificado en su encabezado, en este caso el arreglo pps.
Por ejemplo, si se desea que cada vez que se ejecute el programa de prueba para alumnos,
sólo trabaje con un alumno y que el usuario proporcione los datos personales del alumno
desde el momento de pedir la ejecución del programa, podrı́a hacerse lo siguiente:
java PruebaAlumno Andrea "Calle Chica 5" 921404.
Es decir, se escriben las cadenas después del nombre del programa ejecutable separadas
por espacios en blanco, si se requiere que la cadena lleve espacios entonces sı́ se encierra en
comillas. Ası́ en el ejemplo se están pasando tres cadenas.
Para recuperar esos valores y trabajar con ellos se debe tener en cuenta que cada cadena
se almacena en una localidad del arreglo definido como parámetro del método main. Dentro
del método se trabaja igual que con cualquier otro arreglo de cadenas.
Ejemplo 6.24. La clase PruebaAlumno del ejemplo 6.12 ahora incluyendo código para tra-
bajar con los parámetros que recibe el método main.

public class PruebaAlumno {


public static void main (String[] pps) {
if (pps.length != 3) {
System.out.println("Error: cantidad inadecuada de datos.");
} else {
Alumno alumn= new Alumno(pps[0], pps[1], pps[2], 10);
System.out.print("Los parámetros son ");
for (int i = 0; i < pps.length; i++)
System.out.print(" " + pps[i]);
System.out.println();
alumn.asignarCalificaciones();
... //Todo como antes
}
}
}
144 CAPÍTULO 6. AGRUPACIÓN DE OBJETOS

Lo primero que se hace en este programa es asegurar que se tenga la cantidad adecuada de
datos para poder trabajar, en este caso 3, para ello se verifica el tamaño del arreglo mediante
una consulta a la constante length del arreglo que contiene los parámetros. En este ejemplo
se imprimen las cadenas que recibió main como parámetro para verificar que todo está bien,
aunque esto normalmente no es necesario.
Debido a que los argumentos del método main son cadenas, es posible que se requiera
convertirlas a algún tipo primitivo para trabajar con ellas. Por ejemplo, si se pasaran cua-
tro parámetros, y el cuarto especificara la cantidad de cursos del alumno. Para llamar al
constructor de la clase Alumno se requiere que el cuarto parámetro sea de tipo entero.
Para todos los tipos primitivos existen clases que permite tratarlos como objetos. Es-
tas clases son Char, Double, Boolean, Float, etcétera, y se encuentran en el paquete
java.lang. En cada una de esta clases se tiene un método parsetipo para convertir el
objeto al tipo primitivo especificado. Por ejemplo, el método parseInt convierte el objeto
de la clase Integer a valor entero.
En el programa PruebaAlumno, al llamar al constructor se puede incluir la instrucción:
Alumno alumn= new Alumno(pps[0], pps[1], pps[2], Integer.parseInt(pps[3]));
Un uso común de los parámetros del main es para elegir algunas opciones de ejecución del
programa.
Ejemplo 6.25. Programa que recibe como parámetros las opciones para su ejecución.
Este programa puede recibir uno o cuatro parámetros. El primer parámetro indica el
resultado esperado del programa y es obligatorio. Los otros tres parámetros son opcionales.
En caso de incluirlos representan el nombre, dirección y teléfono del alumno. El primer
parámetro puede ser: -c para mostrar todas las calificaciones del alumno; -p para obtener
el promedio del alumno; -M para obtener la mayor calificación del alumno y -t para conocer
todos los cursos con 10 de calificación. Otra posibilidad para el primer parámetro es el signo
de menos seguido de cualquier combinación de las cuatro letras descritas antes.

/**
* Programa que ilustra el uso de parámetros en el main
* @author Amparo López Gaona
* @version 3a edición
*/
public class PruebaAlumno {
public static void main (String[] pps) {
Alumno alumn = new Alumno("Andrea","Calle Chica 56","921404", 10);
int [] cal = {10,8,9,9,10};
alumn.asignarCalificaciones(cal);

if (pps.length >= 1) {
if(pps.length == 4) { // Crea el alumno
alumn= new Alumno(pps[1],pps[2],pps[3], 10);
6.5 ARREGLOS DE CADENAS 145

alumn.asignarCalificaciones(cal);
}
for (int i=1; i < pps[0].length(); i++) {
switch(pps[0].charAt(i)) {
case ’c’: // Muestra calificaciones
System.out.println(alumn + " tiene las siguientes calificaciones:");
cal = alumn.obtenerCalificaciones();
for(int j=0; j < cal.length; j++) {
System.out.print(cal[j]+" ");
}
System.out.println();
break;
case ’p’: // Promedio de alumno
System.out.println(alumn+" tiene " + alumn.promedio()+" de promedio.");
break;
case ’M’: // Calificación mayor
System.out.println("La mayor calificación de " +alumn + "es" +
alumn.mayorCalificacion());
break;
case ’t’: //Todos los dieces
System.out.println(alumn + " tiene 10 en los cursos ");
cal = alumn.todosLosDieces();
for(int j=1; j < cal[0]+1; j++) {
System.out.print(cal[j]+" ");
}
System.out.println();
break;
default:
System.out.println("Opción incorrecta");
}
}
} else {
System.out.println("El programa debe ejecutarse como sigue: "+ ...);
}
}
}

Si el programa no recibe parámetros envı́a un mensaje indicado la falta de información. Si


sólo tiene un parámetro, trabaja con el alumno definido en el programa para este caso. Si son
cuatro parámetros crea un alumno tomando como datos los parámetros y en ambos casos
trabaja con las calificaciones definidas en el programa (esto por tratarse de un programa de
prueba). Para hacer su trabajo se va extrayendo cada carácter del primer parámetro pps[0]
y de acuerdo al carácter extraı́do realiza la opción especificada.
146 CAPÍTULO 6. AGRUPACIÓN DE OBJETOS

6.6 Arreglos de objetos


Lo desarrollado hasta este punto se ha centrado en un alumno, pero el trabajo de la sec-
ción escolar consiste en trabajar con datos de varios alumnos: darlos de alta, actualizar sus
calificaciones y consultar su información.
Este problema general es frecuente, es decir, se deben programar estas acciones: crear un
contenedor para un conjunto de datos, insertar elementos adicionales (dar de alta), eliminar
elementos (dar de baja), actualizar la información de un elemento (cambios), consultar la
información de un elemento.
Debido a que se requiere trabajar con varios datos del mismo tipo, de manera natural se
piensa en almacenar estos datos en un arreglo. Los arreglos no están restringidos a almacenar
datos de los tipos predefinidos en el lenguaje, en particular se pueden tener arreglos cuyos
elementos son objetos. Un arreglo de objetos se declara utilizando la siguiente sintaxis:
nombreDeClase [] nombreDeArreglo = new nombreDeClase[tamaño];
Recordar que en el caso de datos no primitivos lo que se tiene con la declaración de un arreglo
es espacio para referencias a objetos de esa clase. Por ejemplo, con la siguiente declaración
Alumno [] grupo = new Alumno[5]; se tiene espacio para cinco referencias a objetos de
la clase Alumno. Cada elemento del arreglo tiene un valor null indicando que aún no hay
objetos creados y almacenados ahı́ (figura 6.2 lado izquierdo).
Para llenar el arreglo es necesario crear los objetos mediante el uso del operador new. Para
ello existen las siguientes modalidades:

• Crear los objetos al mismo tiempo que el arreglo,

Alumno [] grupo = {
new Alumno ("Andrea","Manı́ 123", "567123", 20),
new Alumno ("Alejandra","Insurgentes 452","558976",12),
new Alumno ("Blanca","Tlalpan 334","551234",12);
new Alumno ("Marı́a","Revolución 645","787965", 20),
new Alumno ("Jorge","Patriotismo 908","456789",20),
}

Esto se ilustra en la parte central de la figura 6.2.

• Declarar el arreglo de objetos y después llenarlo.

Alumno [] grupo = new Alumno [5];

for (int i = 0; i < grupo.length; i++)


grupo[i] = new Alumno();
6.6 ARREGLOS DE OBJETOS 147

Esto se ilustra en la parte derecha de la figura 6.2. En este caso se crearon los objetos
con el constructor por omisión; es responsabilidad del programador llenar cada objeto
con los datos correspondientes.

grupo grupo grupo


Andrea
...

0 null 0 Alejandra 0
...
1 null 1 1
Blanca
2 null 2 ... 2
3 null 3 María 3
...
4 null 4 4
Jorge
...

Figura 6.2 Creación de un arreglo de Alumnos.

La solución al problema de la sección escolar se encuentra en la clase SeccionEscolar


cuya programación se presenta en los siguientes ejemplos.
Ejemplo 6.26. Estructura de la clase SeccionEscolar.
La estructura tiene un arreglo de alumnos y un número entero. La variable length asociada
al arreglo especifica la capacidad del arreglo, pero el arreglo no necesariamente está lleno,
de ahı́ que se necesita la variable nAlumnos para indicar la cantidad de alumnos registrados,
y por tanto contenidos en el arreglo.

/**
* Clase que programa el manejo de la información de alumnos en una escuela.
* Objetivo: ilustrar el uso de arreglos de objetos
* @author Amparo López Gaona
* @version 3a edición
*/
public class SeccionEscolar {
Alumno [] poblacionEst ;
int nAlumnos;

Ejemplo 6.27. Constructores.

/**
* Constructor por omisión con espacio para cien alumnos y el
* número de alumnos en el arreglo es cero
148 CAPÍTULO 6. AGRUPACIÓN DE OBJETOS

*/
public SeccionEscolar () {
this(100);
}
/**
* Constructor. Declara espacio para la cantidad de alumnos especificada, si
* es negativa se crea un arreglo de 100 localidades. En ambos casos, el
* número de alumnos almacenados en el arreglo es cero.
* @param tam - cantidad de alumnos que se pueden almacenar.
*/
public SeccionEscolar (int tam) {
poblacionEst = (tam > 0) ? new Alumno[tam]: new Alumno[100];
nAlumnos = 0;
}

Ejemplo 6.28. Alta de un alumno. Para dar de alta un alumno es necesario que haya espacio
en el arreglo y que el alumno no se encuentre en el arreglo. Una vez cumplidas estas dos
condiciones se almacena al final del arreglo.
/**
* Método para dar de alta un alumno en la sección escolar
* @param alum - Alumno que se dará de alta.
*/
public void insertar(Alumno alum) {
if (nAlumnos >= poblacionEst.length) {
System.out.println("Cupo lleno. No es posible dar de alta a "+
alum.obtenerNombre());
} else if (buscar(alum) == -1) {
poblacionEst[nAlumnos++] = alum;
} else {
System.out.println("El alumno "+alum.obtenerNombre()+" ya está dado de alta.");
}
}

Al agregar a un alumno se actualiza la cantidad de alumnos registrados. Debido a que se


usa el operador de autoincremento, después de la variable primero se almacena el alumno en
el arreglo y luego se incrementa la variable nAlumnos.
Ejemplo 6.29. Método de búsqueda.
El método buscar recibe al alumno que se busca y llama a un método local para hacer la
búsqueda sobre una cadena. Este método recorre el arreglo desde el principio hasta encontrar
la cadena o que se hayan revisado todos los datos del arreglo en cuyo caso la cadena no se
encuentra ahı́. Si encuentra el dato devuelve la posición en donde lo encontró, en otro caso
devuelve -1 para indicar que no lo encontró.
6.6 ARREGLOS DE OBJETOS 149

/**
* Método para buscar un alumno.
* @param alum - Alumno que se buscará en el arreglo.
* @return int - posición donde se encuentra el alumno o -1 si no está
*/
public int buscar(Alumno alum) {
return buscar (alum.obtenerNombre());
}

/*
* Método privado para buscar un alumno
*/
private int buscar(String alumn) {
boolean encontro = false;
int i;

for (i = 0; i < nAlumnos && !encontro; i++) {


if (poblacionEst[i].obtenerNombre().equalsIgnoreCase(alumn)) {
encontro = true;
}
}
return (encontro) ? i - 1 : -1;
}

Debido a que las localidades del arreglo poblacionEst almacenan referencias a objetos de
la clase Alumno, es válido utilizar la instrucción poblacionEst[i].obtenerNombre() para
obtener el nombre del alumno almacenado en la localidad i del arreglo. Como el resultado de
esta expresión es una cadena se puede aplicar cualquier método de esta clase, en particular
se aplica equalsIgnoreCase.
En lugar de colocar al final del arreglo el alumno que se inserta, se puede colocar de tal
forma que los nombres se encuentren ordenados en el arreglo, con el propósito de evitar
recorrer todo el arreglo para determinar si un dato en particular se encuentra o no en el
arreglo.
Ejemplo 6.30. Alta de un alumno (versión ordenada).
El algoritmo que se programa es el siguiente:
1. Se recorre el arreglo buscando el nombre. Mientras lo almacenado en el arreglo sea
menor que el dato que se quiere insertar se avanza en el arreglo. Se termina el recorrido
del arreglo cuando el nombre es mayor o igual que el almacenado o bien se terminaron
los datos del arreglo.
2. Si el nombre es igual al almacenado termina el método, pues no se trata de un nuevo
alumno.
150 CAPÍTULO 6. AGRUPACIÓN DE OBJETOS

3. Si se recorrió todo el arreglo buscando dónde insertar significa que el alumno va a


quedar en el último lugar. En caso contrario, se hace un espacio en el arreglo para
almacenar el nuevo alumno, para ello se mueven los datos a la siguiente posición.

/**
* Método insertar. Inserta un alumno en la localidad que le corresponde
* @param alumn - Alumno que se insertará
*/
public void insertar(Alumno alum) {
String alumn = alum.obtenerNombre();
int i = 0;
compara = -1;

if (nAlumnos >= poblacionEst.length) {


System.out.println("Cupo lleno");
} else {
while (i < nAlumnos && compara < 0) {
compara = poblacionEst[i++].obtenerNombre().compareToIgnoreCase(alumn);
}
if (i < nAlumnos && compara = 0) {
System.out.println("El alumno "+alumn+" ya esta dado de alta");
} else {
for (int j = nAlumnos; j > i; j--) {
poblacionEst[j] = poblacionEst[j-1];
}
poblacionEst[i] = alum; // Agrega al nuevo alumno
nAlumnos++;
}
}
}

En este caso se utiliza el método compareToIgnoreCase para comparar dos cadenas porque
no basta saber si son iguales o diferentes, se quiere saber la relación de orden entre ellas.
El método compareToIgnoreCase trata igual las mayúsculas que las minúsculas; devuelve
un entero negativo si la primera cadena es menor que la segunda, un entero positivo si es lo
contrario y un cero si son iguales.
Es una decisión del programador cuál de los dos métodos de inserción incluir en la clase
SeccionEscolar; aquı́ se presentaron los dos para ilustrar cómo se programan. No pueden
incluirse ambos debido a que tienen la misma firma.
Para realizar búsquedas en un arreglo ordenado se utiliza el algoritmo denominado de
búsqueda binaria, que consiste en comparar el elemento de la mitad del arreglo con el
buscado, si ambos valores son iguales ya se encontró el elemento buscado. Si el elemento
6.6 ARREGLOS DE OBJETOS 151

buscado es menor, entonces se sabe (porque el arreglo está ordenado) que se encuentra en
la primera mitad del arreglo, en otro caso se encuentra en la mitad superior del arreglo.
Este proceso de ir dividiendo el arreglo de búsqueda en mitades se repite hasta encontrar el
elemento o bien hasta determinar que el elemento buscado no se encuentra.
Antes de escribir el método se presenta un ejemplo con datos numéricos. Se tiene el arreglo
de 10 elementos de la figura 6.3 y se desea buscar el número 15. Primero se compara el
número buscado con el elemento que se encuentra en la mitad del arreglo, el elemento en
a[4], en este caso lo encuentra de inmediato.
Considerar que ahora se busca el número 30, ası́ que nuevamente se compara con el elemento
que se encuentra a la mitad del arreglo, a[4]. En este caso, el número 15 es menor que 30,
ası́ que se debe buscar entre los elementos que se encuentran de la posición 5 a la 9 del arreglo
(como se ve en la parte central de la figura 6.3). El siguiente paso consiste en comparar el
número buscado con el elemento que se encuentra a la mitad del espacio de búsqueda; en este
caso es con a[7], que es mayor que el número buscado (48 >30), por lo que ahora se debe
buscar entre los elementos que van de la posición 5 a la 7 del arreglo, como se muestra en la
parte inferior de la figura 6.3. En el siguiente paso se compara el número 30 con el elemento
que se encuentra a la mitad del espacio de búsqueda, que ahora es el que se encuentra en el
lugar 6 del arreglo; en este caso se da la igualdad entre el número buscado y el elemento del
arreglo, por lo que termina la búsqueda.

0 1 2 3 4 5 6 7 8 9
2 4 8 10 15 25 30 48 52 95

25 30 48 52 95

25 30 48

Figura 6.3 Búsqueda binaria.

Ejemplo 6.31. Búsqueda de un alumno en un arreglo ordenado.

/**
* Método de búsqueda binaria para buscar un elemento en un arreglo ordenado
* @param buscado -- Nombre del alumno que se busca
* @return int -- posición donde está el alumno o -1 si no está
*/
private int busquedaBinaria(String buscado) {
int inf = 0, sup = nAlumnos-1, mitad;
buscado = buscado.trim().toLowerCase();
152 CAPÍTULO 6. AGRUPACIÓN DE OBJETOS

while (inf <= sup) {


mitad = (inf + sup)/2; // Calcula la mitad del arreglo

if (poblacionEst[mitad].obtenerNombre().compareToIgnoreCase(buscado) < 0) {
inf = mitad + 1; // Buscará en la mitad inferior
} else
if (poblacionEst[mitad].obtenerNombre().compareToIgnoreCase(buscado) > 0) {
sup = mitad - 1; // Buscará en la mitad superior
} else {
return mitad; // Lo encontró
}
}
return -1;
}

Si se decide trabajar con un arreglo ordenado, el método público buscar del ejemplo 6.29
debe quedar como sigue:

/** Método para buscar un alumno en un arreglo


* @param alum - Alumno que se buscará en el arreglo
* @return int - posición donde se encuentra el alumno o -1 si no está
*/
public int buscar(Alumno alum) {
return busquedaBinaria (alum.obtenerNombre());
}

Ejemplo 6.32. Método consultar. Busca la información de un alumno a partir de su nombre.


Si el alumno está registrado, se muestra toda su información, en caso contrario avisa que no
está registrado el alumno.

/**
* Método para consultar la información de un alumno particular. Si el
* alumno no está registrado se avisa de ello
* @param alum - Alumno del que se desea información
*/
public void consultar(Alumno alum) {
int pos = buscar(alum);
if (pos != -1) {
System.out.println(poblacionEst[pos]);
} else {
System.out.println("El alumno "+ alum +" no está registrado");
}
}
6.6 ARREGLOS DE OBJETOS 153

El problema original requiere además de ésta otras consultas, como son calcular el promedio
del alumno, su calificación más alta, etcétera. De ahı́ que para las consultas se programe un
menú para que el usuario elija la consulta que desea hacer y luego simplemente se hace una
llamada al método correspondiente de la clase alumno. Este menú es parecido al visto en el
ejemplo 6.14, sólo falta incluir las otras opciones.
En los ejemplos anteriores se ha mostrado que es más eficiente realizar la búsqueda si los
datos están ordenados, aunque la inserción de datos cuidando el orden es más complicada.
Un término medio en esta situación es insertar todos los datos que se deseen, al final del
arreglo y en algún momento ordenarlos. El tema de ordenamiento es muy importante en
computación. Existen varios algoritmos, el que aquı́ se presenta aunque no es el más eficiente
es muy sencillo de comprender.
Ejemplo 6.33. Algoritmo de ordenamiento.
El algoritmo empleado se llama algoritmo de selección; éste consiste en encontrar el elemen-
to menor del arreglo y colocarlo en el primer lugar del arreglo. Luego se busca el menor
elemento del arreglo, ignorando el primer elemento, y se coloca en la segunda posición, y
ası́ sucesivamente. En cada iteración al arreglo se busca el menor elemento y se coloca en su
lugar, también en cada una la cantidad de elementos que se van a buscar es menor en una
unidad que en la anterior.

/** Método de selección para ordenar un arreglo **/


public void ordenar() {
for (int k = 0; k != nAlumnos; k++) {
int j = calcularMenor(k);
Alumno tmp = poblacionEst[k]; // Intercambia el elemento de la
poblacionEst[k]= poblacionEst[j]; // posición j, el menor del arreglo
poblacionEst[j] = tmp; // con el que está en la posición
} // adecuada
}
private int calcularMenor(int k) {
int i = k+1;
int menor = k;

while (i < nAlumnos) {


String actual = poblacionEst[i].obtenerNombre();
String minimo = poblacionEst[menor].obtenerNombre();
if (actual.compareTo(minimo) < 0) {
menor = i;
}
i++;
}
return menor;
}
154 CAPÍTULO 6. AGRUPACIÓN DE OBJETOS

Lo que falta para concluir con este programa es una clase que cree un objeto de la clase
SeccionEscolar, muestre un menú de opciones y de acuerdo con ello realice las operaciones
especificadas por el usuario. Ya se ha visto la forma de hacer clases como estas por lo que
no se incluye en esta sección.

6.7 Arreglos bidimensionales


Existen aplicaciones en que los arreglos como se han presentado no son suficientes, por ejem-
plo aplicaciones que requieren el uso de matrices en matemáticas o bien juegos de mesa que
requieren de tableros. En estos casos se requiere un tipo de dato que tenga dos dimensiones:
el renglón y la columna para almacenar datos del mismo tipo. Un arreglo de dos dimensiones
se declara de la siguiente forma:
tipo dato [][] nombre arreglo = new tipo dato [n renglones][n columnas];
de acuerdo a lo que se ha presentado, al escribir tipo dato[] se indica que se quiere declarar
un arreglo de elementos del tipo especificado, el siguiente par de corchetes significa que se
tendrá un arreglo donde cada localidad apunta a un arreglo de datos del tipo especificado.
En este caso es necesario especificar dos valores: el primero es la cantidad de renglones o de
elementos que tendrá el arreglo y el segundo es la cantidad de elementos que tendrá cada
elemento (renglón) del arreglo.
Ejemplo 6.34. Matriz de diez renglones, cada uno de cinco columnas. En cada casilla de la
matriz se tiene la suma de sus ı́ndices.

final int renglones = 10, columnas = 5;


double [][] matriz = new double[renglones][columnas];

for (int i = 0; i < matriz.length; i++) {


for (int j = 0; j < matriz[i].length; j++) {
matriz[i][j] = i + j;
}
}

En el ejemplo anterior se tiene un arreglo de diez elementos donde cada uno es un arreglo de
cinco elementos de tipo double (Figura 6.4). En el ejemplo 6.34 se utilizan dos instrucciones
iterativas anidadas para llenar la matriz. La forma de trabajar es la siguiente: para cada
valor de la variable i, que va de 0 a renglones - 1, se realiza por completo la segunda
instrucción for que consiste en asignar un valor a cada elemento del i-ésimo renglón.
Ejemplo 6.35. Creación de una matriz. Primero los renglones y más adelante las columnas.
Debido a la forma en que se definen las matrices, la expresión matriz[i] se refiere al
i-ésimo arreglo. Como consecuencia de ello se puede diferir la creación de cada renglón de la
matriz, como en el siguiente código en el que con la instrucción for se crea cada renglón de
la matriz, en este caso de tamaño fijo.
6.7 ARREGLOS BIDIMENSIONALES 155

matriz
0 1 2 3 4
0
1

2
3
4
5
6
7

8
9

Figura 6.4 Arreglo de 10*5 elementos.

double [][] matriz = new double[Renglones][];


for (int renglón = 0; renglón < matriz.length; renglón++)
matriz[renglón] = new double[Columnas];

Ejemplo 6.36. Matriz triangular.


Es posible definir matrices donde cada renglón tenga diferente cantidad de elementos como
en el siguiente ejemplo en que se crea una matriz triangular, el primer renglón tiene un
elemento, el segundo dos elementos, etcétera.
final int Renglones = 5;
int [][] triángulo = new int [Renglones][];
for (int i = 0; i < triángulo.length; i++) {
triángulo[i] = new int [i+1];
}

Ejemplo 6.37. Intercambio de renglones en una matriz.


Debido a que se tienen arreglos de arreglos resulta posible cambiar completamente un
renglón de una matriz por otro con sólo asignar una referencia apropiada:
double [] temp = matriz[3];
matriz [3] = matriz[4];
matriz [4] = temp;

en este caso no se copian los datos, sólo se copian las referencias.


A continuación se muestra la forma de asignar valor inicial a cada elemento de una matriz,
el procedimiento es similar a la inicialización de arreglos. En este caso, la inicialización en la
156 CAPÍTULO 6. AGRUPACIÓN DE OBJETOS

declaración de los arreglos consiste en poner en una llave tantas llaves con elementos como
renglones tenga la matriz, y en cada llave interna tantos elementos como tenga ese renglón.
Por ejemplo:
• Declaración e inicialización de una matriz de cuatro renglones cada uno de tres enteros.
int [][] matriz4x3 = {
{0, 23, 32},
{67, 89, 98},
{56, 34, 7},
{17, 20, 89},
};

• Declaración e inicialización de una matriz triangular de cinco renglones.


int [][] triánguloPascal = {
{1},
{1,1},
{1,2,1},
{1,3,3,1},
{1,4,6,4,1},
};

En el caso de matrices se tiene una constante length que indica la cantidad de renglones
que tiene la matriz, por cada renglón hay otra constante length que indica la longitud de ese
renglón. Esto se debe a que una matriz es un arreglo de arreglos, ası́ se puede conocer tanto
el tamaño del arreglo de arreglos (cantidad de renglones o de arreglos contenidos), como la
longitud de cada renglón. Ası́ matriz[i].length devuelve la cantidad de elementos en el
renglón i, y matriz.length devuelve la cantidad de renglones que tiene la matriz.
Ejemplo 6.38. Se desea extender el lenguaje Java para que se pueda trabajar con matrices
de números reales para las aplicaciones que ası́ lo requieran.
1. Encontrar los objetos principales.
Matrices de números reales.
2. Determinar el comportamiento deseado.

Matriz:
Llenar la matriz.
Determinar el número de renglones y de columnas de la matriz.
Calcular la matriz transpuesta.
Determinar si la matriz es simétrica.
Sumar y multiplicar dos matrices.
6.7 ARREGLOS BIDIMENSIONALES 157

3. Definir escenario.
(a) El programa muestra un menú con las opciones.
(b) El usuario selecciona una opción.
(c) Dependiendo de la opción seleccionada, se solicita más información.
(d) Estos pasos se repiten hasta que el usuario seleccione la opción para terminar de
trabajar.
A continuación la programación de la clase Matriz según el diseño descrito.
Ejemplo 6.39. Estructura y los constructores para matrices.
/**
* Clase para trabajar con matrices
* @author Amparo López Gaona
* @version 3a edición
*/
public class Matriz {
private double [][] mat;

/**
* Constructor por omisión, crea una matriz de 10 * 10, asignando el
* valor 0 a cada elemento.
*/
public Matriz () {
this(10,10);
}
/**
* Constructor de una matriz de n * m elementos, con el valor 0 en cada elemento
* Si alguno de los parámetros es negativo envı́a un mensaje de error.
* @param n -- cantidad de renglones
* @param m -- tama~
no de los renglones
*/
public Matriz (int n, int m) {
if (n > 0 && m > 0) {
mat = new double[n][];
for (int i = 0; i < n; i++) {
mat[i] = new double[m];
for (int j = 0; j < mat[i].length; j++) {
mat[i][j] = 0;
}
}
} else new Error("Valores incorrectos para la dimensión de la matriz");
}
158 CAPÍTULO 6. AGRUPACIÓN DE OBJETOS

Ejemplo 6.40. Constructor para crear una matriz a partir de un arreglo de dos dimensiones,
para ello crea la matriz con el número de renglones del arreglo que recibe como parámetro.
Luego para cada renglón, determina su tamaño, lo construye y copia el contenido del paráme-
tro.
/**
* Constructor que crea una matriz a partir de un arreglo de dos dimensiones
* @param m - arreglo de dos dimensiones
*/
public Matriz (double [][] m) {
mat = new double[m.length][];
for (int i = 0; i < mat.length; i++) {
mat[i] = new double[m[i].length];
for (int j = 0; j < mat[i].length; j++) {
mat[i][j] = m[i][j];
}
}
}

Es conveniente indicar que si en lugar de la última instrucción for se hubiera escrito


mat[i] = m[i]; se tendrı́a un alias de m, es decir al modificar el arreglo m, se modificarı́a el
contenido de la matriz mat.
Ejemplo 6.41. Constructor para crear una matriz a partir de otra.
/**
* Constructor que crea una matriz a partir de otra
* @param m - matriz que se copiará
*/
public Matriz (Matriz m) {
this (m.mat);
}

Ejemplo 6.42. Método que devuelve la cantidad de renglones de la matriz.


/**
* Método para obtener la cantidad de renglones de la matriz
* @return int - el número de renglones de la matriz
*/
public int renglones() {
return mat.length;
}
Ejemplo 6.43. Método que devuelve la cantidad de columnas de la matriz. Se debe recordar
que la clase Matriz trabaja con matrices rectangulares, por lo tanto, todos los renglones son
del mismo tamaño.
6.7 ARREGLOS BIDIMENSIONALES 159

/** Método para obtener el número de columnas de la matriz


* @return int - el número de columnas de la matriz
*/
public int columnas() {
return mat[0].length;
}
Ejemplo 6.44. Método para crear la matriz transpuesta de la matriz original de n*m. Crea
una matriz de m*n donde cada renglón es la columna de la original y cada columna es el
renglón correspondiente de la original.
/** Método para obtener la matriz transpuesta de la matriz original de n*m
* @return Matriz - la matriz transpuesta de la original
*/
public Matriz transponer() {
double [][] m = new double[columnas()][renglones()];

for (int i = 0; i < m.length; i++) {


for (int j = 0; j < m[0].length; j++) {
m[i][j] = mat[j][i];
}
}
return new Matriz(m);
}
Notar que no se devuelve m, debido a que no es un objeto de la clase Matriz.
Ejemplo 6.45. Método para determinar si la matriz es simétrica, es decir, si lo que está en
la posición (i,j) es igual a lo que hay en la posición (j,i). Este método es válido sólo para
matrices cuadradas, es decir, matrices de n*n.
/** Método para determinar si la matriz, cuadrada, es simétrica
* @return boolean -- true si es simétrica, false en otro caso.
*/
public boolean esSimetrica() {
if (mat.length == mat[0].length) {
for (int i = 0; i < mat.length; i++) {
for (int j = 0; j < mat[0].length; j++) {
if (mat[i][j] != mat[j][i]) {
return false;
}
}
}
return true;
} else return false;
}
160 CAPÍTULO 6. AGRUPACIÓN DE OBJETOS

Ejemplo 6.46. Método para sumar dos matrices. Éstas deben ser de igual tamaño. Para
ello se suma el elemento (i,j) de cada matriz.
/**
* Método para calcular la suma de dos matrices de igual tama~no
* @param m - Matriz que será sumada a la que llama al método.
* @return Matriz - La suma de las dos matrices
*/
public Matriz sumar(Matriz m) {
if (m.mat.length != mat.length || m.mat[0].length != mat[0].length)
new Error("Dimensiones incorrectas");

Matriz suma = new Matriz(mat.length, mat[0].length);


for (int i = 0; i < mat.length; i++) {
for (int j = 0; j < mat[0].length; j++) {
suma.mat[i][j] = m.mat[i][j] + mat[i][j];
}
}
return suma;
}

Ejemplo 6.47. Método para sumar dos matrices, una que es la que se usa para llamar al
método y la otra es aquella que se da como parámetro, el resultado queda en la matriz que
llama al método. Estos dos métodos para la suma no pueden estar en la clase debido a que
tienen la misma firma, aquı́ se incluyen sólo para ilustrar ambas opciones.
/** Método para calcular la suma de dos matrices
* @param m - Matriz que será sumada a la que llama al método
*/
public void sumar(Matriz m) {
if ((m.mat.length != mat.length) || (m.mat[0].length != mat[0].length)) {
new Error(""No se puede efectuar la suma. Dimensiones incorrectas");
} else {
for (int i = 0; i < mat.length; i++) {
for (int j = 0; j < mat[0].length; j++) {
mat[i][j] += m.mat[i][j];
}
}
}
}

Ejemplo 6.48. Método para multiplicar dos matrices, la primera matriz es el objeto que
llama al método y la segunda es la que se recibe como parámetro, el resultado es una nueva
matriz.
6.8 EJERCICIOS 161

/**
* Método para calcular la multiplicación de dos matrices
* @param m -- Matriz que será sumada a la que llama al método.
* @return Matriz -- El producto de las dos matrices.
*/
public Matriz multiplicar(Matriz m) {
if (mat[0].length != m.mat.length)
new Error(""No se puede efectuar la suma. Dimensiones incorrectas");

Matriz producto = new Matriz(mat.length, m.mat[0].length);


for (int i = 0; i < producto.mat.length; i++) {
for (int j = 0; j < producto.mat[0].length; j++) {
for(int k = 0; k < mat[0].length; k++) {
producto.mat[i][j] += (mat[i][k] * m.mat[k][j]);
}
}
}
return producto;
}

6.8 Ejercicios
1. ¿Los arreglos son objetos? ¿Qué métodos tienen asociados?

2. ¿Qué tipos de datos pueden almacenarse en un arreglo?

3. ¿Cómo se especifica el tipo de elementos almacenados en un arreglo?

4. ¿Qué significa el parámetro del método main y cómo se le asigna valor?

5. ¿Cómo se puede saber el tamaño de un arreglo?

6. ¿El tamaño de un arreglo puede variar?

7. Describir tres formas de dar valor inicial a un arreglo.

8. ¿Se permite crear arreglos de referencias a objetos?

9. Los tres segmentos de código pretenden calcular la suma de los cinco primeros elementos
del arreglo de enteros arreglo. Decir cuáles lo consiguen y cuáles no. Justificar la
respuesta.
162 CAPÍTULO 6. AGRUPACIÓN DE OBJETOS

for (int suma = 0, i = 0; i < 5; i++) { //Segmento 1


suma += arreglo[i];
}

int suma = 0, i = 0; //Segmento 2


while ( i < 5) {
i++;
suma += arreglo[i];
}

int suma = 0, i = 0; //Segmento 3


do {
suma += arreglo[i++];
} while ( i < 5);

10. Explicar el objetivo del siguiente código.

public class Duda {


public static void main(String [] pps) {
final int n = pps.length;
for (int i = 0; i < n; i++)
System.out.println(pps[i]);
}
}

11. Escribir el método actualizar, el método mostrar y el método borrar para la cla-
se SeccionEscolar. El método actualizar debe mostrar la información del alumno
y permitir cambiar el valor de sus campos (uno a la vez). El método mostrar debe
mostrar la información de todos los alumnos registrados. El método borrar debe eli-
minar el registro de un alumno para evitar dejar huecos o basura en el arreglo; una vez
encontrado el alumno recorrer la información del arreglo de la última localidad hasta
donde se encuentra el alumno que se desea eliminar.

12. Escribir un programa que cree y pruebe una clase para manejar los meses del año; debe
tener sus constructores, un método para, dado un entero, devolver una cadena con el
nombre del mes, un método que reciba una cadena y devuelva el número de mes al que
corresponde, otro para determinar la cantidad de dı́as en un mes.

13. Escribir una clase Poligono como arreglo de al menos tres puntos, incluir constructores,
métodos para determinar si dos polı́gonos son el mismo, para imprimirlo como una
sucesión de puntos, para verificar si es regular, para determinar la cantidad de lados
que tiene y para calcular su perı́metro.
6.8 EJERCICIOS 163

14. Escribir un programa que simule, de manera muy simple, una contestadora de teléfono
con capacidad para 10 mensajes. Lo que debe hacer es guardar mensajes en ella, “leer-
los” (es decir, mostrarlos) en el orden en que se reciben y marcarlos como borrados. Se
recomienda usar la clase Mensaje del ejercicio 12 del capı́tulo 4.

15. Una liga de fútbol consta de un número fijo de equipos. Cada equipo tiene nombre,
entrenador, estadio y durante la temporada va adquiriendo ciertos puntos de acuerdo
con los juegos que va jugando. Escribir un programa que muestre la puntuación de los
equipos. Crear constructores, métodos para obtener y asignar valor a los componentes
de la estructura, un método para determinar el nombre de todos los entrenadores
activos, un método para obtener el nombre del entrenador de un equipo determinado,
un método para determinar el nombre del estadio de un cierto equipo, un método
para determinar el nombre del equipo que tiene más puntos, otro para encontrar el
nombre del equipo que consiguió menos puntuación y otro más para obtener el nombre
de los equipos que tuvieron más empates durante la temporada. Opcionalmente, crear
un método que muestre la tabla de posiciones ordenada por puntuación de mayor a
menor.

16. Escribir una clase para trabajar con conjuntos de enteros cuyo valor está entre 1 y
100. El conjunto puede representarse como un arreglo de valores booleanos, donde
si a[i] tiene valor true significa que el entero i está en el conjunto y si es false
significa que no está ahı́. En esta clase se debe tener un constructor, un método para
cada operación de conjuntos: unión, intersección, diferencia, determinar si un elemento
está en el conjunto y otro para introducir elementos al conjunto, además de un método
para determinar si dos conjuntos son iguales.

17. Escribir una clase para almacenar las fechas que se consideren importantes, incluyendo
la razón por la cual son importantes. Por ejemplo, “14 de abril” es la fecha y la razón
“cumpleaños de mi hija”. Esta clase debe utilizar la clase Fecha del ejercicio 14 del
capı́tulo 4 sin modificarla. Esta clase debe tener métodos para imprimir todas las
fechas importantes registradas, las primeras n, o todas las de cierto mes de acuerdo
al parámetro que reciba el método main: -t para imprimir todas, -n númeroDeFechas
para especificar cuántas se desea imprimir o bien -m nombreDeMes para imprimir las
correspondientes al mes especificado.

18. Escribir un programa que juegue cartas con el usuario de acuerdo con las siguientes
reglas. Inicialmente el jugador y la computadora tienen tres cartas cada uno, del mazo
restante se voltea la de arriba. Cada jugador (el usuario y la computadora) tiran
alternadamente. La tirada consiste en soltar una carta del mismo valor o figura (aquı́ se
consideran sólo dos grupos de figuras, picas y tréboles son lo mismo, y corazones y
diamantes son lo mismo) a la que está arriba, es decir, la que acaba de tirar el jugador
164 CAPÍTULO 6. AGRUPACIÓN DE OBJETOS

contrario. Si no tiene, debe tomar del mazo tantas como necesite. Gana el jugador que
se deshace primero de todas sus cartas.

19. Escribir un programa para que el usuario juegue a las cartas con la computadora. El
juego consiste en que se muestran dos cartas y el usuario elige una de ellas. A partir
de ese momento se van volteando cartas hasta que salga una con el mismo palo que
cualquiera de las dos volteadas originalmente. Si el palo de la reciente coincide con el
de la elegida por el usuario gana éste, si no gana la máquina.

20. Escribir un programa que sirva como reloj checador de empleados de cierta empresa.
Cada empleado tiene una tarjeta con cinco ranuras y cada ranura tiene espacio para
almacenar hora de entrada y hora de salida. Se debe registrar la hora de llegada y de
salida de cada empleado cada dı́a. Al final de la semana se requiere calcular el número
de horas trabajadas. Si fueron menos de 40, se envı́a un mensaje de alerta y si son más
de 40 se especifica el número de horas extras trabajadas.

21. Escribir un programa que lleve la puntuación de los jugadores en el boliche. En cada
mesa hay un máximo de seis jugadores. Cada lı́nea tiene 10 entradas. Cada entrada tiene
un cuadro para indicar los bolos derribados en el primer intento, otro para indicar los
bolos derribados en el segundo intento (siempre que en el primero no se hayan derribado
los 10 bolos) y un tercer espacio para indicar la puntuación hasta ese momento (figura
6.5). En cada entrada el jugador tiene máximo dos lanzamientos. La anotación para la
entrada i se calcula tomando en cuenta los bolos tirados en el primer lanzamiento (p)
y los del segundo lanzamiento (q), como sigue:

1 ... i−1 i i+1 10


p q r s
....
x

Figura 6.5 Anotaciones para el boliche.

• Si (p + q) < 10. El total se calcula simplemente sumando al total acumulado en


la entrada anterior la cantidad de bolos derribados.
total(i) = total(i-1) + p + q.
Para facilitar el cálculo se puede crear un arreglo de 11 elementos y en la localidad
cero poner un cero.
• Si (p + q) = 10 con q = 0. A este tiro se le conoce como spare. El total se calcula
sumando, al total acumulado hasta la entrada anterior, 10 y la cantidad de bolos
tirados en el primer lanzamiento (r) de la siguiente entrada.
6.8 EJERCICIOS 165

total(i) = total(i-1) + 10 + r
Si r = 10 entonces se aplica la regla de la chuza. Si se está en la décima entrada
se debe hacer un lanzamiento adicional.
• Si p = 10, esto es una chuza. El total se calcula sumando a la cantidad acumu-
lada hasta la entrada anterior 10 más el número de bolos derribados en los dos
lanzamientos de la siguiente tirada.
total(i) = total(i-1) + 10 + r + s.
Si se está en la décima entrada se pueden hacer dos lanzamientos adicionales.

22. Escribir un programa que ayude al usuario a obtener valores estadı́sticos. Este programa
debe tener métodos para agregar un valor a un conjunto de datos, eliminar todos los
datos, determinar el tamaño del conjunto, calcular el mı́nimo valor del conjunto, el
máximo, la media, la moda, la mediana y la desviación estándar. La moda es el valor
que más se repite en el conjunto. La mediana es el valor que queda en la posición de en
medio del conjunto una vez ordenado. Opcionalmente, incluir un método para mostrar
un histograma con los datos. Un histograma despliega los valores como rectángulos
(aquı́ puede ser como una lı́nea de asteriscos) de diferentes tamaños dependiendo del
valor que se desea graficar.

23. Escribir un programa que lea información de clientes de cierto almacén: nombre, di-
rección, total de compras de cada uno en los últimos tres meses. El programa debe
permitir dar de alta cliente, cambiar sus datos, actualizar su total de compras, calcular
el promedio de compra de cada cliente y el promedio global. Luego, para cada cliente
cuyas compras están por arriba del promedio global, enviarle una carta de agradeci-
miento e invitación a seguir ası́. De otra forma enviarles una carta con un cupón de
descuento para su siguiente compra con la finalidad de que sigan comprando.
Capı́tulo 7

Herencia de clases

Las clases no existen aisladas, en general se relacionan unas con otras como se ha visto en los
ejemplos de los capı́tulos anteriores: un triángulo está formado por puntos, en la máquina
expendedora de boletos se tienen clases que necesitan de otras para hacer sus tareas, la
sección escolar conteniendo expedientes de alumnos, etcétera. En estas clases existe una
relación de uso entre un cliente y un servidor.
En ocasiones es preciso modificar o adaptar clases existentes a nuevas necesidades, desde
luego sin violar el principio de encapsulación. En este capı́tulo se muestra la forma de crear
nuevas clases por combinación, extensión y/o especialización de clases existentes, a través
de la herencia de clases. También se introduce el concepto de polimorfismo, el cual permite
determinar en el momento de ejecución a qué clase enviar el mensaje.

7.1 Ampliación mediante herencia


Para ilustrar la forma de usar la herencia de clases se trabajará con el siguiente problema. Se
requiere hacer un programa para el mantenimiento de cuentas bancarias teniendo cuentas de
débito, cuentas con pago automático de servicios y cuentas de crédito. Con todas las cuentas
se permite retirar dinero, depositar dinero y conocer el dinero disponible de la misma. Las
cuentas con pago de servicio además permiten el pago automático del teléfono. Las cuentas
de crédito tienen un lı́mite de crédito para poder realizar compras y en cualquier momento
se puede consultar el crédito disponible y el monto de la deuda. Siguiendo la metodologı́a de
diseño presentada en el capı́tulo 1 se tiene lo siguiente.

1. Encontrar los objetos principales.


En este caso son: cuenta bancaria, cuenta de débito, cuenta con pago de servicios y
cuenta de crédito. Las cuentas de débito no tienen ningún comportamiento adicional
al de cualquier cuenta bancaria, ası́ que pueden considerarse sinónimos.

167
168 CAPÍTULO 7. HERENCIA DE CLASES

2. Determinar el comportamiento deseado para cada objeto.


A continuación se listan las acciones que se pueden realizar para cada tipo de cuentas,
según la descripción del problema.

Cuenta bancaria: Cuenta con pagos: Cuenta de crédito:


Retirar dinero. Retirar dinero. Retirar dinero.
Depositar dinero. Depositar dinero. Depositar dinero.
Consultar disponible. Consultar disponible. Consultar disponible.
Pagar servicios. Comprar a crédito.
Consultar lı́mite de crédito.
Consultar importe de deuda.

3. Definir escenarios. Sólo se presentan algunos escenarios:


Escenario: inicio del programa.
(a) El programa da la bienvenida al usuario.
(b) El programa presenta al usuario un menú con las diferentes opciones para usar
las cuentas.
(c) El usuario elige una opción.
(d) El programa valida la opción.
(e) De acuerdo con la opción elegida el programa solicita o muestra la información
requerida. Si la opción es inválida regresar a (b).
(f) El programa repite los pasos anteriores hasta que el usuario elige la opción de
terminar.
Escenario: retiro de dinero de una cuenta bancaria.
(a) El programa solicita al usuario la cantidad de dinero que desea retirar.
(b) El usuario indica la cantidad de dinero que desea retirar.
(c) El programa valida la cantidad indicada, verificando que sea positiva y menor o
igual que el disponible de la cuenta.
(d) Si la cantidad es válida el programa “proporciona” al usuario la cantidad solici-
tada, en caso contrario envı́a un mensaje de error.

Se puede suponer que existe una clase Cuenta con métodos para realizar operaciones con
cuentas bancarias, como son: crear cuentas, retirar dinero, depositar dinero y conocer el
disponible de una cuenta. Esta clase podrı́a ser resultado de resolver el problema 15 del
capı́tulo 4. La clase Cuenta podrı́a estar definida como a continuación se muestra:
7.1 AMPLIACIÓN MEDIANTE HERENCIA 169

public class Cuenta {


private double disponible;
private static int num = 2013;
private final long numCta;

public Cuenta(double montoInicial) { ... }


public void retirar(double monto) { ... }
public void depositar(double monto) { ... }
public double obtenerDisponible() { ... }
public long obtenerNumCta() { ... }
}

Debido a que ya se tiene la clase Cuenta, se empezará por programar los métodos para
la clase CuentaConServicios. Esta clase es muy parecida a la clase Cuenta, excepto por
el método para pagar los servicios. Por lo tanto, en este caso el problema de las cuentas
con pago de servicios se reduce a un problema que requiere ampliar la funcionalidad de la
clase Cuenta, añadiendo los métodos necesarios para instrumentar el pago de servicios. Para
resolver este problema existen al menos tres posibilidades:

1. Modificar la clase Cuenta agregando los métodos requeridos.


Ésta es una mala opción, pues nunca se debe modificar el código de una clase que
ya está funcionando, ya sea porque no se dispone de él o bien porque no es posible
garantizar que la modificación no altere el funcionamiento de programas existentes que
utilizan objetos de la clase original. Además, si los nuevos métodos no se aplican a
todos los objetos de la clase Cuenta se deben incluir instrucciones condicionales en
estos métodos para evitar pagar servicios de una cuenta que no está autorizada para
ello. Si después hay otra ampliación, y se sigue este procedimiento, habrı́a que incluir
otro tipo de condicionales.

2. Copiar el código de Cuenta en el programa para el pago de servicios y hacer el cambio


necesario.
Tampoco es buena idea, pues con ello se estarı́a duplicando código (suponiendo que se
tiene el código fuente), y esto a la larga puede causar problemas; por ejemplo, si en
algún momento cambia el código original de la clase Cuenta se tendrı́a que actualizar
en la nueva clase, de no hacerlo se tendrı́a inconsistencia entre las clases.

3. Usar el concepto de herencia de clases.


Esta es una buena idea, pues se tiene la funcionalidad de las cuentas (sin tocar su códi-
go) y con la herencia sólo es necesario crear una clase que extienda la clase de cuentas
con la nueva funcionalidad, es decir, la pequeña parte que diferencia el comportamiento
de ambos tipos de cuentas.
170 CAPÍTULO 7. HERENCIA DE CLASES

La herencia permite definir una nueva clase Cn muy parecida a una clase existente C
definiendo sólo los atributos y los métodos que difieren de los existentes en C y automática-
mente se incluyen los métodos y atributos de C. Como resultado, los atributos y métodos de
Cn son todos los de C más los especificados en Cn . Estos últimos pueden ser nuevos métodos
o bien la redefinición o cancelación de métodos ya existentes en C. Por lo anterior, se dice
que la herencia facilita desarrollar programas de manera incremental evitando la duplicidad
de código. La clase Cn se denomina subclase o clase derivada y la clase C se conoce como
superclase, clase base o clase padre.
Ejemplo 7.1. La clase CuentaConServicios agrega a la funcionalidad de las cuentas la de
poder pagar la renta de un teléfono determinado, con el beneficio de abonar $100.00 a la
cuenta bancaria por hacer uso de este tipo de servicios.

/**
* Clase para trabajar cuentas con pago automático de servicios
* Objetivo: Ilustrar el uso herencia de clases
* @author Amparo López Gaona
* @version 3a edición
*/
public class CuentaConServicios extends Cuenta {
/**
* Método para pagar el teléfono
* @param numTel - Número telefónico a donde se hará el pago
* @param monto - Cantidad que debe pagarse
*/
public void pagarTelefono(String numTel, double monto) {
retirar(monto);
... // Código para pagar el teléfono
disponible += 100.00;
}
}

En este ejemplo se omite el código para pagar el teléfono, para concentrarse en los aspectos
del mecanismo de herencia.
Para especificar que se va usar el mecanismo de herencia se utiliza la palabra reservada
extends seguida del nombre de la clase que se heredará. Ası́, en este caso la instrucción
class CuentaConServicios extends Cuenta especifica que la clase CuentaConServicios
que se va a definir será una subclase de la clase Cuenta. Como se explicó antes, un objeto
de la subclase tiene los atributos y métodos de la clase de la cual hereda, más los definidos
en esta misma clase, como se muestra en la tabla 7.1.
7.2 CONTROL DE ACCESO 171

Clase: Cuenta CuentaConServicios


Estructura: disponible disponible
numCuenta numCuenta
Comportamiento: retirar retirar
depositar depositar
obtenerDisponible obtenerDisponible
pagarTelefono
Tabla 7.1 Atributos y métodos de una clase y su subclase

7.2 Control de acceso


Los objetos de la clase CuentaConServicios son como los objetos de cualquier otra clase, por
lo tanto los elementos públicos de su superclase y de cualquier otra clase pueden ser usados
en ella sin ningún problema, sin embargo, los atributos y métodos privados no pueden ser
usados desde fuera de la clase en que se definieron, ası́ sea una subclase.
En la definición del método pagarTelefono del ejemplo 7.1 se requiere modificar la variable
disponible de la clase Cuenta. No es correcto hacerlo como se hizo debido a que se trata de
un atributo privado, ası́ que se debe llamar a un método que permita modificar el atributo.
Si se quiere tener atributos y métodos que sean privados para todas las clases excepto,
para las clases derivadas, es necesario que en su declaración se precedan de la palabra re-
servada protected, con lo cual son privados para clases no derivadas y públicos para las
derivadas. Como consecuencia de este tipo de declaración, en la programación de un método
se pueden usar los atributos públicos de cualquier clase, todos los atributos definidos en su
clase, ası́ como los atributos protegidos de sus superclases con sólo escribir su identificador,
en cualquier otro caso se debe llamar a métodos apropiados para trabajar con el valor de esos
otros atributos. En la figura 7.1 se agrupan las clases que tienen acceso a los atributos de la
clase A, según el nivel de acceso especificado. Ası́, en la clase A se puede acceder a cualquier
atributo de ella; en las clases A1 y A2 se puede acceder a cualquier atributo protegido y
público de A, y tanto en la clase A como en A1 y A2 se puede acceder a cualquier atributo
público definido en la clase B.
Por lo especificado con anterioridad, para que funcionen correctamente los métodos de la
clase CuentaConServicios la definición de la estructura de la clase Cuenta deberı́a ser como
sigue:

public class Cuenta {


protected double disponible;
...
}
172 CAPÍTULO 7. HERENCIA DE CLASES

Clase A private

Clase B

public Clase A1 Clase A2

protected

Figura 7.1 Niveles de acceso de los elementos de la clase A.

o bien se debe acceder a estos elementos a través de los métodos creados para ese propósito,
por ejemplo, en lugar de usar la instrucción disponible += 100.00 usar la instrucción
depositar(100.00) con lo cual se ejecuta el método depositar de la clase Cuenta que
permite incrementar el disponible.

7.3 Constructores
Como se sabe, al crear un objeto se llama a un constructor de su clase para asegurar que
se cree con un estado inicial válido. Al programar una subclase es necesario programar
algún método constructor, el cual, en general, incluye la llamada a algún constructor de la
superclase; esto se hace mediante la instrucción super con los argumentos adecuados. La
instrucción super en una subclase, sirve para acceder a elementos de su superclase, en este
caso al constructor.
La llamada al constructor de la superclase debe ser la primera instrucción del constructor
de la subclase, con lo cual se empieza por asignar un estado inicial a la parte heredada y
luego se inicializa la parte propia de la clase.
Si no se incluye la llamada explı́cita al constructor de la superclase, Java realiza una
llamada implı́cita al constructor por omisión de la superclase. Si no existe tal constructor
y no hay llamada explı́cita con super se genera un error, puesto que no se puede crear un
objeto sin un estado inicial apropiado.
Ejemplo 7.2. Constructor de la clase CuentaConServicios.

/**
* Constructor de una cuenta con disponible mı́nimo de $2500
*
* @param montoInicial -- monto con el que se creara la cuenta.
*/
7.4 USO DE CLASES DERIVADAS 173

public CuentaConServicios (double montoInicial) {


super(montoInicial);
}

En este caso el constructor de la subclase no añade nada al constructor de la superclase,


pero puede hacerlo, como se verá más adelante.
La creación de un objeto de una clase derivada puede causar una serie de llamadas a cons-
tructores en la jerarquı́a de clases. Se ejecuta primero el constructor de la clase superior en la
jerarquı́a y se va descendiendo hasta llegar a la clase que llamó originalmente al constructor.

7.4 Uso de clases derivadas


En esta sección se presenta un ejemplo de uso de las cuentas con pago de servicios. Se crea
una cuenta validando que el capital inicial sea mayor a $2 500.00, luego se presenta un
menú con las opciones para manejo de esa cuenta y se pide al usuario seleccionar alguna; de
acuerdo con la selección se puede hacer un retiro, un depósito, mostrar el disponible, pagar
el teléfono o terminar la ejecución del programa.
Ejemplo 7.3. Uso de una clase derivada.
import java.util.Scanner;
/**
* Clase que permite utilizar objetos de clases derivadas
* @author Amparo López Gaona
* @version 3a edición
*/
public class UsaCuentas {
static Scanner in = new Scanner(System.in);
static CuentaConServicios cuenta;
/**
* Menú de opciones para trabajar con una cuenta de servicios
*/
public static void menu () {
System.out.println("1. Retirar capital");
System.out.println("2. Depositar capital");
System.out.println("3. Consultar disponible");
System.out.println("4. Pagar teléfono");
System.out.println("0. Terminar");
}
/**
* Método para solicitar una cantidad, leerla y verificar que sea positiva
* @param mensaje -- mensaje que se despliega
*/
174 CAPÍTULO 7. HERENCIA DE CLASES

public static double cantidadValida(String mensaje) {


double cantidad;

do {
System.out.println(mensaje);
cantidad=in.nextDouble();
} while (cantidad <= 0);
return cantidad;
}
/*
* Método para ejecutar las acciones adecuadas, según la opción elegida
* @param opcion -- opción elegida por el usuario
*/
public static void realizarAccion(int opcion) {
double capital;
switch(opcion) {
case 1: //Retiro
capital= cantidadValida("¿Cuánto dinero quieres retirar?");
cuenta.retirar(capital);
break;
case 2: //Depósito
capital = cantidadValida("¿Qué cantidad deseas depositar?");
cuenta.depositar(capital);
break;
case 3: // Disponible
System.out.println("Tu saldo disponible es $" + cuenta.obtenerDisponible());
break;
case 4: // Pago de teléfono
capital = cantidadValida("¿Cuánto dinero vas a pagar?");
cuenta.pagarTelefono("12345",capital);
System.out.println("Teléfono pagado, gracias");
break;
case 0: //Fin del programa
System.out.println("*** Hasta pronto. ***");
default:
System.out.println("Opción inválida");
}
}

public static void main(String[] pps) {


int opcion;
// Solicita capital inicial
double capital=cantidadValida("¿Con cuánto quieres iniciar tu cuenta?");
7.5 ESPECIALIZACIÓN MEDIANTE HERENCIA 175

while(capital<2500) { //Valida el depósito inicial para una cuenta


capital = cantidadValida("La cantidad mı́nima es de $2500"+
"\nIndica de nuevo la cantidad");
}
cuenta=new CuentaConServicios(capital); // Crea la cuenta
do { //Muestra menú de opciones y pide seleccionar alguna
menu();
opcion = in.nextInt();
realizarAccion(opcion);
} while(opcion != 0);
}
}

Es conveniente notar que al llamar a un método no hay diferencia entre los métodos
heredados y los métodos propios de la subclase. El usuario de la cuenta con servicios sólo
sabe que esta clase tiene los cuatro métodos mencionados, no sabe que tres de ellos son
heredados.

7.5 Especialización mediante herencia


En esta sección se presenta la programación de las cuentas de crédito; estas cuentas permiten
efectuar compras hasta un cierto lı́mite de crédito. Para implementar esa funcionalidad, las
cuentas de crédito requieren, además de los atributos de una cuenta común, otro atributo
para definir el lı́mite de crédito. También se requieren métodos especı́ficos para este tipo de
cuentas: comprar y obtenerLimite. Una cuenta de crédito tiene los siguientes atributos y
métodos (tabla 7.2):

Clase: Cuenta CuentaDeCredito


Estructura: disponible disponible
numCuenta numCuenta
limiteCredito
Comportamiento: retirar retirar
depositar depositar
obtenerDisponible obtenerDisponible
comprar
obtenerLimiteCredito
asignarLimiteCredito
obtenerMontoDeuda
Tabla 7.2 Atributos y métodos de la clase CuentaDeCredito.
176 CAPÍTULO 7. HERENCIA DE CLASES

Ejemplo 7.4. Estructura para las cuentas de crédito.


/**
* Clase para trabajar cuentas de crédito
* @author Amparo López Gaona
* @version 3a edición
*/
public class CuentaDeCredito extends Cuenta {
private double limiteCredito;

De esta forma las cuentas de crédito tienen tres atributos: dos heredados (numCta y
disponible) y otro definido en ella.
Ejemplo 7.5. Constructor para cuentas de crédito.
/**
* Constructor de una cuenta de crédito
* @param credito - lı́mite de crédito otorgado
*/
public CuentaDeCredito (double credito) {
super (credito);
limiteCredito = credito;
}

El constructor de cuentas de crédito inicializa la parte correspondiente a Cuenta, mediante el


uso de la instrucción super, con lo cual la cantidad disponible de esta cuenta es la cantidad
recibida. Luego inicializa la única variable particular de las cuentas de crédito. No se valida
el lı́mite de crédito, porque en la clase Cuenta se hace esto y en caso de ser una cantidad
incorrecta no permite la creación de la cuenta. El código del constructor de la clase Cuenta
se muestra en el capı́tulo 8.
Ejemplo 7.6. El método obtenerMontoDeuda permite conocer el importe de la deuda de la
tarjeta de crédito.
/**
* Método para conocer el importe de la deuda
* @return double - importe de la deuda
*/
public double obtenerMontoDeuda() {
return limiteCredito - disponible;
}

Ejemplo 7.7. El método comprar permite realizar una compra mediante la cuenta de crédi-
to, para ello verifica que el monto de la compra sea menor que el disponible en la tarjeta. Si
es el caso, se autoriza la compra y se decrementa el disponible. En caso contrario se rechaza
la compra.
7.5 ESPECIALIZACIÓN MEDIANTE HERENCIA 177

/**
* Método para realizar una compra mediante la cuenta de crédito.
* @param monto - importe de la compra
*/
public void comprar(double monto) {
if (monto > 0.0 && monto < disponible ) {
disponible -= monto;
} else {
System.out.println("No se autoriza la compra por ese monto");
}
}

En este método no se utiliza la clase Error porque no se quiere terminar el programa, sólo
avisar que no es posible realizar la compra.
Ejemplo 7.8. El método retirar permite retirar dinero de una cuenta de crédito siempre
y cuando el monto sea menor que el disponible. Este método es similar al método retirar
de la clase Cuenta, excepto que en las cuentas de crédito se cobra una comisión por retiro de
dinero, por lo que es necesario redefinir este método para las cuentas de crédito, ya que no
es lo mismo retirar de una cuenta normal que de una cuenta de crédito, aunque el usuario
utilice un método con el mismo nombre.

/**
* Método para retirar dinero de una cuenta de crédito
* @param monto - importe del retiro
*/
public void retirar (double monto) {
if (monto >0.0 && monto <= disponible ) {
double comision = monto *0.02;
super.retirar(monto+comision);
}
}

En la redefinición de retirar se llama al método retirar de la clase Cuenta mediante


la instrucción super.retirar(), con lo cual se evita volver a escribir código, es decir, se
reutiliza el código existente.
Con frecuencia se presenta esta situación en la que la nueva clase amplı́a la semántica del
método equivalente definido en la superclase, pero aprovechando la definición existente en
ésta. En este caso, se dice que la clase CuentaDeCredito es más especializada que la clase
Cuenta.
La especialización de clases se obtiene agregando atributos y métodos con los cuales se
especializa la forma en que la subclase ofrece sus servicios con respecto a la superclase. El
178 CAPÍTULO 7. HERENCIA DE CLASES

modificar la forma de trabajar de un método en una subclase sin modificar la firma se conoce
como sobrescritura de métodos.
La sobrescritura difiere de la sobrecarga de métodos estudiada en el capı́tulo 4 debido a
que en la sobrecarga, los métodos están en la misma clase y lo único que tienen en común
en la firma es el nombre del método, pues los parámetros son distintos, en cambio, con la
sobrescritura los métodos están en clases diferentes ligadas por la herencia y la firma es
exactamente la misma.
Cuando se envı́a un mensaje a un objeto para que realice una tarea, la subclase verifica
si tiene un método con esa firma. Si es ası́, ejecuta el método, si no turna el mensaje a la
superclase. Este proceso puede repetirse en la jerarquı́a de herencia hasta llegar a la raı́z,
si el método buscado no se encuentra entonces se tiene un error. Debido a ello se dice que
un método sobrescrito en una subclase oculta el método del ancestro, pues al llamar a un
método primero se busca en la clase que lo llamó y si ahı́ lo encuentra no sigue buscando,
ası́ que se ignora (oculta) el mismo método en las clases superiores. Por ejemplo, en la clase
CuentaDeCredito se tiene el método retirar, que oculta el método con el mismo nombre
de la clase Cuenta.
La habilidad de decidir cuál método aplicar a un objeto en una jerarquı́a de herencia se
denomina polimorfismo. La palabra polimorfismo significa varias formas, esto significa que
se permite usar el mismo nombre para referirse a distintos métodos. Por lo tanto, la misma
llamada puede, en distintos momentos, referirse a métodos diferentes dependiendo de la clase
del objeto que la hace.
Ejemplo 7.9. El método depositar permite realizar pagos a la cuenta de crédito, para
ello es necesario verificar que el pago sea positivo y en ese caso se reduce de la deuda. Si
se paga más de lo que se debe, ese excedente pasa a formar parte del disponible, más una
bonificación igual al excedente.

/**
* Deposita una cantidad de dinero en la cuenta
* @param monto cantidad que se desea depositar
*/
public void depositar(double monto) {
super.depositar(monto);
if (obtenerMontoDeuda() < 0) {
double gratificacion = Math.abs(limiteCredito - disponible);
super.retirar(monto);
super.depositar(monto + gratificacion);
}
}

Ejemplo 7.10. El método obtenerImporteDeuda permite conocer el monto de la deuda.


/*** Método para conocer el importe de la deuda de la tarjeta de crédito
7.6 JERARQUÍA DE CLASES 179

* @return double - importe de la deuda


*/
public double obtenerImporteDeuda() {
return limiteCredito - disponible;
}

El método obtenerDisponible no se programa porque tiene el mismo funcionamiento que


el método del mismo nombre de la clase Cuenta.

7.6 Jerarquı́a de clases


La relación de herencia entre clases forma una estructura jerárquica similar a un árbol (figura
7.2), cada clase se representa como un nodo en el árbol. Una superclase tiene una relación
jerárquica con sus subclases. Una clase mientras más arriba está en la jerarquı́a, menor nivel
de detalle requiere, es decir, se trata de una clase más general.
Cuando una clase se crea con el mecanismo de herencia se convierte ya sea en una superclase
que proporciona elementos a otras clases, o en una subclase que hereda elementos de otras.
La relación de herencia es transitiva, por tanto una superclase puede ser subclase de otra
clase superior y una subclase puede ser superclase de otra clase. Esto implica que además
de las propiedades definidas en una clase, un objeto contendrá todas las propiedades de su
padre, del padre de su padre, etcétera.

Cuenta
Atributos
Métodos

CuentaCon Crédito
Servicios
Atributos Atributos

Métodos Métodos

Figura 7.2 Jerarquı́a de clases con raı́z Cuenta.

Esta jerarquı́a no necesariamente es estática, puede cambiar hacia arriba, hacia abajo o
a los lados debido a nuevos requerimientos o bien el mejoramiento de ellos. Por ejemplo, al
tener que manejar cuentas de crédito tanto nacionales como internacionales, el árbol crece
180 CAPÍTULO 7. HERENCIA DE CLASES

hacia abajo (lado izquierdo de la figura 7.3). Al introducir cuentas de inversión, el cambio
del árbol es a lo ancho (lado derecho de la figura 7.3).

Cuenta Cuenta
Atributos Atributos

Métodos Métodos

CuentaCon Crédito CuentaCon Crédito Inversión


Servicios Servicios
Atributos Atributos Atributos Atributos
Atributos
Métodos Métodos Métodos Métodos
Métodos

Nacional Internacional Nacional Internacional


Atributos Atributos Atributos Atributos

Métodos Métodos Métodos Métodos

a) Crecimiento a lo largo b) Crecimiento a lo ancho

Figura 7.3 Crecimiento de la jerarquı́a de la clase Cuenta.

Las clases se derivan (directa o indirectamente) de sus clases ancestros (padre, abuelo,
etc.). Por ejemplo: en la figura 7.3 la clase Cuenta es padre de la clase Crédito, ésta a su
vez es hermana de CuentaConServicios. La clase Cuenta es abuela de la clase Nacional.
En caso que no se desee permitir que se deriven clases de una clase se debe preceder su
definición de la palabra reservada final, con lo cual se tratan como clases constantes en el
sentido que no se pueden ampliar. Esta situación va en contra de la filosofı́a de programación
orientada a objetos, pues en caso de requerir nuevas funcionalidades se tiene que programar
toda la clase. Si se desea que un método de una clase no se oculte en una jerarquı́a de clases,
su definición debe empezar con la palabra reservada final.
En resumen, la palabra reservada final se utiliza para definir datos constantes, para definir
clases que no se pueden extender y para definir métodos que no se pueden sobrescribir.

7.7 Ventajas de la herencia


El mecanismo de herencia tiene grandes ventajas, muchas de ellas se aprecian al desarrollar
programas grandes y complejos. Entre las ventajas del uso de la herencia destacan:
7.8 COMPATIBILIDAD 181

• Se evita la escritura de código redudante.

• Se reduce el tiempo que toma construir nuevos programas, debido a que el software no
tiene que reinventarse (ni volver a escribirse y depurarse). Una aplicación se construye
tomando clases ya escritas y extendiéndolas.

• Es posible extender clases sin necesidad de tener el código de ellas.

• Se facilita el trabajo de mantenimiento debido a que cualquier cambio en un atributo


o método compartido sólo se hace en un lugar, evitando posibles inconsistencias.

• Las clases creadas utilizando herencia son pequeñas debido a que sólo contienen las
diferencias con respecto a sus superclases.

7.8 Compatibilidad
Un objeto de una clase puede usarse en cualquier lugar en que se usarı́a un objeto de su
superclase; esto se debe a que el objeto también pertenece a su superclase, posiblemente con
más atributos y métodos. De esta manera, al definir una referencia a un objeto de cierta clase,
se está en posibilidad de almacenar también una referencia a algún objeto de cualquiera de
sus subclases, pero no es posible almacenar una referencia a un objeto de su superclase.
Ejemplo 7.11. Si se tienen las cuentas bancarias definidas en las secciones anteriores, se
puede tener un método que tenga las siguientes instrucciones:

Cuenta cuenta;
CuentaConServicios ctaServicios = new CuentaConServicios();
CuentaDeCredito ctaCredito = new CuentaDeCredito();
...
cuenta = ctaCredito; //Correcto
ctaCredito = cuenta; //Incorrecto

Siempre es posible asignar un objeto especializado a uno general. Para hacer la asignación
contraria, es decir asignar un objeto general a uno especializado se requiere hacer una con-
versión explı́cita. Por ejemplo, todas las cuentas de crédito son cuentas bancarias por eso
es válido hacer cuenta = ctaCredito. Sin embargo, no todas las cuentas bancarias son de
crédito por eso no es correcta la asignación ctaCredito = cuenta, en particular cuenta
podrı́a ser una CuentaConServicios. Si se insiste en hacer esta última asignación, se de-
be usar una conversión explı́cita del tipo como sigue: ctaCredito = (CuentaDeCredito)
cuenta; para evitar el error de sintaxis, sin embargo, se debe entender que sólo se podrán
usar los métodos de la superclase, es decir, es tratada como una cuenta normal, no como una
cuenta de crédito.
182 CAPÍTULO 7. HERENCIA DE CLASES

Antes de asignar una referencia a un objeto es conveniente utilizar el operador instanceof,


el cual devuelve la clase a la que pertenece el objeto.
Ejemplo 7.12. Clase para probar el uso de las diferentes cuentas bancarias. La estructura
de esta clase contiene las mismas declaraciones que el ejemplo 7.3, pero en lugar de definir
una referencia a objetos de CuentaConServicios la define a objetos de la clase Cuenta.

/**
* Clase que permite utilizar objetos de una jerarquı́a de cuentas bancarias
* @author Amparo López Gaona
* @version 3a edición
*/
public class PruebaCuentas {
static Scanner in = new Scanner(System.in);
static Cuenta cuenta = null;

Ejemplo 7.13. Método menu con las diferentes opciones para trabajar con los distintos tipos
de cuentas bancarias.

public static void menu() {


System.out.println("1. Crear una cuenta");
System.out.println("2. Retirar capital");
System.out.println("3. Depositar capital");
System.out.println("4. Consultar disponible");
System.out.println("5. Pago de servicios");
System.out.println("6. Comprar a crédito");
System.out.println("0. Terminar");
}

Ejemplo 7.14. Método realizarAccion, aquı́ se programan las acciones para cada una de
las opciones del menú.

/**
* Método para ejecutar las acciones adecuadas, según la opción elegida
* @param opcion -- opción elegida por el usuario
*/
public static void realizarAccion(int opcion) {
double capital;
switch(opcion) {

Ejemplo 7.15. Creación de una cuenta. Se pregunta al usuario qué tipo de cuenta desea
crear y de acuerdo con esto leer y validar el capital inicial o bien asignar un lı́mite de crédito.
Independientemente del tipo de cuenta creada, todas se asignan a la referencia cuenta.
7.8 COMPATIBILIDAD 183

case 1: // Creación
System.out.println("Especifica el tipo de cuenta que quieres crear");
System.out.println("1. Débito");
System.out.println("2. Crédito");
System.out.println("3. Con pago a servicios");
int eleccion = in.nextInt();
switch(eleccion) {
case 1: // Cuenta de Débito
do {
System.out.println("Introduce tu depósito inicial");
capital = in.nextDouble();
} while (capital < 2500);
cuenta=new Cuenta(capital);
break;
case 3: // Cuenta con pago de servicios
do {
System.out.println("Introduce tu depósito inicial");
capital = in.nextDouble();
} while (capital < 2500);
cuenta=new CuentaConServicios(capital);
break;
case 2:
System.out.println("Tu lı́mite de crédito es de $5,000.00");
cuenta = new CuentaDeCredito(5000);
break;
default: System.out.println("No existe ese tipo de cuentas");
}
break;

Ejemplo 7.16. Opciones válidas para todas las cuentas. Las opciones de retiro, depósito y
disponible son válidas para cualquier tipo de cuenta, ası́ que el código queda como antes.
Notar que no es necesario preguntar de qué tipo de cuenta se trata, directamente se realiza
el método.

case 2: //Retiro
System.out.println("Indica la cantidad que deseas retirar");
capital=in.nextDouble();
cuenta.retirar(capital);
break;
case 3: //Depósito
System.out.println("Indica la cantidad que deseas depositar");
capital=in.nextDouble();
cuenta.depositar(capital);
184 CAPÍTULO 7. HERENCIA DE CLASES

break;
case 4: // Disponible
cuenta.obtenerDisponible();
System.out.println("Tu disponible es de:$" + cuenta.obtenerDisponible());
break;

Ejemplo 7.17. Pago de teléfono. Esta opción es válida sólo para cuentas con pago de
servicios, por lo tanto, primero se verifica que se trata de una cuenta de esa clase mediante
el operador instanceof. Luego se realiza el pago creando temporalmente una referencia a
una cuenta con servicios para poder llamar al método apropiado.
case 5: // Pago de teléfono
if (cuenta instanceof CuentaConServicios) {
CuentaConServicios cs = (CuentaConServicios)cuenta;
capital = cantidadValida("Indica la cantidad a pagar a tu cuenta telefónica");
cs.pagarTelefono(capital);
cuenta = cs;
} else System.out.println("Tu cuenta no tiene habilitado este servicio");
break;

Ejemplo 7.18. Compra con tarjeta de crédito. Primero se verifica que se trata de una tarjeta
de crédito y luego se hace la compra.
case 6: // Compra con tarjeta de crédito
if (cuenta instanceof CuentaDeCredito) {
CuentaDeCredito cc = (CuentaDeCredito) cuenta;
capital =cantidadValida("Cuánto vas a comprar");
cuenta = cc;
} else System.out.println("Tu cuenta no tiene habilitado este servicio");

En el programa anterior, en una variable de la clase Cuenta se almacenaron referencias


a objetos de cualquiera de sus subclases. Serı́a más notoria la utilidad de esta propiedad si
se tuvieran que manejar todas las cuentas de cierta institución y, por ejemplo, obtener su
disponible; para eso se define un arreglo de referencias a Cuenta y en cada localidad del
mismo se coloca la referencia a una cuenta que puede ser de cualquier clase.
Ejemplo 7.19. Arreglo de cuentas bancarias de diferente clase.

...
Cuenta [] cuentas;

cuenta[0] = new Cuenta();


cuenta[1] = new CuentaConServicios();
cuenta[2] = new CuentaDeCredito();
...
7.8 COMPATIBILIDAD 185

Se tiene una variable que puede almacenar objetos de diferentes clases, todas ellas relacio-
nadas por la herencia.
Ejemplo 7.20. Cálculo de la suma del disponible de cada cuenta almacenada en el arreglo
del ejemplo 7.19.
/**
* Método para obtener el total de disponible acumulado en las cuentas
*/
public void sumarDisponibles() {
double suma = 0;
for (int i = 0; i < cuenta.length; i++) {
suma += cuenta[i].obtenerDisponible();
}
}

Se puede apreciar que en una sola instrucción for se llama al método para calcular el
disponible de una cuenta sin preguntar la clase a la que pertenece, pues todas las cuentas
tienen el método obtenerDisponible independientemente de la clase de Cuenta de que se
trata.
Ejemplo 7.21. Suponer que por una circunstancia extraña y favorable a los clientes que
tienen cuenta en el arreglo del ejemplo 7.19, el banco les va a hacer un depósito de $100.00.
Este problema se resolverı́a con un método como el siguiente:
/**
* Método para depositar un bono de cien pesos a cada cuenta
*/
public void regaloNavidad () {
for (int i = 0; i < cuenta.length; i++) {
cuenta[i].depositar(100.00);
}
}

En este caso, se aplica el concepto de polimorfismo definido antes. Debido a que hasta el
momento de ejecución se determina cuál método usar para hacer el depósito, dependiendo de
la clase del objeto almacenado en la localidad cuenta[i]. Recordar que el método depositar
de las cuentas de crédito es diferente del método depositar de las otras cuentas bancarias.
Si no se tuviera polimorfismo se tendrı́a que escribir una serie de proposiciones if (o bien
una proposición switch) para determinar qué método llamar dependiendo de la clase a la
que pertenece cada objeto. El mantenimiento de ese programa se volverı́a tortuoso, sobre
todo si se recuerda que una jerarquı́a de clases no siempre es estática, ası́ que se tendrı́a
que cambiar la cantidad de instrucciones if requeridas cada vez que la jerarquı́a de clases
cambiara. Por su parte, con el uso del polimorfismo no es necesario cambiar este código al
modificar la jerarquı́a con lo cual se tiene un programa compacto y seguro.
186 CAPÍTULO 7. HERENCIA DE CLASES

7.9 Generalización mediante herencia


La herencia se ha presentado como especialización de alguna clase existente para crear nue-
vas clases agregando los atributos o métodos necesarios para lograr la especialización; esta
forma de desarrollar programas se utiliza bastante, pues es natural agregar más funciones
a programas que trabajan correctamente. Cuando durante el diseño de la solución de un
problema se obtienen diversas clases relacionadas y no existen clases que extender se puede
crear una jerarquı́a de herencia tomando clases que tienen elementos en común, “factorizan-
do” éstos en una superclase y creando cada subclase con las diferencias de ésta con respecto
a la superclase. Para mostrar esta forma de desarrollar programas se trabaja con el siguiente
problema.
Ejemplo 7.22. Una compañı́a proveedora de insumos para cafeterı́as requiere generar electró-
nicamente las facturas que emite para sus clientes. Los artı́culos que vende son cafés, cafeteras
y desechables. Venden cafés de diferentes tipos y lugares de origen, además se venden por cos-
tal de 50kg antes de que caduquen. Las cafeteras se venden por pieza y las hay con diferente
capacidad. Los desechables se venden por paquete de diferente tamaño: grande, mediano y
pequeño.
Tomando la descripción anterior y aplicando la metodologı́a de diseño presentada en el
capı́tulo 1 se tiene:
1. Encontrar los objetos principales.
En este caso son: factura, cliente, café, cafetera, desechable.
Se descartaron: compañı́a proveedora, cafeterı́as y clientes. LA primera es quién solicita
el programa; cafeterı́a y clientes son sinónimos y no se requiere que se programen.
2. Determinar el comportamiento de los objetos.
Para ello, se determina su estructura a partir de la descripción del problema.

Café: Cafetera: Desechable:


nombre nombre nombre
descripción descripción descripción
precio por costal precio unitario precio paquete
lugar de origen capacidad tamaño del paquete
fecha de caducidad

Cliente: Factura:
nombre nombre cliente
dirección dirección cliente
tarjeta de crédito lı́neas
total de compra
7.9 GENERALIZACIÓN MEDIANTE HERENCIA 187

Las lı́neas incluidas en la factura contienen: cantidad, descripción, precio unitario y


precio total de cada producto vendido.
Los métodos que se requieren en cada clase son los necesarios para asignar/leer valor
de cada atributo y el método toString.

3. Definir escenarios.
Escenario: generación de facturas.

(a) El programa solicita los datos del cliente.


(b) El programa recibe los datos del cliente.
(c) El programa recibe una lista con la cantidad de cada producto vendido al cliente.
(d) El programa imprime cada elemento en la lista y calcula el precio total de cada
uno.
(e) El programa calcula el total de la factura, calcula los impuestos e imprime esta
información.

Las clases principales para almacenar la información que se requiere para la factura son
una clase para café, una para cafeteras y una para desechables. Como se puede observar en la
descripción de estas clases, tienen elementos en común, por lo que se va a construir el árbol
de herencia de las hojas hacia la raı́z. Para ello se extrae la información común de cada clase
y se crea la clase Producto con los atributos: nombre, descripción y precio unitario, que son
los atributos comunes a las tres clases. Luego una clase Cafe con la fecha de caducidad y
lugar de origen del mismo. Una clase Cafetera con la capacidad de la misma. Finalmente
una clase Desechables con el tamaño del paquete de los desechables.
La jerarquı́a de clases para este problema se muestra en la figura 7.4. Al ver el árbol no es
posible determinar si éste fue creado de las hojas a la raı́z o al revés.
Cada clase tiene, además del constructor con todos los datos, métodos para obtener el
valor de cada atributo, métodos para asignar valor a cada atributo y el método toString.
Estos métodos son muy sencillos de programar, por lo que se dejan para que el lector los
programe como ejercicio; en esta sección sólo se incluye la estructura que tendrán los objetos
de cada clase, el constructor y el método toString.
Ejemplo 7.23. Clase Producto
/**
* Clase raı́z de la jerarquı́a de clases para los productos para cafeterı́as.
* @author Amparo López Gaona
* @version 3a edición
*/
public class Producto {
private String nombre;
188 CAPÍTULO 7. HERENCIA DE CLASES

Producto
nombre
descripcion
precio
Métodos

Desechable Cafetera Cafe


tamañoPaq capacidad caducidad
modelo origen
Métodos Métodos Métodos

Figura 7.4 Jerarquı́a de clases de artı́culos para cafeterı́as.

private String descripcion;


private int precioU;
/** Constructor del producto
* @param nombre - nombre del nombre del producto
* @param desc - descripcion de la obra
* @param precio - precio unitario del producto
*/
public Producto(String nombre, String desc, int precio){
this.nombre = nombre;
descripcion = desc;
precioU = precio;
}
... // Aquı́ los métodos para obtener y asignar valor a cada atributo
/**
* Método para obtener una cadena con los datos del producto
* @return String - cadena con los datos del producto
*/
public String toString() {
return nombre + "\t"+ descripcion + "\t" + precioU;
}
}

Ejemplo 7.24. Clase Cafetera que es una subclase de Producto.

/**
* Clase para registrar los datos de una cafetera
* @author Amparo López Gaona
7.9 GENERALIZACIÓN MEDIANTE HERENCIA 189

* @version 3a edición
* @see Producto
*/
public class Cafetera extends Producto {
private int capacidad;
private String modelo;

/** Constructor de una cafetera


* @param nombre- nombre de la cafetera
* @param descripcion- descripción de la cafetera
* @param precio - precio de publicación de la cafetera
* @param litros - capacidad de la cafetera
* @param mod - modelo de la cafetera
*/
public Cafetera(String nombre, String descripcion, int precio, int litros,
String mod){
super(nombre, descripcion, precio);
capacidad = litros;
modelo = mod;
}

// Aquı́ los métodos para obtener y asignar valor a cada atributo

/**
* Método para obtener una cadena con los datos de la cafetera
* @return String - cadena con los datos de la cafetera
*/
public String toString() {
return super.toString() + "\t modelo"+ modelo + " con capacidad de " +
capacidad +"litros." ;
}
}
Ejemplo 7.25. Clase Cafe subclase de Producto.
/**
* Clase para registrar los datos del café
* @author Amparo López Gaona
* @version 3a edición
* @see Producto
*/
public class Cafe extends Producto {
private String origen;
private java.util.Date caducidad;
190 CAPÍTULO 7. HERENCIA DE CLASES

/** Constructor de un café


* @param nombre- nombre del café
* @param descripcion- descripcion del café
* @param precio - precio del costal de café
* @param originario - lugar de origen del café
* @param vence - fecha de caducidad del café
*/
public Cafe(String nombre, String descripcion, int precio, String originario,
java.util.Date vence){
super(nombre, descripcion, precio);
origen = originario;
caducidad = vence;
}
... // Aquı́ los métodos para obtener y asignar valor a cada atributo
/**
* Método para obtener una cadena con los datos del café
* @return String - cadena con los datos del café
*/
public String toString() {
return super.toString() + "\t origen"+ origen ;
}
}

En el constructor de cada subclase de Producto se llama al constructor de la superclase


con los parámetros apropiados y se da valor inicial a los atributos particulares de cada clase.
Además, se redefine el método toString en cada subclase, al llamar al de la superclase con
la instrucción super.toString() y agregar la información particular de cada subclase.
Ejemplo 7.26. Clase Factura.

/**
* Clase para generar facturas
* Objetivo Probar la jerarquı́a de clases de productos para cafeterı́as
* @author Amparo López Gaona
* @version 3a edición
*/
public class Factura {
private Cliente cliente;
private Linea[] lineas;
/**
* Constructor que recibe los datos del cliente y un arreglo con la
* información de los productos vendidos al cliente.
*/
7.9 GENERALIZACIÓN MEDIANTE HERENCIA 191

public Factura(Cliente cl, Linea[] renglones) {


cliente = cl;
lineas = renglones ;
}
/**
* Método para imprimir la factura
*/
public void imprimirFactura() {
long granTotal = 0;

// Aquı́ los encabezados, incluyendo la información del cliente

for (int i = 0; i < lineas.length; i++) {


System.out.println(lineas[i]);
granTotal +=
lineas[i].obtenerCantidad()*
lineas[i].obtenerProducto().obtenerPrecioUnitario();
}
System.out.println("\n\t\t\t Total "+granTotal);
}
}

Ejemplo 7.27. Clase Lı́nea que tiene la información de cada producto que será incluida en
la factura.

/**
* Clase con la información de los productos que se incluirán en la factura
* @author Amparo López Gaona
* @version 3a edición
*/
public class Linea {
int cantidad;
Producto producto;

/**
* Constructor de una lı́nea
* @param cuantos -- cantidad del mismo producto
* @param prd -- producto
*/
public Linea(int cuantos, Producto prd) {
cantidad = cuantos;
producto = prd;
}
192 CAPÍTULO 7. HERENCIA DE CLASES

... // Aquı́ los métodos obtener y asignar


/**
* Método para mostrar la información de una lı́nea en una cadena
* @return String -- cadena con la información a facturar
*/
public String toString() {
int precio = producto.obtenerPrecioUnitario();
return cantidad +"\t"+ producto+"\t"+ precio +"\t"+ precio*cantidad;
}
}

En este ejemplo, se pueden apreciar distintas formas de relación entre clase, pues se tienen
clases que se relacionan por herencia, como los productos y sus diferentes subclases; y otras
por contención, como la factura que contiene lı́neas.

7.10 La clase Object


En Java se tiene definida una clase llamada Object que es superclase de toda clase definida
por los programadores. Esta clase se encuentra definida en el paquete java.lang y en ella
se define la estructura y comportamiento mı́nimos de todos los objetos. Por ejemplo, todo
objeto se puede comparar con otro, convertir a cadena, esperar a una condición, notificar
a otros objetos que tal condición cambió, determinar su clase, etc. Son alrededor de 10
métodos, algunos pueden sobrescribirse en las clases hijas y otros no; entre los primeros se
encuentran:

• Método equals para determinar la igualdad de dos objetos. En el capı́tulo 4, se ex-


plicó que si se comparan dos “objetos” o1 == o2, en realidad se está comparando la
referencia a ellos y aunque tengan el mismo estado no son el mismo objeto. Para poder
decir que dos objetos son iguales si su estado es el mismo es necesario sobrescribir este
método, cuya firma es: public boolean equals(Object o).
Notar que el método equals recibe un parámetro de tipo Object. Si se define el
método equals con un parámetro de tipo diferente se está sobrecargando el método
no sobrescribiéndolo.
Como se explicó en la sección anterior, si un método espera como parámetro un objeto
de una clase particular puede recibir uno de cualquiera de sus subclases. Como toda
clase es hija de la clase Object, se puede llamar a este método con objetos de cualquier
clase.
Ejemplo 7.28. En el capı́tulo 4 en la clase Punto se sobrescribió el método equals
para determinar que dos puntos son iguales si representan la misma coordenada. Ahora
se presenta una implementación robusta del mismo.
7.10 LA CLASE OBJECT 193

/**
* Determina si dos puntos son iguales
* @param o -- Punto contra el cual se va a comparar
* @return boolean -- true si son iguales y false en otro caso
*/
public boolean equals (Object o) {
if (o == this) return true;
if (o == null) return false;
if (o instanceof Punto) {
Punto p = (Punto) o;
return obtenerX() == p.obtenerX() && obtenerY() == p.obtenerY();
}
return false;
}

Después de verificar que el objeto recibido no es el mismo ni nulo, se verifica que sea
de la clase Punto mediante el uso del operador instanceof. Si es el caso, se continúa
con la comparación, y en caso contrario, se determina que no son iguales. Si no se hace
esta comparación y se llama al método con un objeto que no sea de la clase esperada
se genera un error en la ejecución del programa.

• Método toString para convertir a cadena la representación de un objeto. Este método


se ha usado en diferentes clases a lo largo del libro, en particular en la clase Punto.
La firma de este método es: public String toString()
Este método no necesariamente se llama explı́citamente, en general se utiliza implı́ci-
tamente; al escribir objetos con solo escribir la referencia lo mostrará en la forma
programada. Por ejemplo:

Punto origen = new Punto (25,27);


...
System.out.println("El origen está en "+origen);

Esta instrucción de escritura mostrará El origen está en (25,27) si en la progra-


mación del método toString está especificado ası́.
Si no se sobrescribe este método, se utiliza el de la clase Object que muestra el con-
tenido de la referencia, es decir, una dirección en memoria que en general no significa
nada para el programador.

Ahora se puede comprender la razón de las imposiciones del capı́tulo 4 con estos dos
métodos.
194 CAPÍTULO 7. HERENCIA DE CLASES

7.11 Ejercicios
1. ¿Cómo se especifica la herencia en Java?

2. ¿Todas las clases en Java tienen una superclase? ¿Cuál? ¿Y una subclase? ¿Cuál?

3. Explicar qué significa que un atributo tenga visibilidad protected.

4. ¿Cuál es la diferencia entre redefinir y sobrecargar un método?

5. Explicar cómo se crea el estado inicial de la parte heredada de un objeto.

6. Explicar la diferencia entre herencia por generalización y por especialización.

7. Explicar la razón de la necesidad de poner un operador de conversión explı́cita cuando


se asigna a una referencia a una clase la referencia de alguna de sus superclases.
8. ¿Qué imprime el siguiente código?:

public class AnimalDeGranja {


String nombre;

public void ponerNombre(String n) { nombre = n; }


public void decir() { System.out.println(nombre + " hace "); }
}
public class Vaca extends AnimalDeGranja {
public void decir() {
System.out.println(nombre + " hace Muuuuuu...");
}
}
public class Caballo extends AnimalDeGranja {
public void ponerNombre(String n) { nombre = n + " [es un caballo]"; }
}
...
Vaca v = new Vaca();
Caballo c = new Caballo ();
v.ponerNombre("Clarabella");
c.ponerNombre("Mr. Ed");
v.decir();
c.decir();
...

9. En la siguiente jerarquı́a de clases se desea que en el método mostrarD se puedan


imprimir los valores de las variables x y y. Explicar dos posibles formas de lograrlo.
7.11 EJERCICIOS 195

public class Base {


private int x, y;
public Base(int a) { x = a; y = 1000;}
public Base(int a, int b) { x = a; y = b;};
public void mostrarB() {System.out.println(x+ " " + y);}
}

public class Derivada extends Base{


private int z;
public Derivada (int n) { super(n); z = n*10;}
public void mostrarD() {System.out.println(z);}
}

public class PruebaConstructoresYHerencia{


public static void main(String x[]) {
Base unB = new Base(1,2);
Derivada unD = new Derivada(4);
unB.mostrarB();
unD.mostrarD();
}
}

10. Se tiene una clase Base con un arreglo de enteros y un constructor:

public class Base {


private int [] valores;
public Base (int [] v) {
valores = v;
}
}

Definir una subclase Derivada de Base que tenga una constante y un constructor.

11. Hacer un programa que permita trabajar con figuras geométricas como rectángulos y
cuadrados. Se sugiere primero escribir la clase para los rectángulos con largo y ancho
como estructura. Luego escribir una clase para manejo de los cuadrados como subclase
de los rectángulos. Para cada clase escribir métodos para encontrar la altura, el ancho,
el perı́metro y el área.

12. Hacer un programa que permita conocer y actualizar datos de los alumnos de cierta
universidad. Los datos de los alumnos deben incluir al menos: nombre, fecha de primera
inscripción, clave del alumno. Crear un constructor con los valores para cada dato,
196 CAPÍTULO 7. HERENCIA DE CLASES

métodos para acceder a cada dato, un método toString para crear una cadena con los
datos del alumno y un método equals para determinar que dos alumnos son iguales
siempre y cuando tengan la misma clave. Para empezar se puede utilizar la clase Alumno
del capı́tulo 6.
Los alumnos pueden ser becarios que reciben cierta cantidad monetaria mensual de
acuerdo con el porcentaje de avance en su licenciatura. El alumno debe tener un pro-
medio mı́nimo de 8 para poder ser becado. Hay alumnos que son los prestadores de
servicio social, éstos tienen cursado al menos 75 % de su licenciatura; en este caso se
desea conservar la fecha de inicio del servicio social, la actividad que realizan y el lugar
en donde lo hacen. Por otro lado están los tesistas, para ellos se requiere tener el tı́tulo
del trabajo, el tema a desarrollar, el nombre del director y el grado de avance de la
tesis. En cada clase incluir un método toString para generar una cadena con todos
los datos del alumno.
Finalmente, crear un programa que tenga 20 alumnos y permita actualizar y conocer
los datos de cada alumno. La única restricción es que no se puede dar de alta a alumnos
que tengan la misma clave de otro alumno que ya está dado de alta; para esto usar el
método equals.

13. Escribir un programa para imprimir actas en un registro civil. Las actas pueden ser:
actas de nacimiento (fecha, nombre, ciudad), actas de matrimonio (fecha, nombre1,
nombre2, ciudad), cartilla de vacunación (fecha, nombre, arreglo con las vacunas).

14. Un joven desea tener un programa que le permita llevar control acerca de los libros,
discos (CD) y pelı́culas que posee. La información que le interesa de cada uno de sus
libros es: tı́tulo, autor, editorial y género; de los CD: tı́tulo, intérprete y género, y de las
pelı́culas su tı́tulo, actor principal, actriz principal, género y año de filmación. Escribir
el programa para guardar la información de al menos 20 artı́culos.

15. Un grupo de amigos formaron un club de cine con las pelı́culas que todos tienen, y les
interesa llevar el control de ellas. La información que les interesa de cada pelı́cula es su
tı́tulo, director, actor principal, actriz principal y género. De las pelı́culas dobladas al
español les interesa tener el nombre de la persona que da la voz al actor principal y a
la actriz principal. De las pelı́culas de Hitchock les interesa tener una descripción con
la escena en donde aparece él. De las pelı́culas premiadas, interesa conocer el nombre
del premio, la categorı́a y el año en que fueron premiadas. Escribir un programa para
llevar el control de las pelı́culas.

16. Escribir un programa para que el usuario pueda tener un directorio telefónico. Los datos
que debe contener son los datos de amigos (nombre, apodo, teléfono celular), familiares
(parentesco, nombre, teléfono), clientes (nombre, compañı́a, teléfono, extensión). Las
operaciones permitidas para el directorio son:
7.11 EJERCICIOS 197

• Crear un directorio.
• Insertar datos de una nueva persona.
• Mostrar la información de una persona, dado su nombre.
• Eliminar los datos de una persona, a partir del nombre.
• Actualizar la información de alguien, a partir del nombre.

17. Una tienda de ventas por Internet desea tener información acerca de los artı́culos que
vende ası́ como generar facturas de los artı́culos comprados por sus clientes. Antes de
efectuar una compra, los clientes se dan de alta proporcionando sus datos personales.
Los clientes depositan los artı́culos deseados en un carrito de compra y si hay en
existencia se le envı́an. La tienda vende artı́culos perecederos, artı́culos importados y
artı́culos por peso. Además de las facturas, el almacén requiere reportes de productos
por paı́s, reportes de productos a punto de caducar, etc.
Capı́tulo 8

La clase Exception

A lo largo de este libro se ha resaltado la importancia de escribir programas robustos, es decir,


programas que incluyan los errores que pueden ocurrir debido, principalmente, a intentar
trabajar con algún dato que esté fuera del rango permitido. En este capı́tulo se muestra la
forma de crear programas robustos utilizando el mecanismo de excepciones, para forzar al
usuario a enfrentarse a los errores. Se describe qué sucede después de que ocurre un error,
cómo manejarlo, dónde hacerlo y cómo puede el programa recuperarse de tal error.

8.1 Excepciones
Como se explicó en el capı́tulo 1 existen varios tipos de errores de lógica. Por ejemplo, se
puede tener la implementación errónea de un método, lo que genera un resultado distinto
del esperado, por ejemplo, al escribir la fórmula para calcular un promedio puede omitirse
hacer la división o no hacerla entre el número correcto. Estos errores no pueden detectarse al
momento de compilar el programa, se detectan al ver el resultado de la ejecución del mismo
y para localizarlos y corregirlos se describieron algunas técnicas.
Existen errores de lógica que están contemplados en Java, tales como acceso a un arreglo
con ı́ndice fuera de rango, intento de división entre cero, intento de abrir un archivo que no
existe, etcétera. En este caso, al ejecutar el programa Java envı́a un mensaje indicando el
tipo de error, el método y lı́nea en que ocurrió.
Ejemplo 8.1. Programa que utiliza la clase Punto desarrollada en el capı́tulo 4 para probar
el cálculo de la distancia entre dos puntos.

public class Prueba {


public static void main(String[] pps) {
Punto origen = null;

double dist = origen.distancia(new Punto(3,5));

199
200 CAPÍTULO 8. LA CLASE EXCEPTION

System.out.println("La distancia del origen al (3,5) es "+dist);


}
}

Al ejecutar el programa se obtiene el siguiente resultado.

Exception in thread "main" java.lang.NullPointerException


at Prueba.main(Prueba.java:5)

El mensaje de la primera lı́nea indica que el programa terminó al dispararse la excepción


java.lang.NullPointerException. En la segunda, que ocurrió en la lı́nea 5 del método
main de la clase Prueba. Esto puede ayudar a descubrir que no se creó el punto origen antes
de ser usado.
Otros errores son los que se pueden prever desde el momento de programar métodos, por
ejemplo, el tratar de manejar valores fuera del rango lógico de la aplicación como manejar
números negativos para representar edades, calificaciones, depósitos bancarios, etc. Otro
ejemplo se tiene al hacer búsquedas y no encontrar un dato que se supone que existe y tratar
de
presenta una forma de tratar estos errores mediante el uso de excepciones.
Una excepción es un evento que ocurre en cualquier momento de ejecución de un programa
y que modifica el flujo normal de éste. Las excepciones son objetos de la clase Exception.
que almacenan información que se regresa en caso de que ocurra una anormalidad. Todos
los métodos que hayan llamado al método en que se produce la excepción pueden darse por
enterados y alguno de ellos o todos, tomar las medidas apropiadas. Si todos los métodos
sólo se dan por enterados el programa termina abruptamente en el lugar en que ocurrió el
problema.

8.2 Jerarquı́a de excepciones


La clase Exception, que se encuentra en el paquete java.lang, es la raı́z de una jerarquı́a de
clases para los errores más comunes. Al igual que todas las clases, Exception es descendiente
de la clase Object.
En la figura 8.1 se muestra una fracción la jerarquı́a de excepciones. Aunque esta vez se
presenta el árbol creciendo a la derecha no hacia abajo y entre un rectángulo con lı́neas
discontinuas se especifica el paquete en el que están definidas.
En particular se tiene la clase RuntimeException de la que se derivan varias clases de uso
frecuente. Por ejemplo, durante la ejecución de algún programa es posible que se efectúe una
división entre cero, y por lo tanto se haya disparado la excepción ArithmeticException,
que es subclase de RuntimeException.
8.3 ESTADOS DE UNA EXCEPCIÓN 201

java.util
InputMismatchException

NoSuchElementException

java.lang ArithmeticException
RuntimeException
ArrayStoreException
ClassNotFoundException ClassCastException
Object

IllegalAccessException IllegalArgumentException
Throwable IndexOutOfBoundsException
NoSuchMethodException NegativeArraySizeException
Exception
NoSuchFieldException NullPointerException

java.io
IOEXception EOFException

FileNotFoundException

NotSerializableException

Figura 8.1 Jerarquı́a de clases a partir de Exception.

8.3 Estados de una excepción


Una excepción se activa para indicar que ocurrió una falla durante la ejecución de un método.
La excepción se propaga hasta encontrar un método en el cual se indica qué se debe hacer
en circunstancias anómalas. Este comportamiento se describe en términos técnicos definidos
como estados por los que pasan las excepciones. Estos estados son:

• Disparo. Al ocurrir un error se activa una excepción creando un objeto de la clase


Exception con información como: tipo de excepción y estado del programa.

• Atrapado. Una vez que un método dispara una excepción, el sistema de ejecución busca
las instrucciones que especifican qué hacer para esa excepción. Por ejemplo, imprimir
un mensaje de error y no hacer nada más, terminar el programa (con o sin mensaje de
202 CAPÍTULO 8. LA CLASE EXCEPTION

error), realizar la tarea de cualquier forma, pedir la corrección de manera interactiva,


etcétera.

• Terminación. Si no se encuentra un método que maneje la excepción, termina la ejecu-


ción del programa. En caso contrario, se dice que el manejador de excepciones atrapó la
excepción y el programa se recupera de la excepción, pues la ejecución de éste continúa
en forma normal.

8.3.1 Disparo de excepciones


Disparar una excepción es la forma más efectiva para indicar que no es posible atender la
petición del objeto que envı́a el mensaje. Una de las principales ventajas de esta forma de
tratar con errores o casos excepcionales es que resulta imposible que el usuario ignore que
ocurrió una excepción.
Para disparar una excepción se utiliza la instrucción throw con un objeto de la clase
Exception o de cualquiera de sus subclases. La ejecución de esta instrucción significa que
ocurrió un error en el método y por lo tanto no puede continuar su ejecución.
Si en un método se puede disparar una excepción y ésta no es de la clase RuntimeException
ni de sus descendientes, entonces al final de la firma del método se debe incluir la cláusula
throws seguida del nombre de la excepción que se puede disparar. Esta cláusula tiene el
propósito de advertir a los usuarios del método para que tengan cuidado y tomen las acciones
necesarias en caso de que se dispare la excepción.
Ejemplo 8.2. Método depositar, de la clase Cuenta, con disparo de excepciones.

/**
* Método para depositar dinero a una cuenta bancaria
* @param monto -- cantidad de dinero que será depositada
* @throws Exception -- si la cantidad a depositar es incorrecta
*/
public void depositar(double monto) throws Exception {
if (monto < 0) {
throw new Exception("Deposito incorrecto");
}
saldo += monto;
}

En el comentario del método aparece una nueva etiqueta para la documentación con ja-
vadoc. La etiqueta @throws sirve para especificar cuál excepción podrı́a dispararse y en
qué caso. No debe confundirse este comentario con la instrucción throws del encabezado.
En el método depositar, mediante la instrucción throw se crea un objeto de la clase
Exception, y luego se dispara la excepción. La creación de un objeto para tratar la excepción
siempre lleva una cadena de caracteres que se usa como mensaje de diagnóstico.
8.3 ESTADOS DE UNA EXCEPCIÓN 203

Al disparar una excepción termina la ejecución del método que la disparó, es decir, es
tratada como una instrucción return, esto es debido a que ocurrió algo anormal y no se
puede terminar la ejecución normal del método. No hay garantı́a que donde se llamó a
ejecutar el método se tomen las acciones adecuadas para recuperarse del error, pero sı́ que
este método no continuará su ejecución.
En el método depositar la condición de excepción se colocó antes que el flujo correcto para
ilustrar este punto y debido a que la instrucción throw actúa como instrucción de retorno, no
es necesario incluir la cláusula else en la instrucción if. Si el monto es negativo se dispara
la excepción y termina la ejecución del método y del programa. Si el monto es menor o igual
a cero se incrementa el saldo.
El mecanismo de excepción es independiente del valor regresado por un método; esto
permite que sea usado por todos los métodos sin importar si devuelven o no valor ni el tipo
de valor que devuelvan.
Ejemplo 8.3. Método retirar, de la clase Cuenta, con disparo de excepciones.

/**
* Método para retirar dinero de una cuenta bancaria
* @param monto -- cantidad de dinero que será retirada
* @return double -- cantidad de dinero retirada
* @throws Exception si la cantidad es negativa o mayor que el saldo
*/
public double retirar(double monto) throws Exception {
if (monto <= 0) {
throw new Exception("La cantidad a retirar debe ser positiva");
}
if (saldo < monto) {
saldo -= 500;
throw new Exception("Fondos insuficientes para realizar el retiro");
}
saldo -= monto;
return monto;
}

En las instrucciones condicionales if que validan el parámetro recibido por el método


retirar no es necesario especificar el valor de retorno debido a que se incluye la instrucción
throw, que es equivalente a la instrucción return, aunque no es necesario devolver ningún
valor; esto es una ventaja porque no se tienen que inventar valores para códigos erróneos.
Un uso importante de las excepciones es evitar la creación de objetos con un estado inicial
inválido cuando el constructor recibe parámetros con valores no permitidos
Ejemplo 8.4. Constructor que dispara una excepción.

/** Constructor que crea una cuenta con saldo mı́nimo de $2500
204 CAPÍTULO 8. LA CLASE EXCEPTION

* @param saldoInicial -- monto con el que se creará la cuenta


* @throws IllegalArgumentException -- si el saldo es menor a 2500
*/
public Cuenta(double saldoInicial) {
if (saldoInicial < 2500) {
throw new IllegalArgumentException("El monto inicial es menor a $2,500.00");
}
saldo = saldoInicial;
}

En el constructor se dispara la excepción IllegalArgumentException cuando el saldo


inicial de una cuenta es menor a $2 500.00 y con ello se evita la creación de una cuenta si el
valor del parámetro no es válido. No se incluye la instrucción throws en el encabezado del
método porque la excepción que se dispara es subclase de RuntimeException.
Es posible que en un método se disparen excepciones de diferente tipo; en ese caso, en el
encabezado después de la palabra throws se especifican éstas separadas por comas. Dentro
del método debe haber al menos una instrucción throw para cada uno de los diferentes tipos
de excepciones mencionadas en el encabezado.

8.3.2 Manejo de excepciones


Hasta ahora se ha visto cómo disparar una excepción dentro de un método; en esta sección
se explica qué hacer en caso de haberse disparado una excepción. Para tratar con ellas
se requiere escribir un manejador de excepciones. Si se dispara una excepción y hay un
manejador de excepciones éste se encarga de tratar con la situación anómala que la disparó.
Para programar un manejador de excepciones se debe utilizar la instrucción try, que tiene
la siguiente sintaxis:

try {
instrucciones
}
catch (Excepción1 e1 ) {
instrucciones
}
...
catch (Excepciónn en ) {
instrucciones
}
finally {
instrucciones
}
8.3 ESTADOS DE UNA EXCEPCIÓN 205

La cláusula try contiene código que incluye las instrucciones que pueden disparar la(s)
excepción(es).
La cláusulas catch tienen como parámetro un objeto de alguna clase de excepción. En
una instrucción try puede haber varias cláusulas catch; en el cuerpo o bloque de cada una
se coloca el código que implementa la acción a realizar en caso de que ocurra una excepción
del tipo de su parámetro.
Por último, la cláusula finally contiene el código para establecer un estado adecuado
para continuar la ejecución del método donde aparece la instrucción try. Incluir la cláusula
finally en el manejador de excepciones es opcional.
Ejemplo 8.5. Tratamiento de excepciones. Para explicar el efecto del disparo de una excep-
ción en el código que la trata se muestra el siguiente código, en el cual se usan los métodos
de la clase Cuenta definidos antes.
/**
* Clase para probar las excepciones lanzadas por la clase Cuenta
* Objetivo. Ilustrar el uso de la instrucción try para manejar excepciones
* @author Amparo López Gaona
* @version 3a edición
*/
public class PruebaExcepcionesCuenta {
static public void main(String pps[]) {
double monto;
Scanner in = new Scanner(System.in);

try{
System.out.println("Proporciona el monto inicial");
monto = in.nextDouble();
Cuenta cuenta = new Cuenta(monto);

System.out.println("Proporciona el monto del retiro");


monto =in.nextDouble();
cuenta.retirar(monto);

System.out.println("El saldo actual es"+cuenta.obtenerDisponible());


} catch (Exception e) {
System.out.println(e);
}
System.out.println("Hasta luego!");
}
}

El código en el bloque try es el que podrı́a disparar alguna excepción, ya sea al leer el dato
(al dar uno no numérico), en la creación de la cuenta (al dar un monto inferior a $2 500.00), al
206 CAPÍTULO 8. LA CLASE EXCEPTION

hacer un depósito con un monto negativo, o bien, al hacer un retiro con un monto negativo o
superior al disponible; en estos últimos casos se dispara la excepción llamada Exception. En
el momento que el programa encuentra una excepción ya no continúa con las instrucciones del
bloque try, denominadas instrucciones protegidas, ejecuta lo que indica la cláusula catch,
cuyo parámetro coincide con la excepción disparada y continúa normalmente con la ejecución
del programa en la instrucción que sigue al catch. En caso de que no se dispare ninguna
excepción se ejecuta completo el bloque try y se ignoran las cláusulas catch.
Ejemplo 8.6. Ejecución del programa 8.5 donde se proporciona un monto no numérico.

Proporciona el monto inicial


a50
java.util.InputMismatchException
Hasta luego!

En caso de no haber tecleado un número, se dispara la excepción InputMismatchException.


Se ignoran todas las instrucciones restantes del bloque try y se ejecuta la instrucción de la
cláusula catch, que consiste en imprimir la excepción. La ejecución continúa en la instrucción
que está después de la instrucción try, que indica el final del programa. De no estar la
instrucción try, una vez ocurrido el error termina, abruptamente, la ejecución del programa.
Ejemplo 8.7. Ejecución del programa 8.5 donde se proporciona un monto inicial incorrecto.

Proporciona el monto inicial


90
java.lang.IllegalArgumentException: El monto inicial es menor a$2,500.00
Hasta luego!

En esta ejecución aparece el mensaje que indica cuál excepción fue disparada y se explica
la razón de ello con el mensaje colocado en el constructor.
Ejemplo 8.8. Ejecución del programa 8.5 donde se intenta retirar una cantidad mayor a la
disponible.
Proporciona el monto inicial
3000
Proporciona el monto del retiro
5000
java.lang.Exception: Fondos insuficientes para realizar el retiro
Hasta luego!

Ejemplo 8.9. Ejecución del programa 8.5 sin errores.

Proporciona el monto inicial


3000
Proporciona el monto del retiro
8.3 ESTADOS DE UNA EXCEPCIÓN 207

1000
El saldo actual es $2000.0
Hasta luego!

Si no se hubiera incluido el manejador de excepciones, se tendrı́a una serie de instrucciones


if anidadas para manejar los errores. Esto dificultarı́a la comprensión y mantenimiento del
programa.

8.3.3 Fin de la excepción


La instrucción try puede terminar con una cláusula finally, su propósito es realizar tareas
indispensables sin importar el flujo del programa, es decir, si aparece se ejecuta independien-
temente de que se haya ejecutado alguna cláusula catch.
Ejemplo 8.10. Programa 8.5 ahora incluyendo la cláusula finally.
/** Clase para probar las excepciones lanzadas por la clase Cuenta
* Objetivo. Ilustrar el uso de la cláusula finally de la instrucción try
* @author Amparo López Gaona
* @version 3a edición
*/
public class PruebaExcepcionesCuentaF {
static public void main(String pps[]) {
double monto;
Scanner in = new Scanner(System.in);

try{
System.out.println("Proporciona el monto inicial");
monto = in.nextDouble();
Cuenta cuenta = new Cuenta(monto);

System.out.println("Proporciona el monto del retiro");


monto =in.nextDouble();
cuenta.retirar(monto);

System.out.println("El saldo actual es"+cuenta.obtenerDisponible());


} catch (Exception e) {
System.out.println(e);
} finally {
System.out.println("Estoy en el cuerpo de la instruccion finally");
}
System.out.println("Hasta luego!");
}
}
208 CAPÍTULO 8. LA CLASE EXCEPTION

Ejemplo 8.11. Ejecución del programa 8.10 donde se proporciona un monto inicial inco-
rrecto.

Proporciona el monto inicial


400
java.lang.IllegalArgumentException: El monto inicial es menor a$2,500.00
Estoy en el cuerpo de la instruccion finally
Hasta luego!

Se puede observar que a pesar de que se disparó una excepción, se ejecutan las instrucciones
que están en la cláusula finally.
Ejemplo 8.12. Ejecución del programa 8.10 sin errores.

Proporciona el monto inicial


3000
Proporciona el monto del retiro
1000
El saldo actual es $2000.0
Estoy en el cuerpo de la instruccion finally
Hasta luego!

En este ejemplo se muestra que aunque no se dispare una excepción se ejecutan el cuerpo
de la cláusula finally.

8.4 Creación de excepciones propias


En ocasiones puede suceder que las clases de excepciones existentes no describan la naturaleza
del error que se tiene, en ese caso y para dar mayor claridad a los programas, es posible crear
excepciones propias, esto se logra extendiendo la clase Exception; todas las nuevas clases
requieren que se proporcione una cadena de diagnóstico al constructor. Por ejemplo, es
posible crear la excepción ExcepcionBancaria y usarla en los métodos para depósito y para
retiro de una cuenta bancaria.
Ejemplo 8.13. Clase ExcepcionBancaria.

/** Excepción para manejo de cuentas bancarias


* @author Amparo López Gaona
* @version 3a edición
*/
public class ExcepcionBancaria extends Exception {
public ExcepcionBancaria() {
super();
}
8.4 CREACIÓN DE EXCEPCIONES PROPIAS 209

public ExcepcionBancaria(String s) {
super(s);
}
}

Aunque se crean igual que cualquier subclase no añaden nuevo comportamiento, sólo sirven
para mejorar la legibilidad del código.
Ejemplo 8.14. Definición de los métodos para depositar y para retirar haciendo uso de la
ExcepcionBancaria

/**
* Método para depositar una cantidad de dinero en la cuenta
* @param monto cantidad que se desea depositar
* @throws ExcepcionBancaria en caso de que el depósito sea negativo
*/
public void depositar(double monto) throws ExcepcionBancaria {
if (monto <= 0)
throw new ExcepcionBancaria("No es posible realizar un depósito negativo.");
saldo += monto;
}
/**
* Método para retirar de dinero de una cuenta
* @param monto cantidad que se desea retirar
* @throws ExcepcionBancaria en caso de que el retiro sea negativo, o
* mayor que el disponible
*/
public void retirar(double monto) throws ExcepcionBancaria {
if (monto <= 0 || monto > disponible) {
throw new ExcepcionBancaria("No es posible hacer un retiro negativo.");
}
saldo -= monto;
}

Es posible crear una jerarquı́a de excepciones propias a partir de de la clase ExcepcionBancaria.


Ejemplo 8.15. Jerarquı́a de excepciones para el manejo de cuentas bancarias.

/**
* Excepción para la depósitos negativos
* @author Amparo López Gaona
* @version 3a edición
*/
public class ExcepcionDepositoNegativo extends ExcepcionBancaria {
public ExcepcionDepositoNegativo(String s) {
210 CAPÍTULO 8. LA CLASE EXCEPTION

super (s);
}
public ExcepcionDepositoNegativo() {
super ("Los depositos deben ser positivos");
}
}
/**
* Excepción para la retiros negativos
* @author Amparo López Gaona
* @version 3a edición
*/
public class ExcepcionRetiroNegativo extends ExcepcionBancaria {
public ExcepcionRetiroNegativo(String s) {
super (s);
}
public ExcepcionRetiroNegativo() {
super ("Los retiros deben ser positivos");
}
}

... //Otras excepciones para cuentas bancarias

Al crear esta jerarquı́a de excepciones es necesario modificar la firma de los métodos de la


clase Cuenta para que disparen la excepción apropiada.
Ejemplo 8.16. Método con posibilidad de disparar dos tipos de excepciones bancarias.
/**
* Método para retirar de dinero de una cuenta
* @param monto -- cantidad que se desea retirar
* @throws ExcepcionRetiroNegativo -- si el retiro sea negativo
* @throws ExcepcionFaltaDeFondos -- si la cantidad solicitada es
* mayor que el disponible
*/
public void retirar(double monto) throws ExcepcionRetiroNegativo,
ExcepcionFaltaDeFondos {
if (monto <= 0)
throw new ExcepcionRetiroNegativo();
if (monto > disponible)
throw new ExcepcionFaltaDeFondos("Fondos insuficientes para el retiro");
disponible -= monto;
}

Ejemplo 8.17. Programa que prueba la clase Cuenta utilizando las excepciones creadas
para problemas manejo de casos excepcionales en las cuentas bancarias.
8.4 CREACIÓN DE EXCEPCIONES PROPIAS 211

/**
* Clase para probar las excepciones definidas para manejo de cuentas
* @author Amparo López Gaona
* @version 3a edición
*/
public class PruebaExcepcionesPropias {
public static void main(String pps[]) {
Cuenta cuenta;
double monto = 0;
Scanner in = new Scanner(System.in);

try{
System.out.println("Proporciona el monto inicial");
monto = in.nextDouble();
cuenta = new Cuenta(monto);

System.out.println("Proporciona el monto del depósito");


monto = in.nextDouble();
cuenta.depositar(monto);

System.out.println("Proporciona el monto del retiro");


monto =in.nextDouble();
cuenta.retirar(monto);

System.out.println("El saldo actual es "+cuenta.obtenerDisponible());


} catch(ExcepcionDepositoNegativo)
System.out.println(e + " (en la cuenta "+cuenta.obtenerNumCuenta()+").");
} catch(ExcepcionRetiroNegativo)
System.out.println(e + " (en la cuenta "+cuenta.obtenerNumCuenta()+").");
} catch(ExcepcionFaltaDeFondos)
System.out.println(e + " (en la cuenta "+cuenta.obtenerNumCuenta()+").");
} catch(ExcepcionBancaria)
System.out.println(e + " (en la cuenta "+cuenta.obtenerNumCuenta()+").");}
} catch (Exception e) {
System.out.println(e);
}
System.out.println("Hasta luego!");
}
}

Si las instrucciones protegidas pueden activar más de un tipo de excepción, éstas se listan
con cláusulas catch separadas. Si hay más de una cláusula catch que pueda manejar el error,
se van revisando en orden de aparición hasta encontrar una cuyo parámetro definido coincida
212 CAPÍTULO 8. LA CLASE EXCEPTION

con el tipo de excepción lanzada. Por tanto, es recomendable especificar estas cláusulas en
orden contrario al de su jerarquı́a de clases para que, en caso de no tener considerado un
tipo particular de excepción, se realice la cláusula catch de su superclase. Puede incluirse
como última cláusula catch la que atrapa Exception puesto que es la raı́z de la jerarquı́a y
con el objetivo de asegurar que alguna cláusula que atrapa cualquier excepción y no termine
el programa abruptamente. Esto puede hacerse debido a que en el lugar de un objeto de una
clase se puede poner alguno de cualquiera de sus subclases.
Ejemplo 8.18. Programa que utiliza varios manejadores de excepciones. En un programa
puede haber tantas instrucciones try como se requieran, no está limitado a una sola.
/** Clase para probar el uso de varias instrucciones try
* @author Amparo López Gaona
* @version 3a edición
*/
public class ExcepcionesCon2Try {
static public void main(String pps[]) {
Cuenta cuenta = new Cuenta(3000);
double monto = 0;
Scanner in = new Scanner (System.in);
// Algunas instrucciones
try{
System.out.println("Proporciona el monto del depósito");
monto = in.nextDouble();
cuenta.depositar(monto);
} catch(ExcepcionDepositoNegativo e) {
System.out.println(e + " (en la cuenta "+cuenta.obtenerNumCuenta()+").");
}
//Otras instrucciones
monto = cuenta.obtenerDisponible(); // Esta no dispara excepciones..
System.out.println("El saldo actual es "+cuenta.obtenerDisponible());
if (monto > 10000) {
try {
System.out.println("Proporciona el monto del retiro");
monto =in.nextDouble();
cuenta.retirar(monto);
} catch(ExcepcionRetiroNegativo e) {
System.out.println(e + " (en la cuenta "+cuenta.obtenerNumCuenta()+").");
} catch(ExcepcionFaltaDeFondos e) {
System.out.println(e + " (en la cuenta "+cuenta.obtenerNumCuenta()+").");
}
}
// Otras instrucciones
}}
8.5 PROPAGACIÓN DE EXCEPCIONES 213

8.5 Propagación de excepciones


Las excepciones pueden propagarse a otros métodos. Si una excepción no se trata en el
método que se originó, se propaga al método que lo llamó y ası́ sucesivamente hasta encontrar
un manejador de excepciones o llegar al entorno de Java y terminar abruptamente.
Ejemplo 8.19. Programa que propaga una excepción.

/**
* Clase para probar las excepciones lanzadas por la clase Cuenta
* Objetivo. Ilustrar la propagación de excepciones
* @author Amparo López Gaona
* @version 3a edición
*/
public class Propagacion {
/**
* Método para efectuar un depósito adicional a una cuenta
* @param cta -- cuenta en la que se efectuará el depósito
* @param monto -- cantidad a depositar
* @throws ExcepcionDepositoNegativo si el monto es negativo
*/
public static void depositarCuenta(Cuenta cta, int monto)
throws ExcepcionDepositoNegativo {
System.out.println("Saldo inicial "+cta.obtenerDisponible());
cta.depositar(monto);
System.out.println("Saldo despues del deposito "+cta.obtenerDisponible());
}

public static void main(String pps[]) {


Cuenta cta = null;
try {
cta = new Cuenta(5000);
depositarCuenta(cta, 100);
System.out.println("Primer saldo en el main: "+cta.obtenerDisponible());
depositarCuenta(cta, -100);
System.out.println("Segundo saldo en el main: "+cta.obtenerDisponible());
} catch (ExcepcionDepositoNegativo e) {
System.out.println("Excepcion capturada");
} finally {
System.out.println("Instruccion finally del metodo main");
}
System.out.println("Ya acabe");
}
}
214 CAPÍTULO 8. LA CLASE EXCEPTION

El método depositarCuenta llama al método depositar de la clase Cuenta. En caso de


generarse una excepción en depositar, por ejemplo al intentar depositar -100, ésta se envı́a
al método que lo llamó, es decir a depositarCuenta y se espera que sea atrapada y manejada
en él. Como no sucede, se pasa el control al main que es el encargado de atraparla, por ser
único de los tres métodos que tiene la instrucción try. (Figura 8.2).

depositarCta() depositar()
main () throws Ex throws Ex
try {
... ... ...
depositarCta(); depositar(); throw new Ex()
... ... ...
} catch(Ex e) }
{
...
}finally {
...
}

Figura 8.2 Propagación de excepciones.

Al programar un método es necesario decidir, si la excepción va a ser manejada en él o bien


si se va a propagar hacia los métodos de donde proviene la llamada al método que dispara
la excepción.
Los métodos vistos en la sección 8.3 propagan la excepción. Esto se sabe por dos motivos:
el primero es que en el encabezado se especifica la excepción que disparan y el segundo es
que no contienen un manejador de excepciones en su cuerpo.
Ejemplo 8.20. Método retirar, de la clase Cuenta, sin propagación de excepciones.

/** Retira dinero de una cuenta bancaria


* @param monto -- cantidad de dinero que será retirada
* @return double -- cantidad de dinero retirada
* @throws ExcepcionRetiroNegativo si la cantidad a retirar es negativa
* @throws ExcepcionFaltaDeFondos si la cantidad a retirar mayor que el saldo.
*/
public double retirar(double monto) {
try {
if (monto <= 0)
throw new ExcepcionRetiroNegativo();
if (monto > disponible)
throw new ExcepcionFaltaDeFondos();
disponible -= monto;
8.6 RECUPERACIÓN DE EXCEPCIONES 215

} catch(ExcepcionRetiroNegativo e) {
System.out.println("Los retiros deben ser positivos");
} catch (ExcepcionFaltaDeFondos e) {
System.out.println("Los fondos son insuficientes para el retiro");
System.out.println("Tienes una penalización de $200");
disponible -= 200;
}
return disponible;
}

En este método no se propagan las excepciones, estas se tratan en el mismo. Si la excepción


es causada por un retiro negativo, sólo envı́a un mensaje de error y si se debe a la falta de
fondos además del mensaje se penaliza al usuario de la cuenta con $200.00.

8.6 Recuperación de excepciones


Como se ha visto, el uso de excepciones permite notificar la existencia de algún error, sin
embargo es importante que además de notificar el problema se pueda seguir trabajando. Lo
usual es tratar de corregir el error y volver a ejecutar las instrucciones protegidas, para ello
se puede incluir la instrucción try en un ciclo.
Ejemplo 8.21. Creación de una cuenta, verificando que el monto inicial sea correcto.

/** Método para crear una cuenta en un máximo de cinco intentos


* @return Cuenta -- cuenta creada o bien null si no pudo crearse
*/
public Cuenta crearCuenta() {
boolean OK = false;
int intentos = 0;
double monto;
Cuenta cuenta = null;
do {
try {
System.out.println("Proporciona el monto inicial");
monto = in.nextDouble();
cuenta = new Cuenta(monto);
OK = true;
} catch (ExcepcionBancaria e) {
intentos ++;
}
} while (!OK && intentos < 5);
return cuenta;
}
216 CAPÍTULO 8. LA CLASE EXCEPTION

Solicita al usuario una cantidad inicial para crear una nueva cuenta y está en un ciclo
intentando crearla hasta que se proporciona una cantidad dentro del rango permitido o bien
se ha intentado hacer esta creación cinco veces. Debido a que el constructor no devuelve
ningún valor se incluye la variable booleana OK, que se pone en verdadera cuando se ha
logrado construir el objeto, es decir, cuando no se ha disparado la excepción.

8.7 Ventajas
Entre las ventajas de trabajar con excepciones se tienen las siguientes:

1. Permiten separar el código para manejo de error del código normal. Aunque esto no
evita que se deba especificar cuáles son los posibles errores y qué hacer en caso de que
ocurran.

2. Se pueden agrupar tipos de errores y también se pueden diferenciar errores. Por ejem-
plo, todos los que pueden ocurrir al trabajar con arreglos, con archivos, etc., pero
también se puede trabajar con cada uno por separado.

3. Al crear excepciones propias se están creando códigos de error propios y son manejados
por Java de manera idéntica a sus excepciones.

8.8 Ejercicios
1. ¿Qué es una excepción en Java?

2. Explicar cómo se dispara una excepción.

3. Si no se dispara ninguna excepción dentro de la cláusula try, ¿dónde continúa la


ejecución del método?

4. ¿Se pueden manejar diferentes excepciones en una sola instrucción try? ¿Cómo?

5. ¿Cuándo se ejecuta la cláusula finally de una instrucción try?

6. Explicar qué sucede si varias clausulas catch coinciden con la excepción disparada.

7. ¿Java sólo puede reconocer las excepciones definidas en el lenguaje?

8. Explicar por qué no es recomendable usar sólo una cláusula catch como la siguiente:
8.8 EJERCICIOS 217

try {
...
} catch (Exception e) {
...
}

9. Explicar qué tipo de excepciones pueden ser atrapadas por el siguiente manejador, y
si hay algo mal en ello, corregirlo.

try {
...
} catch (Exception e) {
...
} catch (ArithmeticException e) {
...
}

10. En el siguiente programa incluir las instrucciones necesarias para atrapar las excep-
ciones que puedan dispararse al ejecutarlo, instrucciones para escribir los mensajes
adecuados y continuar con la siguiente iteración del ciclo.

/* El siguiente código pide al usuario la posición de tres elementos


* del arreglo. Divide los dos primeros y almacena el resultado en el
* tercero
*/
public class Duda {
public static void main (String [] pps) {
final int[] valores = {10,0,30,40,100,5,2,45};
Scanner in = new Scanner(System.in);

final int cambios = 5;


for(int cambio = 0; cambio < cambios; i++) {
System.out.println("Los valores del arreglo son:");
for (int i=0; i < valores.length; i++)
System.out.print(valores[i]+" ");
System.out.println();

System.out.println("Dar posición de los datos que se dividirán");


int pos1= in.nextInt();
int pos2 = in.nextInt();
int resultado = valores[pos1]/valores[pos2];
System.out.println("Dar la posición de almacenamiento");
int destino = in.nextInt();
218 CAPÍTULO 8. LA CLASE EXCEPTION

valores[destino] = resultado;
}
}
}

11. Modificar el siguiente programa para que funcione correctamente.

public class Duda {


public static void main (String [] pps) {
Scanner in = new Scanner(System.in);
int numero;

System.out.println("Da un entero :");


try {
lı́nea = in.nextInt();
if ((825 % numero) == 0)
System.out.println("825 es múltiplo de " + numero);
else
System.out.println("825 no es múltiplo de " + numero);
}
}
}

12. Describir qué hace el siguiente programa:

public class Circulo {


private final double radio;
public Circulo (double r) throws RuntimeException {
if (r <= 0) {
throw new RuntimeException("El radio debe ser positivo");
}
radio = r;
}
public static void main(String [] pps) {
for (int i = -2; i < 3; i++) {
try {
System.out.println("Crearé el cı́rculo con radio "+ i);
Circulo c1 = new Circulo((double)i);
System.out.println("Se creó un cı́rculo con radio "+ i);
}
catch(RuntimeException e) {
System.out.println(e.getMessage());
}
8.8 EJERCICIOS 219

}
}

13. Volver a escribir la clase Alumno del capı́tulo 6 para que incluya excepciones en los
métodos que sea pertinente.

14. Volver a escribir el programa para manejo de fechas de la sección de ejercicios del
capı́tulo 4, ahora incluyendo excepciones.

15. Volver a escribir la clase Hora presentada en el capı́tulo 4 para que dispare excepciones
en los casos pertinentes.

16. Volver a escribir las clases para resolver el problema de impresión de facturas presen-
tadas en el capı́tulo 7 para que disparen excepciones en los casos pertinentes.
Capı́tulo 9

Clases abstractas e interfaces

En este capı́tulo se describe la forma de crear clases tan generales que representan un concepto
abstracto, por lo que no es posible definir la implementación de todos sus métodos y por
lo tanto son clases de las que no es posible generar objetos, pero sirven para especificar el
comportamiento de sus descendientes. También se describe la forma de definir interfaces que
son especificaciones del comportamiento deseado para las clases que las implementen.

9.1 Clases abstractas


En esta sección se va a presentar una solución al problema de jugar gato con la computadora.
El juego requiere un tablero de 3 × 3 y dos jugadores. Cada jugador elige una ficha diferente
(cruz o cı́rculo) y alternadamente va colocando su ficha en el tablero con la intensión de
colocar tres de ellas en lı́nea recta ya sea horizontal, vertical o en diagonal. El primero que lo
consiga gana. Si el tablero se llena y ningún jugador hace lı́nea recta se declara un empate.
Aplicando la misma metodologı́a que ha usado para resolver los problemas de los capı́tulos
anteriores, se tiene:

1. Encontrar los objetos principales.


Sustantivos: Tablero, jugadores, ficha, lı́nea recta. Lı́nea recta no es objeto.

2. Determinar el comportamiento de cada objeto encontrado.


El comportamiento para tablero no se obtiene de la descripción del problema sino del
conocimiento de éstos. EL comportamiento incluye: crear un tablero, limpiar el tablero,
colocar una pieza, quitar una pieza, ver si una celda está vacı́a, mostrar el tablero y
los métodos para obtener el estado del tablero.
En este caso sı́ se va a programar una clase para los jugadores. Cada jugador va a
trabajar en un tablero y va a tener su estrategia de juego.

221
222 CAPÍTULO 9. CLASES ABSTRACTAS E INTERFACES

La ficha puede ser un objeto, sin embargo, en este problema no se va a considerar como
tal.

3. Definir escenario.

(a) El sistema solicita al usuario los datos de cada jugador.


(b) El usuario proporciona al sistema los datos de cada jugador.
(c) Mientras no haya un ganador y haya una celda disponible alternadamente cada
jugador coloca su pieza en el tablero.
(d) El programa determina el ganador del juego.

Ejemplo 9.1. La estructura de la clase Tablero contiene una matriz cuadrada del tamaño
indicado por el usuario o de 3 × 3 según el constructor que se utilice. Se omite la programación
de esta clase, debido a que los métodos son sencillos y se utiliza lo presentado en la sección
6.6.7.
La estructura de la clase Jugador contiene un tablero, en el que se va a jugar, y la ficha
elegida por el jugador. Los métodos contenidos son el constructor, los necesarios para obtener
la estructura de la clase y un método jugar para la estrategia del jugador. Sin embargo,
aquı́ se presenta el problema de que cada jugador tiene su propia estrategia y puede ser
diferente entre ellos. Para solucionar este problema se utiliza una clase abstracta.
Una clase abstracta es aquella que tiene al menos un método sin implementación porque
no es posible definirlo, este método se denomina abstracto y su implementación se deja a
las clases descendientes. Las clases abstractas sirven para especificar el comportamiento que
deben tener las clases derivadas sin incluir la implementación de algunos métodos. Estas
clases facilitan el polimorfismo, pues se tiene al menos un método con la misma firma pero
cuyo comportamiento depende de cada subclase.
Una clase abstracta se programa igual que cualquier otra clase, excepto que se precede la
palabra class de la palabra abstract y al menos uno de sus métodos no tiene cuerpo, en
ese caso la firma del método empieza con la palabra abstract y el cuerpo se sustituye por
un punto y coma.
Ejemplo 9.2. Implementación de la clase abstracta Jugador.

/**
* Clase para modelar a jugadores de juegos con tablero
* @author Amparo López Gaona
* @version 3a edición
*/
public abstract class Jugador {
protected Tablero tablero;
protected char ficha;
/**
9.1 CLASES ABSTRACTAS 223

* Constructor de un jugador con su tablero y ficha


* @param tablero -- tablero en que va a jugar
* @param ficha -- ficha con que va a jugar
*/
public Jugador(Tablero tablero, char ficha) {
this.tablero = tablero;
tablero.limpiarTablero();
this.ficha = ficha;
}
/**
* Método para obtener el tablero en que se va a jugar
* @return Tablero -- tablero en que se juega
*/
public Tablero obtenerTablero() {
return tablero;
}
/**
* Método para obtener la ficha con que se va a jugar
* @return Tablero -- ficha con la que jugara este jugador
*/
public char obtenerFicha() {
return ficha;
}
/**
* Método abstracto para implementar la estrategia del jugador
*/
public abstract void mover();
}

En este juego se tienen dos clases de jugadores: la computadora y las personas. A conti-
nuación se presenta su implementación como subclases de la clase Jugador.
Ejemplo 9.3. Programación de la clase Persona.

/**
* Clase para representar a los jugadores que interactúan con el programa
* Objetivo. Mostrar la extensión de una clase abstracta.
* @author Amparo López Gaona
* @version 3a edición
*/
public class Persona extends Jugador {
/**
* Constructor de un jugador humano con su tablero y ficha
* @param tablero -- tablero en que va a jugar
224 CAPÍTULO 9. CLASES ABSTRACTAS E INTERFACES

* @param ficha -- ficha con que va a jugar


*/
public Persona(Tablero tablero, char ficha) {
super (tablero, ficha);
}
/**
* Método que implementa la estrategia del jugador
*/
public void mover() {
java.util.Scanner in = new java.util.Scanner(System.in);
int ren, col;

do {
System.out.println("Dar una posicion para la jugada");
ren = in.nextInt();
col = in.nextInt();
} while (!tablero.colocarFicha(ren,col,ficha));
}
}

En la clase Persona se está obligado a programar el método abstracto mover. En este caso
la estrategia consiste en solicitar al usuario una posición para hacer su jugada tantas veces
hasta que sea posible colocar la ficha en el tablero.
Ejemplo 9.4. Programación de la clase Computadora

/**
* Clase para representar a las computadoras que juegan en un tablero
* Objetivo. Mostrar la extensión de una clase abstracta.
* @author Amparo López Gaona
* @version 3a edición
*/
public class Computadora extends Jugador {
/**
* Constructor de una computadora con su tablero y ficha
* @param tablero -- tablero en que va a jugar
* @param ficha -- ficha con que va a jugar
*/
public Computadora(Tablero tablero, char ficha) {
super (tablero, ficha);
}
/**
* Método que implementa la estrategia del jugador
*/
9.1 CLASES ABSTRACTAS 225

public void mover() {


int renglon, col;
do{
renglon = generarPosicion();
col = generarPosicion();
} while(!tablero.colocarFicha(renglon,col,ficha));

System.out.println("Voy a tirar en "+renglon+","+col);


}
/*
* Método privado para generar una posible posición para jugar
*/
private int generarPosicion(){
return (int) (Math.random() * tablero.obtenerTamanoTablero());
}
}

En este caso, la estrategia de la computadora es muy sencilla: simplemente genera una


posición al azar y si está vacı́a ahı́ coloca su ficha. Este método puede ser tan complicado
como se quiera.
En una clase puede haber otros métodos además de los abstractos heredados y obligados a
programarse como en el caso de la clase Computadora que agrega el método generarPosicion.
Ejemplo 9.5. La clase Gato tiene un tablero, dos jugadores y es la encargada de implementar
el juego.

/**
* Clase que implementa el juego del gato entre dos jugadores
* Objetivo: ilustrar el polimorfismo a través de clases que implementan
* una clase abstracta
* @author Amparo López Gaona
* @version 3a edición
*/
public class Gato {
private Tablero tablero;
private Jugador jugador1, jugador2;
public static final char BOLA = ’0’, CRUZ = ’X’, NADA = ’.’;
public static final int INCONCLUSO = 0, EMPATE = 1, GanoBola = 2, GanoCruz = 3;

/**
* Constructor del juego
* @param uno -- primer jugador
* @param dos -- segundo jugador
*/
226 CAPÍTULO 9. CLASES ABSTRACTAS E INTERFACES

public Gato(Jugador uno, Jugador dos) {


jugador1 = uno;
jugador2 = dos;
tablero = jugador1.obtenerTablero();
}
/**
* Método para dar turno al siguiente jugador
* @param jugadorActual -- jugador en turno
* @return Jugador -- siguiente jugador
*/
public Jugador sgteJugador(Jugador jugadorActual){
return (jugadorActual == jugador1) ? jugador2 : jugador1;
}
/**
* Método para jugar al gato
*/
public void jugar(){
Jugador jugador = jugador1;
tablero.limpiarTablero();

while(estadoDelJuego() == INCONCLUSO){
jugador.mover();
tablero.mostrarTablero();
if(!gano(jugador)){
jugador = sgteJugador(jugador);
}
}
reportarResultado();
}

En el método jugar se hace uso del polimorfismo al llamar al método mover de la clase
Jugador y dependiendo del jugador en turno es la estrategia utilizada.
Los métodos restantes de la clase son los necesarios para determinar si se puede seguir
jugando y para determinar el ganador del juego.

/**
* Método para determinar el estado del juego
* @return int -- entero con el estado del juego
*/
public int estadoDelJuego(){
if(gano(jugador1)){
return GanoBola;
}
9.1 CLASES ABSTRACTAS 227

else if(gano(jugador2)){
return GanoCruz;
} else{
int tamano = tablero.obtenerTamanoTablero();
for(int renglon = 0; renglon < tamano; renglon++)
for(int col = 0; col < tamano; col++)
if(tablero.obtenerFicha(renglon, col) == NADA)
return INCONCLUSO;
return EMPATE;
}
}
/**
* Método para determinar el ganador del juego
* @param jugador -- jugador que se quiere saber si gano
* @return boolean -- true si gano el jugador y false en otro caso
*/
public boolean gano(Jugador jugador){
int diagonalIzq = 0, diagonalDer = 0;
int tamano = tablero.obtenerTamanoTablero();
char ficha = jugador.obtenerFicha();

for(int x = 0; x < tamano; x++){


int nRenglones = 0;
int nCols = 0;
for(int y = 0; y < tamano; y++){
if(tablero.obtenerFicha(x,y) == ficha)
nRenglones++;
if(tablero.obtenerFicha(y,x) == ficha)
nCols++;
}
if((nRenglones == tamano) || (nCols == tamano))
return true;
if(tablero.obtenerFicha(x,x) == ficha)
diagonalIzq++;
if(tablero.obtenerFicha(x,tamano-x-1) == ficha)
diagonalDer++;
}
return ((diagonalIzq == tamano) || (diagonalDer == tamano));
}
228 CAPÍTULO 9. CLASES ABSTRACTAS E INTERFACES

9.2 Jerarquı́a de clases abstractas


Con lo visto en los capı́tulos anteriores se puede resolver gran cantidad de problemas, pero
en ocasiones se necesita de mecanismos especiales para hacer un buen programa, que no sea
tan complicado en su estructura e implementación. Para comprender este planteamiento se
trabajará con el siguiente problema.
Se requiere escribir un programa que imprima los recibos de pago para los empleados de
una organización. En dicha organización se tienen empleados temporales y empleados con-
tratados o permanentes; de estos últimos existen empleados de tiempo completo y empleados
por horas. Cada empleado, independientemente de su categorı́a,tiene su nombre, dirección,
CURP y el sueldo que percibe.
Los empleados temporales son aquellos que se contratan para hacer un trabajo particular
y se acuerda el pago del mismo de antemano. Los empleados de tiempo completo tienen un
trabajo permanente y su salario se calcula multiplicando el valor de cada hora (de acuerdo
con su categorı́a) por 40, y se aplican las deducciones correspondientes al servicio médico y
jubilación. El sueldo de un empleado por horas se calcula multiplicando el número de horas
trabajadas por el valor que se da a cada hora y restando el 2 % de este total para su fondo
de retiro.
Aplicando la misma metodologı́a que ha usado para resolver los problemas de los capı́tulos
anteriores, se tiene:

1. Encontrar los objetos principales.


Sustantivos: recibo de pago, empleados, organización, empleados temporales, emplea-
dos permanentes, empleados de tiempo completo, empleados por horas, nombre, direc-
ción, CURP, sueldo y horas.
Recibo de pago es el resultado del programa. Empleados, empleados temporales, em-
pleados permanentes, empleados de tiempo completo, empleados por horas serán clases.
Los otros sustantivos son atributos de los empleados.
Como los sustantivos para clases tienen atributos en común, se organizan las clases
usando herencia por generalización, ası́ que además de las cuatro clases de empleados,
se tiene la clase Empleado como raı́z de la jerarquı́a (figura 9.1).

2. Determinar el comportamiento de cada objeto encontrado.


Las oraciones “aplicar deducciones” y “ganar sueldo” están incluidos como métodos
en la figura 9.1.

3. Definir escenario.

(a) El sistema solicita al usuario los datos de cada empleado que se va a incluir en la
nómina.
9.2 JERARQUÍA DE CLASES ABSTRACTAS 229

Empleado
curp
nombre
dirección
pago
ordenDePago()
calcularSueldo()

Temporal Permanente
contrato
deducciones
pagoAcordado() categoría
... deducciones()

PorHoras TiempoCompleto
númeroDeHoras fechaIngreso
pagoPorHora bonificación
. . . . . .

Figura 9.1 Jerarquı́a de empleados.

(b) El usuario proporciona al sistema los datos de cada empleado que se va a incluir
en la nómina.
(c) El programa valida los datos, en caso de error avisa al usuario para que los corrija.
(d) El programa calcula para cada empleado su sueldo.
(e) El programa imprime la nómina.

Suponer que de un desarrollo anterior se tiene la clase Persona con los atributos generales
de una persona y métodos para trabajar con ellos, ası́ que se empezará por programar la
clase Empleado. Al hacerlo se presenta un gran problema, ya que no es posible escribir el
método para calcular el sueldo debido a que la fórmula para hacerlo depende del tipo de
empleado de que se trata; se sabe que todo empleado debe tener un método para ello, pero
no es posible especificarlo aquı́. Ası́ que se programará como clase abstracta.
Ejemplo 9.6. Implementación de la clase abstracta Empleado.

/**
* Clase que implementa a los empleados como subclase de personas
* Objetivo: ilustrar el uso de clases abstractas
* @author Amparo López Gaona
* @version 3a edición
*/
230 CAPÍTULO 9. CLASES ABSTRACTAS E INTERFACES

abstract public class Empleado extends Persona {


protected double pago;

public abstract double calcularSueldo(); // Método abstracto

/**
* Constructor de un empleado. Este llama al constructor de la clase Persona
* @see Persona
* @param nombre - nombre del empleado
* @param direccion - direccion del empleado
* @param curp - curp del empleado
*/
public Empleado(String nombre, String direccion, String curp) {
super(nombre, direccion, curp);
pago = 0;
}
/**
* Método para imprimir la orden de pago del empleado.
*/
public void imprimirOrdenDePago() {
System.out.print("\n\n_________________________________________");
System.out.println("\n\n\t\t\t\tORDEN DE PAGO DE:");
System.out.println("\tNombre\t\t" + nombre);
System.out.println("\tDomicilio\t" + direccion);
System.out.println("\tCURP\t\t" + curp);
System.out.println("\n\nLa cantidad de $" +calcularSueldo());
System.out.println("\n\n_______________________________________\n\n");
}
}

No es necesario que todos los métodos de una clase abstracta sean abstractos, puede ha-
ber de los otros métodos, a los que se denomina concretos, como es el caso del método
imprimirOrdenDePago que tendrán por herencia todas las subclases de Empleado. Es im-
portante notar que se llama al método calcularSueldo aunque no está definido.
Debido a que no está completa la programación del comportamiento de los objetos de las
clases abstractas, no es posible crear objetos de ellas. Si se permitiera, se podrı́a tener un
código como el siguiente:

Empleado juan = new Empleado();


...
juan.calcularSueldo();

el problema está en que el objeto juan no puede responder al mensaje calcularSueldo,


9.2 JERARQUÍA DE CLASES ABSTRACTAS 231

porque aunque esté especificado en la clase Empleado, no tiene las instrucciones necesarias
para actuar, sólo se sabe que ese método estará programado en las subclases.
Ejemplo 9.7. La clase Temporal no agrega nada a la estructura de los empleados, sin
embargo, en el constructor se requiere además de los datos del empleado una constante para
el pago, pues a este tipo de empleado se le paga por obra terminada. En esta clase sı́ se
puede programar el método calcularSueldo porque sı́ está definido cómo hacerlo.
/**
* Clase que implementa a la subclase de los empleados temporales
* Objetivo. Ilustrar la implementación de un método abstracto
* @author Amparo López Gaona
* @version 3a edición
*/
public class Temporal extends Empleado {

/**
* Constructor de un empleado temporal
* @param nombre - nombre del empleado
* @param direccion - direccion del empleado
* @param curp - curp del empleado
* @param p - pago acordado
*/
public Temporal(String nombre, String direccion, String curp, double p){
super(nombre, direccion, curp);
pago = p;
}
/**
* Calcula el sueldo de un empleado temporal
* @return double - sueldo del empleado
*/
public double calcularSueldo() {
return pago;
}
}

Ejemplo 9.8. La clase Permanente tiene, además de los datos de la clase Empleado, un
contrato y deducciones. Esta clase también es abstracta, puesto que sigue siendo imposible
determinar el importe del pago a empleados de este tipo. Además se requiere crear otro
método abstracto para calcular las deducciones de los empleados de esta categorı́a, debido
a que dicho cálculo es diferente en cada caso. Esto no siempre es ası́, podrı́a tenerse el caso
de que se supiera calcular el sueldo pero no las deducciones, y por tanto también serı́a una
clase abstracta. También podrı́a suceder que se pudiera calcular el sueldo de este tipo de
empleados y no hubiera deducciones con lo cual la clase ya no serı́a abstracta.
232 CAPÍTULO 9. CLASES ABSTRACTAS E INTERFACES

/**
* Clase que implementa a la subclase de los empleados permanentes
* Objetivo. Ilustrar la definición de una clase abstracta, como subclase de otra
* @author Amparo López Gaona
* @version 3a edición
*/
abstract public class Permanente extends Empleado {
private Contrato contrato;
protected double deducciones;
/**
* Constructor de un empleado permanente
* @param nombre - nombre del empleado
* @param direccion - direccion del empleado
* @param curp - curp del empleado
*/
public Permanente(String nombre, String direccion, String curp) {
super(nombre, direccion, curp);
contrato = new Contrato();
}

abstract double calcularDeducciones();


}

En la clase Permanente se tienen dos métodos abstractos: calcularSueldo que es here-


dado, y calcularDeducciones.
Ejemplo 9.9. Los empleados por horas incluyen, además de los datos de los empleados per-
manentes, la cantidad de horas trabajadas, el pago por hora y el porcentaje que le será des-
contado de su sueldo.

/**
* Clase que implementa a la subclase de los empleados por horas
* @author Amparo López Gaona
* @version 3a edición
*/
public class PorHoras extends Permanente {
private double horasTrabajadas;
private double sueldoHora;
private final double porcentaje = 0.02;

/**
* Constructor de un empleado por horas. Éste llama a permanente
* @param nombre - nombre del empleado
* @param direccion - direccion del empleado
9.2 JERARQUÍA DE CLASES ABSTRACTAS 233

* @param curp - curp del empleado


* @param horasT - cantidad de horas trabajadas
* @param pagoH - sueldo por hora trabajada
*/
public PorHoras(String nombre, String direccion, String curp,
double horasT, double pagoH) {
super(nombre, direccion, curp);
horasTrabajadas=horasT;
sueldoHora=pagoH;
}
/** Calcula las deducciones de un empleado por horas
* @return double - deducciones calculadas
*/
public double calcularDeducciones() {
return (horasTrabajadas*sueldoHora)*porcentaje;
}
/** Calcula el sueldo de un empleado por horas, tomando en cuenta
* las deducciones
* @return double - sueldo calculado
*/
public double calcularSueldo() {
deducciones = calcularDeducciones();
pago=(horasTrabajadas*sueldoHora) - deducciones;
return pago;
}
}

Ejemplo 9.10. La clase de los empleados de tiempo completo es un tipo especial de la clase
de los empleados permanentes a la que se le agrega un pago quincenal y se le aplica un
descuento por servicio médico más otro para su fondo de retiro.

/**
* Clase que implementa a la subclase de los empleados de tiempo completo
* @author Amparo López Gaona
* @version 3a edición
*/
public class TiempoCompleto extends Permanente {
private double pagoQuincenal;
private final double servicioMedico = 0.02;
private final double jubilacion = 0.03;
/**
* Constructor de un empleado de tiempo completo
* @param nombre - nombre del empleado
234 CAPÍTULO 9. CLASES ABSTRACTAS E INTERFACES

* @param direccion - direccion del empleado


* @param curp - curp del empleado
* @param pagoQ - sueldo por quincena
*/
public TiempoCompleto(String nombre, String direccion, String curp,
double pagoQ) {
super(nombre, direccion, curp);
pagoQuincenal = pagoQ;
}
/** Calcula las deducciones de un empleado de tiempo completo
* @return double - deducciones calculadas
*/
public double calcularDeducciones() {
return pagoQuincenal*servicioMedico + pagoQuincenal*jubilacion ;
}
/** Calcula el sueldo de un empleado de tiempo completo, tomando en cuenta
* las deducciones
* @return double - sueldo calculado
*/
public double calcularSueldo(){
deducciones = calcularDeducciones();
pago = pagoQuincenal - deducciones;
return pago;
}
}

Notar que en el constructor de TiempoCompleto se llama al constructor de Permanente,


que a su vez llama al de Empleado y éste a su vez llama al de Persona.
Ejemplo 9.11. Programa que prueba el funcionamiento de la jerarquı́a de herencia. En él se
crea un arreglo de empleados y se llena con empleados de diferente tipo, cabe recordar que
Empleado es una clase abstracta y por tanto no está permitido crear objetos de esa clase,
pero sı́ referencias a objetos de esa clase abstracta. Luego, en cada una de las localidades del
arreglo se almacena una referencia a algún objeto de cualquiera de sus subclases concretas.

/**
* Clase para probar la jerarquı́a de empleados
* @author Amparo López Gaona
* @version 3a edicion
*/
public class Nomina {
public static void main(String [] pps) {
Empleado p[] = new Empleado[3];
9.3 INTERFACES 235

p[0] = new Temporal("Juan Pérez","Reforma 11","pejua345622",40);


p[1] = new PorHoras("Carmelo Gómez","Calvario 222","carmelo345622",30,85);
p[2] = new TiempoCompleto("Andrea López","Acoxpa 4444","andy345622",500);

System.out.println("Nómina para la empresa ABSTRACTA S.A.");


for (int i = 0; i <3; i++) {
System.out.println(p[i].obtenerNombre()+"\t\t"+p[i].calcularSueldo());
p[i].imprimirOrdenDePago();
}
}
}
Aquı́ se puede apreciar nuevamente el uso del polimorfismo al tener una sola proposición
for en la que se llama al método para calcular el sueldo de un empleado sin importar
la clase a la que pertenece. También se utiliza al llamar al llamarlo a través del método
imprimirOrdenDePago. En el momento de ejecución, dependiendo de la clase del objeto se
llama al método calcularSueldo apropiado.

9.3 Interfaces
En la sección anterior se presentaron clases denominadas abstractas, que no contienen la
implementación de algunos de sus métodos. El caso extremo es cuando todos los métodos
son abstractos, es decir, sólo se define el comportamiento común de los objetos mediante los
métodos sin especificar la implementación de ninguno. En este caso se tiene una interfaz.
Una duda qué surge es: ¿por qué usar interfaces si las clases abstractas proporcionan gran
flexibilidad? La respuesta es porque Java permite que una clase implemente más de una
interfaz y sólo permite extender una superclase. Otra razón es que debido a que una interfaz
no es una clase, no es parte de una jerarquı́a de clases y por lo tanto dos clases sin relación
pueden implementar la misma interfaz.
Para ilustrar la necesidad y la forma de utilizar interfaces se plantea el siguiente problema.
Se tiene una jerarquı́a de clases para figuras geométricas como la mostrada en la figura
9.2 como extensión del problema planteado al inicio del capı́tulo 4. Durante el uso del pro-
grama surge la necesidad de que el cı́rculo y el rectángulo puedan escalarse, pero sólo esas
figuras. Esta es una tarea de mantenimiento adaptativo, pues es una necesidad no planteada
inicialmente.
Una forma de satisfacer esta necesidad es definir en las clases Circulo y Rectangulo un
método para agrandar la figura y otro para encogerla. Sin embargo, podrı́a suceder que en
alguna de estas subclases no se implementen todos los métodos mencionados.
Otra forma de hacerlo y que resuelve el problema de que no se implementen ambos métodos
es definir una interfaz que especifique los métodos para la escalabilidad y que las clases
Circulo y Rectangulo la implementen.
236 CAPÍTULO 9. CLASES ABSTRACTAS E INTERFACES

FiguraGeométrica

Punto Línea Polígono Círculo

Triángulo Rectángulo Pentágono

Figura 9.2 Jerarquı́a de figuras geométricas.

La definición sintáctica de una interfaz es similar a la de cualquier clase, excepto que


empieza con la palabra interface, en lugar de class, como se muestra a continuación:

visibilidad interface nombre {


declaración de constantes y métodos
}

La interfaz puede definirse pública o sin modificador de acceso, y tiene el mismo significa-
do que para las clases. En el cuerpo de la interfaz aparecen constantes públicas y firmas de
métodos públicos. Debido a que no tiene sentido utilizar cualquier otro calificador de visibi-
lidad para los atributos y métodos de una interfaz, pueden omitirse y se asume que todo es
público. Una interfaz puede extender otra interfaz, es decir, está permitido crear jerarquı́as
de interfaces.
Ejemplo 9.12. Definición de la interfaz para escalar.

/**
* Interfaz para definir comportamiento de objetos que se pueden escalar
* @author Amparo López Gaona
* @version 3a edición
*/
public interface Inflable {
/**
* Método para inflar un objeto en la cantidad indicada
* @param cuanto -- cantidad en la que crecerá el objeto
*/
public void inflar (int cuanto);

/**
* Método para desinflar un objeto en la cantidad indicada
9.3 INTERFACES 237

* @param cuanto -- cantidad en la que encogerá el objeto


*/
public void desinflar (int cuanto);
}

Notar que no se especifica el cuerpo de los métodos y, sin embargo, no se preceden de la


palabra abstract, porque por definición todos los métodos de una interfaz son abstractos.
La interfaz Inflable está representando lo que un objeto de la clase que la implemente
es capaz de hacer. En términos de programación, las interfaces permiten decir a una clase
cómo debe comportarse. Lo común es que las interfaces tengan un nombre que termine con
el sufijo able.
Toda interfaz, al igual que toda clase, define un tipo de datos, esto significa que pueden
declararse variables del tipo de una interfaz. Debido a que el tipo de datos definido en una
interfaz no define la implementación de ningún método, no se pueden generar objetos de tal
tipo, pero sirven como supertipos para objetos de las clases que implementan la interfaz.
Para usar una interfaz se debe especificar en una clase que ésta la implementa mediante
la palabra reservada implements. La clase que implementa la interfaz debe implementar al
menos todos los métodos de ella, es decir, puede contener otros métodos. Resulta indispen-
sable que la firma de cada método definido en la interfaz coincida con la firma del método en
la clase que los implementa. De otra forma se tiene sobrecarga de métodos y el compilador
envı́a un mensaje de error indicando que falta de implementarse un método.
Ejemplo 9.13. Implementación de la interfaz Inflable por la clase Circulo.

/**
* Clase de cı́rculos que pueden escalarse
* @author Amparo López Gaona
* @version 3a edición
*/
public class Circulo extends FiguraGeometrica implements Inflable {
private Punto centro;
private int radio;
/*
* Constructor por omisión. Crea un cı́rculo con centro en el origen y radio 1
*/
public Circulo(){
this(new Punto(),1);
}
// Otros constructores

/* Método para calcular el área de un cı́rculo


* @return double -- área de un cı́rculo
*/
238 CAPÍTULO 9. CLASES ABSTRACTAS E INTERFACES

public double area() {


return Math.PI*radio*radio;
}
/* Método para calcular el perı́metro de un cı́rculo
* @return double -- perı́metro de un cı́rculo
*/
public double perimetro() {
return Math.PI*radio*2;
}
/* Método para inflar un cı́rculo
* @param int -- cantidad en que va a crecer el cı́rculo
*/
public void inflar (int cuanto) {
if (cuanto >0)
radio += cuanto;
}
/* Método para desinflar un cı́rculo
* @param int -- cantidad en que va a decrecer el cı́rculo
*/
public void desinflar (int cuanto) {
if (cuanto > 0)
radio -= cuanto;
}
/* Método para obtener la representación del cı́rculo como cadena
* @return String -- cadena que tiene la representación del cı́rculo
*/
public String toString() {
return "Circulo con centro en "+centro+" y radio "+radio;
}
}

Cuando se extiende una clase e implementan interfaces, primero se debe especificar la


superclase de la que se heredará y luego las interfaces que se implementarán. En general,
puede decirse que por convención la cláusula extends precede a implements.
En la clase Circulo se tienen métodos propios como son los constructores, el método para
calcular el área, el método para calcular el perı́metro y la implementación de los métodos
definidos en la interfaz.
El compilador se encarga de verificar que la clase efectivamente declare e implemente todos
los métodos de la interfaz. En caso de no hacerlo envı́a un mensaje como el siguiente:

Circulo.java:6: Circulo is not abstract and does not override abstract


method desinflar(int) in Inflable
9.3 INTERFACES 239

Una vez definida la interfaz, ésta puede ser implementada por varias clases. Por ejemplo,
también puede ser implementada por la clase Rectangulo.
Ejemplo 9.14. Implementación de la interfaz Inflable por la clase Rectangulo.

/**
* Clase de rectángulos que pueden escalarse
* @author Amparo López Gaona
* @version 3a edición
*/
public class Rectangulo FiguraGeometrica implements Inflable {
private Punto centro;
private int largo, ancho;

/**
* Constructor por omisión. Crea un rectángulo con centro en el origen, largo 2
* y ancho 1
*/
public Rectangulo(){
this(new Punto(0,0),2,1);
}
//... Otros constructores

/** Método para calcular el área de un rectángulo


* @return double -- área de un rectángulo
*/
public double area() {
return largo * ancho;
}
/** Método para calcular el perı́metro de un rectángulo
* @return double -- perı́metro de un rectángulo
*/
public double perimetro() {
return 2* (largo + ancho);
}
/** Método para inflar un rectángulo
* @param cuanto -- cantidad en que crecerá el largo y el ancho del rectángulo
*/
public void inflar (int cuanto) {
if (cuanto >0) {
largo += cuanto;
ancho += cuanto;
}
}
240 CAPÍTULO 9. CLASES ABSTRACTAS E INTERFACES

/** Método para desinflar un rectángulo


* @param cuanto -- cantidad en que decrecerá el largo y el ancho del rectángulo
*/
public void desinflar (int cuanto) {
if (cuanto > 0) {
largo -= cuanto;
ancho -= cuanto;
}
}
/** Método para obtener la representación en cadena del rectángulo
* @return String -- Cadena con la representación del rectángulo
*/
public String toString() {
return "Rectangulo con centro en "+centro+", largo "+largo+" y ancho "+ ancho;
}
}

9.4 Referencias a interfaces


Es posible crear referencias a interfaces, pero al igual que con las referencias a objetos de las
clases abstractas, no es posible generar objetos de ellas. En una referencia a una interfaz se
almacena una referencia a un objeto de la clase que implementa tal interfaz.
Ejemplo 9.15. Programa que utiliza una referencia a una interfaz.

/**
* Programa para probar referencia a interfaces
* @author Amparo López Gaona
* @version 3a edición
*/
public class PruebaCirculosInflables {
public static void main(String[] pps) {
Inflable bola = new Circulo(new Punto(4,4),3);
System.out.println(bola);
bola.inflar(20);
System.out.println(bola);
bola.desinflar(3);
System.out.println(bola);
}
}

En el programa se crea una referencia a Inflable (que bien podrı́a ser directamente a
Circulo), y se le asigna el valor devuelto por el constructor de Circulo.
9.4 REFERENCIAS A INTERFACES 241

Debido a que la clase Rectangulo también implementa la interfaz Inflable, en este caso
con la referencia a la interfaz, se puede tener polimorfismo.
Ejemplo 9.16. Polimorfismo con referencias a objetos de clases que implementan la interfaz
Inflable.

/**
* Programa para probar el polimorfismo mediante referencia a interfaces
* @author Amparo López Gaona
* @version 3a edición
*/
class PruebaPolimorfismoInterfaces {
public static void main(String[] pps) {
Inflable [] figuras = new Inflable[5];

figuras[0] = new Rectangulo(new Punto(4,4),30,5);


figuras[1] = new Rectangulo();
figuras[2] = new Circulo();
figuras[3] = new Circulo(new Punto(-20,14), 10);
figuras[4] = new Rectangulo(new Punto(-1,-1), 20,3);

for (int i= 0; i < figuras.length; i++){


System.out.println(figuras[i]);
}

for (int i= 0; i < figuras.length; i++) {


figuras[i].inflar(2);
}
System.out.println("Despues de inflar");
for (int i= 0; i < figuras.length; i++) {
System.out.println(figuras[i]);
}
}
}

En el programa PruebaPolimorfismoInterfaces se define un arreglo de referencias a


objetos de clases que implementan la interfaz Inflable, y esto es muy válido. El arreglo
se llena con referencias a cı́rculos y rectángulos inflables. Al imprimirlos dentro de una
instrucción for y luego al inflarlos se hace uso del polimorfismo, porque cada clase tiene su
propio método toString e inflar, debido a que se implementan de manera diferente. En
el caso de los cı́rculos, en el método inflar se modifica el radio y en el de los rectángulos
se modifica el largo y el ancho. Sin embargo, cuál método toString e inflar utilizar lo
determina Java al momento de ejecución.
242 CAPÍTULO 9. CLASES ABSTRACTAS E INTERFACES

9.5 Implementación de más de una interfaz


Suponer que el problema que se va a resolver con la jerarquı́a de figuras geométricas de la
figura 9.2 requiere que algunas figuras se puedan desplazar. Estas figuras son el cı́rculo y el
cuadrado. Es decir, se requiere que el cı́rculo, que es una figura geométrica, sea tanto inflable
como desplazable. Si Desplazable fuera una clase, como se muestra en la figura 9.3, no se
podrı́a resolver el problema debido a que en Java se permite heredar, directamente, sólo de
una clase.

FiguraGeométrica Desplazable Inflable

Circulo

Figura 9.3 Cı́rculo heredando de dos clases.

Sin embargo, si Desplazable fuera Interfaz se puede resolver el problema porque en Java
está permitido implementar más de una interfaz.
Ejemplo 9.17. Definición de la interfaz para desplazar objetos.

/**
* Interfaz para definir comportamiento de objetos que se pueden desplazar
* @author Amparo López Gaona
* @version 3a edición
*/
public interface Desplazable {
/**
* Método para desplazar un objeto en la cantidad indicada
* @param cuanto -- cantidad en la que se desplazará el objeto
*/
public void desplazar (int cuanto);

/**
* Método para desplazar un objeto a la posición indicada
* @param nuevaPosicion -- posición en la que se colocaré el objeto
*/
public void desplazar (Punto nuevaPosicion);
}

Ejemplo 9.18. Al tener la interfaz anterior la implementación de la clase Circulo solicitada


queda como sigue:
9.5 IMPLEMENTACIÓN DE MÁS DE UNA INTERFAZ 243

/**
* Clase de cı́rculos que pueden escalarse y desplazarse
* Objetivo: Ilustrar la implementación de dos interfaces
* @author Amparo López Gaona
* @version 3a edición
*/
class Circulo extends FiguraGeometrica implements Inflable, Desplazable {
private Punto centro;
private int radio;

// Todo como antes más los siguientes métodos

/**
* Método para desplazar un objeto en la cantidad indicada
* @param cuanto -- cantidad en la que se desplazará el objeto
*/
public void desplazar (int cuanto) {
centro.asignarX(centro.obtenerX()+cuanto);
centro.asignarY(centro.obtenerY()+cuanto);
}
/**
* Método para desplazar un objeto a la posición indicada
* @param nuevaPosicion -- posición en la que se colocaré el objeto
*/
public void desplazar (Punto nuevaPosicion) {
centro = nuevaPosicion;
}
/**
* Método para convertir a cadena un cı́rculo
+ @return String -- cadena que contiene la información del cı́rculo
*/
public String toString() {
return "Circulo con centro en "+centro+" y radio "+radio;
}
}

Cuando una clase implementa varias interfaces éstas se listan después de la palabra reser-
vada implements, separadas por coma.
Conceptualmente, es posible relacionar clases sin relación aparente a través de las inter-
faces. Por ejemplo, se podrı́a tener una clase para un videojuego, en donde se desplazan
las figuras del mismo y la forma de asegurar que se desplacen es implementando la interfaz
Desplazable pero estas figuras no están obligadas a pertenecer a la jerarquı́a de figuras
geométricas pues podrı́an ser aves, bicicletas, letras, monstruos, etc.
244 CAPÍTULO 9. CLASES ABSTRACTAS E INTERFACES

9.6 Definición de grupos de constantes


Debido a que todos los atributos que se definen en una interfaz son constantes, las interfaces
pueden utilizarse para definir grupos de valores constantes.
Ejemplo 9.19. Definición de constantes usando una interfaz.
public interface Meses {
int ENERO = 1, FEBRERO = 2, MARZO = 3, ... ;
}

Con esta interfaz se están definiendo 12 constantes enteras correspondientes a los meses del
año. Obviamente esta interfaz no tiene que ser implementada, ası́ que para utilizarla basta
usar la notación punto como sigue: nombreDeInterfaz.nombreDeConstante. Por ejemplo,
Meses.ABRIL en lugar de poner un 4.
Ejemplo 9.20. Interfaz con constantes numéricas y cadenas.

/**
* Interfaz para definir los meses del a~
nos con número y con letra
* @author Amparo López Gaona
* @version 3a edición
*/
public interface Meses {
int ENERO = 1, FEBRERO = 2, MARZO = 3, ... ;

String [] NOMBRE_MES = { " " , "Enero" , "Febrero" , ... };


}

Y un ejemplo de uso, System.out.println(Meses.NOMBRE MES[Meses.ENERO]); con lo


cual el programa imprime Enero.

9.7 Interfaces definidas en Java


Java proporciona gran cantidad de interfaces definidas para diferentes tareas. En esta sección
se presenta una utilizada para comparar dos objetos.
En el ejemplo 6.10 se presentó el método mayorCalificacion para encontrar la mayor
calificación de un alumno. Este método trabaja con números enteros, sin embargo puede
generalizarse y crear una clase para poder encontrar el mayor elemento de un arreglo de
objetos. Recordar que en lugar de Object puede usarse cualquiera de sus subclases. El
problema se presenta al tratar de determinar la relación de orden entre dos objetos.
El operador < no puede utilizarse para comparar dos objetos, pues está definido sólo para
datos numéricos. El método equals sólo puede determinar si dos objetos son iguales o
diferentes, pero no su relación de orden. Entonces se debe crear un método para comparar
9.7 INTERFACES DEFINIDAS EN JAVA 245

dos objetos, pero los objetos y el método para comparar tienen que ser de una subclase
particular, porque de otra forma no se puede saber de antemano cómo comparar dos objetos
cualquiera.
Podrı́a crearse una clase abstracta que sólo tuviera un método para comparar y de esta
forma obligar a sus descendientes a implementarlo. Si este es el caso, es mejor definir una
interfaz con ese método y que las subclases de Object lo implementen. Como la tarea de com-
paración de objetos es muy común, en el paquete java.util existe la interfaz Comparator,
definida como sigue:

public interface Comparator {


public int compare(Object o1, Object o2);
public boolean equals(Object obj);
}

Esta interfaz para comparar objetos tiene dos métodos, uno para determinar la relación
de orden entre dos objetos y el método para determinar si dos objetos son iguales o no. El
método compare recibe dos objetos y devuelve un entero mayor que cero si el objeto o1 es
mayor que o2; devuelve un número entero negativo si o1 es menor que o2 y devuelve cero si
ambos objetos son iguales. El método equals tiene el mismo significado que el de la clase
Object.
Ejemplo 9.21. Implementación de un comparador para objetos de la clase Alumno, de
acuerdo con su promedio.

/**
* Clase que implementa la interfaz Comparator para comparar alumnos
* @author Amparo López Gaona
* @version 3a edición
*/
public class ComparaAlumnos implements Comparator {
public int compare(Object o1, Object o2) {
if ((o1 instanceof Alumno) && (o2 instanceof Alumno)) {
if (o1 == o2) return 0;
return (int)((((Alumno)o1).promedio() - ((Alumno)o2).promedio())*100);
}
throw new IllegalArgumentException(
"Los objetos a comparar no son de la clase Alumno");
}
}

La clase ComparaAlumnos determina, dados dos alumnos, cuál tiene mayor promedio. Pri-
mero verifica que ambas referencias sean a objetos de la clase Alumno, si no es el caso dispara
la excepción IllegalArgumentException porque no se debe modificar la firma de ningún
246 CAPÍTULO 9. CLASES ABSTRACTAS E INTERFACES

método de una interfaz. Esta excepción no tiene que especificarse en la firma por ser subclase
de RuntimeException.
Si la referencia a cada alumno es la misma, se trata del mismo alumno y por eso devuelve
cero. En otro caso compara los promedios con una resta, si es positiva el promedio del primer
alumno es mayor que el del segundo, si es cero los promedios son iguales y si es negativa el
promedio del segundo alumno es mayor que el del primero. Para hacer la diferencia primero
se hace una conversión explı́cita de los objetos pasados como parámetro que son de la clase
Object a objetos de la clase Alumno para poder aplicarles el método promedio. Después el
resultado de la diferencia se multiplica por 100, debido a que si éste es menor que uno, al
convertir a entero lo trunca y da como resultado cero que significa que ambos promedios son
iguales, lo cual es incorrecto.
Ejemplo 9.22. Estructura de la clase para determinar el mayor elemento de un arreglo de
objetos de cualquier clase.
Cuando se tiene una clase en la que se define alguno de los atributos de su estructura como
objeto de la clase Object se tiene una clase genérica puesto que no está definida para
objetos de clases particulares.

import java.util.Comparator;
/*
* Clase para determinar el mayor de un arreglo de objetos
* @author Amparo López Gaona
* @version 3a edición
*/
public class ObjetoMayor {
private Object [] arreglo;
private final Comparator prueba;

La clase ObjetoMayor tiene un arreglo de objetos y un objeto de alguna implementación de


Comparator para tener la programación de la comparación necesaria para el tipo de objetos
que se trate.
Ejemplo 9.23. Constructor de la clase ObjetoMayor.

/**
* Constructor para la clase Objeto Mayor
* @param c - un comparador
* @param o - arreglo de objetos donde se buscará
*/
ObjetoMayor(Object[] o, Comparator c) {
arreglo = o;
prueba = c;
}
9.7 INTERFACES DEFINIDAS EN JAVA 247

El nombre de la interfaz puede usarse como un tipo para un parámetro formal de cualquier
método, en este caso se usa en el constructor para especificar el método de comparación entre
estos objetos. El parámetro real debe ser alguna implementación de esa interfaz.
Ejemplo 9.24. Método para determinar el elemento mayor de un arreglo de objetos.

/**
* Método para comparar objetos de un arreglo, utilizando un comparador
* @return int - posición del objeto mayor
*/
public Object mayor() {
Object max = arreglo[0];

for (int i = 1; i < arreglo.length; i++)


if (prueba.compare(max,arreglo[i]) < 0)
max = arreglo[i];
return max;
}

Se asume que el elemento mayor está en la primera localidad del arreglo y luego se compara
con los otros elementos buscando el mayor de ellos. Para comparar los elementos del arreglo se
hace uso del método compare del objeto de la clase que implemente la interfaz Comparator.
Esta clase es la que se envı́a al constructor de la clase ObjetoMayor.
Ejemplo 9.25. Clase para probar la clase ObjetoMayor. En el método main se crea un
arreglo de objetos de la clase Alumno, luego un objeto de la clase ObjetoMayor, al cual se
pasa como parámetro un objeto de ComparaAlumnos y el arreglo recién creado; finalmente
se llama al método mayor con el objeto creado como parámetro.

public class PruebaComparator {


public static void main(String[] pps) {
Alumno[] clase = new Alumno[5];
ObjetoMayor mejorAlumno;

for (int i=0; i < clase.length; i++) {


System.out.println("Dar los datos del alumno número "+(i+1));
// Lee los datos
clase[i] = new Alumno(.., ..., ..., );
}

mejorAlumno = new ObjetoMayor(clase, new ComparaAlumnos());


System.out.println("El mayor es "+ (Alumno)mejorAlumno.mayor());
}
}
248 CAPÍTULO 9. CLASES ABSTRACTAS E INTERFACES

Si ahora se requiere usar otro criterio de comparación sólo es necesario cambiar la imple-
mentación de la interfaz.
Ejemplo 9.26. Comparación de alumnos por promedio y nombre. Por lo que ahora, si dos
alumnos tienen igual promedio, entonces se compara el nombre de cada uno, esta vez usando
el método compareTo de clase String.

/**
* Clase que implementa la interfaz Comparator para comparar alumnos
* @author Amparo López Gaona
* @version 3a edición
*/
import java.util.Comparator;

public class ComparaAlumnos implements Comparator {


public int compare(Object o1, Object o2) {
if ((o1 instanceof Alumno) && (o2 instanceof Alumno)) {
if (o1 == o2) return 0;
Alumno a1 = (Alumno) o1, a2 = (Alumno) o2;
int difPromedio = (int) (a1.promedio() - a2.promedio());
if (difPromedio == 0)
return a1.obtenerNombre().compareTo(a2.obtenerNombre()) * (-1);
else
return difPromedio;
}
throw new RuntimeException("Los objetos no son de la clase Alumno");
}
}

Como puede apreciarse en el ejemplo, la programación del comparador puede ser tan
compleja como se requiera.
Si se requiere comparar otro tipo de objetos, por ejemplo, cı́rculos por su diámetro, personas
por su estatura, clientes por sus compras anuales, etcétera, sólo es necesario cambiar la
implementación de la interfaz e indicar al constructor de la clase ObjetoMayor la clase que
implementa la interfaz Comparator.
La clase ObjetoMayor funciona para almacenar objetos de cualquier clase, debido a que
como se vio en el capı́tulo 7, en lugar de un objeto de una cierta clase (en este caso Object)
puede ir algún objeto de cualquiera de sus subclases. Sin embargo, los tipos primitivos no
son clases, por tanto no son subclases de Object, ası́ que el método mayor no trabaja con
tipos primitivos. Esto no es un problema sin solución porque Java proporciona envolturas
para cada tipo primitivo.
Una envoltura es una clase que permite trabajar con un dato primitivo como si fuera
un objeto y por lo tanto usarlo cuando se requiera un objeto de la clase Object. En el
9.7 INTERFACES DEFINIDAS EN JAVA 249

paquete java.lang existen envolturas para todos los tipos primitivos, éstas son Boolean,
Byte, Double, Float, Integer, Long, Short y Character que corresponden a los tipos
primitivos boolean, byte, double, float, int, long, short y char, respectivamente.
Estas clases tienen dos tipos de métodos:

• Constructores con un parámetro que corresponde a su tipo primitivo. Estos constructo-


res crean la referencia al objeto que almacena el valor proporcionado como parámetro.
Ejemplo 9.27. Métodos para permitir que el dato primitivo sea tratado como un
objeto de la clase que lo envuelve. Por ejemplo, para la clase Integer se puede tener
lo siguiente:

Integer miEntero; // Define un objeto de la clase Integer


int i = 242; // Define una variable de tipo entero.

miEntero = new Integer(i); //Convierte el entero a objeto

• Métodos que regresan el valor almacenado dentro de la envoltura. Estos métodos ge-
neralmente tienen como nombre el del tipo primitivo seguido de la palabra Value y no
tienen parámetros.
Ejemplo 9.28. Para convertir un objeto de la clase Integer a entero (int) se usa el
método intValue de la clase Integer como sigue:

i = miEntero.intValue();

Ejemplo 9.29. Comparador para enteros. Se puede definir un comparador para números
enteros como sigue:

/**
* Clase que implementa la interfaz Comparator para comparar enteros
* @author Amparo López Gaona
* @version 3a edición
*/
private class ComparaEnteros implements java.util.Comparator {
public int compare(Object o1, Object o2) {
if ((o1 instanceof Integer) && (o2 instanceof Integer)) {
return ((Integer)o1).intValue() - ((Integer)o2).intValue();
}
throw new RuntimeException("Los objetos no son de la clase Integer");
}
}
250 CAPÍTULO 9. CLASES ABSTRACTAS E INTERFACES

Esta clase se puede incluir en ObjetoMayor, además de un constructor que no reciba


comparador y utilice el de enteros.
Ejemplo 9.30. Otro constructor para la clase ObjetoMayor.

/**
* Constructor para trabajar con objetos de la clase Integer
* @param enteros - arreglo donde se buscará el elemento mayor
*/
ObjetoMayor(Integer[] enteros) {
this(enteros, new ComparaEnteros());
}

9.8 Clases abstractas vs. interfaces


A simple vista parece que las interfaces y las clases abstractas son lo mismo, pero no es el
caso, existen diferencias entre ellas, como son:

• En una interfaz no se implementa ningún método (aunque no se precedan de la palabra


abstract). En una clase abstracta puede haber implementaciones.

• En una interfaz no pueden existir elementos privados. En una clase abstracta puede
haber privados, protegidos y públicos.

• Una interfaz no es parte de la jerarquı́a de clases. Clases sin relación pueden imple-
mentarse en la misma interfaz.

• Una interfaz no tiene constructores. Una clase abstracta puede tener constructores.

• Una interfaz no puede extender clases. Una clase abstracta puede extender otra clase.

• Al implementar una interfaz se deben implementar todos los métodos definidos en ella.
Al extender una clase abstracta es posible posponer la implementación de métodos.

• Una clase puede implementar varias interfaces, pero sólo puede tener una superclase.

9.9 Ejercicios
1. ¿Cómo se especifica una interfaz en Java? ¿Cómo se implementa?

2. Describir en qué se diferencia una interfaz de una clase abstracta.

3. ¿Es posible crear objetos de clases abstractas? ¿Y de interfaces?


9.9 EJERCICIOS 251

4. ¿Qué significa tener una variable de tipo referencia a una clase abstracta? ¿Y a una
interfaz?

5. Describir cómo ayuda a la extensibilidad el uso del polimorfismo.

6. Describir el significado de la siguiente clase y las restricciones que impone.

abstract public class CuentaX {


private double saldo;

public double obtenerSaldo () {


return saldo;
}
public void deposito (final double monto){
// Código del método
}
abstract public double prestamo (final double monto);
}

7. Describir y corregir los errores del siguiente código. Es importante mantener una in-
terfaz y una clase.

interface Erronea {
int valor;
public Erronea() { valor = 45;}
public void método1(int a) { valor = a; }
public void método2(int a, int b);
}
public class algoEstaMal extends Erronea {
int dato;
public algoEstaMal() { dato = 0; }
public algoEstaMal(int i) { dato = i; }
public void método2(int a, int b) { dato = a/b; }
}

8. Escribir un programa que defina una jerarquı́a de clases teniendo como raı́z la clase abs-
tracta FiguraGeometrica. La jerarquı́a debe incluir las clases FiguraBidimensional,
FiguraTridimensional, Cuadrado, Cubo, Triángulo, Esfera, etc. Se deben defi-
nir los métodos abstractos para estas clases. ¿Alguna otra clase serı́a abstracta?

9. Dar un ejemplo de polimorfismo tomando la jerarquı́a de clases del problema 7.8.

10. Dar un ejemplo de polimorfismo tomando la jerarquı́a de clases del problema 7.12.
252 CAPÍTULO 9. CLASES ABSTRACTAS E INTERFACES

11. ¿Cuál es el resultado del siguiente programa?

public class Padre {


public void mostrar() {
System.out.println("Soy el padre");
}
}
interface Desplegable {
public void desplegar();
}
public class Hija1 extends Padre {
public void desplegar () {
System.out.println("Soy el hijo");
}
}
public class Hija2 extends Padre implements Desplegable {
public void mostrar() {
System.out.println("Soy el hijo2");
}
public void desplegar () {
mostrar();
}
}
public class Main {
static public void main(String[] pps) {
Padre [] a = new Padre[3];

a[0] = new Hija1();


a[1] = new Hija2();
a[2] = new Padre();
for (int i= 0; i <3; i++) {
a[i].mostrar();
}
for (int i= 0; i <2; i++) {
a[i].desplegar();
}
}
}

12. Crear una jerarquı́a de clases a partir de la clase abstracta Colección. Las clases de
esta jerarquı́a deben tener métodos para manejar colecciones de datos del mismo tipo,
por ejemplo, pilas y colas. La clase Colección debe tener métodos para insertar un
9.9 EJERCICIOS 253

elemento en la colección, suprimir un elemento de la colección, imprimir los datos de


la colección, determinar si la colección está vacı́a, determinar si la colección está llena
y determinar si un dato está en la colección. La subclase Pila sólo permite sacar el
último elemento que ha insertado; por su parte, en la subclase Cola los elementos se
pueden suprimir sólo en el orden en que se insertaron.

13. Escribir un programa para que indique los posibles movimientos de una pieza de ajedrez
dada una posición. Considerar que el tablero tiene piezas en las posiciones indicadas.
Crear una clase abstracta que sea la pieza de ajedrez y clases particulares para los
peones, reina, rey, alfil, caballo y torre. Cada pieza debe tener su posición y color.

14. Agregar a la jerarquı́a de clases definida en el ejercicio 8 una interfaz Desplazable y


otra Coloreable para que ciertas figuras puedan desplazarse y mostrarse de color.

15. Escribir una clase Conjunto para trabajar con objetos de cualquier clase que imple-
mente la interfaz Conjuntable.

/**
* Interfaz que describe las operaciones sobre conjuntos
* @author Amparo Lopez Gaona
* @version 3a edición
*/
public interface Conjuntable {
/** Método para obtener el tama~
no de un conjunto
* @return int -- Cantidad de elementos en el conjunto
*/
public int tamanio();
/** Método para agregar un elemento al conjunto
* @param elemento -- Objeto que se incorporara al conjunto
*/
public void agregar(Object elemento);
/** Método para eliminar un elemento al conjunto
* @param elemento -- Objeto que se eliminara del conjunto
*/
public void eliminar(Object elemento);
/** Método para determinar si un elemento pertenece al conjunto
* @param elemento -- Objeto que se va a buscar en el conjunto
* @return true -- si el elemento esta en el conjunto y false en otro caso.
*/
public boolean contiene (Object elemento);
/** Método para obtener la union de dos conjuntos
254 CAPÍTULO 9. CLASES ABSTRACTAS E INTERFACES

* @param c1 -- conjunto que se va unir


* @return Conjuntable -- conjunto con la union
*/
public Conjuntable union(Conjuntable c1);
/** Método para obtener la interseccion de dos conjuntos
* @param c1 -- conjunto que se va intersectar
* @return Conjuntable -- conjunto con la interseccion
*/
public Conjuntable interseccion(Conjuntable c1);
/** Método para obtener la diferencia de dos conjuntos
* @param c1 -- conjunto que con el se va a calcular la diferencia
* @return Conjuntable -- conjunto con la diferencia
*/
public Conjuntable diferencia(Conjuntable c1);
}
Capı́tulo 10

Serialización de objetos

Los datos con los que trabajan los programas desarrollados hasta ahora no permanecen
de una ejecución del programa a otra. Hay ocasiones en los que esto no es necesario, por
ejemplo, en los juegos. Sin embargo, en programas como la nómina es importante que los
datos permanezcan más allá de la ejecución del programa. En este capı́tulo se presenta la
forma de conseguir que los objetos que se crean en un programa no se destruyan al terminar
la ejecución del mismo, es decir, que sigan existiendo independientemente de que el programa
termine su ejecución.

10.1 Objetos serializables


La serialización de objetos permite escribir objetos a archivos con una sola instrucción, con
lo cual quedan grabados hasta que se decida eliminarlos o modificarlos. También permite
recuperar los objetos grabados en archivos.
La serialización de un objeto consiste en generar una secuencia de bytes lista para su
almacenamiento o transmisión. Después, mediante el proceso inverso, se puede recuperar
el estado original del objeto. Guardar un objeto para que pueda existir incluso cuando la
aplicación finaliza; se conoce como persistencia.
Para que objetos de una clase puedan serializarse, es decir, hacerse persistentes, es necesario
que la clase de tales objetos implemente la interfaz Serializable, que se encuentra definida
en el paquete java.io. La interfaz Serializable tiene el siguiente código:
public interface Serializable {
}

Lo curioso es que esta interfaz no define métodos, por tal razón es conocida como una
interfaz de marcado. Cualquier clase que implemente esta interfaz puede grabar y leer objetos
de un archivo sin mayor intervención por parte del programador, es decir, el proceso de
serialización se lleva a cabo automáticamente por el sistema de ejecución en Java.

255
256 CAPÍTULO 10. SERIALIZACIÓN DE OBJETOS

Para que un objeto sea serializable todas las variables de la estructura deben ser serializa-
bles. Todos los tipos primitivos, los objetos de la clase String y algunos de otras clases de
Java son serializables.
Ejemplo 10.1. Preparando la clase Persona para poder serializar sus objetos.

import java.io.Serializable;

public class Persona implements Serializable {


private String nombre;
private int peso;
private double estatura;
...
}

Debido a que la interfaz Serializable se encuentra en el paquete java.io se debe incluir


una instrucción import para indicar en donde encontrarla.

10.2 Serialización
Para serializar objetos, además de especificar que serán serializables se requiere trabajar con
un archivo, en el cual se almacenen o bien se recuperen de ahı́ cuando se requiera.
El trabajo con archivos involucra tres tareas: abrir el archivo, leer/escribir el dato, cerrar el
archivo. En cada una de ellas es posible que exista algún error, no necesariamente atribuible
al programador. Por ejemplo, puede ser que se requiera leer un archivo inexistente, que el
disco esté lleno y por lo mismo no pueda grabarse la información deseada, etcétera, por lo que
los métodos que implementan estas tareas deben tratar con las excepciones correspondientes,
por lo menos con la excepción IOException definida en el paquete java.io. En este paquete
están también definidas una serie de subclases de esta excepción, entre ellas EOFException
y FileNotFoundException.
Para grabar objetos en un archivo, es decir, para serializar, se requiere un objeto de la
clase ObjectOutputStream; esta clase está contenida en el paquete java.io. Para crear tal
objeto es necesaria la siguiente instrucción:
ObjectOutputStream obj = new ObjectOutputStream(new FileOutputStream(nombreArch));

Una vez creado el objeto de la clase ObjectOutputStream se puede utilizar el método


writeObject(objeto) para grabar el objeto que toma como parámetro. Si el objeto que se
intenta grabar no es de una clase que implemente la interfaz Serializable se dispara la
excepción NotSerializableException.
Al terminar de serializar todos los objetos se debe llamar al método close para asegurar
que no se pierdan los objetos grabados en el archivo especificado. Independientemente de
10.2 SERIALIZACIÓN 257

que haya habido un error o no, es necesario cerrar el archivo por esto está incluida en la
clausula finally.
Ejemplo 10.2. Serialización de objetos de la clase Persona presentada en el ejemplo 10.1.
/**
* Clase para probar la serialización de objetos de la clase Persona
* @author Amparo López Gaona
* @version 3a edición
*/
public class PruebaSerializacion {
public static void main (String [] pps) {
ObjectOutputStream escritor = null;
String nombreDeArchivo = (pps.length != 0) ? pps[0] : "prueba.ser";

try {
escritor = new ObjectOutputStream (new FileOutputStream(nombreDeArchivo));
escritor.writeObject(new Persona("Andrea", 50, 1.52));
escritor.writeObject(new Persona("Maria", 52, 1.40));
escritor.writeObject(new Persona("Daniela", 50, 1.40));
escritor.writeObject(new Persona("Mariana", 45, 1.20));
} catch(NotSerializableException e){
System.out.println("Error en la grabacion. Objeto no serializable.");
} catch(IOException e){
System.out.println("Error en la grabacion: "+e);
} finally {
if (escritor != null) {
System.out.println("Cerrando el archivo "+nombreDeArchivo);
try { escritor.close(); } catch (IOException e) {}
} else {
System.out.println("No hay ningun archivo abierto.");
}
}
}
}

El programa espera recibir como parámetro el nombre del archivo en que se grabará la
información, si no lo recibe se grabará en el archivo prueba.ser. En la instrucción try se
abre el archivo y se graban cuatro objetos de la clase Persona. Esta instrucción tiene dos
cláusulas catch, la primera es para atrapar la excepción NotSerializableException en
caso de que Persona no implemente la interfaz Serializable. La segunda cláusula catch
es para cualquier otra excepción de las que pudiera dispararse.
Recordar del capı́tulo 8 que la cláusula finally se ejecuta independientemente de que se
haya disparado o no una excepción. Por esa razón se utiliza para cerrar el archivo. Aunque
258 CAPÍTULO 10. SERIALIZACIÓN DE OBJETOS

es raro, podrı́a darse el caso que ocurriera algún error al ejecutar el método close, por eso
también está incluido en una cláusula try. Aquı́ primero se verifica que se haya abierto el
archivo, si es el caso envı́a un mensaje y lo cierra.
Como se ha mencionado, la serialización de objetos convierte éstos a formato binario propio
de Java, ası́ que el contenido de un archivo creado con esta técnica no puede ser visto por
un editor de textos ni mostrado en la pantalla.

10.3 Deserialización
La operación complementaria a grabar objetos es la de recuperarlos. Para recuperar objetos
de un archivo se requiere crear un objeto de la clase ObjectInputStream como sigue:

ObjectInputStream objeto = new ObjectInputStream(new FileInputStream(nombreArch));

Para leer el objeto se utiliza el método readObject(), el cual construye un objeto de la


clase indicada pero lo regresa como una referencia de tipo Object, por lo que es necesario
hacer una conversión explı́cita.
Ejemplo 10.3. Recuperación de objetos almacenados en un archivo.

/**
* Clase para probar la recuperación de objetos serializados de la clase Persona
* @author Amparo López Gaona
* @version 3a edición
*/
public class PruebaDeserializacion {
public static void main (String [] pps) {
ObjectInputStream lector = null;
String nombreDeArchivo = (pps.length != 0) ? pps[0] : "prueba.ser";

try {
lector = new ObjectInputStream (new FileInputStream(nombreDeArchivo));
Persona obj;
do {
obj = (Persona) lector.readObject();
System.out.println(obj);
} while (obj != null);
} catch(EOFException e) {
System.out.println("Fin de la lectura del archivo");
}catch (ClassNotFoundException e) {
System.out.println("El objeto recuperado no es de la clase Persona");
} catch(IOException e) {
System.out.println("Error "+ e);
10.4 SERIALIZACIÓN Y AGREGACIÓN 259

} finally {
if (lector != null) {
System.out.println("Cerrando el archivo "+nombreDeArchivo);
try { lector.close(); } catch (IOException e) {}
} else {
System.out.println("No hay archivo abierto.");
}
}
}
}

El programa es parecido al presentado en el ejemplo 10.2, excepto que ahora se recuperan


los objetos de un archivo. Nuevamente, se incluyen las instrucciones para abrir archivo y leer
de un archivo en una instrucción try, porque en cualquiera de ellas podrı́a ocurrir un error.
Al llegar al final del archivo se dispara la excepción EOFException, en este caso se envı́a un
mensaje para indicar que se acabó la lectura, pero podrı́a no hacerse nada en la cláusula
catch, aunque es necesario atrapar la excepción porque de otra forma el programa termina
abruptamente.
Al intentar hacer una conversión explı́cita a un tipo diferente del recuperado se dispara la
excepción ClassNotFoundException. En el ejemplo no ocurre, pero es bueno estar prevenido.
Recordar que las cláusulas catch deben colocarse en orden contrario a su jerarquı́a, por
eso la última atrapa IOException para atrapar cualquier excepción no contemplada.

10.4 Serialización y agregación


Para serializar un objeto, todos sus atributos deben ser serializables. Los datos primitivos,
los de la clase String y algunos otros de Java sı́ son serializables, pero con objetos de otras
clases se deben serializar éstos. Esto es válido a cualquier nivel. Por ejemplo, si se tiene una
clase C1 que incluye un objeto de la clase C2 y los objetos de la clase C2 incluyen un objeto
de la clase C3 , entonces para serializar objetos de C1 se deben serializar los de C2 y los de
C3 .
Si se tiene un objeto compuesto de otros, por ejemplo, un objeto de la clase Linea que
está compuesto de dos objetos de la clase Punto y se desea serializar, no basta con que Linea
implemente la interfaz Serializable, en este caso se debe asegurar que Punto también la
implemente. En el caso de que Punto no implemente la interfaz Serializable se dispara
una excepciónón java.io.NotSerializableException al intentar serializar un objeto de
la clase Linea.
260 CAPÍTULO 10. SERIALIZACIÓN DE OBJETOS

10.5 Serialización y herencia


Para serializar objetos de una jerarquı́a de clases, no es necesario que las subclases imple-
menten la interfaz Serializable, basta con que lo haga la clase base.
Ejemplo 10.4. Un investigador desea llevar control de los libros, tesis y artı́culos que tiene
para realizar su trabajo. La información que requiere de los libros es el nombre del autor,
tı́tulo, editorial y año de publicación. De las tesis requiere el autor, tı́tulo, director de la
misma, año de graduación y grado obtenido. De los artı́culos necesita el autor del mismo,
tı́tulo del artı́culo, nombre de la revista en donde se publicó, año de publicación, volumen y
número de la revista.
Tomando la descripción anterior y aplicando la metodologı́a de diseño presentada en el
capı́tulo 1 se tiene:
1. Encontrar los objetos principales.
Investigador, libro, tesis, artı́culo. Investigador es el usuario del programa, ası́ que no
se programa como una clase.

2. Determinar la estructura de cada objeto encontrado.

Libro: Tesis: Artı́culo:


autor autor autor
tı́tulo tı́tulo tı́tulo
tema tema tema
editor director nombre revista
año de publicación año de graduación año de publicación
grado volumen
número

Los métodos que se requieren son los necesarios para asignar y leer el valor de cada
atributo.

3. Definir escenarios.
Los escenarios serı́an, uno principal que da la bienvenida al usuario y muestra un
menú, otro para alta del material que tiene el investigador, otro para actualización del
material y otro más para consultas. Se dejan como ejercicio al lector.
Se crea la clase Obra con los atributos: autor, tı́tulo, tema y año, que son los atributos
comunes a las tres clases encontradas en el análisis de la descripción del problema. Luego
una clase Libro con la editorial del mismo. Una clase Tesis con el nombre del director y el
grado obtenido por el autor de la tesis. Finalmente una clase Articulo con el nombre de la
revista, el volumen y número en que aparece el artı́culo
10.5 SERIALIZACIÓN Y HERENCIA 261

Obra
autor
título
tema
año
Métodos

Libro Tesis Artículo


editorial director revista
grado volumen
número
Métodos Métodos Métodos

Figura 10.1 Jerarquı́a de clases de obras impresas.

La jerarquı́a de clases para este problema se muestra en la figura 10.1. Cada clase tiene,
además del constructor con todos los datos, métodos para obtener el valor de cada atributo,
métodos para asignar valor a cada atributo y el método toString. Estos métodos son muy
sencillos de programar, por lo que se dejan para que el lector los programe como ejercicio;
en esta sección sólo se incluye la clase Obra.
Ejemplo 10.5. Especificación de serialización en la clase Obra.

/**
* Clase para registrar los datos de una obra literaria
* @author Amparo López Gaona
* @version 3a edición
*/
public class Obra implements java.io.Serializable{
private String autor;
private String titulo;
private String tema;
private int anio;

/** Constructor de una obra


* @param a- nombre del autor de la obra
* @param t- titulo de la obra
* @param tem - tema de clasificacion de la obra
* @param an - anio de publicacion de la obra
*/
public Obra(String a, String t, String tem, int an){
autor = a;
262 CAPÍTULO 10. SERIALIZACIÓN DE OBJETOS

titulo = t;
tema = tem;
anio = an;
}

// Métodos asignar y obtener para cada atributo

/**
* Método para obtener una cadena con los datos de la obra
* @return String - cadena con los datos de la obra
*/
public String toString() {
return autor + "\t"+ titulo + "\t"+ tema + "\t" + anio;
}
}

No es necesario que las subclases de Obra implementen la interfaz Serializable puesto


que, por la relación de herencia, automáticamente son serializables también. La excepción es
cuando algún atributo de la subclase no es serializable.
En esta sección se presenta el desarrollo de la clase ColeccionPersistente, la cual permite
trabajar con objetos persistentes de la jerarquı́a de clases Obra. Para ello, además de tener
objetos serializables se requiere trabajar con un archivo, en el cual se almacene la información
de la colección de obras y del cual se leerá cuando se requiera.
Ejemplo 10.6. Estructura de la clase ColeccionPersistente. Se requiere del nombre del
archivo en donde se tiene la colección, ası́ como del arreglo en donde se dejará o tomará la
colección de obras para trabajar con ella en el programa.

import java.io.*;

/**
* Clase para tener una colección persistente de obras impresas
* @see Obra
* @author Amparo López Gaona
* @version 3a edición
*/
public class ColeccionPersistente {
private final String nombreArch;
private final Obra[] coleccion;
private int nObras;

Ejemplo 10.7. Constructor. El constructor recibe el nombre del archivo y verifica que exista,
si es el caso se lee la colección de obras y se almacenan en el arreglo. En el constructor se
10.5 SERIALIZACIÓN Y HERENCIA 263

dispara la excepción RuntimeException en caso de que haya algún problema con el archivo,
por ejemplo, que no exista o que no se tenga permiso para leer o escribir en él.

/**
* Constructor que deja listo el archivo para trabajar.
* @param nombre - nombre del archivo con el que se va a trabajar.
*/
public ColeccionPersistente(String nombre) {
nombreArch = nombre;
File archObras = new File(nombreArch);

coleccion = new Obra[100]; // No hay colección grabada


nObras = 0;

if(archObras.exists()){
if(!archObras.canRead()){
throw new RuntimeException("No es posible leer el archivo " +nombreArch);
}
if(!archObras.canWrite()){
throw new RuntimeException("Imposible escribir en el archivo "+nombreArch);
}
leerColeccion(); // Lee una colección grabada
}
}

En este caso se utiliza un objeto de la clase File contenida en el paquete java.io. Esta
clase proporciona métodos para verificar si un archivo existe y en ese caso si es legible
o grabable antes de abrirlo, con lo cual se evita que un programa dispare la excepción
FileNotFoundException al intentar abrir un archivo que no existe.
En su forma más sencilla el constructor de la clase File sólo requiere una cadena con el
nombre del archivo. Luego se verifica que el archivo exista, si existe es necesario ver que se
tengan permisos para leer canRead o para escribir canWrite.
En el ejemplo, en caso de que exista el archivo se lee y almacena su contenido en el arreglo
coleccion.
Ejemplo 10.8. Inclusión de nuevas obras en la colección.
Para dar de alta una nueva obra se utiliza el método agregarObra, que recibe como paráme-
tro una obra que es insertada al final del arreglo. Al finalizar se incrementa el contador de
obras.

/**
* Método para agregar una nueva obra al arreglo
* @param Obra -- obra que será agregada
264 CAPÍTULO 10. SERIALIZACIÓN DE OBJETOS

*/
public void agregarObra(Obra o) {
coleccion[nObras++] = o;
}

Ejemplo 10.9. Grabación de objetos serializados.

/**
* Método para serializar los objetos almacenados en un arreglo de obras
*/
public void guardarColeccion(){
ObjectOutputStream escritor = null;

try{
escritor = new ObjectOutputStream(new FileOutputStream(nombreArch));
for (int i=0; i < nObras; i++)
escritor.writeObject(coleccion[i]);
} catch(NotSerializableException e){
System.out.println("Error en la grabación: "+e+". Objeto no serializable.");
} catch(IOException e){
System.out.println("Error en la grabación: "+e);
} finally {
if (escritor != null) {
System.out.println("Cerrando el archivo "+nombreArch);
try { escritor.close(); } catch (IOException e) {}
} else {
System.out.println("No se abrió ningún archivo.");
}
}
}

En este método se abre el archivo especificado, se graba cada objeto de la colección. Se


está preparado para atrapar las diversas excepciones que pueden dispararse, tanto en la
serialización del arreglo como al cerrar el archivo.
Ejemplo 10.10. Recuperación o deserealización de objetos.

/**
* Método para deserealizar una colección de obras
*/
public void leerColeccion() {
ObjectInputStream lector = null;

try{
10.5 SERIALIZACIÓN Y HERENCIA 265

lector = new ObjectInputStream(new FileInputStream(nombreArch));


nObras = 0;
Object objeto;
do{
objeto = lector.readObject();
if (objeto != null)
coleccion [nObras++] = (Obra) objeto;
} while (objeto != null);
} catch(java.lang.ClassNotFoundException e) {}
catch(java.io.EOFException e) {
System.out.println("Encontro el fin de archivo");
} catch(IOException e){
System.out.println("Lectura fallida: "+e);
} finally {
if (lector != null) {
System.out.println("Cerrando el archivo "+nombreArch);
try { lector.close(); } catch (IOException e) {}
} else {
System.out.println("No se abrió ningún archivo.");
}
}
}

En este método se abre, para lectura, el archivo especificado, se lee cada objeto grabado
y se almacena en el arreglo para la colección. Se está preparado para atrapar las diversas
excepciones que pueden dispararse tanto en la deserealización del arreglo como al cerrar el
archivo.
Ejemplo 10.11. Métodos que complementan las operaciones para leer y escribir objetos.

/**
* Método que devuelve el arreglo de obras.
* @return ArregloObras - arreglo que contiene las obras de una colección.
*/
public Obra[] obtenerColeccion(){
return coleccion;
}
/**
* Método que devuelve el nombre del archivo donde están las obras.
* @return String - nombre del archivo que contiene una colección de obras.
*/
public String obtenerNombreArch(){
return nombreArch;
}
266 CAPÍTULO 10. SERIALIZACIÓN DE OBJETOS

/**
* Método para listar todas las obras de la colección
*/
public void imprimirColeccion(){
for(int i = 0; i < nObras; i++){
System.out.println(coleccion[i]);
}
}

Al trabajar con archivos, a pesar de que en cualquiera de sus tres tareas (apertura, lectura,
escritura) puede ocurrir una excepción, sólo en el caso de que ocurra al intentar abrir un
archivo puede hacerse algo, como dar otro nombre de archivo; en los otros casos posiblemente
el usuario no pueda hacer nada más que notificar el error al encargado del programa.
Ejemplo 10.12. Programa para grabar y recuperar la colección de obras.

import java.util.Scanner;
import java.io.*;

/**
* Programa que ilustra la forma de trabajar con archivos de objetos
* pertenecientes a una jerarquı́a de clases.
* @author Amparo López Gaona
* @version 3a edición
*/
class PruebaColeccion {
private static ColeccionPersistente misObras;
private Scanner io;

public static void menu () {


System.out.println("Qué clase de obra vas a dar de alta?");
System.out.println("1. Libro");
System.out.println("2. Artı́culo");
System.out.println("3. Tesis");
System.out.println("0. Fin");
}

public static void realizarAccion(int opcion) {


String tema = null;
String editorial = null;
... //Declaración de las otras variables
int numero = 0;
switch(opcion) {
case 0 :
10.5 SERIALIZACIÓN Y HERENCIA 267

break;
case 1 :
System.out.print("Da el tı́tulo del libro ");
titulo = io.nextLine();
System.out.print("Da el nombre del autor ");
autor = io.nextLine();
... // Pide los otros datos
misObras.agregarObra(new Libro(autor, titulo, tema, anio, editorial));
break;
case 2 :
// Código parecido al caso 1 para datos del artı́culo
System.out.println("Da el titulo del articulo");
titulo = io.nextLine();
... // Pide los otros datos
misObras.agregarObra(new Articulo(autor, titulo, tema, anio, revista,
volumen, numero));
break;
case 3 :
// Código parecido al caso 1 para datos de la tesis
System.out.println("Da el titulo de la tesis");
titulo = io.nextLine();
... // Pide los otros datos
misObras.agregarObra(new Tesis(autor, titulo, tema, anio, director, grado));
break;
default :
System.out.println("Opcion incorrecta");
}
}

public static void main(String [] pps) {


int opcion;
String nombreDeArchivo = (pps.length != 0) ? pps[0] : "miColeccion.ser";

misObras = new ColeccionPersistente(nombreDeArchivo);


io = new Scanner(System.in);

do {
menu();
opcion = io.nextInt();
realizarAccion(opcion);
} while(opcion != 0);

misObras.guardarColeccion();
268 CAPÍTULO 10. SERIALIZACIÓN DE OBJETOS

misObras.leerColeccion();
misObras.imprimirColeccion();
}
}

Este programa recibe como parámetro el nombre del archivo en donde está la colección,
en caso de no recibirlo se asume que el archivo es miColeccion.ser. Este programa crea un
objeto de la clase ColeccionPermanente. Si el archivo especificado existe, lee la información
ahı́ almacenada y la deposita en el arreglo coleccion. Si no existe el archivo no hay nada
en el arreglo. Luego solicita al usuario datos de las obras que desea dar de alta. Estas nuevas
obras las coloca en arreglo con obras de cualquiera de sus tres clases posibles. Cuando ya no
se desea dar de alta más obras, se serializa el arreglo, luego lee la colección almacenada y la
imprime para asegurar que trabaja bien.

10.6 Ejercicios
1. ¿Qué significa serializar un objeto?

2. ¿Qué se necesita para serializar objetos?

3. ¿En qué situaciones se dispara la excepción ClassNotFoundException?

4. Al deserializar, ¿cómo se sabe que se encontró el final del archivo?

5. ¿Qué se necesita para que los objetos de la Subclase sean serializables?

import java.util.Scanner;
import java.io.*;

class Clase implements Serializable {


private int i;
private String s;

public Clase() {
i = 25;
s = "hola s";
}
public String toString() {
return s+" "+i;
}
}
10.6 EJERCICIOS 269

class Subclase extends Clase {


private String s2;
private Hora hora;

public Subclase() {
super();
s2 = "hola s2";
hora = new Hora(20,44,0);
}
public String toString() {
return super.toString()+ " y ademas "+s2+ "\n Son las "+hora;
}
}

6. Utilizando la clase ColeccionPersistente escribir el programa para el problema plan-


teado en la sección 7.9.

7. Incluir en la clase ColeccionPersistente los métodos necesarios para que al imprimir


la colección, ésta se muestre en orden alfabético por autor y tema.

8. Escribir un programa para el problema del directorio planteado en el ejemplo 1.2.

9. Escribir el programa que resuelve el problema de los horarios de medicamentos plan-


teado en la sección 4.3.8.

10. Escribir el programa para la sección escolar del capı́tulo 6 para que trabaje con datos
persistentes.

11. Escribir el programa para la liga de fútbol, planteado en el ejercicio 15 del capı́tulo 6
para que trabaje con datos persistentes.

12. Escribir el programa para el control de pelı́culas, planteado en el ejercicio 15 del capı́tulo
7 para que trabaje con datos persistentes.
Apéndice A

Normas de estilo en Java

Este apéndice contiene una recopilación de las guı́as de estilo seguidas en los programas
de este libro; recomendadas por la gente de Sun para organizar y dar formato al código
fuente de los programas en Java. La documentación completa se encuentra en la dirección
http://www.oracle.com/technetwork/java/codeconv-138413.html

Archivos con código Java.

– Cada archivo debe contener una clase o interfaz y el orden en que los elementos
deben aparecer en el archivo es:
∗ Comentario de inicio que incluya el nombre del programa, el objetivo, autor
y versión.
∗ Instrucciones import.
∗ Declaración de la clase o interfaz.

Elementos de una clase.

– Comentario de inicio.
– Encabezado de la clase.
– Variables estáticas.
– Variables de instancia.
– Métodos. Primero los constructores.

Nombres de identificadores.

– Elegir nombres con significado en el contexto y de fácil lectura. Por ejemplo, es


mejor pesoNeto que pesNet y que p o pn.

271
272 APÉNDICE A. NORMAS DE ESTILO EN JAVA

– Los identificadores de variables empiezan con minúsculas y si constan de más de


una palabra, cada una después de la primera empieza con mayúscula sin espacios
en blanco entre ellas.
– Los identificadores de clases, interfaces y paquetes empiezan con mayúscula.
– Los identificadores de constantes se escriben sólo con mayúsculas. Si llevan más
de una palabra, éstas se separan con guión bajo.

Modificadores de variables.

– En general las variables deben tener visibilidad privada.


– En una interfaz no se utilizan modificadores.
– Es preferible utilizar variables con modificador final a utilizar literales.

Declaraciones.

– Incluir sólo una declaración por lı́nea.


– Tratar de asignar valor inicial en la declaración a menos que su valor se vaya a
calcular después.
– Colocar las declaraciones sólo al inicio de los bloques.
– Evitar declaraciones locales que oculten declaraciones externas.

Alineación (indentación).

– Alinear el código de cualquier bloque con cuatro espacios.


– Colocar la llave de inicio de un bloque al final de la instrucción iterativa, condi-
cional o encabezado de método y colocar la llave de fin del bloque alineada con
la instrucción que contiene la llave de inicio. Ejemplo:
if (valor > 0) {
instrucción 1;
instrucción 2;
...
instrucción n;
}
– Alinear cada cláusula case con respecto a la instrucción switch.
– Utilizar lı́neas en blanco en donde se quiera llamar la atención de caracterı́sticas
del programa.
– Dejar una lı́nea en blanco antes de iniciar un método.
273

– Dejar una lı́nea en blanco entre las declaraciones y las instrucciones.


– Dejar un espacio después de la coma que separa cada parámetro.
– Dejar un espacio antes y otro después de cada operador binario.
– No dejar espacio en blanco después de un paréntesis izquierdo ni antes de uno
derecho, pero sı́ antes de un paréntesis izquierdo.
– No dejar espacio antes de un punto y coma.
– No poner espacios entre los corchetes de un arreglo.

Comentarios.

– Suponer que el lector de la documentación no sabe lo que hace el programa.


– Asegurar que los comentarios sean precisos y concisos.
– Mantener actualizados los comentarios.
– Cada archivo fuente debe contener un bloque de documentación que contenga
información acerca del contenido y del autor.
– Cada clase, interfaz y método debe tener un comentario que describa su propósito.
– No esperar a terminar el programa para documentarlo.
Apéndice B

El programa javadoc

En la siguiente figura (B.2) se muestra la descripción de los métodos restantes.

275
276 APÉNDICE B. EL PROGRAMA JAVADOC

Figura B.1 Documentación de la clase CuentaDeCrédito (tercera parte).


277

Figura B.2 Documentación de la clase CuentaDeCrédito (cuarta parte).


Apéndice C

Archivos de texto

Los pasos involucrados en el almacenamiento de datos en un archivo son:

1. Abrir el archivo.

2. Escribir los datos en el archivo.

3. Cerrar el archivo.

En caso de necesitar trabajar con archivos de texto, estos pasos se implementan con las
siguientes instrucciones.

1. Abrir un archivo de texto para grabar en él, utilizando cualquiera de las dos instruc-
ciones siguientes:

FileWriter identificador = new FileWriter("nombre del archivo");


FileWriter identificador = new FileWriter("nombre del archivo", true);

Con la primera se abre un archivo para escritura. Si el archivo existe se borra su


contenido y lo deja listo para grabar en él. Con la segunda instrucción, si el archivo
existe se agrega al final del mismo lo que se desea grabar.

2. Para escribir se utiliza el método write de la clase FileWriter con el objeto creado,
como se especifica en el paso anterior. Este método recibe una cadena de caracteres,
que es la que se va a grabar en el archivo.

3. Para cerrar el archivo se utiliza el método close de la clase FileWriter.

Ejemplo C.1. Programa que lee una serie de lı́neas del teclado y las graba en un archivo.

279
280 APÉNDICE C. ARCHIVOS DE TEXTO

import java.io.*;
import java.util.Scanner;

class EscribeTexto {
public static void main (String[] pps) {
FileWriter escritor = null;
Scanner in = new Scanner(System.in);

try {
escritor = new FileWriter("nombres.txt");
escritor.write(’5’+"\n");
System.out.println("Da el nombre de 5 personas, cada uno en lı́nea aparte");
for (int i = 0; i< 5; i++) {
escritor.write(in.nextLine());
}
} catch (IOException e) {
System.out.println(e);
} finally {
try {
if (escritor != null)
escritor.close();
}catch(IOException e) {System.out.println(e);}
}
}
}

En el programa anterior se graba un 5 en la primera lı́nea del archivo nombres.txt y las


cinco lı́neas que se leen del teclado. Es importante notar que todas las instrucciones para
trabajar con archivos se encuentran en una cláusula try, esto es debido a cualquiera puede
disparar la excepción IOException. En el ejemplo no se hace nada para recuperarse del
error, sólo se envı́a el mensaje adecuado.
Se podrı́a esperar que la lectura fuera con FileReader, sin embargo, no puede leer lı́neas
sólo lee un caracter a la vez. Para leer lı́neas, Java proporciona la envoltura BufferedReader.

1. Para abrir un archivo de texto para lectura se utiliza la siguiente instrucción:

BufferedReader identificador = new BufferedReader(


new FileReader("nombre del archivo"));

Si el archivo no existe, se genera la excepción FileNotFoundException


281

2. Para leer se utiliza el método readLine de la clase BufferedReader con el objeto


creado, como se especifica en el paso anterior. Este método devuelve la cadena de
caracteres leı́da del archivo.

3. Para cerrar el archivo se utiliza el método close.

Ejemplo C.2. Programa que lee de un archivo una serie de lı́neas de texto.

import java.io.*;
import java.util.Scanner;

class LeeTexto {
public static void main (String[] pps) {
BufferedReader lector = null;
String linea = null;

try {
lector = new BufferedReader(new FileReader("nombres.txt"));
do {
linea = lector.readLine();
System.out.println(linea);
} while (linea != null);
} catch (FileNotFoundException e) {
System.out.println(e);
} catch (IOException e) {
System.out.println(e);
} finally {
try {
lector.close();
}catch(IOException e) { System.out.println(e);}
}
}
}
Bibliografı́a

[AhUl94] Aho, A. y Ullman, J., Foundations of Computer Science C Edition (Principles


of Computer Science Series), Computer Science Press, 1994.

[ArDW04] Arnow, D., Dexter, S. y Weiss, G. Introduction to Programming Using Java: An


Object-Oriented Approach, 2a. ed., Addison-Wesley, 2004.

[Barn00] Barnes, D., Object-Oriented Programming with Java. An Introduction, Prentice-


Hall, 2000.

[BaKö08] Barnes, D. y Kölling, M., Object First with Java. A Practical Introduction using
BlueJ, Prentice-Hall, 2008.

[Booc98] Booch, G., The Unified Modeling Language User Guide, Addison-Wesley, 1998.

[Budd01] Budd, T., The Introduction to Object Oriented Programming. 3a. ed., Addison-
Wesley, 2001.

[CaWH00] Campione, M., Walrath, K. y Huml, A., The Java Tutorial: A Short Course on
the Basics, 3a ed., Addison-Wesley, 2000.

[CoDa06] Cohoon, J. y Davidson, J., Java 5.0 Program Design, McGraw-Hill, 2006.

[Dale08] Dale, N., A Laboratory Course for Programming with Java, Jones y Bartlett
Computer Science, 2008.

[Flan05] Flanagan, D., Java in a Nutshell, 5a. ed., O’Reilly, 2005.

[GoJS96] Gosling, J., Joy, B. y Steele, G., The Java Language Specification, Addison-
Wesley, 1996.

[Lian10] Liang, D. Introduction to Java Programming, Comprehensive, 8a. ed., Prentice-


Hall, 2010.

[Lind04] Linden, P., Just Java, 6a. ed., Prentice-Hall, 2004.

283
284 BIBLIOGRAFÍA

[Savi10] Savitch, W., Absolut Java, 4a. ed., Addison-Wesley, 2010.

[Wu09] Wu, T., An Introduction to Object-Oriented Programming with Java, 5a. ed.,
McGraw Hill, 2009.
Índice alfabético

abstracción, 108 Scanner, 41


agregación, 100 String, 47
algoritmo, 4 derivada, 170
algoritmo de ordenamiento genérica, 246
selección, 153 abstracta, 222
alias, 37, 79 definición, 43
arreglo, 127 ejemplar de, 5
ı́ndice de un, 128 encabezado, 65
bidimensional, 154 envoltura de, 248
como parámetro, 138 especializada, 177
constructor de un, 127 instancia de, 5
de cadenas, 141 codificación, 9
de objetos, 146 comentarios, 43
elemento de un, 128 de una lı́nea, 22
tamaño de un, 128 de varias lı́neas, 43
asignación múltiple, 23 inicial de método, 70
atributo iniciales, 43
constante, 67 javadoc, 43
de clase, 66, 129 compilador, 11
privado, 66 constante, 19
protegido, 171 clase, 180
método, 180
búsqueda binaria, 150 contador, 118
biblioteca de clases, 10 conversión de tipo, 30
véase paquetes, 10
bloque de instrucciones, 51 declaración, 19
bytecode, 10 de constantes, 20
de variables, 19
código portable, 10 constantes en interfaces, 244
cadena de caracteres, 21 depuración, 77
clase, 5 diseño
Exception, 200 metodologı́a de, 6
RuntimeException, 202 documentación, 14, 43

285
286 ÍNDICE ALFABÉTICO

en programas, 14 do, 93
para el usuario, 14 else, 52, 57
técnica, 14 extends, 238
false, 21
encapsulación, 66, 69 finally, 205
error, 12 final, 20
de ejecución, 13 float, 19
de lógica, 12 for, 118
de ligado, 12 if, 51
de sintaxis, 12 implements, 237
escenario, 7 import, 42, 256
evaluación instanceof, 182
asociatividad de operadores, 28 interface, 236
con cortocircuito, 27, 57, 133 int, 19
precedencia de operadores, 28 long, 19
excepción, 200 main, 77
expresión, 22 new, 35
condicional, 27 null, 22, 37
lógica, 27 package, 65
private, 66
firma de un método, 39, 69
protected, 66, 171
herencia, 170 public, 65, 66
ampliación, 169 return, 73
atributos heredados, 171 short, 19
de interfaces, 236 static, 66
especialización, 175 super, 172
generalización, 186 switch, 135
método constructor, 172 this, 76
throws, 202
instrucción throw, 202
abstract, 222 true, 21
boolean, 19 try, 204
break, 135 void, 77
byte, 19 while, 120
case, 135 instrucción condicional, 51
catch, 205 instrucción condicional
char, 19 if, 51
class, 65 switch, 135
default, 135 anidada, 57, 131
double, 19 con operador condicional, 103
ÍNDICE ALFABÉTICO 287

instrucción de iteración redefinido, 177


anidada, 154 robusto, 106
instrucción de iteración, 93 sobrecargado, 72, 90
for, 118 sobrescrito, 178
while, 120 manejador de excepciones, 204
do, 93 mantenimiento, 13
anidada, 132 adaptativo, 13, 235
instrucciones correctivo, 13
protegidas, 206 mensaje, 5, 38
interfaz, 235 envı́o de, 67
vs. clase abstracta, 250 metodologı́a de diseño, 6
Comparator, 245 modularización, 108
Serializable, 255
de marcado, 255 notación punto, 38, 244

javadoc, 43 objeto, 5
author, 43 System.out, 40
anónimo, 79, 89
param, 70
atributos de un, 66
return, 70, 73
ciclo de vida, 35
see, 100
cliente, 66, 115
throws, 202
colaboración de un, 8
version, 43
comportamiento, 65
JVM, 10
comportamiento del, 5
literal, 21 creación, 35
estado del, 5, 66
máquina virtual de Java, 10 estructura, 65
método, 5, 67 estructura de un, 66
abstracto, 222 estructura del, 5
concreto, 230 identidad de un, 5
constructor, 75–79, 172 persistencia de un, 255
constructor de copia, 76 servidor, 115
constructor por omisión, 75 uso de un, 38
cuerpo de un, 69 operador
de acceso, 72 instanceof, 182
firma de un, 69 new, 35
inspector, 72 asignación compuesto, 26
interno, 89 autodecremento, 25
modificador, 70 autoincremento, 25
privado, 88 condicional ?:, 103
que devuelve un objeto, 89 operadores
288 ÍNDICE ALFABÉTICO

aritméticos, 24 alcance de una, 69


asociatividad de, 28 de clase, 129
de asignación, 22 instancia, 66, 69
de relación, 26 local, 69
lógicos, 26 visibilidad
precedencia de, 28 de una clase, 65
método, 67
palabras reservadas, 18
véase instrucción, 18
paquete, 10
java.io, 255
java.lang, 46, 74
java.util, 42
java.lang, 192
parámetro, 38
actual, 69
arreglo como, 138
del método main, 143
formal, 68
paso por valor, 69
real, 69
polimorfismo, 178, 185, 222
programa, 1
alineación, 44
compilación, 44
robusto, 13, 53, 106

recolector de basura, 40
redefinición de métodos, 177
referencia, 19, 35
null, 22
alias, 37

serialización, 255
signatura
véase firma de un método, 39
subclase, 170
superclase, 170

tipo primitivo de dato, 18

variable, 19
Introducción al desarrollo de programas con Java
editado por la Facultad de Ciencias
de la Universidad Nacional Autónoma de México,
se terminó de imprimir el 14 de septiembre de 2017
en los talleres de Impresos Vacha S. A. de C. V.
José María Bustillos No. 59. Col. Algarín
C.P. 52170. Metepec. Estado de México.

El tiraje fue de 500 ejemplares.

Está impreso en papel Creamy book de 60 g.


En su composición se utilizó tipografía Computer Modern de
11:13.5, 14:16 y 16:18 puntos de pica.

Tipo de impresión: offset.

El cuidado de la edición estuvo a cargo de


Patricia Magaña Rueda

También podría gustarte