NotasProgII - MC Yolanda Moyao Martínez

Anuncio
Capítulo I:
CONCEPTOS BÁSICOS
DEL
PARADIGMA
ORIENTADO A
OBJETOS
Yolanda Moyao Martínez
Ana Patricia Cervantes Márquez
Capítulo I: Conceptos Básicos del Paradigma
Orientado a Objetos
I.1. Clase y Objeto
Objeto
Un objeto es una entidad tanto tangible como intangible, que se puede imaginar y que tiene
un estado, un comportamiento y una identidad. (Joy2011)(Tho08).
Por ejemplo, el cuadrado es un objeto tangible que tiene propiedades tales como longitud,
color, etc. Además tiene cierto comportamiento tal que este puede ser dibujado,
fraccionado, también se le puede calcular el área, calcular el perímetro, etc.
Fig. 1.1. Objeto Tangible
Por ejemplo, el seguro es un objeto intangible que tiene propiedades tales como tipo de
cobertura, costo, vigencia, etc. Además tiene cierto comportamiento tal que este puede ser
contratado, renovado, cancelado, modificado, etc.
Fig. 1.2. Objeto Intangible
Clase
Una clase es un tipo de molde o plantilla que dicta lo que los objetos pueden o no hacer.
Así una clase es un conjunto de objetos que comparten una estructura y un
comportamiento. (Joy2011)(Tho08).
Por ejemplo, figuras geométricas es una clase que define objetos tangibles que tienen
propiedades tales como número de lados, área, perímetro, color, etc. Además los objetos
que son definidos dentro de esta clase tienen cierto comportamiento tal como calcular área,
calcular perímetro, colorearlo, etc.
Fig. 1.3. Clase que define objetos tangibles
Por ejemplo, servicios es una clase que define objetos intangibles que tienen propiedades
tales como tipo de cobertura, costo, vigencia, etc. Además tienen cierto comportamiento tal
que este puede ser contratado, renovado, cancelado, modificado, etc.
Servicios
+Tipo_Cobertura
+Costo
+Vigencia
+Contratar()
+Renmovar()
+Cancelar()
+Modificar()
Fig. 1.4. Clase que define objetos intangibles
Atributos
Los atributos son los datos necesarios para describir los objetos creados a partir de alguna
clase en particular (Joy2011).
En la clase figuras geométricas los atributos necesarios para describir a los objetos son:
número de lados, área, perímetro, color, etc.
Fig. 1.5. Atributos del objeto Cuadro
En la clase Servicios los atributos necesarios para describir a los objetos son: tipo de
cobertura, costo, vigencia, etc.
Fig. 1.6. Atributos del objeto Seguro_Médico
Método
Un método es una secuencia de instrucciones que una clase u objeto sigue para realizar una
tarea, es decir, un método es un conjunto de operaciones que manipulan a los atributos del
objeto.
En la clase figuras geométricas los métodos que manipulan a los atributos de los objetos
son: calcular área, calcular perímetro, colorear, etc.
En la clase Servicios los métodos que manipulan a los atributos de los objetos son:
contratar, renovar, cancelar, modificar, etc.
Los métodos se clasifican en dos tipos (Tho08):
Métodos de clase: Es un método que al ser ejecutado no es necesario que sea invocado a
través de un objeto.
Métodos de instancia: Es un método que para ser ejecutado requiere ser invocado a través
de un objeto.
Por ejemplo: En la clase Seguros el método Cancelar es un método de clase.
Creación de un objeto
Una vez que una clase se modela o se define, es posible crear o instanciar uno o más
objetos que se identifican con dicha clase.
Por ejemplo: el triángulo, el círculo, etc. son objetos que se identifican con la clase figuras
geométricas.
Fig. 1.7. Objeto Círculo
Fig. 1.8. Objeto Triángulo
Mensaje
Un mensaje es una instrucción que se envía a un objeto, el cual se ejecutará al recibirlo;
incluye el identificador que contiene la acción a realizar por el objeto junto con los datos
que este necesita para efectuar su trabajo. (Joy2011)
Por ejemplo: Si se requiere calcular el área del triángulo, solamente, el objeto triangulo
tiene que enviar un mensaje solicitando al método calcular_área que efectué tal operación
Triangulo.calcular_area(b,h )
Fig.1.9.Objeto triángulo envía mensaje Calcular_area
I.2. Abstracción de Datos
En términos simples, una abstracción es un proceso mental, mediante el cual se extraen los
rasgos esenciales de algo para representarlos por medio de un lenguaje gráfico o escrito.
(Mar02)
Por ejemplo, en la vida cotidiana hacemos frecuentemente abstracciones cuando
describimos lugares, personas, etc. En el caso de los datos, la abstracción se refiere a la
metodología que permite diseñar estructuras de datos, representar sus características
esenciales.
La técnica de la abstracción de datos establece que al diseñar una nueva estructura de datos,
ésta pasa a ser un Tipo de Dato Abstracto (TDA), que podrá implementarse en cualquier
lenguaje y aplicarse en cualquier concepto. (Mar02)
En realidad el concepto TAD ya existe en los lenguajes de programación bajo la forma de
los tipos predefinidos (Fra02), como son: enteros, cadenas, etc.
La verdadera utilidad de los TAD aparece en el diseño de nuevos tipos de datos.
Los TDA guardan valores sobre los cuales se puede aplicar un conjunto dado de
operaciones, siendo sobre esta idea que se apoya la programación orientada a objetos.
I.3 TDA
I.4 Encapsulamiento
Encapsulación o encapsulamiento significa reunir en cierta estructura todos los elementos
que a determinado nivel de abstracción, se pueden considerar de una misma entidad. Oculta
lo que hace un objeto de lo que hacen otros objetos del mundo exterior. (Joy2011)
Por ejemplo: en el objeto seguro médico el ocultamiento significa que el usuario del seguro
cuando hace uso del mismo, no necesita conocer el procedimiento que se lleva a cabo entre
la aseguradora y el hospital que brinda el servicio.
I.5 Herencia
La herencia es un mecanismo para modelar dos o más entidades que son diferentes pero
que comparten muchas características comunes (Tho08). Manifiesta que dichas entidades
tienden a organizase de manera jerárquica, donde esta jerarquía no se limita a un solo nivel.
Clase Padre
La clase Padre, superclase o clase base es aquella contiene los atributes comunes de las
entidades (Tho08).
Clase Hija
Las clases Hijas, subclases o derivadas son todas las clases que heredan a la cases padre.
Por ejemplo: Si tenemos como clase padre a la clase servicios podemos generar tres clases
hijas tales como Servicios de Vivienda, Servicios Médicos y Servicios Para Auto las cuales
heredan atributos y métodos comunes a ellas.
Fig. 1.10. Herencia entre clases
I.6 Polimorfismo
El polimorfismo es la propiedad que le permite a una operación o función tener el mismo
nombre en clases diferentes y actuar de modo distinto en cada una de ellas, es decir, implica
la capacidad de una operación de ser interpretada solo por el propio objeto que la invoca.
(Joy11)
Por ejemplo: Si tenemos como clase padre a la clase servicios podemos generar tres clases
hijas tales como Servicios de Vivienda, Servicios Médicos y Servicios Para Auto las cuales
heredan atributos y métodos comunes a ellas. En la figura 1.10 podemos observar que las
clase Seguro de Vivienda y Seguro de Auto utilizan un método Reparar el cual tiene el
mismo nombre pero actúa de forma diferente ya que en la Vivienda cuando se habla de
reparar nos referimos a la instalación eléctrica, instalación de gas, pintura, etc. Y en el caso
de Seguro para Auto cuando hablamos de reparar nos referimos a alguna parte del mismo.
BIBLIOGRAFÍA Y REFERENCIAS
(Joy11) Luis, Joyanes. A. (2011). Programación en Java 6. Algoritmos, programación orientada a
objetos e interfaz gráfica de usuario.(3ª. Edición). Mc Graw Hill.
(Mar02)Roman, Martínez. (2002). Estructuras de datos. México: International Thomson Editores,
S.A.
(Tho08)C.,Thomas Wu., (2008).Introducción a la programación orientada a objetos. Promación en
Java.(Primera Edición). Mc Graw Hill.
(Fra02)Xavier,Franch Gutiérrez (2002).Estructuras de datos. Especificación, diseño e
implementación.(4ª. edición). Alfaomega.
Capítulo II:
Abstracción del Mundo
Real al Paradigma
Orientado a Objetos
María Luz A. Sánchez Galvéz
Mario Anzures García
Capítulo II: Abstracción del Mundo real al
Paradigma Orientado a Objetos
II.1. Principios básicos del modelado de objetos
II.2. Modelar Clases y Objetos
II.3 Modelar relaciones entre clases
II.3.1 Dependencia
II.3.2 Asociación
II.3.3 Agregación y Composición
II.3.4 Herencia e Interfaces
BIBLIOGRAFÍA Y REFERENCIAS
[AHO74]
Aho Ullman, Hopcroft. “The Design and Analysis of Computer Algorithms”,
Addison-Wesley, 61-65. 1974.
[AKL92]
Akl Selim G., “Diseño y Análisis de Algoritmos Paralelos”, Ra-Ma Serie
Paradigma, Madrid. 1992.
[AND96]
Andreoli Jean-Marc, Pareschi Remo. “Integrated Computational Paradigms
for flexible Client-Server Communication”. ACM Computing Surveys, Vol.
28, No.2, pp.297-299. 1996.
[AND83]
Andrews G. R., Schneider F. B. “Concepts and Notations for Concurrent
Programming”. Computing Surveys, Vol. 15, No. 1, pp. 3-43. 1983.
Capítulo III:
LENGUAJE DE
PROGRAMACIÓN
ORIENTADO
A OBJETOS
Yalú Galicia Hernández
Laura Cuayahuilt Romero
Darnes Vilariño Ayala
José Andrés Vázquez Flores
Graciano Cruz Almanza
Capítulo III: Lenguaje de Programación
Orientado a Objetos
III.1. Tipos de Datos primitivos
En Java se tienen dos tipos de datos disponibles, los tipos de datos primitivos y los tipos de
datos de referencia, en esta sección se describen los tipos de datos primitivos.
Se les llama primitivos porque están integrados en el sistema y en realidad no son objetos,
lo cual hace que su uso sea más eficiente.
Un tipo de datos es el conjunto de valores que puede tomar una variable; así, el tipo
de datos char representa la secuencia de caracteres Unicode y una variable del tipo char
podrá tener uno de esos caracteres; los tipos de datos primitivos representar valores
escalares o individuales, los cuales pueden ser char o los enteros
Hay ocho tipos de datos primitivos soportados por Java. Los tipos de datos primitivos
están predefinidos por el lenguaje y nombrados por una palabra clave. Veamos ahora en
detalle acerca de los ocho tipos de datos primitivos.
La tabla 3.1 muestra los tipos de datos primitivos, sus tamaños en bytes y el rango de
valores.
Tipo
char
byte
short
int
long
fload
double
boolean
Tamaño
en bytes
2
1
2
4
8
4
8
1 bit
Rango de valores Mínimo y Máximo
´\0000´… ´\ffff´
-128 a 127
-32768 a 32767
-2147483648 a 2147483647
-9223372036854775808 … +9223372036854775807
3.4*(10-38) … 3.4*(1038)
1.7*(10-308) … 1.7*(10308)
false , true
Tabla 3.1. Tipos de datos primitivos en Java
Enteros: int , byte, short, long.
Probablemente el tipo de datos más familiar es el entero o int; adecuado para aplicaciones
que trabajan con datos numéricos; en Java hay cuatro tipos de datos enteros byte, short, int
y long. El tipo más utilizado, por semejanza de nombre, es int; sus valores se almacenan
internamente en 4 bytes (o 32 bits) de memoria.
Ejemplos de declaración de variables de tipo numérico:
int valor; int valor = 99;
int valor1, valor2; int num_parte = 1141, num_items = 45;
long sim, jila = 999111444222;
Java siempre realiza la aritmética de enteros de tipo int en 32 bits a menos que en la
operación intervenga un entero long; por ello conviene utilizar variables de estos tipos
cuando se realicen operaciones aritméticas; en el siguiente ejemplo se generan errores por
el tipo de variables:
short x;
int a = 19, b = 5;
x = a+b;
Al devolver a+b, no se puede asignar a x un valor de tipo int porque es de tipo short;
aunque en este caso es mejor declarar las variables de tipo int, el error también se puede
resolver forzando una conversación de tipo de datos:
x = (short) (a+b);
aunque es mejor, en este caso, declararlas variables de tipo int. Las constantes enteras como
-1211100, 2700 o 4250 siempre se consideran de tipo int; para que el compilador considere
a una constante de tipo long se utiliza el sufijo l o L. Por ejemplo -2312367L.
También hay que considerar que si el resultado de una operación sobrepasa el máximo
valor entero, no se genera un error de ejecución, sino que se pierden los bits de mayor peso
en la representación del resultado.
En aplicaciones generales, las constantes enteras se pueden escribir en decimal o base 10,
en octal o base ocho o en hexadecimal o base dieciséis. Una constante octal es cualquier
número que comienza con un cero y contiene dígitos en el rango del 1 a 7. Una constante
hexadecimal comienza con 0x y va seguida de los dígitos 0 a 9 o las letras A a F o bien de a
a f. La tabla 3.2 muestra ejemplos de constantes enteras representadas en sus notaciones
decimal, hexadecimal y octal.
Base 10 decimal
8
10
16
65536
24
17
Base 16 hexadecimal
0x08
0x0A
0x10
0x10000
0x18
0x11
Base 8 octal
010
012
020
0200000
030
021
Tabla 3.2. Constantes enteras en tres bases diferentes
Reales: float, double.
Los tipos de datos punto flotante representan números reales que contienen un punto
decimal, tal como 3.14159, o números muy grandes, tales como 1.85*1015. Java soporta
dos formatos de punto flotante. El tipo float (simple precisión) requiere de 4 bytes de
memoria y tiene una precisión de 7 dígitos, el tipo double (doble precisión) requiere de 8
bytes de memoria y tiene una precisión de 15 dígitos.
Ejemplos de declaración de variables de tipo real (punto flotante):
double d = 5.65;
float x = -1.5F;
Las constantes que representan números reales (punto flotante), Java las considera por
defecto double; para que una constante sea considerada de tipo float se añade el sufijo F o
f. También se puede expresar que una constante es de tipo double con el sufijo D o d.
Caracteres: char.
Un carácter es cualquier elemento de un conjunto de caracteres predefinidos o alfabeto.
Java fue diseñado con el objetivo de poder ser utilizado en cualquier país, con
independencia del tipo de alfabeto. Para poder reconocer cualquier tipo de carácter, los
elementos de este tipo utilizan 16 bits, dos bytes, en vez de los 8 bits que utilizan la
mayoría de los lenguajes de programación. De esta forma Java puede representar el
estándar Unicode, que recoge más de 30000 caracteres distintos procedentes de las distintas
lenguas escritas. La mayoría de las computadoras utilizan el conjunto de caracteres ASCII,
que se almacenan en el byte de menor peso de un char; el valor inicial de los caracteres
ASCII y también Unicode es ´\u0000´ y el último carácter ASCII, ´\u00FF´.
Ejemplos:
char letra = ´A´ ;
char respuesta =´S´;
Internamente, los caracteres se almacenan como números. La letra A, por ejemplo, se
almacena internamente como el número 65, la letra B es 66, etc.
Puesto que los caracteres se almacenan internamente como números, se pueden realizar
operaciones aritméticas con datos tipo char. Por ejemplo, se puede convertir una letra
minúscula a a una mayuscula A, restando 32 del código ASCII y convirtiendo el entero a
char. Así, para realizar la conversión, restar 32 del tipo de datos char, como sigue:
char carLetra = ´a´;
carLetra = (char)(carLetra – 32);
Esto convierte a (código ASCII 97) a A (código ASCII 65). De modo similar , añadiendo
32 convierte el carácter de letra mayúscula a minúscula:
carLetra = (char) (carLetra + 32);
Existen caracteres que tienen un propósito especial y no se pueden describir utilizando el
método normal. Java proporciona secuencias de escape. Por ejemplo, el literal carácter de
un apóstrofe se puede escribir como ´\´´ y el carácter nueva línea ´\n´. La tabla 3.3 muestra
las diferentes secuencias de escape en Java.
Código
de Escape
´\n´
´\r´
´\t´
´\b´
´\f´
´\\´
´\´´
´\”´
´\000´
´\uhhhh´
Significado
Nueva línea
Retorno de carro
Tabulación
Retroceso de espacio
Avance de página
Barra inclinada inversa
Comilla simple
Doble comilla
Número octal
Número hexadecimal
Códigos ASCII
Dec
Hex
13 10
0D 0A
13
0D
9
9
8
8
12
0C
92
5C
39
27
34
22
Todos
Todos
Todos
Todos
Tabla 3.3. Caracteres de secuencia de escape (códigos)
Booleananos: boolean.
Los compiladores de Java incorporan el tipo de dato boolean, cuyos valores son verdadero
(true) y falso (false). Las expresiones lógicas devuelven valores de este tipo true o false; se
forman con operandos y operadores relacionales o lógicos, por ejemplo:
boolean encontrado, indicador = false;
encontrado = (x>2) && (x<10) ; // encontrado toma el valor ver//dadero si x está comprendido
//entre 2 y 10.
La mayoría de las expresiones lógicas aparecen es estructuras de control que sirven para
determinar la secuencia en que se ejecutan las sentencias Java. Raramente se tiene la
necesidad de leer valores boolean como dato de entrada o de visualizar valores boolean
como resultados de programa.
III.2. Declaración de clases
Puesto que Java es un lenguaje orientado a objetos, un programa Java se compone
solamente de objetos. Un objeto es la concreción de una clase, y que una clase equivale a la
generalización de un tipo específico de objetos. La clase define los atributos del objeto, así
como los métodos para manipularlos. Formalmente:
Una clase es un tipo definido que describe los atributos y los métodos de los objetos que se
crearán a partir de la misma. Los atributos definen el estado de un determinado objeto y los
métodos son las operaciones que definen su comportamiento. Forman parte de estos
métodos los constructores, que permiten iniciar un objeto, y los destructores, que permiten
destruirlos. Los atributos y los métodos se denominan en general miembros de la clase.
La definición de una clase consta de dos partes: el nombre de la clase precedido por la
palabra reservada class, y el cuerpo de la clase encerrado entre llaves. Es decir:
class Nombreclase{
cuerpo de la clase
…
}
El cuerpo de la clase consta de modificadores de acceso (public, protected y private),
atributos, mensajes y métodos. Un método implícitamente define un mensaje (el nombre
del método es el mensaje).
Los atributos constituyen la estructura interna de los objetos de una clase.
class circulo{
private double x,y;
private double radio;
//…
}
Los métodos generalmente forman lo que se denomina interfaz o medio de acceso a la
estructura interna de los objetos; ellos definen las operaciones que se pueden realizar con
sus atributos. Desde el punto de vista de la Programación orientada a Objetos, el conjunto
de todos los métodos se corresponde con el conjunto de mensajes a los que los objetos de
una clase pueden responder.
En Java un método es una definición incluida siempre dentro del cuerpo de una clase. Los
métodos no se pueden anidar.
El concepto de clase incluye la idea de ocultación de los datos, que consiste en que no se
puede acceder a los datos directamente, sino que hay que hacerlo a través de métodos de la
clase, estos métodos se denominan métodos de acceso.
Para controlar el acceso a los miembros de una clase, java provee las palabras clave
private(privado), protected(protegido) y public (público), aunque es posible omitirlas y el
acceso es predeterminado.
Un miembro declarado public (público) está accesible para cualquier otra clase o subclase
que necesite utilizarlo. La interfaz pública de una clase o simplemente interfaz está formada
por todos los miembros públicos de la misma, es importante recalcar que los atributos static
de la clase generalmente son declarados públicos.
Un miembro declarado private(privado) es accesible solamente por los métodos de su
propia clase, es decir no puede ser accedido por los métodos de cualquier otra clase
incluidas las subclases.
Un miembro declarado protected(protegido) se comporta exactamente igual que uno
privado para los métodos de cualquier otra clase, excepto para los métodos de las clases del
mismo paquete o de sus subclases con independencia del paquete al que pertenezcan, para
las que se comportan como un miembro público.
A continuación se enumeran algunas características importantes de las clases:
Todas las variables y funciones de Java deben pertenecer a una clase. No hay
variables ni funciones globales
Si una clase hereda de otra (extends), hereda todas sus variables y métodos.
Java ofrece una jerarquía de clases estándar de la que pueden derivar las clases que
los usuarios crean.
Una clase sólo puede heredar de una única clase (en Java no hay herencia múltiple).
Si al definir una clase no se especifica de qué clase deriva, por defecto la clase
deriva de Object. La clase Object es la jerarquía de todas las clases en Java.
En un fichero se pueden definir varias clases, pero en un fichero no puede haber
más de una clase public. Este fichero se debe llamar como la clase public que
contiene con extensión *.java.
Si una clase contenida en un fichero no es public, no es necesario que el fichero se
llame como la clase.
Los métodos de una clase pueden referirse de modo global al objeto de esa clase
que se aplica por medio de la referencia this.
Las clases se pueden agrupar en package, poniendo al inicio del fichero (package
Packagename;). Esta agrupación en packages está relacionada con la jerarquía de
directorios y ficheros en las que se guardan las clases.
El control de acceso a una clase determina la relación que tiene esa clase con otras clases de
otros paquetes. Se pueden distinguir dos niveles de acceso: de paquete y público. Una clase
con nivel de acceso de paquete sólo puede ser utilizada por las clases de un paquete( no está
disponibles para otros paquetes, ni siquiera para los subpaquetes). Una clase pública puede
ser utilizada por cualquier otra clase de otro paquete.
Declarar un método final supone que la clase se ejecute con más eficiencia, porque el
compilador puede colocar el código de bytes del método directamente en lugar del
programa donde se invoque a dicho método, ya que se garantiza que el método no va a
cambiar. Cuando un clase se declara final se impide que de esa clase se puedan derivar
subclases y todos sus métodos se convierten automáticamente en final, esto no es muy
conveniente ya que se sacrifica una de las características más importantes del Paradigma
orientado a Objetos, que es la reutilización de código.
Un objeto es un ejemplar concreto de una clase. Las clases son como los tipos variables,
mientras que los objetos son como variables concretas de un tipo determinado.
Nombreclase objeto1;
Nombreclase objeto2;
El operador new
El operador new crea una instancia de una clase asignando la cantidad de memoria
necesaria de acuerdo al tipo de objeto. El operador new se utiliza en conjunto con
un constructor. El operador new regresa una referencia a un nuevo objeto.
Acceso a variables y métodos
Una vez que se ha creado un objeto, seguramente se querrá hacer algo con él. Tal vez se
requiera obtener información de éste, se quiera cambiar su estado, o se necesite que realice
alguna tarea.
Los objetos tienen dos formas de hacer esto:
Manipular sus variables directamente
Para accesar a las variables de un objeto se utiliza el operador punto ( . ). La sintaxis es la
siguiente:
nombreObjeto.nombreVariable;
Llamar a sus métodos
Para llamar a los métodos de un objeto, se utiliza también el operador punto ( . ). La
sintaxis es la siguiente:
nombreObjeto.nombreMetodo([lista de argumentos opcionales]);
Ejemplo
/* Usuario2.java */
class Usuario2
{
String nombre;
int edad;
String direccion;
Usuario2( )
/* Equivale al contructor por
omisión */
{
nombre = null;
edad = 0;
direccion = null;
}
Usuario2(String nombre, int edad, String
direccion)
{
this.nombre = nombre;
this.edad = edad;
this.direccion = direccion;
}
Usuario2(Usuario2 usr)
{
nombre = usr.getNombre();
edad = usr.getEdad();
direccion = usr.getDireccion();
}
void setNombre(String n)
{
nombre = n;
}
String getNombre()
{
return nombre;
}
void setEdad(int e)
{
edad = e;
}
int getEdad()
{
return edad;
}
void setDireccion(String d)
{
direccion = d;
}
String getDireccion()
{
return direccion;
}
}
Ejemplo
/* ProgUsuario2.java */
class ProgUsuario2
{
void imprimeUsuario(Usuario2 usr)
{
//
usr.nombre equivale en este caso a usr.getNombre()
System.out.println("\nNombre: " + usr.nombre );
System.out.println("Edad: " + usr.getEdad() );
System.out.println("Direccion: " + usr.getDireccion() +"\n");
}
public static void main(String args[])
{
ProgUsuario2 prog = new ProgUsuario2( );
Usuario2 usr1,usr2; /* Se declaran dos
objetos de la clase Usuario2 */
/* Se utiliza el constructor por omisión */
usr1 = new Usuario2( );
prog.imprimeUsuario(usr1);
/* Se utiliza el segundo constructor de
Usuario2 */
usr2 = new Usuario2("Eduardo",24,"Mi
direccion");
prog.imprimeUsuario(usr2);
/* Se utiliza el tercer constructor de
Usuario2 */
usr1 = new Usuario2(usr2);
/*En este caso
usr1.setDireccion("nuevoValor"); equivale
a usr1.direccion = "nuevoValor";
*/
usr1.setDireccion("Otra direccion");
prog.imprimeUsuario(usr1);
prog.imprimeUsuario(usr2);
}
}
Constructor
En java una forma de asegurar que los objetos siempre contengan valores válidos es escribir
un constructor. Un constructor es un método especial de una clase que es llamado
automáticamente siempre que sea un objeto de la misma.
En Java una forma de asegurar que los objetos contengan siempre valores válidos es
escribir un constructor. Un constructor es un método especial de una clase que es llamado
automáticamente siempre que se crea un objeto de la misma. Cuando se crea un objeto Java
hace lo siguiente:
Asigna memoria para el objeto por medio del operador new
Inicia los atributos de ese objeto
Llama al constructor de la clase
El constructor tiene el mismo nombre que la clase a la que pertenece, no se hereda, no
puede retornar un valor y no puede ser declarado final, static, abstract, synchronized o
native.
Constructores múltiples
Cuando se declara una clase en Java, se pueden declarar uno o más constructores
(constructores múltiples) opcionales que realizan la iniciación cuando se instancia un objeto
de dicha clase.
Para la clase Usuario del ejemplo anterior no se especificó ningún constructor, sin embargo,
Java proporciona un constructor por omisión que inicia las variables del objeto a sus
valores predeterminados.
Ejemplo
/* ProgUsuario.java */
class ProgUsuario {
public static void main(String args[]) {
Usuario usr1, usr2; /* Se declaran dos objetos
de la clase Usuario */
boolean si_no;
usr1 = new Usuario(); /* Se utiliza el
constructor por omisión */
si_no = usr1 instanceof Usuario;
if(si_no == true)
System.out.println("\nEl objeto usr1 SI es
instancia de Usuario.");
else
System.out.println("\nEl objeto usr1 NO es
instancia de Usuario.");
usr2 = usr1; /* usr1 y usr2 son el mismo
objeto */
si_no = usr2 instanceof Usuario;
if(si_no == true)
System.out.println("\nEl objeto usr2 SI es
instancia de Usuario.");
else
System.out.println("\nEl objeto usr2 NO es
instancia de Usuario.");
}
}
Destructor
De la misma forma que existe un método que se ejecuta automáticamente cada vez que se
construye un objeto, también existe un método que se invoca automáticamente cada vez
que se destruye. Este método recibe el nombre de destructor y en el caso concreto de Java
se corresponde con finalize.
Cuando un objeto es destruido ocurren varias cosas: se llama al método finalize y después,
el recolector de basura se encarga de eliminar el objeto, es decir, eliminar la memoria y
todos los recursos que formen parte de éste objeto.
Un objeto es destruido automáticamente cuando se eliminan todas las referencias al mismo.
Una referencia a un objeto puede ser eliminada porque el flujo de ejecución salga fuera del
ámbito donde ella está declarada, o porque explícitamente se le asigne el valor null.
Atributos y Métodos static
Un atributo static no es un atributo específico de un objeto, es un atributo de la clase, es un
atributo del que sólo hay una copia y comparten todos los objetos de la clase.
Un método declarado static carece de la referencia this por lo que no puede ser invocado
para un objeto de su clase, sino que se invoca donde se necesite utilizar la operación para la
que ha sido escrito, es por ello que un método static no puede acceder a un miembro no
static de su clase.
static valorRetorno nombreMetodo([lista argumentos opcionales])
{
/* cuerpo del método */
}
Para acceder a las variables o métodos de clase se utiliza el mismo operador punto ( . ).
Aunque se puede acceder a las variables y métodos de clase a través de un objeto, está
permitido y se recomienda utilizar mejor el nombre de la clase.
/* Utilizar esto */
nombreClase.nombreVarClase;
nombreClase.nombreMetodoClase();
/* en lugar de esto */
nombreObjeto.nombreVarClase;
nombreObjeto.nombreMetodoClase();
Ejemplo
/* Usuario3.java */
class Usuario3
{
static char MAS = 'm';
static char FEM = 'f';
String nombre;
int edad;
String direccion;
char sexo;
Usuario3( )
{
nombre = null;
edad = 0;
direccion = null;
sexo = '\0';
}
Usuario3(String nombre, int edad, String direccion,char sexo)
{
this.nombre = nombre;
this.edad = edad;
this.direccion = direccion;
this.sexo = sexo;
}
Usuario3(Usuario3 usr)
{
nombre = usr.getNombre();
edad = usr.getEdad();
direccion = usr.getDireccion();
sexo = usr.getSexo();
}
void setNombre(String n)
{
nombre = n;
}
String getNombre( )
{
return nombre;
}
void setEdad(int e)
{
edad = e;
}
int getEdad()
{
return edad;
}
void setDireccion(String d)
{
direccion = d;
}
String getDireccion( )
{
return direccion;
}
void setSexo(char s)
{
sexo = s;
}
char getSexo( )
{
return sexo;
}
public String toString()
{
return nombre;
}
}
/* ProgUsuario3.java */
class ProgUsuario3
{
static int NUM_USUARIOS = 0;
static java.util.Vector usuarios = new java.util.Vector();
String nombreObj = null;
ProgUsuario3(String nombre)
{
this.nombreObj = nombre;
}
static int getNumUsuarios()
{
return NUM_USUARIOS;
}
static void imprimeUsuario(Usuario3 usr)
{
System.out.println("\nNombre: " + usr.nombre );
System.out.println("Edad: " + usr.getEdad() );
System.out.println("Sexo: " + usr.getSexo() );
System.out.println("Direccion: " + usr.getDireccion() );
}
void addUsuario(Usuario3 usr)
{
usuarios.addElement(usr);
System.out.print(usr.toString(
this.toString() +",");
NUM_USUARIOS ++;
}
)+
"
void delUsuario(Usuario3 usr)
{
boolean b = usuarios.removeElement(usr);
if( b == true )
{
NUM_USUARIOS--;
agregado
por
el
"+
System.out.print(usr.toString( )+ " eliminado por el
this.toString() +",");
}
else System.out.println("No se pudo eliminar al usuario.");
}
"+
public String toString()
{
return nombreObj;
}
public static void main(String args[])
{
ProgUsuario3 obj1 = new ProgUsuario3("objeto1");
ProgUsuario3 obj2 = new ProgUsuario3("objeto2");
Usuario3 usr1,usr2,usr3,usr4;
usr1 = new Usuario3( );
usr2
=
new
Usuario3("Usuario
A",Usuario3.FEM);
usr1 = new Usuario3(usr2);
usr1.setNombre("Usuario A");
usr3
=
new
Usuario3("Usuario
C",Usuario3.MAS);
usr4
=
new
Usuario3("Usuario
D",Usuario3.MAS);
B",24,"La
direccion
C",35,"La
direccion
D",15,"La
direccion
obj1.addUsuario(usr1);
System.out.println( "\t Total: " +ProgUsuario3.getNumUsuarios()
);
obj2.addUsuario(usr2);
System.out.println( "\t Total: " +obj1.getNumUsuarios() );
obj1.addUsuario(usr3);
System.out.println( "\t Total: " +ProgUsuario3.NUM_USUARIOS );
obj2.addUsuario(usr4);
System.out.println( "\t Total: " +getNumUsuarios() +"\n");
obj2.delUsuario(usr4);
System.out.println( "\t Total: " +ProgUsuario3.getNumUsuarios()
);
obj1.delUsuario(usr3);
System.out.println( "\t Total: " +obj1.getNumUsuarios() );
obj2.delUsuario(usr2);
System.out.println( "\t Total: " +ProgUsuario3.NUM_USUARIOS );
obj1.delUsuario(usr1);
System.out.println( "\t Total: " +getNumUsuarios() +"\n");
}
}
III.3 Relaciones entre clases
La asociación
De los temas anteriores recordamos que una asociación relaciona instancias de dos clases.
Existe una asociación entre dos clases cuando una instancia de una clase debe saber sobre
otra instancia para llevar a cabo sus funciones
En una asociación, dos instancias A y B relacionadas entre sí existen de forma
independiente.
No hay una relación fuerte. La creación o desaparición de una de ellas implica únicamente
la creación o destrucción de la relación entre ellas y nunca la creación o destrucción del
otro. Por ejemplo, un cliente puede tener varios pedidos de compra o ninguno.
La relación de asociación expresa una relación (unidireccional o bidireccional) entre las
instancias a partir de las clases conectadas. El sentido en que se recorre la asociación se
denomina navegabilidad de la asociación. Cada extremo de la asociación se caracteriza por
el rol o papel que juega en dicha relación el objeto situado en cada extremo.
La cardinalidad o multiplicidad es el número mínimo y máximo de instancias que pueden
relacionarse con la otra instancia del extremo opuesto de la relación. Por defecto es 1.






Uno y sólo uno (por defecto)
0..1 Cero a uno. También (0,1)
M..N Desde M hasta N (enteros naturales)
0..* Cero a muchos
1..* Uno a muchos (al menos uno)
1,5,9 Uno o cinco o nueve
Agregación y Composición
En una relación todo-parte una instancia forma parte de otra. En la vida real se dice que A
está compuesto de B o que A tiene B. La diferencia entre asociación y relación todo-parte
radica en la asimetría presente en toda relación todo-parte. En teoría se distingue entre dos
tipos de relación todo-parte:
a) la agregación es una asociación binaria que representa una relación todo-parte
(pertenece a tiene un, es parte de). Por ejemplo, un centro comercial tiene clientes.
b) la composición es una agregación fuerte en la que una instancia ‘parte’ está relacionada,
como máximo, con una instancia ‘todo’ en un momento dado, de forma que cuando un
objeto ‘todo’ es eliminado, también son eliminados sus objetos ‘parte’. Por ejemplo: un
rectángulo tiene cuatro vértices, un centro comercial está organizado mediante un conjunto
de secciones de venta…
A nivel práctico se suele llamar agregación cuando la relación se plasma mediante
referencias (lo que permite que un componente esté referenciado en más de un compuesto).
Así, a nivel de implementación una agregación no se diferencia de una asociación binaria.
Por ejemplo: un equipo y sus miembros. Por otro lado, se suele llamar composición cuando
la relación se conforma en una
inclusión por valor (lo que implica que un componente está como mucho en un compuesto,
pero no impide que haya objetos componentes no relacionados con ningún compuesto). En
este caso si se destruye el compuesto se destruyen sus componentes. Por ejemplo: un ser
humano y sus miembros.
Algunas relaciones pueden ser consideradas agregaciones o composiciones, en función del
contexto en que se utilicen.
La dependencia o relación de uso
Una clase A usa una clase B cuando no contiene atributos de la clase B pero, o bien utiliza
alguna instancia de la clase B como parámetro en alguno de sus métodos para realizar una
operación, o bien accede a sus atributos (clases con métodos amigos).
Ejemplos de Agregación o Composición
Como se ha comentado anteriormente, la agregación o composición son mecanismos
diferentes de la herencia que consiste en que uno o más atributos de una clase pertenecen a
una o más clases previamente declaradas. Es decir, un objeto puede componerse de otros
pertenecientes a otras clases.
Por ejemplo, la clase Persona se compone de dos variables de instancia, una de la clase
String y otra de la clase Fecha:
public class Persona {
String nombre;
Fecha fechaNacimiento;
public void asignaDatos(String nombre, Fecha f) {
this.nombre = nombre;
fechaNacimiento = f;
}
public String toString() {
return nombre + " nacido el dia " + fechaNacimiento.toString();
}
}
Se dice que la clase Persona es una agregación de las clases String y Fecha. La
clase
PruebaPersona muestra un ejemplo de uso de la clase Persona:
public class PruebaPersona {
public static void main (String [] args ) {
Persona p = new Persona();
Fecha n = new Fecha(11,2,2002);
p.asignaDatos("Miguel Angel Garcia", n);
System.out.println(p.toString());
}
}
La asociación de clases puede emplearse como técnica que implementa la denominada
anidación o descomposición de código. En el siguiente ejemplo, la clase Fecha se
compone de otras tres clases: Dia, Mes y Anho. Estas tres clases se encapsulan como
clases internas dentro de la clase Fecha.
public class Fecha {
private Dia dd;
private Mes mm;
private Anho aa;
public Fecha(int d, int m, int a) {
this.dd = new Dia(d);
this.mm = new Mes(m);
this.aa = new Anho(a);
}
public void siguienteDia() {
dd.siguiente();
}
private class Dia {
private int d; // 1 <= d <= mes.dias()
public Dia(int d){
this.d = d;
}
public void siguiente() {
d = d + 1;
verificar();
}
private void verificar() {
if (d > mm.dias()) {
d = 1;
mm.siguiente();
}
}
}
private class Mes {
private int m;
private final int[] diasDelMes =
{0,31,28,31,30,31,30,31,31,30,31,30,31};
/* 1 2 3 4 5 6 7 8 9 10 11 12) */
public Mes (int m) {
this.m = m;
}
public int dias() {
int result = diasDelMes[m];
if (m==2 && aa.esBisiesto()) {
result = result+1;
}
return result;
}
public void siguiente() {
m = m + 1;
verificar();
}
private void verificar() {
if (m>12) {
m=1;
aa.siguiente();
}
}
}
private class Anho {
private int a;
public Anho(int a) {
this.a = a;
}
public void siguiente() {
a=a+1;
}
public boolean esBisiesto() {
return ((a % 4 == 0) && (a % 100 !=0) || (a % 400 == 0));
}
}
}
Las clases Dia, Mes y Anho son muy simples y se distribuyen las funcionalidades de
una forma fácilmente reconocible. Los identificadores facilitan la legibilidad del código lo
que ayuda a que la descomposición extrema sea la base de una comprehensión, desarrollo y
verificación independiente e incremental de cada una de las cuatro clases, así como la de
sus métodos.
III.4 Declaración de herencia e interfaces
III.5 Polimorfismo
El polimorfismo es una característica importante en la programación orientada a objetos,
después de la herencia. Esta característica, inexistente en la programación estructurada,
permite al programador continuar con el uso del importante concepto que es la
“abstracción”.
Antes de presentar la definición de polimorfismo, es importante comentar algunos
conceptos básicos, que si bien son independientes del tema, son necesarios para su
comprensión [Cru13][4].
Concepto:
Idea que concibe o forma el entendimiento.
Especificar:
Explicar, declarar con individualidad de algo.
Abstraer:
Separar por medio de una operación intelectual las cualida
des de un objeto para considerarlas aisladamente o para considerar el mismo objeto en su pura esencia o noción.
Generalizar:
Abstraer lo que es común y esencial a muchas cosas, para
formar un concepto general que las comprenda todas.
Clase Abstracta:
Clase que no será instanciada, pero cuyas clases desciendentes poseen instancias. Permiten agrupar, también llamado factorizar, los atributos y los métodos.
Método Abstracto: Método sin cuerpo definido.
Además de los conceptos previos, es conveniente mencionar que dependiendo del autor
se utilizan diferentes referencias a las clases relacionadas en la herencia y por consecuencia
en el polimorfismo. Los conceptos utilizados son: Clase, Subclase; Superclase, Clase
Derivada; Clase Padre, Clase Hija; Clase Superior, Clase Inferior; etc.
La definición de polimorfismo puede ser descrita con enfoques diferentes, es decir, desde
el punto de vista de los Métodos, de los Objetos o de las Llamadas. A continuación se
presentan tres diferentes definiciones, todas ellas válidas de Polimorfismo:
“Capacidad que tienen algunos métodos de presentar “múltiples formas”, en el sentido de
que una llamada a un mismo método puede ofrecer comportamientos distintos en función
del contexto en el que se ejecute” [1].
“El Polimorfismo permite que diferentes objetos respondan de modo diferente al mismo
mensaje. Y adquiere su máxima potencia cuando se utiliza en unión de herencia” [Joy98].
“Característica que permite referirse a objetos de clases diferentes mediante la misma
llamada y realizar “la misma operación” de diferentes formas, según sea el objeto que se
referencia en ese momento” [Cru13].
Estas definiciones, suelen presentarse en POO y generan confusión a buena parte de los
estudiantes. Debemos enfatizar que estas definiciones son válidas y muy usadas en el
medio, pero para programadores principiantes es necesario hacer un desglose de los pasos y
los elementos involucrados en el concepto de polimorfismo.
Por esta razón, en estas notas presentamos los siguientes aspectos referentes al concepto
de polimorfismo, con el objetivo de que el estudiante comprenda en toda su magnitud este
concepto.
El polimorfismo involucra inicialmente a dos clases relacionadas por herencia.
En cada una de las clases, existe definido, un método con el mismo nombre.
Los dos métodos tienen el mismo nombre, pero cuerpo diferente.
Existen dos formas de usar el polimorfismo, llamados: Sobreescritura y Clases
Abstractas.
La llamada a uno de los dos métodos depende del objeto que se referencia en ese
momento y por tal motivo la respuesta es distinta.
La herencia extiende el polimorfismo a subclases de subclases.
III.5.1 Clases Abstractas
Esta forma de usar el polimorfismo consiste en definir métodos con el mismo nombre en
clases relacionadas por Herencia, con la característica de que en la superclase se define un
método abstracto con cuerpo vacío. En Java la superclase misma debe definirse como clase
abstracta. Las clases abstractas permiten definir atributos y métodos que existen en todas
las subclases derivadas de la superclase.
La superclase debe definirse abstracta ya que incluye al menos un método abstracto.
Remarquemos que un método abstracto tiene definido su cuerpo vacío y que será redefinido
en las subclases. El argumento o justificación para usar clases abstractas y no
Sobreescritura radica en que en este momento del diseño no se cuenta con una definición para
dicho método abstracto. A continuación se muestran ejemplos desarrollados en Java que son descritos
detalladamente.
Ejemplo de polimorfismo (mediante clases abstractas) del método abstracto CalculaPeri de la clase
abstracta Figura, del método CalculaPeri de la clase Cuadrado y del método CalculaPeri de la clase
Rectangulo (ejemplo modificado de [Joy98]).
En el siguiente ejemplo se muestra la clase abstracta Figura que empieza en la línea 2 y
termina en la línea 18. Nótese que en la línea 17 se definió el método abstracto CalculaPeri.
Posteriormente se define la clase Cuadrado derivada de la clase abstracta Figura que inicia
en la línea 19 y termina en la línea 24, en esta clase se redefine el método CalculaPeri para
un cuadrado (línea 21), lo cual constituye una redefinición del método abstracto de la línea
17. Además, se define la clase Rectangulo derivada de la clase abstracta Figura que inicia
en la línea 25 y termina en la línea 34, en esta clase se redefine nuevamente el método
CalculaPeri para un rectángulo (línea 30), lo cual constituye una redefinición del método
abstracto de la línea 17.
De la línea 35 a la línea 47 se define la clase FiguraPolimorfismo. En el método main se
crean dos objetos llamados C y R, donde C es una instancia de la clase Cuadrado y R es
una instancia de la clase Rectangulo, esto se ve en la líneas 37 y 38 respectivamente. Y
finalmente se llama al método CalculaPeri para calcular el perímetro correspondiente de
cada uno de los objetos, en las líneas 40 y 44.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package figurapolimorfismo;
abstract class Figura
{
protected int ancho;
protected int perimetro;
public Figura() {
ancho = 0;
perimetro = 0;
}
public void GuardaAncho(int x) {
ancho = x;
}
public int SacaPerimetro()
{
return perimetro;
}
abstract void CalculaPeri();
}
class Cuadrado extends Figura
{
public void CalculaPeri() {
perimetro = ancho*4;
}
}
class Rectangulo extends Figura {
private int alto;
public void GuardaAlto( int x) {
alto = x;
}
public void CalculaPeri()
{
perimetro = (ancho*2) + (alto*2);
}
}
public class FiguraPolimorfismo {
public static void main(String[] args) {
Cuadrado
C = new Cuadrado();
Rectangulo R = new Rectangulo();
C.GuardaAncho(3);
C.CalculaPeri();
System.out.println("El perimetro del cuadrado
es:"+C.SacaPerimetro());
42
R.GuardaAncho(3);
43
44
45
R.GuardaAlto(4);
R.CalculaPeri();
System.out.println("El perimetro del rectangu
lo es:"+R.SacaPerimetro());
46 }
47 }
A continuación se muestra el resultado de la ejecución del código anterior.
run:
El perimetro del cuadrado es:12
El perimetro del rectangulo es:14
BUILD SUCCESSFUL (total time: 0 seconds)
Ejemplo de polimorfismo (mediante clases abstractas) de los métodos abstracto
CalculaSalario e ImprimeRecibo de la clase abstracta Persona, de los métodos
CalculaSalario e ImprimeRecibo
de la clase Academico y de los métodos
CalculaSalario e ImprimeRecibo de la clase Administrativo (ejemplo modificado de
[3]).
En el siguiente ejemplo se muestra la clase abstracta Persona que empieza en la línea 2 y
termina en la línea 12. Nótese que en la línea 10 se definió el método abstracto
CalculaSalario y en la línea 11 el método abstracto ImprimeRecibo. Posteriormente se
define la clase Academico derivada de la clase abstracta Persona que inicia en la línea 13 y
termina en la línea 24, en esta clase se redefinen los métodos CalculaSalario e
ImprimeRecibo (líneas 16 y 19) con su código correspondiente. Además, se define la clase
Administrativo derivada de la clase abstracta Persona que inicia en la línea 25 y termina en
la línea 36, en esta clase se redefinen nuevamente los métodos CalculaSalario e
ImprimeRecibo (líneas 28 y 31).
De la línea 37 a la línea 52 se define la clase PersonaPolimorfismo. En el método main se
crean dos objetos llamados p y a, donde p es una instancia de la clase Academico y a es
una instancia de la clase Administrativo, esto se ve en la líneas 39 y 40 respectivamente.
Finalmente se llama al método CalculaSalario e ImprimeRecibo en las líneas 44 y 45 para
el objeto p y se llama al método CalculaSalario e ImprimeRecibo en las líneas 49 y 50 para
el objeto a.
1 package personapolimorfismo;
2 abstract class Persona {
3
protected String Nombre;
4
protected int SalXdia;
5
protected int Salario;
6
public Persona() { Nombre=""; SalXdia=0; }
7
public void GuardaNombre(String x) {Nombre=x; }
8
public void GuardaSalario(int x) {SalXdia = x;}
9
public String SacaNombre() { return(Nombre);}
10 abstract void CalculaSalario();//Depende de la
//persona
11 abstract void ImprimeRecibo();
12 }
13 class Academico extends Persona {
14
private int Becas;
15
public void GuardaBecas(int x) { Becas = x; }
16
public void CalculaSalario() {
17
Salario = (15 * SalXdia) + (Becas*SalXdia);
18
}
19
public void ImprimeRecibo() {
20
System.out.println("RECIBO BUAP PARA
PROFESOR");
21
System.out.println("Nombre: " + Nombre + "
Becas: " + Becas);
22
System.out.println("Salario: " + Salario);
23
}
24 }
25 class Administrativo extends Persona {
26
private int Canasta;
27
public void GuardaCanasta(int x){Canasta = x;}
28
public void CalculaSalario() {
29
Salario = (15 * SalXdia) + Canasta;
30
}
31
public void ImprimeRecibo() {
32
System.out.println("RECIBO BUAP PARA
ADMINISTRATIVO");
33
System.out.println("Nombre: " + Nombre + "
Canasta:" + Canasta);
34
System.out.println("Salario: " + Salario);
35
}
36 }
37 public class PersonaPolimorfismo {
38
public static void main(String[] args) {
39
Academico
p = new Academico();
40
Administrativo a = new Administrativo();
41
p.GuardaNombre("Salvador Lopez");
42
p.GuardaSalario(500);
43
p.GuardaBecas(5);
44
p.CalculaSalario();
45
p.ImprimeRecibo();
46
a.GuardaNombre("Rocio Saavedra");
47
a.GuardaSalario(300);
48
a.GuardaCanasta(100);
49
a.CalculaSalario();
50
a.ImprimeRecibo();
51
}
52 }
A continuación se muestra el resultado de la ejecución del código anterior.
run:
RECIBO BUAP PARA PROFESOR
Nombre: Salvador Lopez Becas: 5
Salario: 10000
RECIBO BUAP PARA ADMINISTRATIVO
Nombre: Rocio Saavedra Canasta:100
Salario: 4600
BUILD SUCCESSFUL (total time: 0 seconds)
III.5.2 Sobrecarga
La sobrecarga de métodos es la característica que permite definir dos o más métodos, con
el mismo nombre, dentro de una misma clase. La diferencia entre los métodos radica en el
número de parámetros y/o en el tipo de dato de los parámetros. Los constructores métodos ejecutados en la creación del objeto- también pueden ser sobrecargados.
La sobrecarga no es una propiedad específica de los lenguajes orientados a objetos.
Lenguajes tales como C y PASCAL soportan operaciones sobrecargadas [Joy98]. Tomando
esto como base consideramos que la sobrecarga de métodos es una característica que debe
estudiarse antes del concepto de Polimorfismo, que se verá en la siguiente sección.
A continuación se muestran ejemplos desarrollados en Java que son descritos
detalladamente.
Ejemplo de sobrecarga de constructores en la clase Esfera (ejemplo modificado de [1])
En el siguiente ejemplo se muestra la clase Esfera que empieza en la línea 2 y termina en
la línea 9. Nótese que en la línea 4 se definió el método constructor Esfera sin parámetros y
en la línea 5 se define otro método constructor con el parámetro r. La existencia de dos
métodos con el mismo nombre y diferente número de parámetros, es un ejemplo de
sobrecarga. Formalmente se dice que se sobrecargó el método Esfera.
De la línea 10 a la línea 17 se define la clase EsferaSobrecarga. En el método main se
crean dos objetos llamados miesfera1 y miesfera2 usando diferente constructor y
finalmente se llama al método Escribe para cada uno de los objetos.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package esferasobrecarga;
class Esfera {
double radio;
Esfera( ) { radio = 0.0; }
Esfera( double r ) { radio = r; }
public void Escribe() {
System.out.println("El Radio es: " + radio);
}
}
public class EsferaSobrecarga {
public static void main(String[] args) {
Esfera miesfera1 = new Esfera( );
Esfera miesfera2 = new Esfera( 3.0 );
miesfera1.Escribe();
miesfera2.Escribe();
}
17 }
A continuación se muestra el resultado de la ejecución del código anterior.
run:
El Radio es: 0.0
El Radio es: 3.0
BUILD SUCCESSFUL (total time: 0 seconds)
III.5.3 Sobreescritura
Esta forma de usar el polimorfismo consiste en definir métodos con el mismo nombre en
clases relacionadas por Herencia. En ambas clases existe una definición de estos métodos
con cuerpo no vacío. Normalmente el código del método existente en la subclase es de
mayor tamaño que el existente en la superclase.
El objetivo de asignarles el mismo nombre, es debido a que realizarán “la misma
operación”, donde la misma operación es desde el punto de vista abstracto.
Por ejemplo, los métodos que suman vectores de una, dos, tres,…, n dimensiones; deben
llamarse todos “suma”. A continuación se muestran ejemplos desarrollados en Java que son
descritos detalladamente.
Ejemplo de sobreescritura del método EscribeInfo de la clase Esfera y del método
EscribeInfo de la clase Planeta (ejemplo modificado de [1]).
En el siguiente ejemplo se muestra la clase Esfera que empieza en la línea 2 y termina en
la línea 7. Nótese que en la línea 5 se definió el método EscribeInfo sin parámetros.
Posteriormente se define la clase Planeta derivada de la clase Esfera que inicia en la línea 8
y termina en la línea 14, en esta clase se redefine el método EscribeInfo sin parámetros
(línea 12), lo cual constituye una sobreescritura del método EscribeInfo de la línea 5.
Formalmente se dice que se sobreescribió el método EscribeInfo.
De la línea 15 a la línea 25 se define la clase EsferaPolimorfismo. En el método main se
crean dos objetos llamados E y P, donde E es una instancia de la clase Esfera y P es una
instancia de la clase Planeta, esto se ve en la líneas 17 y 18 respectivamente. Y finalmente
se llama al método EscribeInfo para cada uno de los objetos en las líneas 22 y 23.
1 package esferapolimorfismo;
2 class Esfera {
3
int radio;
4
public void GuardaRadio(int x) { radio = x;}
5
6
public void EscribeInfo() {
System.out.println("El radio es: "+radio);
};
7 }
8 class Planeta extends Esfera
9
{
10
int lunas;
11
public void GuardaLunas(int x) { lunas =x;}
12
public void EscribeInfo()
13
{ System.out.println("El radio es: "+radio+"
y "+lunas+" lunas");}
14
}
15 public class EsferaPolimorfismo {
16 public static void main(String[] args) {
17
Esfera E = new Esfera();
18
Planeta P = new Planeta();
19
E.GuardaRadio(100);
20
P.GuardaRadio(500);
21
P.GuardaLunas(2);
22
E.EscribeInfo();
23
P.EscribeInfo();
24 }
25 }
A continuación se muestra el resultado de la ejecución del código anterior.
run:
El radio es: 100
El radio es: 500 y 2 lunas
BUILD SUCCESSFUL (total time: 0 seconds)
Ejemplo de sobreescritura de los métodos Vender y EscribeInfo de la clase
Producto y de los métodos Vender y EscribeInfo de la clase ProductoaMayoreo.
En el siguiente ejemplo se muestra la clase Producto que empieza en la línea 2 y termina
en la línea 20. Nótese que en la línea 12 se definió el método Vender y en la línea 16 el
método EscribeInfo. Posteriormente se define la clase ProductoaMayoreo derivada de la
clase Producto que inicia en la línea 21 y termina en la línea 33, en esta clase se redefinen
los métodos Vender y EscribeInfo (líneas 25 y 29), lo cual constituye una sobreescritura de
dichos métodos.
De la línea 34 a la línea 43 se define la clase ProductoPolimorfismo. En el método main
se crean dos objetos llamados Prod1 y Prod2, donde Prod1 es una instancia de la clase
Producto y Prod2 es una instancia de la clase ProductoaMayoreo, esto se ve en la líneas 36
y 37 respectivamente. Y finalmente se llama al método Vender para cada uno de los objetos
en las líneas 38 y 39; y se llama al método EscribeInfo para cada uno de los objetos en las
líneas 40 y 41.
1 package productopolimorfismo;
2 class Producto
3 {
4
public int cantidad;
5
public double precio;
6
public double pago;
7
public Producto()
8
{
9
cantidad = 5;
10
precio = 10.50;
11
}
12
void Vender()
13
{
14
pago = cantidad*precio;
15
}
16
void EscribeInfo()
17
{
18
System.out.println("Su pago total es "+pago);
19
}
20 }
21 class ProductoaMayoreo extends Producto
22 {
23 public String NombreEmpresa;
24 public boolean mayoreo;
25 void Vender()
26 {
27
pago = cantidad*precio - cantidad*precio*0.2;
28 }
29 void EscribeInfo()
30 {
31
System.out.println("Pago total con descuento:
"+pago);
32 }
33 }
34 public class ProductoPolimorfismo {
35
public static void main(String[] args) {
36
Producto Prod1 = new Producto();
37
ProductoaMayoreo Prod2 = new ProductoaMa
yoreo();
38
Prod1.Vender();
39
Prod2.Vender();
40
Prod1.EscribeInfo();
41
Prod2.EscribeInfo();
42
}
43 }
A continuación se muestra el resultado de la ejecución del código anterior.
run:
Su pago total es 52.5
Pago total con descuento: 42.0
BUILD SUCCESSFUL (total time: 0 seconds)
COMENTARIO FINAL.
En este capítulo se define el concepto de polimorfismo, así como una cantidad
importante de ejemplos que permiten entender el concepto a nivel básico. Sin embargo, este
concepto tiene un impacto significativo y un alcance que no es posible mostrar en este
documento y que será comprendido a medida que el programador madure al utilizarlo y
pueda vislumbrar el alcance de dicho concepto.
BIBLIOGRAFÍA Y REFERENCIAS
[Llo ] Llobet Azpitarte Rafael, Alonso Jordá Pedro, Devesa Llinares Jaume, Miedes De
Elías Emilí, Ruiz Fuertes María Idoia, Torres Goterris Francisco. “Introducción a la
Programación Orientada a Objetos con Java”. Departamento de Sistemas Informáticos y
Computación Universidad Politécnica de Valencia.
[Joy98] Joyanes Aguilar Luis.(1998) “Programación orientada a objetos”, Mc. Graw Hill
[Cru13] Cruz Almanza Graciano. “Notas 2013 de Programación II”. Facultad de Ciencias
de la Computación, Universidad Autónoma de Puebla.
[
.
] RAE
Capítulo IV:
LENGUAJE DE
PROGRAMACIÓN
ORIENTADO
A OBJETOS,
ELEMENTOS
AVANZADOS
Beatriz Beltrán Martínez
Carmen Cerón Garnica
Meliza Contreras González
Mario Rossainz López
Miguel Rodriguez Hernández
Capítulo IV: Lenguaje de Programación
Orientado a Objetos Elementos Avanzados
IV.1. Tratamiento de Excepciones
En todo desarrollo de software, existen en algún momento condiciones que puede llevar a
contratiempos de programación, ya sea por agentes externos (usuarios) o internos (sistemas
operativos, agotamiento de recursos) al sistema; esto a pesar de que el software
desarrollado sea eficiente y de calidad. Es por eso que para este tipo de situaciones JAVA
ofrece las excepciones.
Una excepción es un error o una condición anormal que se ha producido durante la
ejecución de un programa. Es decir, es un evento que ocurre durante la
ejecución de un
programa y que interrumpe el flujo normal de sus instrucciones JAVA, además mediante
las excepciones permite al programador intentar recuperarse ya sea continuando con la
ejecución o bien detenerla [MAR11].
Identificar Excepciones en JAVA:
Las excepciones en general pueden ser de 2 tipos:
 Errores de Programación: Aquellos errores en donde el programador puede evitarlo porque es un
error al codificar el programa.
 Errores de Datos: Aquellos errores que el programador puede evitar, o simplemente no puede hacerlo,
ya que son problemas en la interacción programa-usuario.
IV.1.1 Condiciones de error
Al escribir programas de manera correcta es una tarea compleja, y es por eso que surge la
necesidad de manejar los errores con eficiencia. Este manejo requiere que se aseguré que
los programas cuando utilizan objeto o métodos de manera incorrecta puede el programa
manipular el error de manera conjunta y no de manera aislada.
Además, se tiene que tomar en cuenta que JAVA tiene diferentes tipos de excepciones:
excepciones de entrada/salida, las excepciones en tiempo de ejecución y las de su propia
creación. Durante el tiempo de compilación se detectan únicamente los errores de sintaxis.
Y el manejo de excepciones de JAVA permite el manipular los errores que ocurren en
tiempo de ejecución, se pueden mencionar las excepciones aritméticas (división entre cero),
excepciones de puntero (acceso a punteros NULL) y excepciones de indexación (acceso por
encima o debajo de los límites de un vector) entre otras.
Algunas excepciones son fatales y causan el fin de la ejecución del programa, cuando
ocurre esto, lo más recomendable es el envío de un mensaje al usuario indicando el
problema que se provocó. Pero existen otras excepciones, por ejemplo cuando no se
encuentra un archivo, en donde el programa puede dar al usuario la oportunidad de corregir
el error.
Para el manejo excepciones dentro de JAVA y ayudando a la portabilidad, se ofrecen
mecanismos para el manejo de excepciones. En esta línea se trabaja con la detección y
manejo de errores, gestión de recursos y especificación de excepciones.
La captura (catch) o localización de errores es un problema dentro de la programación,
puesto que también hay que pensar, no solo en el error, sino en donde es que puede surgir y
como se manejará. En el momento que se tiene un error, ahora lo que se debe tomar en
cuenta es que se debe hacer: terminar con la ejecución del programa o ignorar el error
esperando que no se tengan consecuencias.
Ante un error, la solución que ofrece JAVA, es la de llamar a mecanismos del lenguaje que
soportan manejo de errores, aunque en general, para el programador resulta complicado
detectar todos los errores.
Un programa lanza (throws) una excepción en el momento que se detecta el error y es
cuando JAVA busca el código que maneja o captura el problema y responder de una
manera adecuada. En caso de que no la encuentra, entonces el error se propaga hasta ser
capturada por la rutina que la lanzó, generalizándose para regresar el control al sistema.
En este sentido, de la propagación, se tiene que entender que existe una jerarquía de las
excepciones, como se muestra a continuación:
IV.1.2 Manejo de excepciones en Java
El concepto de excepción indica que hay una irregularidad dentro de la ejecución del
software, esto implica que dentro de la ejecución existe un problema a nivel de ejecución,
que no tiene que confundirse con excepciones de hardware.
Una excepción, entonces es lanzada cuando existe un problema en algún segmento del
programa, por ejemplo, cuando se quiera acceder a una localidad de un arreglo que no está
dentro del rango definido, esto lleva a una violación entre el llamado y el llamador. En el
momento que surge dicha excepción, no desaparece aunque el programador decida
ignorarla, entonces se debe poder reconocer y manejar, sino se propagará hasta alcanzar el
máximo nivel y terminará la aplicación sin ninguna otra opción.
Es por esto que el mecanismo de manejo de excepciones de Java, permite:
1. Detección de errores y posibilidad de recuperación.
2. Limpieza de errores no manejados.
3. Evitar la propagación de errores.
IV.1.3 Mecanismo de manejo de excepciones
El mecanismo proporcionado por Java, lleva a considerar cinco palabras reservadas: try,
throw, throws, catch y finally. Considerando que:





try es un bloque donde se quiere detectar errores.
catch es el manejador que captura las excepciones del bloque try.
throw es una expresión para levantar (raise) excepciones.
throws indica las excepciones que puede levantar un método.
finally es opcional, y se sitúa después de los catch de un try.
Los pasos a seguir son:
1. Establecer un conjunto de operaciones para anticipar errores, dentro de un bloque try.
2. Cuando una rutina encuentra un error, lanza la excepción y el lanzamiento (throwing) es
el efecto de levantar la excepción.
3. Para propósitos de limpieza o recuperación, anticipando el error y capturando (catch) la
excepción
El mecanismo, se completa: El bloque finally, si es que se especifica, es ejecutado después
del try.
El tratar excepciones y detectarlas conlleva a una diferencia, el tratar la excepción realiza
una comunicación lanzando una excepción, pero si se deja que se lance la excepción con un
error (detectarla) es no capturada (uncaught) y el programa termina por omisión, sin el
manejo del error.
La estructura de una excepción se muestra a continuación:
…
try
{
// código del bloque try
}
catch (excepción)
{
// código del bloque catch
}
…
catch (excepción)
{
// código del bloque catch
}
finally
{
// código opcional de finally
}
…
La palabra reservada try sirve para definir el bloque de código donde es posible que exista
una excepción, es donde existen llamados a métodos que generan o definieron excepciones.
La palabra reservada catch, es donde se define el bloque que maneja la excepción, el
parámetro que lleva representa el tipo de excepción que capturará y que podrá manejar, por
cada catch que se coloque, se puede manejar una excepción diferente. Y finally, el cual es
opcional, si se utiliza se coloca al final del último catch definido y la finalidad es liberar
recursos que se utilizaron en el bloque try, además este bloque siempre se ejecuta, exista o
no una excepción.
Cabe hacer mención que el bloque try puede estar anidado, cada try contendrá sus propios
bloques catch. El proceso que se sigue en una estructura que se trata una excepción es:
1. Si una excepción se produce en alguna de las sentencias dentro del bloque try, entonces
se da un salto al primer manejador catch cuyo parámetro coincida con el tipo de
excepción que fue lanzada.
2. Cuando las sentencias en el manejador catch se ejecutaron, se termina el bloque try y la
ejecución prosigue en la sentencia siguiente, nunca se produce un salto hacia atrás, donde
ocurrió la excepción.
3. Si no hay manejadores para tratar con una excepción, se aborta el bloque try y la
excepción es relanzada.
4. Si utiliza el manejador opción del bloque finally, este se debe escribir al final del último
catch y se lance una excepción o no, se ejecuta las sentencias que se encuentren dentro
de este bloque.
IV.1.4 Nuevas clases de excepciones
Las clases de excepciones definidas en Java, pueden ampliarse a las aplicaciones,
definiendo excepciones específicas a errores de alguna aplicación en particular, dichas
excepciones derivan de la clase Exception, de manera directa o indirecta. Por ejemplo:
1 public class MiExcepcion extends Exception
2 {
3
…
4 }
O bien, ya que ArrayIndexOutOfBoundsException hereda de RunTime que a su vez,
hereda de Exception.
1
public
class
ArrayIndexOutOfBoundsException
2 {
3
…
4 }
ArregloExcepcion
extends
En este caso, la excepción que se está definiendo debe ser una clase que contenga como
mínimo un constructor con un argumento de cadena en el que se puede dar información
sobre la excepción generada, en este caso se puede utilizar super, si se requiere invocar al
constructor de la clase base.
Así, por ejemplo, la definición de los ejemplos anteriores quedaría:
1 public class MiExcepcion extends Exception
2 {
3
public MiExcepcion (String error)
4
{
5
super (error);
6
System.out.println (“Constructor de la clase MiExcepcion \n”);
7
}
8 }
9
public
class
ArregloExcepcion
extends
ArrayIndexOutOfBoundsException
10 {
11
private int c;
12
13 public ArregloExcepcion (String mensaje, int a)
14 {
15
super(mensaje);
16
c = a;
17 }
18 }
Estas excepciones, ya definidas pueden ser lanzadas en cualquier aplicación que así lo
requieran, por ejemplo:
1
2
3
4
5
6
7
8
9
…
private final MAX=10;
private int n;
private int v[];
n = entrada.nextInt();
if (n > MAX)
throw new ArregloExcepcion (“Fuera del rango ”,n);
…
Incluso en este sentido, se puede generar una jerarquía de excepciones, esto en caso de que
las excepciones a definir así lo requieran.
IV.1.5 Crear Excepciones y Lanzar en Java
El programador no sólo puede utilizar las excepciones que le provee el lenguaje Java,
también permite generar y lanzar sus propias excepciones mediante la sentencia trhow que
se ajusten al nivel del programa. Una excepción (clase) se guarda con extensión .java :
La sintaxis general para crear una (clase) Excepciones es:
<...>class <Nombre_Nueva_Excepción>extends Exception
{ ··· }
Sintaxis general para aplicar la excepción en la Clase que utiliza la nueva
excepción
<...> class <Nombre_Clase> extends
<Nombre_Clase_ Nueva_Excepción
{ ··· }
La sintaxis para la declaración en la cabecera del método de las excepciones que se
lanzarán:
<...> Nombre_Método (Argumentos)throws
<Nombre Excepción1>,..., <Nombre Excepciónn>
{ ··· }
Crear el objeto de la nueva excepción en el cuerpo del método constructor
Sintaxis general:
<Nombre Excepción> <Nombre Variable> = new
<Nombre Excepciónn>
Lanzamiento de la excepción en el cuerpo del método constructor
Sintaxis general:
< throw new <Nombre Variable> ;
Lanzamiento de la excepción en el cuerpo de un método
Sintaxis general:
<throw new <Nombre Excepción> (“Mensaje a mostrar”)
Ejemplo 1: Generar una nueva Excepción en el Método Constructor
Generar una excepción en una clase para limitar el número máximo de instancias que se
pueden crear de ella. La clase se llama GenearObjetos tiene un atributo Número (static int)
y atributo: Nombre (String). El atributo Numero se decremeta cada vez que se instancia la
clase. La clase solo debe permitir tres instancias, por lo que si esta llega al número máximo
que es de 3 las siguientes llamadas generarán una excepción llamada Maximo_Instancias
con el mensaje “No se puede crear más de tres instancias”. Posteriormente Gestionar la
excepción en la solución requerida. La clase
Paso1. Creamos la excepción por herencia con el nombre de Maximo_Instancias
1
2
3
5
6
7
public class Maximo_Instancias extends Exception
{
public Maximo_Instancias() {}
super(message);
}
}
Paso2. Creamos la Clase GenearObjetos con sus atributos y lanzaremos la excepción ya
definida.
1 public classGenerar Objetos extends
Maximo_Instancias
2{
3
4
static int numero=3;
5
String Nombre;
6
7 //Constructor
8 public GenerarObjetos() throws Maximo_Instancias{
9
if (numero==0)
10 {
11
throw new Maximo_Instancias(" No se puede crear más de tres
instancias");
12 }
13 else
14
numero--;
15
}
16 // Constructor con un argumento nombre
17
18 public GenerarObjetos(String Nom) throws
Maximo_Instancias{
19 Nombre=Nom;
20 if (numero==0)
22 {
23 throw new Maximo_Instancias
(" No se puede crear mas de tres instancias");
24 }
25
else
26
numero--;
27
28}
}
Paso 3. Posteriormente se Gestiona la Excepción creada al instanciar la clase.
1 import java.io.*;
2 import java.util.*;
3 public class EjecutaGenerarObjetos
4 {
5
public static void main (String [] args )
6
{
7
ArrayList a1=new ArrayList();
8
9
for (int i=0;i<5;i++)
10 {
11 System.out.println("Se crea un nuevo objeto"+ i);
12 try
13 {
14 //Creamos objeto con nombre
15 a1.add(new GenerarObjetos ("Objeto"+ i));
16 } catch (Exception e)
17 {
18 System.out.println("Usted no puede crear más
objetos porque:
"+e.getMessage());
19 }
20 finally
21 {
22 System.out.println("Gracias por usar este programa");
23
}//fin finally
24
}// fin for
25 }//fin main
26 }//fin clase
Paso 4. Visualización de la Ejecución de la Nueva Excepción
4.2 Corrida de la Generación de la Excepción
Ejemplo 2: Generar y Lanzar Excepción en un Método de la Clase
Estudiaremos una situación en la que el usuario introduce su edad y se compara si es mayor
o igual a 18 años, si el valor es menor es decir es un dato no permitido, el programa lanza
un excepción, que vamos a llamar ExcepcionEdad. En caso contrario seguirá la ejecución
del programa.
Creamos la excepción por herencia con el nombre de ExcepcionEdad
1 public class ExcepcionEdad extends Exception
2 {
3
public ExcepcionEdad (String message)
4
{
5
super(message);
6
}
7 }
Lanzaremos la excepción ya creada:
1 public class Votar {
2
3 String nombre;
4 String genero;
5 int edad;
6
7 public Votar(String nombre, String genero, int edad){
8
this.nombre=nombre;
9
this.genero=genero;
10
this.edad=edad;
11
}
En el método validadEdad vamos a lanzar la excepción si se produce el error
12 public void validarEdad() throws ExcepcionEdad{
13
if(edad<18){
14
throw new ExcepcionEdad("Es menor de edad");
15
}
16
else{
17
System.out.println("Usted si puede votar");
18
}
19
}
20 } // De la clase
Posteriormente vamos a visualizar la ejecución de la Excepción creada.
1 public class EjecutaException {
2
3 public static void main(String[] args) {
4
Votar v1= new Votar("Pepe","Hombre", 15);
5
try{
6
v1.validarEdad();
7
}
8
catch(Exception e){
9
System.out.println("Usted
no
puede
votar
porque:
"+e.getMessage());
10
}
11
finally{
12
System.out.println("Gracias por usar este programa");
}
13 }
14 } // De la clase
En resumen para utilizar excepciones requiere:
1. Crear la excepción
2. Llamar la excepción para lanzarla en el momento que se produzca el error
3. Gestionar la excepción para controlar el error, con las sentencias: try{} y catch {}
Haz Ahora y Programa
1. Crear una excepción para validar números que guardaran en un arreglo y que se
encuentre en un rango permitido de 1 a 20 y que solo se han números enteros,
cualquier otro número no se permitirá y deberás informar al usuario.
Posteriormente realiza el promedio de los datos del arreglo.
2. Crea una excepción para validar que un objeto llamado alumno se crea con los
atributos
necesarios como son: Matriculo, Nombre y Promedio y en caso
contario avisar que atributo falta.
3. Crea una Excepción para validar números pares al momento de la lectura
4. Crea una Excepción para validar números impares al momento de la lectura
5. Crea Una Excepción para validar las operaciones de un cuenta bancaria, como
son:
5.1 Retiro donde no se pueden retirar cantidades negativas y tampoco si el saldo es
menor a la cantidad retirada.
5.2 Depósito: validar que no inserte cantidades negativas o iguales a cero.
IV.2. Interface Gráficas de usuario
Hasta este momento tu experiencia en la programación se ha limitado a proyectos donde
solo interviene la consola (la típica pantalla negra donde solo puedes manipular texto).
Sin embargo en la mayoría de las aplicaciones que utilizas en tu vida diaria no se emplea
la consola, sino pantallas donde puedes realizar cualquier actividad que desencadena otros
procesos, por ejemplo cuando ingresas a la página de tu correo electrónico aparece una
pantalla donde debes ingresar tu nombre de usuario y contraseña, para que de ahí al
presionar la opción iniciar sesión te muestra otra pantalla donde puedes enviar, eliminar tus
correos, actualizar tus contactos.
A las pantallas con las que interactúas se les llama Interfaces Gráficas de Usuario
(GUI’s) que son el conjunto de componentes gráficos que posibilitan la interacción entre el
usuario y la aplicación [Szna10].
Recuerdas el trabajo que te costo realizar tu primer menú para realizar operaciones
básicas de una calculadora, tuviste que realizar un ciclo hasta que el usuario decidiera no
realizar más operaciones (con la típica opción que empleaba s(sí) o n(no)), solicitabas los
dos números y luego la operación a realizar con un condicional múltiple y si te equivocabas
a la hora de digitar la operación (+,-,*,/) te mandaba a la opción por defecto y tenias en el
peor de los casos, equivocarte a la hora de indicar en lugar de números, caracteres y así no
te quedaba otra opción más que volver a proporcionar los números y la operación. Como te
darás cuenta el usuario de tu propia aplicación fuiste tú, padeciste estas desventajas. Por lo
que la solución a la incomodidad de ejecutar tu aplicación radicaba en que la interfaz para
el usuario no resultaba atractiva y fácil de usar.
Si observas la Figura 4.2.1 te darás cuenta que parece una típica calculadora que viene
por defecto en el sistema operativo que empleas, tiene la ventaja que el usuario ya no puede
equivocarse a la hora de introducir los números solicitados, además las operaciones que
antes tenías que introducir para realizar el proceso están representadas de forma más
sencilla y lo mejor tiene un apartado con la opción igual, lo que implica que puedes realizar
tantas operaciones como desees hasta que decidas dar clic en la conocida x para cerrar la
aplicación.
Figura 4.2.1 Ejemplo de interfaz gráfica
Así esta interfaz tiene los componentes más básicos (ver Figura 4.2.2) sin el botón cerrar
no podríamos detener la ejecución de la aplicación, en la caja de texto aparecerán los
dígitos que componen a cada uno de los números que se irán digitando conforme se pulse
el botón que representa el dígito elegido, si el número es decimal se permite colocar un
punto de separación de la parte entera y decimal. Una vez que se tiene el primer número, a
continuación se elige uno de los botones de la extrema derecha que representan las
operaciones aritméticas básicas, después de pulsar el botón de su preferencia el usuario
debe digitar el otro número que se mostrará completo en la caja de texto, para
posteriormente pulsar el botón con el símbolo igual cuya acción inmediata será mostrar el cálculo
resultante en la caja de texto, y así se pueden realizar operaciones indefinidamente.
Por lo tanto para realizar cualquier aplicación que vincule interfaces gráficas no basta
sólo con realizar un diseño atractivo de la aplicación, sino también determinar qué
funcionalidades realizar cuando suceda una acción específica sobre un componente, a estas
acciones se les llama eventos [Joy11], por cada evento hay quee realizar su proceso de
manipulación por lo que se definirán los elementos básicos para esto.
Un objeto GUI donde el evento se desencadena se conoce como fuente del evento, por
tanto la fuente del evento genera eventos [Wu08], por ejemplo en la calculadora cuando se
presiona el botón del botón donde aparece el número 1 ocasionará que en la caja de
texto(display) se muestre el número, por tanto cuando un evento se genera, el sistema
notifica a los objetos receptores los eventos relevantes, en este caso a la caja de texto, por
tanto esta cumple con el rol de ser un objeto receptor del evento que ejecutará una respuesta
a los eventos generados.
Además de estos objetos, debe asociarse a la fuente del evento una interface oyente que
debe corresponder con el tipo de evento generado[Wu08], por ejemplo, si presionamos un
botón se generará un evento de acción por lo que el sistema buscarán oyentes de acción
registrados, si se pulsa el botón cerrar de la ventana se buscarán oyentes de ventana, si no
existe el oyente el evento se ignora. Los oyentes son interfaces cuya única misión es
atender eventos y poseerán métodos exclusivos sin implementar para el evento generado,
esperando que sea el usuario el que le indique que instrucciones seguir en el caso de que el
oyente haya detectado el evento.
Por tanto para desarrollar una aplicación gráfica en Java se requiere contar con los
siguientes elementos:
1. Diseñar la interfaz gráfica con todos los componentes.
2. Por cada componente agregar el oyente correspondiente o uno sólo si se maneja un solo
tipo de eveto.
3. Definir las clases internas que realizarán el objetivo de la aplicación e intercalarlas con
las fuentes, receptores de evento y los oyentes.
IV.2.1 Componentes
Anteriormente para implementar una interfaz gráfica en Java se empleaban las clases
contenidas en el paquete de AWT[Wu08] que se consideran clase de peso pesado por lo
que en los últimos años se ha optado por el paquete de clases de Swing, consideradas clases
de peso ligero, la ventaja de Swing sobre AWT es que sus clases estan implementadas
completamente en Java mientras que AWT utiliza las directivas gráficas del sistema
operativo por lo que dependiendo de éste se visualizará el control de una o de otra forma,
en cambio en Swing muestra el mismo tipo de interfaces, se recomienda también que no se
mezclen las componentes awt y swing porque visualmente resulta inconsistente. Cabe
mencionar que para el manejo de eventos hasta ahora se requiere utilizar las interfaces y
métodos provistos por AWT para que la interfaz tenga funcionalidad y no sea estática.
Los componentes en Swing se diferencian respecto AWT en que a sus componentes se
les coloca el prefijo J por ejemplo un control Button en AWT es el equivalente al control
JButton de Swing.
Los componentes de las interfaces gráficas en Swing se clasifican de la siguiente
forma[Wu08] y se observa un ejemplo en la figura:
Component: que proviene de la clase Object, ejemplos de ellos tenemos la clase JButton,
JRadioButton, JCheckBox, JLabel, JList, JcomboBox (que contiene una lista y una caja de
texto integrada), JprogressBar, JScrollBar entre otros.
Container: es subclase de la clase Component y tiene el objetivo de almacenar cuantos
componentes y contenedores requiera el usuario, de esta clase provienen las clases hijas
JPanel, Window, JFrame, JDialog con las que se realizan la mayoría de las aplicaciones
donde regularmente se toma un objeto de la clase Frame que viene de ventana y se le
agregan cuantos objetos Panel se requieran para agregar posteriormente componentes en
cada Panel como botones, cajas de texto, barra de menús.
TextComponent: son todos los componentes que permiten la manipulación de texto, los
componentes JTextArea y JTextField, JpasswordField, JpopMenu,
heredan las
características de esta clase.
MenuComponent: son todos los componentes relacionados con la administración de
menús como los componentes JMenuBar, JMenuItem, JMenu, JPopupMenu,
JCheckBoxMenuItem.
Figura 4.2.2 Ejemplo de interfaz gráfica
IV.2.2 Tipos de eventos y métodos de oyentes
Para cada componente se cuentan con distintos eventos asociados a un oyente en
específico, los más significativos se muestra en la siguiente tabla [Joy11]:
Tabla 4.1 Eventos con oyentes
Evento
Componente
Acción
Oyente
Métodos de
ActionEvent
JButton
Hacer clic en el botón
ActionListener
actionPerformed()
JRadioButton
Hacer clic en el botón
de radio
JMenuItem
Seleccionar
un
elemento del menu
JTextField
Terminar de editar un
texto pulsando enter
JList
Hacer doble clic sobre
el element de una lista
JList
Selecciona o suelta un
elemento de una lista
ItemListener
itemStateChanged()
JCheckBox
Selecciona o deja de
seleccionar
un
elemento
TextEvent
JTextComponent
Cambia el texto
TextListener
textValueChanged()
KeyEvent
JTextField
Se activa cuando se
presiona una tecla
dentro del campo de
texto
KeyListener
keyPressed()
keyReleased()
keyTyped()
Window
Pulsa o suelta una
tecla
Window
Pulsa o suelta un botón
del ratón, entra o sale
de un component por
medio del mouse o
mueve o arrastra el
ratón
MouseListener
mouseClicked()
mouseEntered()
mouseExited()
mousePressed()
mouseReleased()
Window
Cuando el mouse se
mueva
a
una
coordenada específica
MouseMotionList
ener
mouseDragged()
mouseMoved()
Window
Obtiene o pierde el
foco
FocusListener
focusGained()
focusLost()
JTextField
Obtiene o pierde el
foco el componente
Window
Actua
sobre
una
ventana abre o cierra o
se reestablece
WindowListener
windowActivated()
windowDeactivated()
windowClosed()
windowClosing()
windowIconified()
windowDeiconified()
windowOpened()
ItemEvent
MouseEvent
FocusEvent
WindowEvent
Oyente
Ahora teniendo todos los elementos se verá como implementar la interfaz gráfica en Swing siguiendo el
esqueleto siguiente:
1 import javax.swing*;
2 import java.awt.*;
3 import java.awt.event.*;
4 public class nombreApp extends JFrame{
5 public nombreApp(){
6 // establecer el contenedor
7 // establecer el administrador de diseño
8 /* crear cada componente y añadirlo al contenedor, cabe mencionar
que incluso se puede agregar un contenedor que a la vez se le
agreguen más componentes o contenedores. */
9 // asociar al menos un oyente por cada componente o uno para
componentes del mismo tipo manejando la funcionalidad a aplicar con
el método getSource() del objeto evento involucrado.
10 // establecer tamaño y visibilidad de la ventana
11 }
12 /* implementar cada oyente mediante una clase de la siguiente
forma:*/
13 class Oyente implements nombreListener{
14 // implementar métodos de nombreListener
15 /* al ser una interface recuerde que si no requiere un método debe
colocar su implementación como {} */
16 }
17 public static void main(String args[]){
18 nombreApp app=new nombreApp();
19 app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)
20 }
21 }
Respecto al código mostrado las líneas 1-3 son las bibliotecas de objetos que se requieren
para los eventos y componentes gráficos, la línea 4 es un ejemplo de cómo definir una
interfaz gráfica de tipo JFrame que es el tipo de contenedor más empleado en el modo
gráfico.
En la línea 5 se define el constructor de la clase generada, se sugiere que sea en el
constructor donde se creen los componentes y sus oyentes, así como la configuración del
contenedor (JFrame).
En la línea 6 se indica que se establezca el contenedor, en el caso de Swing se hace con
la instrucción:
6 Container contenedor = getContentPane()
En el caso de otros contenedores como JPanel, primero debe ser creado el objeto para
agregar los componentes y luego agregarse al contenedor o a la ventana que se requiera.
En la línea 7 se indica que se emplee un administrador de diseño(layout) que se utilizan
para acomodar bajo una cierta distribución los componentes como se describe a
continuación [Szna10]:
Absoluto: se utiliza cuando se trabaja con herramientas visuales para el diseño del GUI.
Relativo: definen reglas y los componentes se acomodan automáticamente dentro del
container, y los más usados son:
o FlowLayout: distribuye los componentes uno al lado de otro en la parte
superior del container, por defecto provee una alineación centrada, pero
también puede alinearlos hacia la izquierda o hacia la derecha.
o BorderLayout: divide el espacio del container en 5 regiones: NORTH,
SOUTH, EAST, WEST y CENTER, admite un único componente por
región.
o GridLayout: divide el espacio del container en una rejilla de n filas por m
columnas donde todas las celdas son de igual tamaño.
Para definir el layout se usa el método setLayout() cuyo parámetro es un objeto
anónimo de los siguientes layout, el elegido por tu preferencia, en el caso de los
constructores se puede consultar el API de Java para revisar las versiones y sus argumentos:
7 contenedor.setLayout( new FlowLayout() );
7 contenedor.setLayout(new BorderLayout());
7 contenedor.setLayout(new GridLayout(3,3));
En la línea 8 se indica que se agreguen los componentes al contenedor, en este caso el
método pertenece a la clase Container, en este caso tenemos ejemplos de cómo agregar un
JButton, una Jlabel
8 botonResta= new JButton( ”-” );
botonResta.setSize(20,50);
contenedor.add( botonResta );
resultado = new JLabel( "el resultado es " );
contenedor.add( resultado );
En la línea 9 se establece la asociación del oyente con el componente veamos un ejemplo:
9 botonResta.addActionListener( new OyenteBoton());
En la línea 10 se establece el tamaño y visibilidad de la ventana con las siguientes
instrucciones:
10 setSize( 275, 170 );
setVisible( true );
o alternativamente puede emplearse en lugar de setSize() el método pack() para que la
ventana se ajuste al tamaño de los componentes contenidos en ella.
De la línea 13 a la 16 se muestra el esqueleto de la implementación de los oyentes, en este
caso se muestran ejemplos de un ActionListener y un ItemListener:
13 private class OyenteBoton implements ActionListener {
public void actionPerformed( ActionEvent evento ) {
if ( evento.getSource() == BotonSuma)
/* instrucciones para realizar la suma de dos números a partir de verificar que el componente de
tipo JButton llamado BotonSuma haya generado el evento, en caso contrario se buscará el evento que lo
desencadeno */
else
if ( evento.getSource() == BotonResta) /* instrucciones*/
}
}
16 }
13 private class ManejadorLista implements ItemListener {
public void itemStateChanged( ItemEvent evento ) {
/* se colocan las instrucciones una vez que se detecto que se pulso el elemento de un JList o un
JCheckbox o un JRadioButton */
}
16 }
De la línea 17 a 20 se declara el método main cuyas únicas instrucciones consisten en crear
un nuevo objeto de la clase que hereda de JFrame línea 18, en la línea 19 se habilita el
método en la ventana para que al pulsar el botón cerrar se termine la ejecución si se omite
la instrucción la ejecución no terminará. La línea 21 termina la definición de la clase.
Ejercicios Propuestos
1. Diseñar una interfaz gráfica que simule un chat de manera que el usuario
interactúa con un objeto robot, cada vez que el usuario ingresa información el
robot le contesta con una frase aleatoria respecto al tema que pregunta. Si el
usuario decide terminar el chat, el robot antes de que termine la ejecución de la
aplicación mostrará un cuadro de diálogo donde le diga “adios”+ el nombre del
usuario.
2. Diseñar una interfaz gráfica que simule una lista de canciones en reproducción,
cuando el usuario da clic en un elemento de la lista mostrará la información
respecto a la canción, si el usuario da doble clic sobre la lista la canción
desaparecerá de la interfaz.
3. Diseñar una interfaz gráfica que simule una tiendita de dulces(crear la clase
dulce, se necesitara un arreglo de dulces), el usuario podrá dar clic en la imagen y
vera la descripción y cantidad de dulces disponibles, si decide comprar un dulce
en el almacén se debe reportar un decremento de la disponibilidad del mismo, si
el dulce se acaba entonces debe aparecer en una etiqueta el mensaje “comprar
dulce” + nombre del dulce, al final si el usuario se acaba los dulces en la etiqueta
deben aparecer todos los dulces a comprar.
4. Diseñar una interfaz gráfica que simule una consulta del clima de manera que se
tenga almacenado en una estructura tipo lista o en un flujo, información aleatoria
de cómo se comportará el clima en el periodo de un mes, cada vez que el usuario
ejecute la aplicación debe mostrarse en un textArea las condiciones del día actual,
mostrar en una etiqueta por medio de una imagen si el día estará soleado,
nublado, lluvioso entre otros. Considerar que el usuario podrá consultar también
la condición del clima en días posteriores o anteriores.
5. Diseñar una interfaz gráfica que simule una hamburguesería considerando
componentes de tipo menu para seleccionar los paquetes y las opciones de
compra, considerar checkBox para la selección de uno o varios ingredientes y
radioButton para la selección del método de pago: efectivo, tarjeta de débito o de
crédito. Considerar que por cada paquete o producto debe visualizarse la imagen
del mismo.
IV.3 Flujos de Entrada /Salida
Uno de los paquetes más importantes de JAVA es el paquete io que contiene el sistema
de E/S básico del lenguaje incluyendo la E/S con archivos. Los programas Java realizan la
entrada/salida a través de flujos (streams). Un flujo es una abstracción que produce o
consume información y esta relacionado con un dispositivo físico a través del sistema de
entrada y salida de Java.
Todos los flujos se comportan de la misma manera, incluso aunque estén relacionados
con distintos dispositivos físicos. Por esta razón se pueden aplicar las mismas clases y
métodos de E/S a cualquier tipo de dispositivo.
Un flujo de entrada puede abstraer distintos tipos de entrada, ya sea desde un disco hasta
un teclado o una conexión de red. De la misma forma un destino de salida puede ser una
consola, un archivo de disco o una conexión de red.
IV.3.1. Clases de Flujos
Java implementa los flujos dentro de una jerarquía de clases definida en el paquete
io.java. En la parte superior de la jerarquía hay dos clases abstractas InputStream y
OutputStream. Java tiene algunas subclases concretas de cada una de ellas para gestionar
las diferencias que existen entre los distintos dispositivos, como archivos de disco, buferes
de memoria, etc.
Las clases abstractas InputStream y OutputStream definen algunos métodos que las otras
clases implementan como son el método read() y write() que respectivamente leen y
escriben bytes de datos.
IV.3.2. Flujos Predefinidos
Todos los programas Java importan el paquete java.lang. Este paquete define una clase
llamada System que encapsula aspectos del entorno de ejecución. Además contiene tres
variables con flujos predefinidos llamadas in, out y err. Estos campos estan declarados
como public y static en System por lo cual se pueden utilizar en cualquier parte del
programa sin tener una referencia a un objeto System específico.



System.out se refiere al flujo de salida estándar (por default es la consola).
System.in hace referencia a la entrada estándar (que por defecto es el teclado).
System.err se refiere al flujo de error estándar (que por defecto también es la consola).
IV.3.3 Entrada por consola
En Java la entrada por consola se efectúa leyendo bytes de un flujo de entrada. Estos
bytes se pueden leer en varios objetos como pueden ser caracteres y cadenas. Read(): El
método de entrada de más bajo nivel es read() y tiene distintas formas. El formato más
común que se utiliza es:
int read() throws IOException
Cada vez que se llama a read() lee un único byte del flujo de entrada y lo devuelve como
un valor entero. El método devuelve –1 cuando encuentra el final del flujo de entrada.
Puede generar una excepcíon del tipo IOException. El siguiente programa muestra el
funcionamiento de read() donde lee caracteres de la entrada estándar hasta que el usuario
pulsa la letra “q”.
// Ejemplo del método read()
import java.io.*;
class UseRead
{
public static void main(String args[])
throws IOException
{
char c;
System.out.println(“Introduzca caracteres;
‘q’ para salir...”);
//lee caracteres
do {
c= (char) System.in.read();
System.out.println(c);
}while (c!=’q’);
}
}
Un ejemplo de ejecución es el siguiente:
Introduzca caracteres, ‘q’ para salir...
123abcq
1
2
3
a
b
c
q
System.in es por defecto un flujo con buffer. Esto significa que realmente no se pasa
ninguna entrada al programa hasta que se pulsa <enter>. Ahora bien, los flujos de entrada y
salida estándares pueden ser redirigidos. El siguiente programa muestra cualquier archivo
de texto en pantalla, suponiendo que la entrada estandar ha sido redirigida al archivo
deseado cuando comienza el programa. Esto hace que System.in utilice dicho archivo como
entrada.
/*
Muestra un archivo de texto.
Para utilizar este programa es necesario redirigir la entrada al
archivo que se quiere mostrar. Por ejemplo, para mostrar un archivo
llamada prueba.txt se utiliza la siguiente orden: java ShowFile <
prueba.txt
*/
import java.io.*;
class ShowFile
{
public static void main(String args[])
throws IOException
{
int i;
// lee caacteres hasta llegar al final del archivo
//(EOF)
do {
i= System.in.read();
System.out.print((char)i);
}while (i!=-1);
}
}
IV.3.3.1 Lectura de una cadena
Java proporciona un método llamado readLine() que automáticamente lee una secuencia
de caracteres de un flujo de entrada y devuelve un objeto del tipo String. Este método
forma parte de la clase DataInputStream que proporciona métodos para leer todos los tipos
simples de Java, además de lineas de texto. La forma general de readLine es la siguiente:
Final String readLine() throws IOException
Este método devuelve un objeto String. Antes de utilizar el método readLine() es
necesario obtener un objeto DataInputStream asociado al flujo de entrada. Para hacer esto
se utiliza el constructor:
DataInputStream( InputStream flujo_de_entrada)
Donde flujo_de_entrada es el flujo que se asocia a la instancia de DataInputStream que
se va a crear. Para leer la entrada por consola se puede utilizar como parámetro System.in.
El siguiente programa lee e imprime líneas de texto hasta que se introduzca la palabra “fin”.
// Ejemplo del método readLine()
import java.io;
class ReadLines
{
public static void main(String args[])
throws IOException
{
String str;
System.out.println(“INTRODUZCA LINEAS DE
TEXTO”);
System.out.println(“Introduzca fin para
finalizar...”);
do {
str= inData.readLine();
System.out.println(str);
}while(!str.equals(“fin”));
}
}
El siguiente ejemplo crea un pequeño editor de texto. Crea una matriz de objetos String y
después lee líneas de texto almacenándolas en la matriz. El programa finaliza cuando haya
leído 100 líneas o cuando lea la palabra “fin”.
// Un pequeño editor
import java.io.*;
class TinyEdit
{
public static void main(String args[])
throws IOException
{
String str[]= new String[100];
System.out.println(“Introduzca lineas
de texto”);
System.out.println(“Introduzca ‘fin’ para
finalizar”);
for (int i=0; i<100; i++)
{
str[i]= inData.readLine();
if (str[i].equals(“fin”)) break;
}
System.out.println(“Este fue el texto que
introduciste...”);
for (int i=0; i<100; i++)
{
if (str[i].equals(“fin”)) break;
System.out.println(str[i]);
}
}
}
IV.3.4 Salida por consola
La salida por consola se realiza con la ayuda de los métodos print() y println() que ya se
han utilizado anteriormente. Estos métodos están definidos en la clase PrintStream, que es
el tipo del objeto referenciado por System.out. Además, PrintStream es un flujo de salida
derivado de OutputStream y también implementa el método de bajo nivel write(). La forma
más simple de usar éste método definida en PrintStream es:
void write(int valor_byte)
Este método escribe el valor especificado por valor_byte. El siguiente programa utiliza el
método write() para imprimir el carácter “A” seguido de un carácter de línea nueva “\n”.
// Ejemplo de System.out.write()
class WriteDemo
{
public static void main(String args[])
{
int b;
b=’A’;
System.out.write(b);
System.out.write(“\n”);
}
}
Normalmente no se suele utilizar el método write() para presentar información en la
consola.
IV.3.5 Flujos de archivo
Las clases FileInputStream y FileOutputStream crean flujos asociados a archivos. Para
abrir un archivo, es necesario crear un objeto de una de estas clases, especificando el
nombre del archivo como argumento del constructor.
FileInputStream(String archivo) throws FileNotFoundException
FileOutputStream(String archivo) throws IOException
Aquí, archivo especifica el nombre del archivo que se desea abrir. Cuando se crea un
flujo de entrada, si el archivo no existe se genera una excepción FileNotFoundException.
Para los flujos de salida, si no se puede crear el archivo se genera una excepción
IOException. Cuando se abre un archivo de salida se destruye cualquier archivo existente
que tuviese el mismo nombre. Cuando se ha terminado de trabajar con un archivo, es
necesario cerrarlo utilizando el método close(). Este método esta definido tanto en
FileInputStream como en FileOutputStream.
void close() throws IOException
Para leer un archivo se puede utilizar el método read() con la siguiente sintaxis definida
en FileInputStream
int read() throws IOException
Cada vez que se llama a este método lee un único byte del archivo y lo devuelve como
un valor entero. Cuando encuentra el final del archivo devuelve –1. El siguiente programa
utiliza el método read() para leer y mostrar el contenido de un archivo de texto cuyo
nombre se pasa como argumento en línea de comandos.
/*
Muestra un archivo de texto. Para utilizar este programa, especifique
el nombre del archivo que desea ver, por ejemplo, para ver un archivo
llamado PRUEBA.TXT se utiliza la siguiente orden: java ShowFile
PRUEBA.TXT
*/
import java.io.*;
class ShowFile
{
public static void main(String args[])
throws IOException
{
int i;
FileInputStream fin;
try {
fin= new FileInputStream(args[0]);
}catch(FileNotFoundException e)
{
System.out.println(“Archivo no
encontrado...”);
return;
}catch(ArrayIndexOutOfBoundsException e)
{
System.out.println(“Formato:
ShowFile archivo”);
return;
}
// lee caracteres hasta llegar al
// final del archivo (EOF)
do {
i= fin.read();
if (i != -1) System.out.println((char)i);
}while (i != -1);
fin.close();
}
}
Para escribir en un archivo se puede utilizar el método write() definido en
FileOutputStream
void write(int valorByte) throws IOException
El siguiente programa utiliza el método write() para copiar un archivo de texto.
/*
Copia un archivo de texto...
Para utilizar este programa, especifique
el nombre de los archivos origen y destino.
Por ejemplo, para copiar un archivo llamado ORIGEN.TXT,
en un archivo llamado DESTINO.TXT,
se utiliza la siguiente orden:
java CopyFile ORIGEN.TXT DESTINO.TXT
*/
import java.io.*;
class CopyFile
{
public static void main(String args[])
throws IOException
{
int i;
FileInputStream fin;
FileOutputStream fout;
try{
fin= new FileInputStream(args[0]);
fout= new FileOutputStream(args[1]);
}catch(FileNotFoundException e) {
System.out.println(“Archivo no
encontrado”);
return;
}catch(IOException e){
System.out.println(“Error al abrir
el archivo de salida”);
return;
}catch(ArrayIndexOutOfBoundsException e){
System.out.println(“Formato: CopyFile
Origen Destino”);
}
// Copia el archivo
try {
do {
i= fin.read();
if (i != -1) fout.write(i);
}while(i != -1);
}catch(IOException e) {
System.out.println(“Error...”);
}
fin.close();
fout.close();
}
}
IV.3.6 File
La clase File trabaja directamente con los archivos y no sobre flujos. Es decir, la clase
File no especifica cómo se recupera o almacena la información la información en los
archivos; sólo describe las propiedades de un objeto archivo. Un objeto File se utiliza para
obtener o modificar la información asociada con un archivo de disco, como los permisos,
hora, fecha y directorio y para navegar por la jerarquía de subdirectorios. Como File no
trabaja con flujos, no es una subclase de InputStream o OutputStream.
Un directorio en Java se trata igual que un archivo con una propiedad adicional: una lista
de nombres de archivo que se pueden examinar utilizando el método list(). Los objetos File
se pueden crear utilizando uno de los siguientes tres constructores disponibles:
File(String directorio)
File(String directorio, String nom_arch)
File(File obj_Dir, String nom_arch)
El parámetro directorio es el nombre del directorio, nom_arch es el nombre del archivo y obj_Dir es un
objeto File que especifica un directorio. El siguiente ejemplo crea tres archivos: f1, f2, f3. El primer objeto
File se construye utilizando un directorio como único argumento. El segundo se crea utilizando dos
argumentos: el directorio y el nombre del archivo. El tercero se crea utilizando el directorio
asignado a f1 y un nombre de archivo; f3 hace referencia al mismo archivo que f2.
File f1= new File(“/”);
File f2= new File(“/”,”autoexec.bat”);
File f3= new File(f1,”autoexec.bat”);
La clase File define muchos métodos para determinar las propiedades de un objeto File:
El método getName() devuelve el nombre de un archivo.
El método getParent() devuelve el nombre del directorio padre.
El método exist() devuelve true si el archivo existe y false en caso contrario.
Sin embargo hay muchos métodos dentro de File con los que podemos examinar las
propiedades del objeto, pero no se proporcionan las funciones correspondientes para
cambiar sus atributos. El siguiente ejemplo muestra alguno de los métodos de File.
// Ejemplo de la clase File
import java.io.File;
class FileDemo
{
static void p(String s)
{ System.out.println(s); }
public static void main(String args[])
{
File f1= new File(“/java/COPYRIGTH”);
p(“Archivo: “+f1.getName());
p(“Directorio: “+f1.getPath());
p(“Directorio Absoluto: “
+f1.getAbsolutePath());
p(“Padre: “+f1.getParent());
p(f1.exists()? “existe” : “no existe”);
p(f1.canWrite() ? “se puede escribir” :
“no se puede escribir”);
p(f1.canRead() ? “se puede leer” :
“no se puede leer”);
p((f1.isDirectory() ? “ ” : “no”)+
”es un directorio”);
p(f1.isFile() ? “es un archivo normal” :
“podría ser un enlace
con nombre”);
p( “última modificacion: ”
+ f1.lastModified());
p(“tamaño del archivo: “+ f1.length() +
“Bytes”);
}
}
La ejecución de este programa sería algo parecido a:
Archivo: COPYRIGHT
Directorio: /java/COPYRIGHT
Directorio Absoluto: /java/COPYRIGHT
Padre: /java
Existe
Se puede escribir
Se puede leer
No es un directorio
Es un archivo normal
Ultima modificación: 812465204000
Tamaño del archivo: 695 Bytes
La clase File también incluye métodos específicos cuyo uso esta restringido al trabajo
con archivos convencionales, esto es, no pueden ser llamados sobre directorios. Algunos de
estos son:
renameTo(): boolean renameTo(File nuevo_nombre)
donde el nombre del archivo especificado por nuevo_nombre es el nuevo nombre del
archivo. Devolverá true si se realiza con éxito y false en caso contrario.
delete(): boolean delete()
Cuyo propósito es eliminar el archivo de disco representado por el trayecto del objeto
File. Este método solo funciona sobre objetos que son archivos simples. Devuelve true si
elimina el archivo y false en caso contrario.
IV.3.7 Directorios
Un directorio es un File que contiene una lista de otros archivos y directorios. Cuando se
crea un objeto File y es un directorio, el método isDirectory() devolverá true. En este caso,
se puede llamar al método list() sobre ese objeto para extraer la lista de los otros archivos y
directorios que contiene. Una de las formas de uso del método list() es:
String[ ] list()
La lista de archivos se devuelve en una matriz de objetos del tipo String. El siguiente
programa muestra como examinar el contenido de un directorio utilizando el método list().
Si se llama al método list() sobre un objeto File que no sea un directorio se provoca una
excepción del tipo: NullPointerException (excepción de apuntador nulo) en tiempo de
ejecución.
// Uso de directorios
import java.io.File;
class DirList
{
public static void main(String args[])
{
String dirname= “/java”;
File f1=new File(dirname);
if (f1.isDirectory())
{
System.out.println(“Directorio de “+dirname);
String s[]=f1.list();
for (int i=0; i< s.length; i++)
{
File f= new File(dirname+”/”+s[i]);
if (f.isDirectory())
{
System.out.println(s[i]+ “ es un
directorio”);
}
else {
System.out.println(s[i]+ “es un
archivo”);
}
}
}
else {
System.out.println(dirname+ “ no es un
directorio”);
}
}
}
IV.3.7.1 FilenameFilter
A menudo se desea limitar el número de archivos devuelto por el método list() para que
se incluyan únicamente aquellos archivos cuyo nombre cumpla con cierto patrón o filtro.
Para hacer esto se utiliza la segunda forma de uso del método list():
String list(FilenameFilter objFF)
En esta forma, el parámetro objFF es un objeto de una clase que implementa la interfaz
FilenameFilter. Esta interfaz define un único método accept() al que se llama una vez por
cada archivo de la lista. Su forma general es:
Boolean accept(File directorio, String nom_archivo)
Este método devuelve true para los archivos del directorio que deberían ser incluidos en
la lista. Es decir, aquellos que coincidan con el argumento nom_archivo y false para
aquellos que deberían ser excluidos. La siguiente clase mostrada a continuación como
ejemplo, implementa la interfaz FilenameFilter. Esta clase restringe la visibilidad de los
nombres de archivo devueltos por el método list(). La restricción se aplica a los archivos
cuyos nombres terminan con la extensión que se pasa como parámetro en su constructor.
import java.io.*;
public class OnlyExt implements FilenameFilter
{
String ext;
public OnlyExt(String ext)
{ this.ext=”.”+ext; }
public boolean accept(File dir, String name)
{ return name.endsWith(ext);}
}
A continuación se muestra el programa que presenta el directorio, de manera que liste
sólo los archivos que tienen extensión .html.
// directorio de archivos .html
import java.io.*;
class DirListOnly
{
public static void main(String args[])
{
String dirname=”/java”;
File f1= new File(dirname);
FilenameFilter only= new OnlyExt(“html”);
String s[]= f1.list(only);
for (int i=0; i<s.length; i++)
{ System.out.println(s[i]);}
}
}
File tiene otros métodos específicos para trabajar con directories. El método mkdir() crea un directorio,
devolviendo true si tiene éxito la creación y false en caso contrario. Si se quiere crear un directorio cuando el
path no existe, hay que utilizar el método mkdirs() el cual no sólo creará un directorio sino que además creará
todos los padres de éste. Se pueden crear directorios utilizando un objeto File, pero sin embargo no se pueden
eliminar.
4.4.8. Las clases Stream
Anteriormente se dio una introducción a la E/S de Java basada en flujos la cual se desarrolla a partir de dos
clases abstractas: InputStream y OutputStream. Estas clases definen una funcionalidad básica común a todas
las clases de flujo.
InputStream:
Es una clase abstracta que define el modelo de Java para el flujo de entrada. Todos sus métodos lanzarán
una excepción IOException si se producen errores. Los métodos de esta clase son:
METODO
DESCRIPCION
int read()
Devuelve
un
entero
como
representación del siguiente byte
disponible en la entrada
int read(byte bufer[])
Intenta leer hasta bufer.length bytes
situándolos en bufer y devuelve el
número real de bytes leidos con éxito
Intenta leer hasta len bytes situándolos
int read(byte bufer[], int off,en bufer comenzando en bufer[off] y
int len)
devuelve el número de bytes leidos
con éxito
int skip(long num)
Omite num bytes de la entrada y
devuelve el número de bytes que se
han omitido.
int available()
Devuelve el número de bytes
disponibles actualmente para su
lectura
void close()
Cierra el origen de entrada
void mark(int num)
Coloca una marca en el punto actual
del flujo de entrada que seguirá siendo
válida hasta que se lean num bytes.
void reset()
Devuelve el apuntador de entrada a la
marca establecida nuevamente.
boolean markSupported()
Devuelve true si se admiten los
métodos mark() o reset() en este flujo.
Tabla 4.1. Métodos de la clase InputStream
OutputStream
Es una clase abstracta que define el flujo de salida. Todos los métodos de esta clase
devuelven un valor void y lanzan una IOException en caso de error.La siguiente tabla
muestra los métodos definidos por esta clase:
METODO
DESCRIPCION
void write(int b)
Escribe un único byte en un flujo de
salida.
void write(byte bufer[])
Escribe una matriz completa de bytes
en un flujo de salida.
void write(byte bufer[], int
off, int len)
Escribe len bytes de la matriz bufer,
comenzando a partir de bufer[off].
void flush()
Inicializa el estado de la salida de
manera que se limpian todos los
buferes.
void close()
Cierra el flujo de salida.
Tabla 4.2. Métodos de la clase OutputStream
FileInputStream:
Esta clase crea un InputStream que se puede utilizar para leer el contenido de un archivo.
Sus constructores más comunes son:
FileInputStream(String directorio)
FileInputStream(File objArch)
Estos constructores lanzan una FileNotFoundException en caso de error. El parámetro
directorio es el nombre completo del directorio de un archivo y objArch es un objeto File
que describe el archivo. Por ejemplo:
FileInputStream f0;
f0=new FileInputStream(“/autoexec.bat”);
File f= new File(“/autoexec.bat”);
FileInputStream f1= new FileInputStream(f);
FileInputStream sobrescribe seis de los métodos de la clase abstracta InputStream. Los
métodos mark() y reset() no sesobrescriben y cualquier intento de utilizar estos métodos en
un FileInputStream generará una IOException.
El siguiente ejemplo muestra cómo leer un único byte, una matriz de bytes y una parte de
una matriz de bytes. También muestra como saber cuántos bytes quedan utilizando
available() y como omitir los bytes no deseados utilizando el método skip(). El programa
lee su propio archivo origen que debe estar en el directorio actual.
// Ejemplo de FileInputStream
import java.io.*;
class FileInputStreamDemo
{
public static void main(String args[])
throws Exception
{
int size;
InputStream f;
f=new FileInputStream(“FileInputStreamDemo.java”),
System.out.println(“Bytes totales disponibles: “
+(size=f.available()));
int n=size/40;
System.out.println(“Los primeros “+n+” bytes del
archivo se leen en una operación read()”);
for (int i=0; i<n; i++)
{ System.out.print((char)f.read());}
System.out.println(“\n Total disponible todavía: “+
f.available());
System.out.println(“leyendo los siguientes: “+n+
“bytes con una llamada
a read(b[])”);
byte b[]= new byte[n];
if (f.read(b) != n)
{
System.err.println(“No se puede leer “+n+
” bytes”);
}
System.out.println(new String(b,0,0,n));
System.out.println(“\n total disponible: “+
(size=f.available()));
System.out.println(“Omite la mitad de los bytes
restantes Con skip()”);
f.skip(size/2);
System.out.println(“\n total disponible: “+
f.available());
System.out.println(“leyendo “+n/2+” bytes del
final de la matriz”);
if (f.read(b,n/2,n/2) != n/2)
{
System.err.println(“No se pueden leer “+n/2+
” bytes”);
}
System.out.println(new String(b,0,0,b.length));
System.out.println(\n total disponible: “
+f.available());
f.close();}}
FileOutputStream
Crea un OutputStream que se puede utilizar para escribir en un archivo. Sus
constructores más comunes son:
FileOutputStream(String directorio)
FileOutputStream(File objArch)
Los dos constructores pueden lanzar una IOException o una SecurityException en caso
de error. El parámetro directorio es el nombre completo del directorio de un archivo y
objArch es un objeto File que describe el archivo. Si se intenta abrir un archivo de sólo
lectura como un FileOutputStream se lanzará una IOException.
El siguiente ejemplo crea un bufer de bytes creando en primer lugar una cadena y
después usa el método getBytes() para obtener la matriz de bytes equivalente. Después se
crean tres archivos. file1.txt que contiene los bytes pares, file2.txt que contiene el conjunto
completo de bytes, y file3.txt que contiene la última cuarta parte del buffer.
//ejemplo de FileOutputStream
import java.io.*;
class FileOutputStreamDemo
{
public static void main(String args[])
throws Exception
{
String source=”Ahora es el momento de que los
hombres buenos\n”+” vengan a
ayudar a su país\n”+”y paguen
sus impuestos”;
byte buf[]= new byte[source.length()];
source.getBytes(0,buf.length,buf,0);
OutputStream f0;
f0= new FileOutputStream(“file.txt”);
for (int i=0; i<buf.length;i+=2)
{f0.write(buf[i]);}
f0.close();
OutputStream f1;
f1= new FileOutputStream(“file2.txt”);
f1.write(buf);
f1.close();
OutputStream f2;
f2= new FileOutputStream(“file3.txt”);
f2.write(buf,buf.length-buf.length/4,
buf.length/4);
f2.close();
}
}
IV.3.9. La clase RandomAccessFile
Esta clase encapsula un archivo de acceso aleatorio y no es derivada de InputStream ni
de OutputStream, sino que implementa las interfaces DataInput y DataOutput que definen
los métodos de entrada y salida básicos. También permite peticiones de posicionamiento, es
decir, se puede situar el puntero dentro del archivo. Esta clase tiene dos constructores:
RandomAccessFile(File objArch, String acceso)
RandomAccessFile(String nomArch, String acceso)
En el primer constructor, objArch especifica el nombre del archivo que se desea abrir
como objeto File. En el segundo constructor, el nombre del archivo se pasa en nomArch.
En ambos casos, acceso determina el tipo de acceso al archivo que se permite. Si es “r”,
entonces sólo se puede leer el archivo y no se puede escribir en él. Si es “rw”, entonces el
archivo se abre en modo lectura-escritura. El método seek() se utiliza para establecer la
posición actual del puntero dentro del archivo.
void seek(long posicion)
Aquí, posición especifica la nueva posición en bytes, del puntero desde el inicio del
archivo. Después de llamar a seek(), la siguiente operación de lectura o escritura se
realizará en la nueva posición dentro del archivo.
RandomAccessFile implementa los métodos de entrada y salida estándar que se pueden
utilizar para leer y escribir en archivos de acceso aleatorio.
IV.3.10. Seriación de objetos
La seriación en Java se utiliza para escribir y leer objetos a y desde un archivo. Es decir,
es la operación mediante la cual se envía una serie de objetos a un archivo para hacerlos
persistentes. El proceso contrario de recuperar el estado de la serie de objetos del archivo
para reconstruirlos en memoria recibe el nombre de “deseriación”.
El paquete java.io proporciona las clases ObjectOutputStream y ObjectInputStream para
realizar operaciones de seriación y deseriación de forma automática.
Para hacer la seriación de los objetos de una clase se debe implementar la interfaz
Serializable, la cual es una interfaz vacía que no tiene métodos y que tiene como propósito
identificar aquellas clases cuyos objetos pueden ser seriados. Por ejemplo, el siguiente
código muestra la definición de una clase cuyos objetos pueden ser seriados.
import java.io.*;
public class Persona implements Serializable
{
// Cuerpo de la clase…
}
IV.3.10.1. Escritura de objetos en un archivo
Utilizaremos un objeto de tipo ObjectOutputStream para enviar un flujo de datos de tipos
primitivos y objetos a un flujo OutputStream, concretamente a un objeto FileOutputStream
puesto que lo que se quiere es almacenar objetos en un archivo. Posteriormente esos objetos
podrán ser reconstruidos a través de un objeto de tipo ObjectInputStream.
Para escribir un objeto dentro de un objeto ObjectOutputStream, utilizaremos el método
writeObject de dicha clase. Éste método lanzará una excepción de tipo
NotSerializableException si se intenta escribir un objeto de una clase que no implementa la
interfaz Serializable.
El siguiente código construye un ObjetoOutputStream sobre un FileOutputStream y lo
utiliza para almacenar una cadena y un objetoPersona en un archivo llamado datos.
FileOutputStream fos=new FileOutputStream(“datos”);
ObjectOutputStream oos=new ObjectOutputStream(fos);
oos.writeUTD(“Archivo datos”);
oos.write(new Persona(nombre, dirección, teléfono));
oos.close();
Como ejemplo, se muestra una clase llamada Directorio que permita almacenar objetos
de tipo Persona en un archivo de nombre “directorio.dat”.
Programa Persona.java
import java.io.*;
public class Persona implements Serializable
{
String nombre;
int edad;
long telefono;
public Persona(String nombre, int edad,
long telefono)
{
this.nombre=nombre;
this.edad=edad;
this.telefono=telefono;
}
public String toString()
{
return "Nombre: "+nombre+"
Edad: "+edad+
" anios
Telefono: "+telefono;
}
}
Programa Directorio.java
import java.io.*;
import java.util.*;
public class Directorio
{
public static void creaArchDir(String nomarch)
throws IOException
{
Scanner inDatos=new Scanner(System.in);
String resp="";
String nombre;
int edad;
long tel;
FileOutputStream out;
out=new FileOutputStream(nomarch);
ObjectOutputStream f;
f=new ObjectOutputStream(out);
do{
System.out.print("\nNombre: ");
nombre=inDatos.next();
System.out.print("\nEdad: ");
edad=inDatos.nextInt();
System.out.print("\nTelefono: ");
tel=inDatos.nextLong();
f.writeObject(new Persona(nombre,
edad,tel));
System.out.print("\nContinuar (S/N: ");
resp=inDatos.next();
}while (resp.equals("S"));
}
public static void main(String args[])
{
try{
creaArchDir("directorio.dat");
}catch(IOException e){ ; }
}
}
IV.3.10.2. Lectura de objetos desde un archivo
Un objeto de la clase ObjectInputStream permite recuperar datos de tipos primitivos y
objetos desde un objeto InputStream. De forma concreta, cuando se trate de datos de tipos
primitivos y objetos almacenados en un archivo, utilizaremos un objeto FileInputStream.
Para leer un objeto desde un flujo ObjectInputStream se deberá utilizar el método
readObject. Si se almacenaron objetos y datos de tipos primitivos, éstos deberán ser
recuperados en el mismo orden.
El siguiente código crea la aplicación MuestraDir que lee los objetos del archivo y
muestra su información en pantalla.
Programa MuestraDir.java
import java.io.*;
public class MuestraDir
{
public static void muestraArchDir(String
nomarch)throws IOException
{
Persona p;
String resp="";
String nombre;
int edad;
long tel;
FileInputStream in;
in=new FileInputStream(nomarch);
ObjectInputStream f;
f=new ObjectInputStream(in);
try{
do{
p=(Persona)f.readObject();
System.out.println(p);
}while(true);
}catch(EOFException e){
;
}catch(ClassNotFoundException e){;}
}
public static void main(String args[])
{
try{
muestraArchDir("directorio.dat");
}catch(IOException e){;}
}
}
IV.3.11. Ejercicios Propuestos
1. Escriba un programa denominado CopiarArchivo que copie el contenido de un archivo
en otro. El programa deberá ser invocado de la manera siguiente:
java CopiarArchivo <archivo_fuente> <archivo_destino>
en el <archivo_destino> se almacenará la copia del contenido del <archivo_fuente> pero
con la variante de que ésta será escrita en mayúsculas. Los archivos deberán ser de texto.
2. Hacer un programa que escriba una secuencia de números primos en un archivo de
acceso directo o binario. El programa deberá preguntar al usuario cuantos números
primos desea generar, a continuación los generará y los ira almacenando en un archivo
binario. Para poder visualizar el contenido de ése archivo deberá elaborar un programa
aparte que lea el archivo binario generado y lo muestre en pantalla.
3. Implementar un programa llamado grep que permita buscar palabras en uno o más
archivos de texto. Como resultado se visualizará en pantalla, por cada uno de los
archivos, su nombre, el número de línea y el contenido de la misma para cada una de las
líneas del archivo que contenga la palabra buscada. El programa podrá pedir los nombres
de los archivos a procesar, así como la palabra a buscar en ellos o bien se podrán dar en
línea de comandos cuando se ejecute el programa.
4. Escribe un frame que le pida al usuario dos números enteros e imprima la suma, resta,
multiplicación y división de los mismos. El frame deberá contar con opciones para que el
usuario guarde los resultados de estas operaciones en un archivo ya sea de texto o
binario.
5. Escriba un programa que muestre en pantalla e imprima en un archivo de texto la
cantidad de caracteres, palabras y líneas del archivo de texto que se le pase para su
análisis. Incluya un método que verifique que los archivos de lectura y de escritura son
diferentes. Las entradas de los nombres de los archivos serán dados por el usuario
utilizando los cuadros de diálogo de la clase JOptionPane.
BIBLIOGRAFÍA Y REFERENCIAS
[Amm98] Ammeraal L. (1998). Computer Graphics for JAVA Programmers. England.
John Wiley & Son.
[Bra97] Brassard G., Bratley P. (1999) Fundamentos de Algoritmia. Madrid. Prentice Hall.
[Ceb11] Ceballos F.J. (2011). Java 2, Curso de Programación. Madrid. Alfaomega – RaMa.
[Fla99] Flanagan D. (1999). JAVA in a NutShell. UK., O´Reilly – Mc Graw Hill.
[Hub04] Hubbard J. R. (2004). Programming with Java. Theory and Problems. Schaum´s
Outline Series. USA. McGrawHill.
[Joy96] Joyanes A. L. (1996). Programación Orientada a Objetos. Madrid. Mc Graw Hill.
MAR11] Martínez, L. J. (2011). Programación en Java 6. México: McGrawHill
[Liw98] Liwu L. (1998). JAVA. Data Structures and Programming. Germany. SpringerVerlag Berlin Heidelberg NewYork.
[Rob99] Roberts S., Heller P., Ernest M. (1999) Complete JAVATM 2. Certification Study
Guide. USA. Sybex Inc.
[Sch97] Schildt H., Naughton P. (1997). JAVA, Manual de Referencia. Madrid. Osborne
Mc Graw Hill.
[Joy11] Joyanes Aguilar L., Zahonero Martínez I. (2011). Programación en Java
Algoritmos, programación orientada a objetos e interfaz gráfica de usuario. México. Mc
Graw Hill.
[Szna10] Sznajdleder P. (2010). Java a fondo Estudio del Lenguaje y desarrollo de
aplicaciones. México. Alfaomega.
[Wu08] Wu T. C. (2008). Programación en Java. Introducción a la programación orientada
a objetos. México. Mc Graw Hill.
Capítulo V:
Tópicos Avanzados de la
Programación
Mireya Tovar Vidal
Pedro Bello López
Capítulo V: Tópicos Avanzados de la
Programación
V.1. Recursividad
Los programas que hemos visto hasta ahora están estructurados generalmente como
métodos que se llaman entre sí, de una manera disciplinada y jerárquica. Sin embargo, para
algunos problemas es conveniente hacer que un método se llame a sí mismo. Dicho método
se conoce como método recursivo; este método se puede llamar en forma directa o indirecta
a través de otro método. La recursividad es un tema importante, que puede tratarse de
manera extensa en los cursos de ciencias computacionales de nivel superior [Dei08].
Se dice que un método es recursivo cuando es posible que se llame a sí mismo o a otro
método que lo llame a él. Esta técnica es especialmente útil cuando una tarea se tiene que
repetir muchas veces siguiendo el mismo proceso, tal como recorrer una lista de datos, o
recurrir a la técnica de “divide y vencerás” en la que al tratar, por ejemplo, de ordenar un
conjunto de números, se llama a ordenar una mitad y luego otra mitad, cada una de estas
mitades llamaría a ordenar las mitades de las mitades y así sucesivamente hasta que
quedara uno o dos números. Al llamar un método a sí mismo, la ejecución de este primero
se pospone hasta terminar de ejecutar el método llamado, y así sucesivamente, con lo que
se produce una cadena de ejecuciones pendientes que completan la ejecución empezando
por el último en ser llamado y terminando por el primero que se ha llamado [Mol11].
Un método recursivo f() se invoca a sí mismo de forma directa o indirecta; en recursión
directa el código del método f() contiene una secuencia que lo evoca, mientras que en
recursión indirecta el método f() invoca a g() el cual a su vez a p() y así sucesivamente
hasta que se llama de nuevo al método f() [Joy11] .
Un método recursivo consta básicamente de dos partes.
 Caso base: también llamado caso trivial o elemental, representa el fin de la
recursión y es necesario para evitar que se cicle el método.
 Caso recursivo o general: es un llamado recursivo a si mismo generalmente
modificando su parámetro de entrada para resolver una parte más pequeña del
problema.
Veamos un ejemplo simple, cuya solución es un llamado recursivo así mismo.
Ejemplo1. Calcular el factorial de un número natural, donde
// Método para calcular el factorial de un numero
1 public int Factorial(n)
2 {
3
if(n==0 ││ n==1) return 1; // Caso base
4
else return n*Factorial(n-1);//Caso recursivo
5
}
En el Ejemplo 1 el caso base (fin de la recursión) está dado por 0! y 1!, que en ambos
casos el factorial es uno y el caso general (caso recursivo) viene dado por el llamado al
mismo método recursivo con n-1 como argumento, de esta forma se va disminuyendo el
argumento hasta llegar a uno o cero. Supongamos que queremos obtener el factorial de 4,
entonces: Factorial(4) = 4*Factorial(3), el Factorial(3) = 3*Factorial(2), el Factorial(2) =
2*Factorial(1), y en este momento termina la recursión debido a que el Factorial(1) ya no
realiza otro llamado recursivo sino que retorna el valor de 1, este valor es devuelto al nivel
anterior y es multiplicado por el valor de 2 y así sucesivamente hasta llegar a Factorial(4) =
4*6=24 .
V.1.1. Tipos de recursividad
La recursividad puede ser de diferentes tipos de acuerdo al número o forma de llamados
del caso recursivo, como se muestra a continuación.
V.1.1.1 Recursividad simple
Es aquella donde en la definición del método solo aparece una llamada recursiva. El
ejemplo 1 es un caso de recursividad simple debido a que dentro del cuerpo del método
solo existe un llamado al mismo método, en la figura 5.1 se muestra la prueba de ejecución
para calcular el factorial de 5, las líneas con dirección hacia abajo indican los llamados
recursivos y las líneas hacia arriba indican los valores que son devueltos en cada llamada al
terminar el proceso recursivo y llegar al caso base.
Es importante notar que la recursión avanza haciendo llamados recursivos hasta llegar a la
condición del caso base y posteriormente retorna los valores subiendo nivel por nivel hasta
llegar al primer llamado del método, sin embargo existen ocasiones que no hay un “return”,
pero aun así el proceso de recursión se realiza del mimo modo, veamos el ejemplo 2 de
recursividad simple.
Ejemplo 2. Método que escribe una secuencia de n números
1 public void escribe(int n)
2 {
3 if(n==0) System.out.print(n+" "); // caso base
4 else {
5
System.out.print(n+" ");
6
escribe(n-1); // caso recursivo
7
}
8 }
En el código del ejemplo 2 al ejecutarse con un llamado de escribe(5), se escriben los
números 5,4,3,2,1,0 debido a que antes de llamar nuevamente a la recursión mandamos a
escribir el valor de n. Pero si cambiamos el letrero del bloque del else después del llamado
recursivo, entonces la secuencia que se escribe es: 0,1,2,3,4,5, esto debido a que la
recursión termina cuando n = 0 y en ese momento escribe un 0 y retorna al llamado anterior
cuyo valor es 1 y demás hasta llegar al valor original que es 5.
Fig. 5.1. Prueba de ejecución del factorial de 5
V.1.1.2. Recursividad múltiple
Este tipo de recursividad se da cuando en el bloque del método recursivo se realizan más
de un llamado al mismo método, el ejemplo 3 se muestra este tipo de recursividad.
Ejemplo 3. Calcular el valor n de la sucesión de Fibonacci 0, 1, 1, 2, 3, 5, 8, 13, 21, ..,
definida de forma recursiva como:
//
1
2
3
4
5
7
código del ejemplo 3
public int Fibonacci(int n)
{
if (n==0) return 0; // caso base
else if(n==1)return 1; // caso base
else return Fibonacci(n-1)+Fibonacci(n-2); // caso recursivo
}
En el código del ejemplo 3 se puede observar que en el cuerpo del método Fibonacci se llama dos veces de
forma recursiva al método. En la figura 5.2 se muestra la prueba de ejecución para Fibonacci(4), donde se
define como la suma de los dos números anteriores. En este caso al llegar al final de la recursión y subir al
nivel anterior se realiza la suma de los valores retornados.
Fig. 5.2. Prueba de ejecución del Fibonacci de 4
V.1.1.3. Recursividad anidada
Este tipo de recursividad seda cuándo en algunos de los argumentos de la llamada
recursiva hay una nueva llamada al mismo método recursivo. Ver ejemplo 4.
Ejemplo 4. La función de Ackerman es uno de los algoritmos recursivos que crece muy
rápido, y se define de la siguiente manera:
//
1
2
3
4
5
6
código del ejemplo 4. Ackerman
public int Ack(int n, int m)
{
if(n==0) return m+1;
else if(m==0) return Ack(n-1,1);
else return Ack(n-1,Ack(n,m-1));
}
Los números generados por la función de Ackerman crecen muy rápido debido a que en
es una recursión con múltiples llamados recursivos y además dentro de los argumentos del
método también existe un llamado recursivo. Ver figura 5.3.
Fig. 5.3. Prueba de ejecución del Ackerman
En la siguiente ejecución se muestra lo complejo del proceso de Ackerman al llamar al
método de Ackerman con los valores 2,2.
//Ejecución de Ackerman con n=2 y m=2
Ack(2,2)= Ack(1,Ack(2,1))
= Ack(1,Ack(1,Ack(2,0)))
= Ack(1,Ack(1,Ack(1,1)))
= Ack(1,Ack(1,Ack(0,Ack(1,0))))
= Ack(1,Ack(1,Ack(0,Ack(0,1))))
= Ack(1,Ack(1,Ack(0,2)))
= Ack(1,Ack(1,3))
= Ack(1,Ack(0,Ack(1,2)))
= Ack(1,Ack(0,Ack(0,Ack(1,1))))
= Ack(1,Ack(0,Ack(0,Ack(0,Ack(1,0)))))
= Ack(1,Ack(0,Ack(0,Ack(0,Ack(0,1)))))
= Ack(1,Ack(0,Ack(0,Ack(0,2))))
= Ack(1,Ack(0,Ack(0,3)))
= Ack(1,Ack(0,4))
= Ack(1,5)
= Ack(0,Ack(1,4))
= Ack(0,Ack(0,Ack(1,3)))
= Ack(0,Ack(0,Ack(0,Ack(1,2))))
= Ack(0,Ack(0,Ack(0,Ack(0,Ack(1,1)))))
= Ack(0,Ack(0,Ack(0,Ack(0,Ack(0,Ack(1,0))))))
= Ack(0,Ack(0,Ack(0,Ack(0,Ack(0,Ack(0,1))))))
= Ack(0,Ack(0,Ack(0,Ack(0,Ack(0,2)))))
= Ack(0,Ack(0,Ack(0,Ack(0,3))))
= Ack(0,Ack(0,Ack(0,4)))
= Ack(0,Ack(0,5))
= Ack(0,6)
= 7
V.1.1.4. Recursividad cruzada o indirecta
En este tipo de recursividad un método provoca un llamado a si mismo de forma
indirecta, a través de otros métodos, en el ejemplo 5 se muestra el método par() que llama
al método impar() y este a su vez realiza una llamada al método par(), con el fin de
determinar si un número es par o impar.
Ejemplo 5. Elaborar un programa para determinar si un número es par o impar.
// código del ejemplo 5. Método par o impar
1 public int Par(int nump)
2 {
3 if(nump==0) return 1;
4 else return impar(nump-1);
6 }
7
8 public int Impar(int numi)
2 {
3 if(numi==0) return 1;
4 else return par(numi-1);
6 }
A continuación en la figura 5.4 se muestra la ejecución de la recursión indirecta con los
métodos par e impar, en el inciso a) se llama al método Par(6) con el argumento cuyo valor
es 6 y se después del fin de la recursión se retorna un 1, lo que significa que el argumento
es par, en el inciso b) se llama al método Impar(6) y se retorna un 0 lo que significa que el 6
no es Impar, en el inciso c) se llama al método Par(5) y se retorna un 0 lo que significa que
el 5 no es par y en el inciso d) se llama al método Impar(5) y se retorna un 1 lo que
significa que el 5 si es impar.
Fig.5.4. Recursión indirecta
V.1.2. Ventajas y desventajas
Cualquier problema que puede resolverse en forma recursiva, también puede resolverse
en forma iterativa (no recursiva). Generalmente se prefiere una metodología recursiva a
una iterativa cuando la primera refleja el problema con más naturalidad, y se produce un
programa más fácil de comprender y depurar [Har04].
La principal ventaja de la recursividad frente a los algoritmos iterativos es que se da
lugar a algoritmos simples y compactos. Otra ventaja es que una solución iterativa podría
no ser aparente para el problema en cuestión [Ang09].
La principal desventaja es que la recursividad resulta más lenta y consume más recursos
al ejecutarse, por lo que, no se recomienda en los casos en los que se requiera un buen
rendimiento.
La razón por la que los métodos recursivos consumen más recursos que los iterativos es
por la forma de resolverse las llamadas recursivas. Al ejecutarse una llamada de un método
recursivo se almacena en la pila:



Los argumentos del método
Las variables locales del método
La dirección de retorno, es decir, el punto del programa que debe ejecutarse una vez
que termine la llamada actual.
Ejercicios propuestos
1.
2.
3.
4.
5.
Construir un método recursivo que determine si un número n es primo o no.
Escribir un método recursivo que determine si una palabra es palíndroma o no.
Construir un método recursivo que calcule el MCD de Euclides.
Escriba un método recursivo llamado potencia(x,y) que calcule la potencia y de x.
Construir un método que muestre la representación binaria de cualquier número
natural.
6. Escribir un método recursivo que, dados dos vectores de números naturales del
mismo tamaño, calcule el resultado de sumar las componentes pares y restar las
componentes impares. Por ejemplo, considera los vectores V1=(5,2,4,3) y
V2=(0,2,1,7) el resultado del cálculo sería: -(5+0)+(2+2)-(4+1)+(3+7)= -5 +4-5+10=
4
7. Escribir un método recursivo que devuelva el total de números primos entre dos
números naturales a y b.
8. Escribir un método recursivo que, dados dos vectores de números naturales del
mismo tamaño, calcule el producto escalar de los dos vectores.
9. Escribir un método recursivo que calcule la norma de un vector.
10. Escribir un método recursivo que determine si los elementos de un vector V=(v1, v2,
.., vn) forman una sucesión estrictamente creciente. El método devuelve “cierto” (1)
si los elementos del vector satisfacen la relación v1 < v2 < … < vn y “falso” (0) de lo
contrario.
BIBLIOGRAFÍA Y REFERENCIAS
[Dei08]
Deitel P, J. D. (2008). Java como programar, Septima edición. México:
Pearson, Educación.
[Mol11]
F.Javier, M. T. (2011). Java 7 Manual Imprescindible. Madrid: Anaya
Multimedia.
[Har04]
Educación.
Harvey M. Deitel, P. J. (2004). Cómo programar en Java. Pearson
[Joy11]
Joyanes Aguilar Luis, Z. M. (2011). programación en JAVA 6 Algoritmos,
programación orientada a objetos e interfaz gráfica de usuario. México: Mc Graw Hill.
[Jes09] Riera, J. B. (2009). Manual de Algorítmica: Recursividad, complejidad y diseño de
algoritmos. Editorial UOC.
[Ang09]
Libros.
Yera, A. C. (2009). Programar desde un punto de vista científico. Visión
Descargar