Documentos de Académico
Documentos de Profesional
Documentos de Cultura
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
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
ISBN: 978-607-02-4241-0
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
i
ii ÍNDICE GENERAL
Bibliografı́a 283
v
vi ÍNDICE DE FIGURAS
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 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 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.
Proceso de programación
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
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.
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.
Agua de limón
Ingredientes:
1L agua
5 limones
4 CAPÍTULO 1. PROCESO DE PROGRAMACIÓN
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.
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.
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.
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
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
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:
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:
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?
6. ¿La estructura y el estado de dos objetos de la misma clase son siempre iguales? ¿Por
qué?
16. Definir estructura y comportamiento para objetos de las siguientes clases: alumno,
automóvil, cuenta bancaria, teléfono celular, computadora.
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.
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.
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.
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
altura
0.48
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.
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
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.
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
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
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;
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 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
veces = 6;
veces++ + 4; // El valor de la expresión es 10 y el de veces 7.
int i = 90, j = 5;
i /= 45; // Equivalente a i = i / 45;
i *= j + 5; // Equivalente a i = i * (j + 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.
Operador Descripción
&& Conjunción
|| Disyunción
! Negación
Tabla 2.6 Operadores lógicos.
• 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.
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.
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 = 6, b = 3, c = 3, d;
int d = a + b /c;
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;
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;
2.5 Ejercicios
1. ¿Cuántos tipos de datos hay para representar números enteros? ¿En qué difieren?
3. Si se tiene la declaración final int UNO = 1; ¿es posible tener la siguiente instrucción
int negativo = -UNO;?
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
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.
13. Escribir el valor que tienen las variables c y d después de ejecutar cada instrucción del
siguiente código.
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?
(a) a3 (a + 1)(a − 7)
1
(b) 1+x2
2.5 EJERCICIOS 33
1 ×t2
(c) q = ( td−k ) + t2
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.
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.
1
Recordar que todo objeto pertenece a una clase.
35
36 CAPÍTULO 3. CREACIÓN Y USO DE OBJETOS
Referencia al objeto
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();.
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:
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
(miAuto = null;)
miAuto
puertas 4
chiquito estandar t
rebelde
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.
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).
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
100
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.
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:
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. 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.
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
/**
* 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
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.
3. Definir escenario.
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.
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
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.
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.
2. Definir escenario.
nombreCompleto = nombreCompleto.substring(posición+1);
aMaterno = nombreCompleto; //Extrae el apellido materno
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.
3. Definir escenario.
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;
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
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();
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 :(");
}
if (pidio.equals("aguila") || pidio.equals("sol")) {
// Jugar
} else {
//Enviar mensaje indicando el error
}
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;
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.
3. Definir escenario.
/*
* 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;
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);
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
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:
y al avisar cuál es el valor mayor se incluye el nombre del ganador como sigue:
3.6 Ejercicios
1. Describir en qué difiere una variable para un tipo primitivo de una variable para refe-
rencias.
(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.
5. ¿Es correcto el siguiente código para intercambiar dos valores? ¿Por qué?
(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 ||.
class UsoDeCadenas{
public static void main (String [] pps) {
String frase = "Una mosca parada en la pared";
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
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.
¿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.
no
¿Tiene fiebre? Información insuficiente
sí
sí ¿Dificultad para respirar sí Posible neumonía o
¿Tos? infección en las vias
o tos con flemas? aéreas
no no
sí
¿Dolor de cabeza? ¿Dolor de cabeza? Posible infección viral
sí no
Posible infección
viral
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.
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.
Del diseño se pasa a la implementación, ası́ que en las siguientes secciones se especifica cómo
hacerlo en Java.
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
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.
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:
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
• 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
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:
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.
/**
* 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 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.
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 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
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;
}
/**
* 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:
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.
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.
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
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
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.
/**
* 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");
}
}
}
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
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");
}
/**
* 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
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.
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();
// 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() { ... }
}
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.
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;
/** 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;
}
/**
* 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
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 */.
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 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() { ... }
}
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
...
}
}
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) ;
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?
3. ¿Cuáles son los datos con los que puede trabajar un método?
4.4 EJERCICIOS 95
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.
(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);
(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)
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
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.
/**
* 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
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
/**
* 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);
}
/**
* 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));
}
/**
* 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();
}
/**
* 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;
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();
}
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:
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
MáquinaBoletos
Boletos Cajero
Caja
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
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
/**
* 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);
}
/**
* 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;
}
/**
* 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.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;
}
/**
* 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
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;
}
/**
* 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.
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;
MáquinaBoletos
double precioB pepe miCaja
int totalBoletos Caja miCaja
Cajero pepe
métodos
/** 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);
return nBoletos;
}
/**
* 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);
}
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:
/*
* 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;
}
/**
* 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;
}
/**
* Método para conocer la cantidad de dinero acumulada en la caja
* @return double -- cantidad de dinero acumulada
*/
public double corteDeCaja() {
return pepe.corteDeCaja();
}
while (condición) {
instrucciones
}
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?
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é?
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.
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:
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;
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:
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.
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];
• 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
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.
/**
* 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
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];
/**
* 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;
}
" 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.
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
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.
/**
* Devuelve todas las calificaciones del alumno
* @return int[] -- arreglo con las calificaciones del alumno
*/
public int[] obtenerCalificaciones() {
return calificaciones;
}
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
/**
* Método para asignar calificaciones al alumno
*/
public void asignarCalificaciones(int [] c) {
calificaciones = c;
}
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:
2. Asignar directamente el bloque con las cadenas sin utilizar el operador new:
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"};
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.
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: "+ ...);
}
}
}
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 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.
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
...
/**
* 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;
/**
* 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.");
}
}
/**
* 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;
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
/**
* 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;
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
/**
* 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
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 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.
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.
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
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},
};
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];
}
}
}
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");
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");
6.8 Ejercicios
1. ¿Los arreglos son objetos? ¿Qué métodos tienen asociados?
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
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:
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.
167
168 CAPÍTULO 7. HERENCIA DE CLASES
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
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:
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 A private
Clase B
protected
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
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");
}
}
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.
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;
}
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);
}
}
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);
}
}
Cuenta
Atributos
Métodos
CuentaCon Crédito
Servicios
Atributos Atributos
Métodos Métodos
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
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.
• 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.
• 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
/**
* 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.
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");
...
Cuenta [] cuentas;
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
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
3. Definir escenarios.
Escenario: generación de facturas.
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
/**
* 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;
/**
* 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
/**
* 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
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
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.
/**
* 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.
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?
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
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.
199
200 CAPÍTULO 8. LA CLASE EXCEPTION
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
• 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
/**
* 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;
}
/** Constructor que crea una cuenta con saldo mı́nimo de $2500
204 CAPÍTULO 8. LA CLASE EXCEPTION
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);
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.
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!
1000
El saldo actual es $2000.0
Hasta luego!
try{
System.out.println("Proporciona el monto inicial");
monto = in.nextDouble();
Cuenta cuenta = new Cuenta(monto);
Ejemplo 8.11. Ejecución del programa 8.10 donde se proporciona un monto inicial inco-
rrecto.
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.
En este ejemplo se muestra que aunque no se dispare una excepción se ejecutan el cuerpo
de la cláusula finally.
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;
}
/**
* 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");
}
}
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);
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
/**
* 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());
}
depositarCta() depositar()
main () throws Ex throws Ex
try {
... ... ...
depositarCta(); depositar(); throw new Ex()
... ... ...
} catch(Ex e) }
{
...
}finally {
...
}
} 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;
}
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?
4. ¿Se pueden manejar diferentes excepciones en una sola instrucción try? ¿Cómo?
6. Explicar qué sucede si varias clausulas catch coinciden con la excepción disparada.
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.
valores[destino] = resultado;
}
}
}
}
}
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
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.
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.
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
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
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
/**
* 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
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();
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
. . . . . .
(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
/**
* 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:
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();
}
/**
* 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
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
/**
* 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
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
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
/**
* 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
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
/**
* 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];
Circulo
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);
}
/**
* 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;
/**
* 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
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, ... ;
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:
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;
/**
* 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];
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.
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;
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:
• 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
/**
* 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());
}
• 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?
4. ¿Qué significa tener una variable de tipo referencia a una clase abstracta? ¿Y a una
interfaz?
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?
10. Dar un ejemplo de polimorfismo tomando la jerarquı́a de clases del problema 7.12.
252 CAPÍTULO 9. CLASES ABSTRACTAS E INTERFACES
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
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.
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
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.
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;
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));
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:
/**
* 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.");
}
}
}
}
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
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;
titulo = t;
tema = tem;
anio = an;
}
/**
* 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;
}
}
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);
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;
}
/**
* 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.");
}
}
}
/**
* Método para deserealizar una colección de obras
*/
public void leerColeccion() {
ObjectInputStream lector = null;
try{
10.5 SERIALIZACIÓN Y HERENCIA 265
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;
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");
}
}
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?
import java.util.Scanner;
import java.io.*;
public Clase() {
i = 25;
s = "hola s";
}
public String toString() {
return s+" "+i;
}
}
10.6 EJERCICIOS 269
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;
}
}
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
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
– 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.
– Comentario de inicio.
– Encabezado de la clase.
– Variables estáticas.
– Variables de instancia.
– Métodos. Primero los constructores.
Nombres de identificadores.
271
272 APÉNDICE A. NORMAS DE ESTILO EN JAVA
Modificadores de variables.
Declaraciones.
Alineación (indentación).
Comentarios.
El programa javadoc
275
276 APÉNDICE B. EL PROGRAMA JAVADOC
Archivos de texto
1. Abrir 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:
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.
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);}
}
}
}
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
[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.
[GoJS96] Gosling, J., Joy, B. y Steele, G., The Java Language Specification, Addison-
Wesley, 1996.
283
284 BIBLIOGRAFÍA
[Wu09] Wu, T., An Introduction to Object-Oriented Programming with Java, 5a. ed.,
McGraw Hill, 2009.
Índice alfabético
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
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
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
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.