Informática
Programaci—ón orientada a objetos en Java¨ Luis Fernando García Llinás
ediciones
de la
Todo lo básico que debería saber sobre
Programación orientada a objetos en Java®
Todo lo básico que debería saber sobre
Programación orientada a objetos en Java® LU I S FE RN A N D O GAR CÍA LLIN Á S
2 0 1 0
JAVA MK.indd 4
García Llinás, Luis Fernando. Todo lo que debería saber sobre programación orientada a objetos en Java / Luis Fernando García Llinás. – Barranquilla : Ediciones Uninorte ; Grupo Editorial Ibánez, 2010. 258 p. ; 16 x 24 cm. ISBN 978-958-741-062-4 1. Java (Lenguaje de programación de computadores). 2. Programación orientada a objetos (Computadores). I. Tít. (005.117 G216 Ed. 22) (CO-BrUNB)
www.uninorte.edu.co Km 5 vía a Puerto Colombia, A.A. 1569, Barranquilla (Colombia)
http://edicionesdelau.com/ Calle 24A n.° 43-22 Bogotá (Colombia)
© Ediciones Uninorte, 2010 © Ediciones de la U, 2010 © Luis Fernando García Llinás, 2010 Coordinación editorial Zoila Sotomayor O. Diseño y diagramación Nilson Ordoñez Diseño de portada Álvaro Bernal Corrección de textos Mercedes Castilla
Impreso y hecho en Colombia X-press Proceso Bogotá Printed and made in Colombia
13/08/2010 06:46:45 p.m.
CONTENIDO 1. TÓPICOS BÁSICOS ..................................................................................................... 1 1.1. Sobre el paradigma de programación estructurada o procedimental ............ 1 1.2. Objeto ...................................................................................................................................... 5 1.3. Clase .......................................................................................................................................... 6 1.4. Atributo ................................................................................................................................... 7 1.5. Instanciación .......................................................................................................................... 12 1.6. Método ..................................................................................................................................... 22 Método constructor, 29
1.7. Encapsulamiento .................................................................................................................. 39 1.8. Atributos finales ................................................................................................................... 52 1.9. Atributos y métodos estáticos ......................................................................................... 54 1.10. Herencia ................................................................................................................................. 59 1.11. Métodos y clases abstractas ........................................................................................... 96 1.12. Casting.................................................................................................................................... 105 1.13. Polimorfismo ........................................................................................................................ 107 1.14. Métodos y clases finales................................................................................................... 114 1.15. Herencia simple y múltiple ............................................................................................. 123 1.16. Interfaces ............................................................................................................................... 124
v
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
2. TÓPICOS AVANZADOS ............................................................................................. 135 2.1. Colecciones ............................................................................................................................. 136 Listas, 144. Conjuntos, 154. Mapas, 157. Genéricos, 159.
2.2. Manejo de excepciones ...................................................................................................... 171 Generación y lanzamiento de excepciones, 174. Captura de excepciones, 177. Definición de excepciones personales, 179.
3. CREACIÓN DE INTERFACES GRÁFICAS DE USUARIO ..................................... 199 3.1. Componentes gráficos ........................................................................................................ 201 3.2. Layouts...................................................................................................................................... 208 FlowLayout, 209. GridLayout, 211. BorderLayout, 213.
3.3. Bordes ....................................................................................................................................... 221 3.4. Manejo de eventos ............................................................................................................... 226
vi
PREFACIO El aprendizaje del paradigma de la programación orientada a objetos presenta considerables problemas a quienes están acostumbrados a trabajar bajo el paradigma estructurado, sobre todo cuando estos tienen varios años de experiencia. Quizás el cambio de mentalidad que se requiere o la gran cantidad de nociones que surgen acentúan esta situación. Inicialmente la programación orientada a objetos puede llegar a verse como un paradigma complejo o complicado, aun cuando en realidad lo que busca es organizar de mejor manera el código de una aplicación desarrollada con el enfoque procedimental. Esto con el fin de facilitar su entendimiento y, por ende, si es necesario, su modificación. La metodología que se emplea en este libro para introducir los tópicos consiste en plantear un problema sumamente sencillo, luego entrar a resolverlo empleando la programación estructurada y después de forma progresiva ir desarrollando la aplicación empleando la programación orientada a objetos. Durante este proceso se van introduciendo cada una de las nociones más importantes para adicionarlas al desarrollo de la solución (en algunos casos se presentan más ejemplos para lograr un mejor entendimiento de cada uno de los tópicos). Es realmente importante analizar cómo se va configurando la solución orientada a objetos y compararla con la solución estructurada.
ObjetivOs • Ser conciso. Uno de los principales objetivos de este libro era escribirlo de manera sumamente concisa, es decir que no excediera las 250 páginas. Por eso únicamente se tocan los temas básicos de la codificación de aplicaciones orientadas a objetos empleando a Java como lenguaje de programación. Por consiguiente, se excluyen temas como el trabajo con redes, la interacción con bases de datos, el desarrollo de aplicaciones para la web, etc. • Servir como punto de partida. En ningún momento este libro pretende reemplazar textos más robustos y completos que tratan estos y muchos otros tópicos. Más bien se presenta como un punto de partida para los programadores y un eficaz complemento de otros textos, pues en la medida en que se comprenda la totalidad de esta obra será mucho más sencillo entender cualquier otra que trate los mismos temas.
vii
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
¿A quién vA dirigidO este librO? Este libro fue ideado como texto guía para un curso de programación orientada a objetos donde llegan estudiantes con nociones básicas de programación y conocimiento de la teoría de algunas estructuras de datos. Por consiguiente, la obra se dirige especialmente a programadores que emplean el paradigma procedimental como principal aproximación a la codificación de soluciones (sobre todo empleando C como lenguaje de programación), y que desean aprender las nociones del desarrollo de aplicaciones orientadas a objetos empleando Java.
¿qué se requiere Antes de leerlO? • Descargar e instalar la máquina virtual de Java que puede ser obtenida de forma gratuita de la web oficial de Sun (http://java.sun.com/javase/downloads/index. jsp) • Leer del Tutorial oficial de Java (http://java.sun.com/tutorial) los apartados que llevan por títulos “About the Java Technology” (http://java.sun.com/docs/books/ tutorial/getStarted/intro/definition.htm) y “Hello World! For Microsoft Windows” (http://java.sun.com/docs/books/tutorial/getStarted/cupojava/win32.html). • Descargar e instalar un entorno de desarrollo (IDE) para Java. Esto facilitará en gran medida la codificación de los ejemplos. Existen muchos entornos gratuitos entre los que sobresalen: Netbeans (http://www.netbeans.org/), JDeveloper (http:// www.oracle.com/technology/products/jdev/index.html), y Eclipse (http://www. eclipse.org/).
OrgAnizAción El libro está organizado en tres capítulos. • En el primer capítulo se presentan los tópicos básicos de la programación orientada a objetos. También se analizan las nociones necesarias para la creación de aplicaciones orientadas a objetos sumamente básicas. • En el segundo capítulo se presentan unos tópicos un poco más avanzados de la teoría de objetos. Aquí las nociones analizadas posibilitan la creación de aplicaciones mas robustas y completas orientadas a objetos. • En el tercer capítulo se presentan las teorías básicas para diseñar y codificar interfaces gráficas de usuario sencillas y moderadamente atractivas. Adicionalmente, en todos estos capítulos se incluye un conjunto de apartados donde se presentan explicaciones más amplias, aclaraciones pertinentes o simplemente ejemplos más completos de los tópicos que se abordan.
retrOAlimentAción Cualquier comentario, observación, sugerencia o recomendación que se desee efectuar sobre la presente obra será bien recibida en las siguientes direcciones de correo electrónico:
[email protected] o
[email protected]
viii
1.
TÓPICOS BÁSICOS
1.1. sObre el pArAdigmA de prOgrAmAción estructurAdA O prOcedimentAl Dentro del mundo del desarrollo de aplicaciones bajo el paradigma estructurado (también conocido como programación procedimental o tradicional) la idea general consiste en especificar el conjunto de instrucciones que brindan solución a un problema específico; este conjunto de instrucciones se definen al interior de las marcas de inicio y fin del bloque de codificación principal del algoritmo. Durante el aprendizaje de este paradigma de programación las recomendaciones iniciales son: entender el problema, definir las variables globales que harán parte de la solución, identificar aquellas secciones de código que conviene incluirlas dentro de funciones o subrutinas para futura reutilización, definir datos de entrada y de salida, entre otras. Suponga que ha sido encargado del desarrollo de la aplicación de software a una compañía que la desea para efectuar el cálculo mensual de su nómina. Esta compañía contrata empleados a quienes les paga dependiendo del número de horas trabajadas y del valor por hora convenido previamente con cada uno. Como información básica de cada empleado debe registrarse el número de la cédula, su nombre y su apellido. Aclaraciones: • Tanto el valor del número de horas trabajadas por cada empleado como el valor de su sueldo por hora puede variar de un empleado a otro. • Se supondrá que la aplicación solo se requiere para calcular el valor de la nómina de un único mes.
1
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
• Para efectos de mantener la simplicidad del ejemplo no se contemplan acciones para manejar la persistencia 1 de los datos. A continuación una posible solución al problema planteado empleando el paradigma estructurado. Inicio Entero: numeroEmpleados, i Caracteres: cedulas[50], apellidos[50], nombres[50] Real: horasTrabajadas[50], sueldoXHora[50] Caracteres: cedula, apellido, nombre Real: horas, sueldo Real: total <- 0 Esc ‘Digite número de empleados: ‘ Lea numeroEmpleados Para i=0,numeroEmpleados-1,1 Esc ‘Digite la cédula del empleado: ‘ Lea cedula Esc ‘Digite el apellido del empleado: ‘ Lea apellido Esc ‘Digite el nombre del empleado: ‘ Lea nombre Esc ‘Digite número de horas trabajadas del empleado: ‘ Lea horas Esc ‘Digite valor de sueldo por hora del empleado:’ Lea sueldo cedulas[i] <- cedula apellidos[i] <- apellido nombres[i] <- nombre horasTrabajadas[i] <- horas sueldoXHora[i] <- sueldo Fin-Para Para i=0, numeroEmpleados-1, 1 total <- total + horasTrabajadas[i] * sueldoXHora[i] Fin-Para Esc ‘La nómina total es: ‘ + total Fin
Entender el anterior seudocódigo no debe presentar mayores problemas para cualquier programador. Sin embargo, es conveniente realizar las siguientes aclaraciones y comentarios: • Aunque se puede condensar el código incluyendo las instrucciones del segundo ‘Para’ dentro del primero, de manera intencional se ha dejado así intencionalmente para delimitar funcionalmente cada bloque de código. • En el algoritmo se captura información, como la cédula, el nombre y el apellido,
1
2
Acciones para preservar de forma permanente y para recuperar los datos, como, por ejemplo, guardarlos en un archivo en disco.
1. TÓPICOS BÁSICOS
que no se utiliza; sin embargo esta información se mantiene porque posteriormente puede ser útil para ampliar la funcionalidad de la aplicación. • Como la intención es que sea un ejemplo didáctico, inicialmente el algoritmo no contempla validaciones como impedir el doble ingreso de un mismo número de cédula.
CodifiCaCión en Java del algoritmo para el CálCulo de la nómina empleando programaCión estruCturada
Se presenta a continuación la codificación del ejemplo anterior aunque apenas se estén dando los primeros pasos en el aprendizaje del lenguaje de programación Java. Esto porque es más sencillo aprender a través de ejemplos y del establecimiento de analogías, asociaciones y comparaciones. import java.io.BufferedReader; import java.io.InputStreamReader; class Nomina { public static void main(String[] args) throws Exception { int numeroEmpleados; String[] cedulas = new String[50]; String[] apellidos = new String[50]; String[] nombres = new String[50]; double[] horasTrabajadas = new double[50]; double[] sueldoXHora = new double[50]; String cedula, apellido, nombre; double horas, sueldo; double total = 0; BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) ); System.out.print(“Digite numero de empleados: “); numeroEmpleados = Integer.valueOf(br.readLine()).intValue(); for(int i=0;i
3
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
sueldo = Double.valueOf(br.readLine()).doubleValue(); cedulas[i] = cedula; apellidos[i] = apellido;
}
nombres[i] = nombre; horasTrabajadas[i] = horas; sueldoXHora[i] = sueldo;
for (int i=0;i
}
}
System.out.println(“\nLa nómina total es: “+ total);
Del código anterior vale la pena resaltar algunos puntos:
•
Las dos primeras líneas donde aparecen instrucciones import sirven para importar componentes necesarios para la lectura de datos por pantalla. La instrucción public static void main(String args[]) corresponde a la definición del método principal. El tipo de dato en Java double se corresponde con el tipo Real en el seudocódigo; el tipo de dato en Java String se corresponde con el tipo Caracteres en seudocódigo; y el tipo de dato int en Java se corresponde con el tipo de dato Entero en seudocódigo. La lectura de información por pantalla requiere la configuración de un componente de
•
tipo BufferedReader. La instrucción que permite leer información de la pantalla (lo que en seudocódigo se
•
escribe como la instrucción Lea, o en C la instrucción cin) corresponde a la función readLine(). Esta función siempre devuelve lo que digita el usuario como una cadena de caracteres; si se desea trabajar con un tipo de dato distinto, deben realizarse conversiones adicionales. La instrucción que permite escribir información en pantalla (lo que en seudocódigo se
• • •
escribe como la instrucción Esc, o en C la instrucción cout) corresponde a la función System.out.println(). El carácter de escape ‘\n’ corresponde al retorno de carro y
• •
nueva línea. El segundo bloque ‘for’ no define llave de apertura({) y de cierre(}), debido a que solo posee una línea de código. La concatenación de un número a una cadena de caracteres se realiza con el simple uso del operador ‘+’.
4
1. TÓPICOS BÁSICOS
• Es conveniente identificar cómo es el manejo de los arrays en Java y cómo se realiza la •
conversión entre tipos de datos. En Java, los índices de los arrays comienzan en el valor 0; es por ello que los contadores de los bloques de código ‘Para’ se inicializan en dicho valor, y llegan hasta el valor menos 1.
1.2. ObjetO Dentro del paradigma de desarrollo de aplicaciones orientadas a objetos cambia el enfoque de la solución. Lo importante para el paradigma procedimental o estructurado es el bloque de código principal (instrucciones dentro de la marca de inicio y fin). Para el paradigma orientado a objetos lo principal es entender y modelar el problema, y luego sí definir el bloque de código principal que empleando el modelo definido brinde solución al problema específico. La programación orientada a objetos requiere inicialmente identificar y modelar cada uno de los entes que hace parte del problema. Facilita la comprensión del tema hacerse una imagen mental de una posible situación; por ejemplo, para el caso del cálculo de la nómina suponga que la empresa cuenta únicamente con tres empleados cuya información se muestra en el siguiente gráfico.
Para un programador con poca experiencia en la orientación a objetos es moderadamente sencillo identificar que para el caso anterior los entes que toman parte del problema corresponden a los empleados de la empresa. En caso de que se necesite incluir un nuevo
5
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
empleado en la empresa, aparecerá otro objeto que seguramente poseerá las características descriptivas de cédula, apellido, nombre, sueldo por hora y horas trabajadas, y demás valores para dichas características. Es conveniente presentar una definición para la noción de objeto. Definición de objeto Un objeto es un concepto, abstracción o cosa con límites bien definidos y con significado dentro del problema.
Como se puede apreciar la definición de objeto es realmente amplia pues en realidad cualquier cosa puede ser un objeto. De allí que puedan existir objetos que representen cosas concretas (como automóviles, casas, libros, etc.) y objetos que representan cosas abstractas (como pensamientos, ideas, etc.).
1.3. clAse Para traducirlo a algún lenguaje de programación se requiere una estructura de datos a fin de almacenar la información de cada uno de los objetos del problema. Sin embargo, no tiene sentido definir una estructura de datos independiente para cada uno de los posibles objetos de tipo empleado (como, por ejemplo, EstructuraTrabajor1, EstructuraTrabajador2, etc.), es más conveniente definir una única estructura de datos que pueda servir para almacenar la información de cualquier objeto del mismo tipo. La siguiente tabla muestra la información que debe permitir registrar esta estructura genérica y su correspondiente tipo de dato.
6
Campo
Tipo dato
Descripción
cédula
Caracteres
En este campo se registra el número de la cédula de ciudadanía de un empleado.
apellido
Caracteres
En este campo se registra la cadena de caracteres que corresponde al apellido de un empleado.
nombre
Caracteres
En este campo se registra la cadena de caracteres que corresponde al nombre de un empleado.
horasTrabajadas
Real
En este campo se registra el número de horas trabajadas por un empleado; por ejemplo, el valor de 1.5 indica que el empleado ha trabajado una hora y media (90 minutos).
sueldoXHora
Real
En este campo se registra el valor que debe ser pagado a un empleado por cada hora de trabajo.
1. TÓPICOS BÁSICOS
Definición de clase Una clase describe a un conjunto de objetos que comparten una estructura y un comportamiento común.
Una clase es un molde o plantilla que indica cómo será un objeto de dicha clase. En el área de la construcción, una clase podría ser el plano de una casa que indica la estructura que debe tener cada una de las casas, y los objetos son la materialización de las casas construidas a partir de dicho plano. Es por ello que se define a un objeto como una instancia de una clase. Para definir una clase en Java se utiliza la palabra reservada ‘class’. La sintaxis de dicha instrucción requiere además de la especificación de un nombre para la clase. La comunidad de programadores de Java maneja una convención de nombramiento para las clases (más que una regla es una simple sugerencia); dicha convención establece que el nombre de la clase debe escribirse todo en minúsculas a excepción de la primera letra del nombre de la clase. Si para establecer el nombre de la clase se requieren varias palabras se deben unir las letras de todas las palabras y la primera letra de cada palabra debe estar en mayúscula (por ejemplo, EmpleadoDeEmpresa). A continuación se presenta el código que permite definir en Java la clase para describir a objetos tipo empleado. class Empleado{ String cedula; String apellido; String nombre; double horasTrabajadas; double sueldoXHora; }
Cada uno de elementos incluidos dentro de una clase recibe el nombre de atributos.
1.4. AtributO Definición de atributo Un atributo es una propiedad que ayuda a describir un objeto.
Es conveniente tener en cuenta lo siguiente: • Hasta el momento y con las definiciones dadas, dos objetos distintos, pero de la misma clase tienen la misma estructura; es decir, comparten los mismos atributos, pero los valores para cada uno de los atributos son independientes.
7
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
• El orden de definición de los atributos es irrelevante. • El concepto de atributo está estrechamente ligado al concepto de variable; en realidad todo atributo es un tipo de variable, sin embargo, no toda variable que pueda definirse en un programa en Java es un atributo. Note que en Java para definir una variable se requiere, además de un nombre2, su tipo de dato. La comunidad de programadores de Java maneja una convención de nombramiento para los atributos (más que una regla es una sugerencia), con la cual establece que el nombre del atributo debe escribirse en letras minúsculas. Si el nombre del atributo está compuesto por varias palabras, se unen las letras de todas las palabras y se colocan en mayúsculas las primeras letras de cada palabra a excepción de la primera letra del nombre del atributo (por ejemplo, horasTrabajadas). Básicamente los tipos de datos para los atributos (y variables) pueden ser de dos clases: • Tipo de dato primitivo3. Corresponde a un tipo de dato predefinido por el lenguaje. Cuando se define un atributo (o variable) de este tipo, entonces hay que separar un espacio en memoria para guardar su valor. Los ocho tipos de datos primitivos son: byte, short, int, long, float, double, boolean y char. Se hace relativamente sencillo inferir el propósito de cada uno de ellos. Es conveniente resaltar que el tipo de dato char corresponde a un solo carácter, lo que significa que no existe un tipo de dato primitivo en Java para una cadena de caracteres. La buena noticia es que existe String que no corresponde a un tipo de dato primitivo, sino a un tipo de dato de referencia. • Tipo de dato de referencia. Corresponde a un objeto de una clase (no obligatoriamente distinta a la definida). En este punto es donde radica gran parte de la importancia, flexibilidad y reutilización del paradigma de orientación a objetos; porque cuando se definen atributos cuyo tipo sea una clase, se amplía el espectro de posibilidades. Cuando se define un atributo de este tipo no se separa espacio en memoria para un nuevo objeto, sino que se define una referencia que apuntará a un espacio de memoria con la estructura definida en la clase.
2 3
8
Detalles sobre la convención de nombramiento de variables pueden ser consultados en la siguiente referencia: http:// java.sun.com/docs/books/tutorial/java/nutsandbolts/variables.html Mayor información sobre cada uno de estos tipos de datos puede ser consultada en la siguiente referencia: http://java. sun.com/docs/books/tutorial/java/nutsandbolts/datatypes.html
1. TÓPICOS BÁSICOS
sobre la ComposiCión de obJetos Uno de los pilares fundamentales de la programación orientada a objetos corresponde a la reutilización. Aquí la idea fundamental no es reinventar la rueda cada vez que se necesite, sino poder reutilizar la que ya esta inventada. La siguiente podría ser una buena definición para la clase Casa. class Casa{ String colorTecho; String tipoTecho; double largoTecho; double anchoTecho; double altoTecho; String colorParedes; String tipoParedes; int numeroDeVentanas; }
En la anterior definición el atributo tipoTecho hace referencia al material con que está construido el techo de la casa, que puede tomar los valores de: paja, cinc, teja, etc.; similarmente, el atributo tipoParedes hace referencia al material que compone las paredes de la casa, por ejemplo: ladrillo, madera, barro, etc. El resto de la anterior definición de la clase Casa no es demasiado compleja y la mayoría de los atributos se entienden fácilmente. Una definición alternativa para modelar una casa podría ser la siguiente: class Techo{ String color; String tipo; int largo; int ancho; int alto; }
9
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
class Pared{ String tipo; String color; } class Casa{ Techo elTecho; Pared lasParedes; int numeroDeVentanas; }
Note que en este caso para lograr la definición de la clase Casa ha sido necesaria la previa
definición de las clases Techo y Pared. Adicionalmente cabe resaltar que en la clase Casa
existe un atributo de tipo de referencia de clase Techo cuyo nombre es elTecho, y, además,
existe un atributo de tipo de referencia de la clase Pared de nombre lasParedes.
¿Cuál de las dos definiciones es la mejor? Depende del problema. La primera definición de la clase Casa es simple y sencilla de trabajar, pero no muy reutilizable. La segunda es un poco más compleja, aunque más reutilizable (imagine que se necesita la clase Edificio, para la cual
se podrían reutilizar algunas clases como Pared y Techo).
10
1. TÓPICOS BÁSICOS
sobre la definiCión y manipulaCión de los tipos de datos primitivos y los de referenCia Es realmente importante tener claridad sobre los tipos de datos de los atributos pues Java trata de forma muy distinta a cada uno de ellos. Imagínese un objeto de la clase Casa donde solo se utilicen datos primitivos (la primera definición que aparece en el apartado sobre la composición de objetos). Java reservaría espacio en memoria de la siguiente manera (el texto en la parte superior del rectángulo es simplemente para denotar la clase del objeto):
: Casa colorTecho = Negro poTecho = teja largoTecho = 15 anchoTecho = 10 altoTecho = 2.5 colorParedes = Rojo poParedes = ladrillo numeroDeVentanas = 3 Para la segunda definición de la clase Casa (en el ejemplo donde se usa composición en el apartado sobre la composición de objetos), el mismo ejemplo anterior tendría la siguiente representación:
Como se mencionó previamente para los atributos cuyo tipo de dato son de referencia no se separan espacios en memoria, es decir, para atributos de tipo Pared, Techo y String. En vez de eso, se definen apuntadores a referencias de objetos de dichos tipos. Simplemente por facilidad y para no complejizar innecesariamente las gráficas, a lo largo de este libro se obviará la definición de objetos para los tipos de datos de referencia String, es decir, que de ahora en adelante se presentarán los diagramas de la siguiente forma.
11
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
Simplemente para hacer claridad en este último punto, la estructura en memoria para el objeto de la clase Casa nunca es como se muestra en el siguiente gráfico.
La definición de clase hace referencia a dos puntos: primero, la estructura y segundo, el comportamiento. La definición de la estructura de los objetos de una clase se consigue a través del establecimiento de sus atributos.
1.5. instAnciAción Es conveniente introducir en estos momentos la instrucción que posibilita la instanciación de una clase, de tal manera que se pueda crear un nuevo objeto para el almacenamiento de valores en sus atributos. En Java, para crear un nuevo objeto es necesario utilizar el comando new, seguido del nombre de la clase que se desea instanciar4; entonces para crear un nuevo empleado se utiliza la siguiente instrucción new Empleado();
4
12
Cuando se llegue al concepto de método constructor se redefine esta afirmación. Por ahora, el objetivo es mantener la explicación lo más simple posible.
1. TÓPICOS BÁSICOS
Con esta instrucción Java separa un espacio en memoria con la estructura definida para la clase Empleado (es decir, debe haber un espacio cedula para almacenar la cédula del empleado, un espacio apellido para almacenar el apellido del empleado, etc.). La pregunta que debe surgir en estos momentos es, si con la instrucción new Empleado() se separa espacio en memoria para crear un objeto de la clase Empleado, ¿con qué valores inician los atributos de este nuevo objeto? La respuesta, Java inicializa los valores de los atributos con valores por defecto de la siguiente manera: • Para atributos cuyo tipo sea de referencia, el valor por defecto es null; es decir, la referencia no apunta a ningún objeto. • Para atributos cuyo tipo sea un tipo de dato primitivo, el valor depende del tipo de dato: si es un valor numérico (byte, short, int, long, float y double), su valor inicial es 0; para tipo boolean, el valor inicial es falso (false), y, para tipo char, el valor inicial es ‘\u0000’, que corresponde al primer carácter que puede ser representado. Es decir, después de ejecutar la instrucción new Empleado(); se crea un objeto de la siguiente manera:
Luego de haber instanciado la clase, puede surgir la siguiente pregunta: ¿cómo se le asignan valores a los atributos del objeto (para poder cambiar los valores por defecto con los que se inicializa)? Como el objeto se crea en un espacio de la memoria, es necesario obtener la referencia a esa posición en memoria y para poder hacerlo se define una variable, a la que se le asigna dicha posición en memoria. Se requiere modificar la anterior instrucción por las siguientes: Empleado elEmpleado; elEmpleado = new Empleado();
O se puede simplificar en una sola instrucción: Empleado elEmpleado = new Empleado();
13
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
El entendimiento de esta definición suele causar problemas a algunos programadores. La siguiente idea puede ayudar a clarificar la situación; al trabajar en cualquier lenguaje de programación a la hora de definir una variable se emplea una instrucción semejante a int a = 50, es decir, primero se define el tipo de dato, luego el nombre de la variable y posteriormente su valor. Si se analiza con detenimiento esta última instrucción y se compara con la de creación de una instancia de Empleado, se obtiene lo siguiente:
Aquí se mencionan los siguientes puntos: • La primera parte de la definición indica el tipo de dato de la variable (que puede ser un tipo de dato primitivo o de referencia). Esta parte cobra gran importancia al trabajar el tópico de herencia. • La segunda parte de la definición corresponde al nombre que identificará a la variable. Para la primera definición, el nombre de la variable es elEmpleado; para la segunda, simplemente el carácter a. • La tercera parte de la definición especifica el valor que le será asignado a la variable. • Es importante considerar que aunque se ha intentado realizar una comparación de ambas instrucciones, por definir variables de tipo de datos distintos, la manipulación interna que hace el lenguaje de programación de estas variables es también distinto. Recuerde que en la primera instrucción la variable elEmpleado corresponde a una referencia de una posición en memoria, mientras que la segunda variable efectivamente guarda el valor que se le asigna. Una vez se tiene una referencia a un objeto (a través de la definición de una variable), es posible consultar o modificar el valor de cualquiera de sus atributos. Para hacer cualquiera de estas dos operaciones basta con generar una instrucción donde se especifique la referencia al objeto y al atributo del objeto que se desea manipular; la delimitación entre ambos se logra empleando el carácter punto (‘.’); por ejemplo, suponiendo que la referencia a un objeto de la clase Empleado se llame elEmpleado, • Se puede cambiar el valor del atributo horasTrabajadas empleando la siguiente instrucción: elEmpleado.horasTrabajadas = 1.5; • Se puede cambiar el valor del atributo sueldoXHora empleando la siguiente instrucción: elEmpleado.sueldoXHora = 100; • Se puede consultar el valor del atributo cédula empleando la siguiente instrucción: elEmpleado.cedula
14
1. TÓPICOS BÁSICOS
un eJemplo ampliado sobre la instanCiaCión Considere el siguiente código: Empleado elEmpleado = new Empleado(); elEmpleado.cedula = “12345”; elEmpleado.apellido = “Pérez”; elEmpleado.nombre = “Pedro”; elEmpleado.sueldoXHora = 50; elEmpleado.horasTrabajadas = 20; Empleado otroEmpleado = new Empleado(); otroEmpleado.cedula = “98765”; otroEmpleado.apellido = “Sánchez”; otroEmpleado.nombre = “María”; otroEmpleado.sueldoXHora = 120; otroEmpleado.horasTrabajadas = 10;
Después de ejecutar este código en Java se obtiene una configuración en memoria similar a la siguiente:
La anterior gráfica debe entenderse de la siguiente manera: existen dos variables de tipo Empleado que referencian a objetos de la clase Empleado; la primera referencia tiene el
nombre de elEmpleado y apunta a un objeto cuyo valor para el atributo cedula es 12345; la segunda referencia tiene el nombre de otroEmpleado y apunta a un objeto cuyo valor para el
atributo cedula es 98765. Es recomendable tener pendiente que los valores para los atributos de un objeto (por lo menos de la manera en que han sido definidos hasta el momento) son independientes de los valores que pueda tener cualquier otro objeto.
15
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
sobre el operador lógiCo de ComparaCión y su funCionamiento sobre las variables de tipo primitivo y de referenCia
El lector perspicaz debe estarse preguntando lo siguiente, si los valores de los atributos de un objeto son totalmente independientes de los valores de los atributos de otros objetos de la misma clase, ¿qué evita que se puedan crear dos objetos con exactamente los mismos valores para sus atributos? La respuesta a este cuestionamiento es: nada. Considere el siguiente código: Empleado elEmpleado = new Empleado(); elEmpleado.cedula = “12345”; elEmpleado.apellido = “Pérez”; elEmpleado.nombre = “Pedro”; elEmpleado.sueldoXHora = 50; elEmpleado.horasTrabajadas = 20; Empleado otroEmpleado = new Empleado(); otroEmpleado.cedula = “12345”; otroEmpleado.apellido = “Pérez”; otroEmpleado.nombre = “Pedro”; otroEmpleado.sueldoXHora = 50; otroEmpleado.horasTrabajadas = 20;
En memoria se obtendría la siguiente configuración:
Es conveniente analizar el comportamiento de las variables al trabajar con el operador lógico de comparación que provee Java (este operador se denota empleando el símbolo del doble igual ‘==’ )
16
1. TÓPICOS BÁSICOS
¿Cuál sería el resultado de ejecutar la siguiente instrucción? elEmpleado == otroEmpleado
Aunque parezca ilógico, la respuesta de esta instrucción es el valor lógico de falso (false).
La explicación de esta respuesta es simple: si elEmpleado y otroEmpleado son referencias a objetos, la anterior instrucción no compara los valores que componen a los objetos sino las posiciones en memoria donde se encuentran, como son dos objetos distintos, son dos posiciones en memoria distintas. Desde este punto de vista, ¿cuál sería el resultado de ejecutar la siguiente instrucción? elEmpleado.horasTrabajadas == otroEmpleado.horasTrabajadas
El error más común que cometen los programadores que apenas están conociendo la teoría de la programación orientada a objetos en Java es responder que la instrucción arrojaría como resultado el valor lógico falso (false). En este caso, la respuesta es el valor verdadero (true). El porqué de esta respuesta es también sencillo: en esta última instrucción no se están comparando referencias sino tipos primitivos, y como los tipos primitivos almacenan el valor, esta instrucción sí está comparando los valores de ambas variables. Como se mencionó en el apartado sobre la definición y manipulación de los tipos de datos primitivos y los de referencia es realmente importante tener claridad sobre los tipos de datos y sobre la manipulación que realiza Java sobre ellos. Un último interrogante que puede surgirle a un programador perspicaz en estos momentos es, ¿cómo comparar dos objetos basándose en su contenido? Aunque después se analizará más ampliamente esta situación, por lo pronto es conveniente mencionar que para realizar dicho tipo de comparación es necesario cotejar uno a uno los valores correspondientes de todos los atributos de los objetos.
17
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
sobre la reutilizaCión de las variables de referenCia Analícese el siguiente código: Empleado e1 = new Empleado(); e1.cedula = “12345”; e1.apellido = “Pérez”; e1.nombre = “Pedro”; e1.sueldoXHora = 50; e1.horasTrabajadas = 20; Empleado e2 = null; Empleado e3 = new Empleado(); e2=e1; e3=e1;
Después de ejecutadas las anteriores instrucciones en memoria, se obtendría la siguiente configuración:
Dos puntos para mencionar en esta situación:
• Existen en memoria tres referencias: e1, e2 y e3. Todas tres apuntan al mismo objeto. Si empleando la referencia e1 se modifica el valor del atributo horasTrabajadas, ese nuevo valor también podrá ser consultado empleando cualquiera de las restantes referencias.
• En la gráfica aparece un objeto Empleado con valores por defecto; cuando un objeto
se queda sin ningún tipo de referencia para ser manipulado, un elemento especial de Java, el recolector de basura, lo elimina automáticamente.
18
1. TÓPICOS BÁSICOS
sobre la instanCiaCión y utilizaCión de arrays En Java, un array es un objeto que representa un conjunto finito y ordenado de elementos homogéneos. En el apartado donde se presenta la codificación en Java del algoritmo para el cálculo de la nómina empleando programación estructurada se encuentran algunos ejemplos de definiciones de arrays. Se pueden definir arrays de tipos de datos primitivos y de referencia; los valores con que se inicializan cada una de las posiciones del array siguen las mismas pautas establecidas para la inicialización de los valores de los atributos explicadas en la sección de la instanciación. La manipulación de un array de un tipo de dato primitivo en Java es análoga a la manipulación que hace cualquier otro lenguaje de programación. Por otro lado, el entendimiento de la manipulación de arrays de tipo de datos de referencia generalmente causa problemas a los programadores que apenas comienzan a aprender Java y la programación orientada a objetos. Es conveniente tener presente lo siguiente:
• Un array (sin importar el tipo de dato del que es definido) es un objeto que representa
•
un conjunto finito, por ello, al momento de crearlo es necesario especificar la cantidad de elementos que van a ser parte de este conjunto (este valor debe ser especificado explícitamente). Resulta útil pensar en un array de un tipo de dato de referencia como una referencia a referencias, es decir, en un apuntador de memoria en donde se registran referencias a otras posiciones en memoria.
Analice el código que se presenta a continuación: Empleado e1 = new Empleado(); e1.cedula = “12345”; e1.apellido = “Pérez”; e1.nombre = “Pedro”; e1.sueldoXHora = 50; e1.horasTrabajadas = 20; Empleado e2 = new Empleado(); e2.cedula = “98765”; e2.apellido = “Sánchez”; e2.nombre = “María”; e2.sueldoXHora = 120; e2.horasTrabajadas = 10;
19
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
Empleado[] conjunto = new Empleado[10]; conjunto[0] = e1; conjunto[1] = e2; conjunto[2] = e1;
Después de ejecutarse el anterior código, la memoria queda de la siguiente manera:
Cabe anotar que al instanciar un array de tipo de datos de referencia todas las posiciones del array se inicializan en el valor de null (como ocurre con los atributos) El error más común entre los programadores cuando empiezan a conocer el mundo de Java es imaginarse que después de ejecutar el anterior código el estado de la memoria es similar al que se muestra a continuación:
20
1. TÓPICOS BÁSICOS
La clave para comprender esta situación se encuentra en tener claro que la instrucción new
Empleado[10] no instancia diez objetos de la clase Empleado, sino que define que el array almacenará hasta un máximo de diez objetos. Considerando esta situación, a lo largo del código
solo se instancian dos objetos Empleado; por consiguiente, solo deben existir en memoria dos objetos Empleado, y un objeto del tipo array de objetos Empleado.
21
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
1.6. métOdO primera aproximaCión a la apliCaCión del CálCulo de la nómina mediante programaCión orientada a obJetos
Con los temas cubiertos hasta el momento es posible codificar un primer prototipo para la aplicación. Se empleará una línea para dividir el código de las distintas clases que se presentan. El primer componente a codificar corresponde a la clase Empleado (se empleará la codificación presentada anteriormente). class Empleado{ String cedula; String apellido; String nombre; double horasTrabajadas; double sueldoXHora; }
import java.io.BufferedReader; import java.io.InputStreamReader;
class Nomina{ public static void main(String[] args) throws Exception{ int numeroEmpleados; Empleado[] losEmpleados = new Empleado[50]; String cedula, apellido, nombre; double horas, sueldo; double total = 0; BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) ); System.out.print(“Digite numero de empleados: “); numeroEmpleados = Integer.valueOf(br.readLine()).intValue(); for(int i=0;i
22
1. TÓPICOS BÁSICOS
nombre = br.readLine(); System.out.print(“Digite num de horas trabajadas del empleado: “); horas = Double.valueOf(br.readLine()).doubleValue(); System.out.print(“Digite sueldo por hora del empleado: “ ); sueldo = Double.valueOf(br.readLine()).doubleValue(); Empleado unEmpleado = new Empleado(); unEmpleado.cedula = cedula; unEmpleado.apellido = apellido; unEmpleado.nombre = nombre; unEmpleado.horasTrabajadas = horas; unEmpleado.sueldoXHora = sueldo;
}
losEmpleados[i]=unEmpleado;
for (int i=0;i
}
}
Puntos convenientes a resaltar en el anterior código:
• Note que en vez de tener arrays independientes para cada uno de los datos a registrar de un empleado (como la cédula, apellido, etc.) en este código se crea un único array de elementos de tipo Empleado.
• Note cómo es la manipulación del array de objetos de tipo Empleado que lleva por nombre losEmpleados.
23
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
aproximaCión alternativa de la apliCaCión del CálCulo de la nómina mediante programaCión orientada a obJetos
La aplicación presentada en el anterior apartado inicialmente indaga la cantidad de empleados que posee la empresa y luego va preguntando la información puntual de cada uno de ellos. Se podría definir una aplicación alternativa que funcione a través de un menú. En este se podrían definir como opciones el ingreso de un nuevo empleado en la nómina de la empresa, calcular la nómina total y, finalmente, abandonar la aplicación. El siguiente código en Java representa esta última situación (se reutiliza la definición de la clase Empleado aunque no es necesario volverla a codificar): class Empleado{ String cedula; String apellido; String nombre; double horasTrabajadas; double sueldoXHora; }
import java.io.BufferedReader; import java.io.InputStreamReader; class Nomina2{ public static void main(String[] args) throws Exception{ int numeroEmpleados=0, opcionMenu=0; Empleado[] losEmpleados = new Empleado[50]; String cedula, apellido, nombre; double horas, sueldo; double total = 0; BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) ); while(opcionMenu!=3){ System.out.println(“Menu de opciones”); System.out.println(“1- Adicionar Empleado”); System.out.println(“2- Calcular nómina total”); System.out.println(“3- Salir”); System.out.print(“Escoja opción: “);
24
1. TÓPICOS BÁSICOS
opcionMenu = Integer.valueOf(br.readLine()).intValue(); if (opcionMenu==1){ System.out.print(“\nDigite la cedula del empleado: “ ); cedula = br.readLine(); System.out.print(“Digite el apellido del empleado: “ ); apellido = br.readLine(); System.out.print(“Digite el nombre del empleado: “ ); nombre = br.readLine(); System.out.print(“Digite num de horas trabajadas: “); horas = Double.valueOf(br.readLine()).doubleValue(); System.out.print(“Digite sueldo por hora del empleado: “ ); sueldo = Double.valueOf(br.readLine()).doubleValue(); Empleado unEmpleado = new Empleado(); unEmpleado.cedula = cedula; unEmpleado.apellido = apellido; unEmpleado.nombre = nombre; unEmpleado.horasTrabajadas = horas; unEmpleado.sueldoXHora = sueldo; losEmpleados[numeroEmpleados]=unEmpleado; numeroEmpleados++; } else if (opcionMenu==2){ for (int i=0;i
}
}
}
La definición de la clase Empleado puede ser reutilizada en una gran variedad de aplicaciones (como, por ejemplo, los códigos presentados en los apartados: Primera aproximación de la aplicación del cálculo de la nómina mediante programación orientada a objetos y Aproximación alternativa de la aplicación del cálculo de la nómina según programación
25
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
orientada a objetos). En caso de ser necesario calcular cuánto se le debe pagar a un empleado bastaría con multiplicar el número de horas trabajadas por el sueldo que devenga por hora; previendo que el uso de esta operación puede que sea necesaria utilizarla en posteriores ocasiones, sería conveniente incluirla como parte de una función (muchos en este momento pueden estar pensando en no definir ninguna función debido a la simplicidad del cálculo; sin embargo, se debe considerar que la fórmula del sueldo mensual de un empleado podría complejizarse al tener en cuenta la serie de aportes parafiscales como pensión, salud, retención en la fuente, entre otros ). ¿Pero dónde definir la función? Se manejan las siguientes alternativas: • Como la clase Empleado puede que sea reutilizada en varias aplicaciones, sería necesario definir dicha función en cada una de ellas; sin embargo, esta no sería una muy buena alternativa, pues se estaría replicando el código en muchos lados, y, además, representaría un serio problema en caso de necesitarse la realización de una modificación en la fórmula del cálculo del sueldo del empleado. • Crear una librería independiente de funciones e incluirla allí. Esta librería podría abarcar esta función y cualquier otra que aparezca en el desarrollo de la aplicación. • La mejor alternativa correspondería definirla dentro de la propia clase; de todas formas qué mejor librería a incluirse que la propia clase que genera la necesidad. En Java desaparecen los conceptos de subrutinas, procedimientos o funciones (que existen dentro del paradigma de programación procedimental) y se reemplazan por la noción de método (sin embargo, el comportamiento y la forma de trabajo son análogos al de las subrutinas). La idea principal es la de ubicar los métodos junto con los datos sobre los que operan (en este caso, ubicar el método que posibilite el cálculo del salario mensual de un empleado junto con los datos del empleado). Definición de método Abstracción de una acción, servicio, comportamiento o tarea que puede ser realizado por un objeto. Generalmente, un método manipula la información registrada en los atributos a través de una o más instrucciones.
La definición de un método en Java requiere básicamente especificar: • El tipo de dato que retornará como resultado el método. Este tipo de dato puede corresponder a un tipo de dato primitivo, a cualquier tipo de dato de referencia, o en caso de no retornar ningún valor debe especificarse el valor de void. • El nombre del método. La comunidad de programadores de Java maneja una convención de nombramiento para los métodos, que establece que el nombre del método debe escribirse en minúsculas. Si el nombre de un método está compuesto por varias palabras; se unen las letras de todas las palabras (sin usar ningún carácter
26
1. TÓPICOS BÁSICOS
especial adicional) y se colocan en mayúsculas las primeras letras de cada palabra, a excepción de la primera letra del nombre del método (por ejemplo, calcularSalario). • Un paréntesis de apertura y un paréntesis de cierre (‘()’), y en caso de requerirse dentro del juego de paréntesis, la definición del conjunto de parámetros que necesita el método para su funcionamiento. Añadiendo la definición del método que posibilite el cálculo del salario del empleado, la clase Empleado quedaría de la siguiente manera: class Empleado{ String cedula; String apellido; String nombre; double horasTrabajadas; double sueldoXHora;
}
double calcularSalario(){ return horasTrabajadas * sueldoXHora; }
En pocas palabras, la definición del método calcularSalario indica que se debe multiplicar el valor que se tenga en los atributos horasTrabajadas y sueldoXHora, y retornar el resultado. Para invocar un método se requiere una instrucción donde se especifique la referencia al objeto y al método dentro de ese objeto; la delimitación entre ambos se logra empleando el carácter punto (‘.’); por ejemplo, suponiendo que la referencia a un objeto de la clase Empleado se llame elEmpleado, se puede invocar el método calcularSalario a través de la siguiente instrucción: elEmpleado.calcularSalario(); Teniendo en cuenta el siguiente código: Empleado e1 = new Empleado(); e1.cedula = “12345”; e1.apellido = “Pérez”; e1.nombre = “Pedro”; e1.sueldoXHora = 50; e1.horasTrabajadas = 20; Empleado e2 = new Empleado(); e2.cedula = “98765”; e2.apellido = “Sánchez”; e2.nombre = “María”; e2.sueldoXHora = 10; e2.horasTrabajadas = 120;
El resultado de ejecutar la instrucción e1.calcularSalario() sería 1000, mientras que la ejecución de e2.calcularSalario() daría como resultado 1200. Lo anterior simplemente
27
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
corrobora que el resultado de la invocación del método calcularSalario depende de los valores de los atributos del objeto al que se hace referencia.
sobre los parámetros en los métodos Determinar qué parámetros deben definirse como parte de un método es una de las situaciones que causan problemas para los programadores novatos en la programación orientada a objetos. Por ejemplo, para el caso de la definición del método calcularSalario, muchos preguntarían por qué no se incluyen como parámetros el valor de las horas trabajadas y el sueldo por hora del empleado. Supóngase por un momento que la definición del método se ha alterado para reflejar dicho cambio, por consiguiente el código de la clase Empleado quedaría de la siguiente manera: class Empleado{ String cedula; String apellido; String nombre; double horasTrabajadas; double sueldoXHora;
}
double calcularSalario(double horasTrabajadas, double sueldoXHora){ return horasTrabajadas * sueldoXHora; }
Suponga, además, que se tiene el siguiente código: Empleado e1 = new Empleado(); e1.cedula = “12345”; e1.apellido = “Pérez”; e1.nombre = “Pedro”; e1.sueldoXHora = 50; e1.horasTrabajadas = 20;
28
1. TÓPICOS BÁSICOS
La instrucción para invocar el método calcularSalario para el objeto e1 sería: e1.calcularSalario(20,50);
El lector perspicaz debe estar notando que en esta instrucción existe algo extraño. El siguiente gráfico ilustra la situación.
Lo extraño de la situación corresponde a que a través de los parámetros se está suministrando información que el objeto ya conoce y almacena. Esta instrucción no tiene sentido alguno. El escenario sería el mismo si se ejecutase la siguiente instrucción: e1.calcularSalario(e1.horasTrabajadas, e1.sueldoXHora);
Cuando se presentó la definición de clase, se hacía referencia a que esta describe la estructura y comportamiento común de un conjunto de objetos. La descripción de la estructura se alcanza a través de la definición de los atributos; y la descripción del comportamiento se alcanza a través de la definición de los métodos.
1.6.1. Método constructor Hasta el momento la forma presentada para posibilitar la creación de un objeto corresponde al empleo de la instrucción new seguida del nombre de la clase; para la asignación de los valores a los atributos se requieren instrucciones posteriores. Si se analiza el código de las instanciaciones efectuadas hasta el momento se podrá notar la cantidad de código que se requiere; se podría pensar en definir un método para facilitar dicho proceso. Dentro de la programación orientada a objetos ese método recibe el nombre de método constructor.
29
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
Definición de método constructor Método especial que crea un objeto de la clase y que puede emplearse para especificar aquellas tareas que deban realizarse en el momento de la creación como, por ejemplo, la inicialización de los valores de los atributos.
El método constructor es un método especial, y su definición varía ligeramente del resto. Las particularidades son: • Un método constructor no puede tener definido un tipo de dato de retorno cuando ni siquiera el valor de void es válido. • El nombre del método debe ser el mismo nombre de la clase (debe coincidir exactamente con el nombre cuidando hasta el uso de letras mayúsculas y minúsculas).
sobre el ámbito de las variables y la palabra reservada ‘this’ Con los lineamientos que se tienen hasta el momento el código de la clase Empleado quedaría de la siguiente forma: class Empleado{ String cedula; String apellido; String nombre; double horasTrabajadas; double sueldoXHora;
}
30
Empleado(String pcedula, String papellido, String pnombre, double phorasTrabajadas, double psueldoXHora){ cedula = pcedula; apellido = papellido; nombre = pnombre; sueldoXHora = psueldoXHora; horasTrabajadas = phorasTrabajadas; } double calcularSalario(){ return horasTrabajadas * sueldoXHora; }
1. TÓPICOS BÁSICOS
Del anterior código hay varios puntos por analizar: •
•
•
•
La cantidad de parámetros definidos es la misma que el número de atributos de la clase. Esta situación se presenta porque para crear un nuevo objeto empleado es necesario que se suministre información para cada uno de los atributos. Sin embargo, no debe seguirse esto como una regla general, porque hay escenarios donde el número de parámetros puede diferir del número de atributos. En el apartado sobre los parámetros en los métodos se estuvo analizando una situación especial, que no se presenta con los parámetros del método constructor porque, a diferencia del ejemplo allí presentado en este caso el objeto no conoce de antemano la información del empleado Para evitar que se presente una confusión con los nombres de los parámetros del método constructor y de los atributos de la clase, se ha optado por anteponerle la letra ‘p’ al nombre del parámetro. El código del método constructor se encarga de asignar a cada uno de los atributos del objeto los valores que se suministran en los parámetros correspondientes (es decir, al atributo cedula se le asigna el valor que se recibe en el parámetro pcedula, etc.).
En Java también existe la noción de variable de ámbito local y de ámbito global, por consiguiente, nada evita que la definición del método constructor de Empleado tenga la siguiente signatura: Empleado(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora)
El problema que se presentaría con la última definición del constructor de la clase Empleado es que tanto los parámetros como los atributos tienen los mismos nombres; por ende, instrucciones como las que se muestran a continuación, además de triviales, son ambiguas e inútiles. cedula = cedula;
apellido = apellido; nombre = nombre;
sueldoXHora = sueldoXHora;
horasTrabajadas = horasTrabajadas;
¿Qué podrían significar las anteriores instrucciones? Habría cuatro posibilidades: • Asígnese al parámetro el valor que tiene el parámetro. Esto carece de sentido porque dicho valor ya se encuentra asignado. • Asígnese al atributo el valor que tiene el atributo. Esto carece de sentido porque uno de los objetivos de un método constructor es inicializar el valor de los atributos del objeto; entonces, la instrucción no está alterando dicho valor.
31
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
•
•
Asígnese al parámetro el valor del atributo. También carece de sentido porque se están reemplazando los valores suministrados para instanciar al objeto por los valores por defecto con los que se inicializó la instancia. Asígnese al atributo el valor suministrado en el parámetro. Esta instrucción si tiene sentido y es la que se busca darle a entender al lenguaje de programación.
Es conveniente detenerse en este punto para explicar esta situación con más detalle. Dentro del paradigma procedimental una variable global2 es aquella que está declarada para el programa o algoritmo principal, del que dependen todos los subprogramas. En otras palabras, es una variable que puede ser usada por cualquier procedimiento. Si se piensa que dentro de una clase un atributo puede ser utilizado en cualquiera de los métodos que se definan al interior de la clase entonces puede asemejarse el concepto de variable global al de atributo (solo bajo la consideración presentada anteriormente de variable global pero únicamente para la clase). Por su parte, una variable local3 es aquella que está declarada y definida dentro de un subprograma. Con esta definición se puede decir que cualquier parámetro declarado para un método, o cualquier variable definida dentro de un método, cae dentro de la definición de una variable local. Considérese el siguiente código de ejemplo: class Ejemplo{ int a=10; void metodo(){ int a = 20; System.out.println(a); } }
En este ejemplo se define una clase de nombre Ejemplo, que posee un único atributo de nombre a que se inicializa en el valor de 10, y, además, posee un método donde se define una variable a que se inicializa con un valor de 20. Hay que comenzar diciendo que por el análisis previo efectuado sobre las variables de ámbito global y local, este ejemplo es totalmente válido. El interrogante sería, cuál es el valor que se mostrará por pantalla en caso de invocar la ejecución de metodo. La respuesta es 20, y la razón es porque el método cuando necesita trabajar con la variable a primero busca dentro de las definiciones de variables locales que tenga; si encuentra, una trabaja con ella; de lo contrario, trabaja con la definición global (exactamente así ocurre dentro del manejo de variables en el paradigma procedimental).
32
1. TÓPICOS BÁSICOS
Volviendo al caso que inició este análisis, ya se mencionó que para la siguiente definición del método constructor de la clase Empleado Empleado(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora){
}
cedula = cedula; apellido = apellido; nombre = nombre; sueldoXHora = sueldoXHora; horasTrabajadas = horasTrabajadas;
Existían cuatro alternativas de ejecución, pero después de conocer cómo es el comportamiento con el ámbito de las variables, se pueden descartar tres de las posibilidades; queda únicamente la alternativa mencionada donde la instrucción ocasionaría la asignación al parámetro del mismo valor que ya tiene (que correspondería a una instrucción trivial). La solución a este asunto se encuentra evitando la trivialidad en el código del constructor de la clase Empleado, y cuando se especifica que la acción a ejecutar es la de asignarla al atributo el valor que se recibe en el parámetro. Existe en Java una palabra reservada cuyo significado ayuda a eliminar la trivialidad; esta palabra es this. this es una propiedad que tienen todos los objetos en Java y a través de ella un objeto puede obtener su propia referencia en memoria (su propia posición). Para ejemplificar esta situación, considere el siguiente código: Empleado e1 = new Empleado(); Empleado e2 = new Empleado();
Suponga que el espacio de memoria que se reserva para el primer objeto (e1) tiene la dirección 16f0472 y que el segundo objeto (e2) tiene la dirección 18d107f; si al interior de cualquier método en el objeto (e1) se hace referencia a this se obtendrá la dirección 16f0472, y si al interior de cualquier método en el objeto e2 se hace referencia a this se obtendrá la dirección 18d107f. ¿Cómo ayuda this a especificar dentro del constructor que se le asignen a los atributos los valores que se pasan en los parámetros? Si dentro de cualquier método se emplea la instrucción conformada por la auto referencia this y el nombre del atributo (delimitándose uno del otro con el carácter punto ‘.’) se le indica al programa que no debe acceder a la variable local, sino
33
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
al atributo en mención. Por consiguiente, las instrucciones dentro del constructor de Empleado debieran definirse de la siguiente manera: this.cedula = cedula; this.apellido = apellido; this.nombre = nombre; this.sueldoXHora = sueldoXHora; this.horasTrabajadas = horasTrabajadas;
Tomando en cuenta lo presentado anteriormente (y en especial lo analizado en el apartado sobre el ámbito de las variables y la palabra reservada ‘this’), el código de la clase Empleado queda de la siguiente manera: class Empleado{ String cedula; String apellido; String nombre; double horasTrabajadas; double sueldoXHora; Empleado(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora){ this.cedula = cedula; this.apellido = apellido; this.nombre = nombre; this.sueldoXHora = sueldoXHora; this.horasTrabajadas = horasTrabajadas; }
}
double calcularSalario(){ return horasTrabajadas * sueldoXHora; }
Con esta nueva definición de la clase se simplifica la instanciación de objetos, pues se pasa de tener que escribir el siguiente código: Empleado e1 = new Empleado(); e1.cedula = “12345”; e1.apellido = “Pérez”; e1.nombre = “Pedro”; e1.sueldoXHora = 50; e1.horasTrabajadas = 20;
34
1. TÓPICOS BÁSICOS
a simplemente la siguiente instrucción: Empleado e1 = new Empleado(“12345”, “Pérez”, “Pedro”, 20, 50);
Al trabajar con métodos constructores los siguientes puntos deben ser tenidos en cuenta: • Es conveniente que el método constructor inicialice los valores para todos los atributos. • Una clase puede tener definido más de un método constructor. Existen varias restricciones que deben cumplirse, pero este tema será abordado en un capítulo posterior. • Por la forma en que se han presentado los ejemplos de las clases empleadas hasta el momento, puede pensarse que dichas estas clases carecían de un método constructor; sin embargo, la situación real es totalmente opuesta. El lenguaje de programación Java cuando encuentra una clase que no tiene definido explícitamente un constructor genera de forma automática un constructor por defecto. Este constructor (que no se le presenta al programador) tiene la particularidad de no tener ningún parámetro definido y, además, lo único que hace es inicializar los atributos con sus valores por defecto (estos valores fueron analizados en la sección donde se habla de la instanciación). Si una clase tiene definido por lo menos un constructor, Java no generará de forma automática el constructor por defecto. • En la sección donde se habla de la instanciación se mencionó que se debe utilizar el comando new seguido por el nombre de la clase; esta definición no es del todo correcta, en realidad para instanciar una clase se debe utilizar el comando new seguido de uno de los posibles constructores de la clase. El lector perspicaz seguramente habrá notado que la clase Empleado que se utilizó para dicho ejemplo no tenía constructor y, por ende, Java generaba el constructor por defecto; era ese constructor el que se utilizaba en aquel entonces. • El lector con un alto nivel de suspicacia debe estarse formulando la siguiente pregunta: si String es un tipo de dato de referencia por qué no es necesario utilizar su constructor para crear objetos de este tipo (corrobore esta situación analizando todos los ejemplos presentados hasta el momento donde se haya requerido un nuevo objeto de la clase String). La clase String es una clase excepcional en Java, es la única que permite instanciar nuevos objetos simplemente definiendo el texto de la cadena de caracteres a instanciar dentro de comillas5. También se pueden instanciar objetos de esta clase empleando cualquiera de los constructores que esta provee. • El funcionamiento y la manipulación de un método constructor lo hacen un método atípico pues, a diferencia de los otros métodos definidos en los ejemplos que se han presentado hasta el momento, este no requiere la referencia a una instancia para ser invocado. Analícese lo siguiente: para invocar el método calcularSalario de la clase Empleado, primero debe tenerse un objeto para luego sí invocar la instrucción; en cambio, para invocar la instrucción new Empleado() no es necesaria la referencia.
5
En realidad en Java no es lo mismo instanciar la clase String empleando uno de sus constructores o empleando el constructor especial; pero esto corresponde a una noción mucho más profunda de la que se desea presentar en este libro
35
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
segunda aproximaCión a la apliCaCión del CálCulo de la nómina mediante programaCión orientada a obJetos
Con los temas cubiertos hasta el momento es posible codificar un segundo prototipo para la aplicación. El primer componente a codificar corresponde a la clase Empleado. El código que aparece en negrilla resalta las modificaciones efectuadas con respecto a la anterior aproximación. class Empleado{ String cedula; String apellido; String nombre; double horasTrabajadas; double sueldoXHora; Empleado(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora){ this.cedula = cedula; this.apellido = apellido; this.nombre = nombre; this.sueldoXHora = sueldoXHora; this.horasTrabajadas = horasTrabajadas; }
}
double calcularSalario(){ return horasTrabajadas * sueldoXHora; }
import java.io.BufferedReader; import java.io.InputStreamReader; class Nomina{ public static void main(String[] args) throws Exception{ int numeroEmpleados; Empleado[] losEmpleados = new Empleado[50]; String cedula, apellido, nombre;
36
1. TÓPICOS BÁSICOS
double horas, sueldo; double total = 0; BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) ); System.out.print(“Digite numero de empleados: “); numeroEmpleados = Integer.valueOf(br.readLine()).intValue(); for(int i=0;i
}
losEmpleados[i]=unEmpleado;
for (int i=0;i
}
}
Puntos convenientes a resaltar en el anterior código: •
El proceso de instanciación de un objeto de la clase Empleado ahora es mucho más simple.
•
Cuando se requiere calcular cuánto devenga un empleado por concepto de salario se invoca la ejecución del método calcularSalario.
37
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
sobre la utilizaCión de paquetes Con los temas tratados hasta el momento es evidente que no debe permitirse la definición de dos clases con el mismo nombre, pues esto podría causarle problemas al sistema a la hora de la instanciación. Entonces, cómo se podría garantizar que dos programadores cualquiera ubicados en dos lugares del mundo no coincidan en el nombre de una clase. El lenguaje de programación Java a través de la definición de paquetes4 brinda una forma de organizar clases que se encuentren relacionadas. Un paquete es un espacio de nombres, cuyo concepto es igual al de una carpeta dentro de un sistema de archivos. Java permite definir paquetes dentro de paquetes (igual que se pueden definir carpetas dentro de carpetas) y definir dentro de ellos clases. De esta manera sí pueden existir clases con el mismo nombre siempre y cuando pertenezcan a distintos paquetes. Para definir que una clase pertenece a un paquete específico, se debe incluir la siguiente instrucción como la primera línea en el archivo donde se define la clase package nombredelpaquete;
Por convención en Java los nombres de los paquetes se encuentran escritos siempre en minúsculas. En caso de querer asignar la clase a un subpaquete de un determinado paquete, los nombres de los paquetes deben separarse empleando el carácter punto ‘.’ (esto implica que dentro de la definición del nombre de un paquete no puede utilizarse dicho carácter). Por ejemplo: package nombredelpaquete.nombredelsubpaquete;
Cuando en la definición de una determinada clase se necesite la funcionalidad de otra que se encuentra definida dentro de un paquete, bastará con declarar explícitamente la importación de la clase en mención, lo cual se logra a través de la siguiente instrucción: import nombredelpaquete.nombredelsubpaquete.nombredelaclase;
De esta manera se importa la definición de la clase dentro de la clase. Si lo que se desea es importar todas las definiciones de clases de dicho paquete, esto bien puede conseguirse a través de la siguiente instrucción: import nombredelpaquete.nombredelsubpaquete.*;
38
1. TÓPICOS BÁSICOS
Existe una alternativa que evita el tener que declarar la instrucción de importación de la clase, basta incluir dicha información a la hora de la declaración de la variable; por ejemplo, si se desea definir una referencia del tipo de dato de los ejemplos presentados hasta el momento en este apartado, se debe colocar la siguiente instrucción: nombredelpaquete.nombredelsubpaquete.nombredelaclase a = new nombredelpaquete.nombredelsubpaquete.nombredelaclase();
En el ejemplo anterior a corresponde al nombre de la variable a definir.
1.7. encApsulAmientO Muchas de las críticas que recibe el paradigma de programación procedimental van dirigidas a la poca protección que se brinda a los datos que utilizan las aplicaciones. El porqué de esta afirmación se explica a continuación. Considere la siguiente gráfica:
El esquema anterior corresponde al esquema general que presenta cualquier aplicación desarrollada bajo el paradigma procedimental. Dicho esquema debe entenderse de la siguiente manera: toda aplicación está compuesta por un conjunto de variables globales junto con un conjunto de subrutinas (incluida la subrutina principal) en las que se definen, en caso de ser necesarios, un conjunto de variables locales para dichas subrutinas. El problema principal de la programación procedimental radica en la poca protección que se brinda a las variables globales porque cualquier subrutina puede alterar sus valores indiscriminadamente. Imagine el siguiente escenario: dos variables globales enteras de nombre v1 y v2; dentro de la aplicación, para la primera de ella solo tiene sentido la asignación de valores positivos, pero la segunda puede tomar cualquier valor. Por un simple descuido (de digitación) dentro de una subrutina se le asigna el valor de -2 a la variable v1 cuando en realidad dicho valor debía ser asignado a v2; el resultado de ese descuido pone a mal funcionar todas las subrutinas que trabajan con el valor de v1 y, por ende, a toda la aplicación. En el apartado sobre el ámbito de las variables y la palabra reservada ‘this’ se analizó cómo es el comportamiento del ámbito de las variables cuando se trabaja bajo el paradigma de orientación a objetos. El siguiente gráfico ejemplifica, a grosso modo, cómo es la situación.
39
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
El anterior esquema debe entenderse de la siguiente manera: las aplicaciones orientadas a objetos están conformadas por un conjunto de clases; estas poseen atributos (que se pueden entender como variables globales pero solo al interior de las clases) y métodos en los que se definen, en caso de ser necesarias, variables locales. Aunque el enfoque de ambos paradigmas varía considerablemente; los dos presentan el mismo problema: permitir que desde cualquier clase se altere el valor del atributo de otro objeto (como, por ejemplo, cuando se accede y modifica el valor de la cédula sobre un objeto empleado). El encapsulamiento ayuda a corregir este problema. Definición de encapsulamiento El encapsulamiento hace referencia a ocultar los detalles de implementación internos del objeto a los demás. Esta propiedad permite asegurar que el contenido de la información de un objeto se encuentra seguro del mundo exterior.
La forma de implementar el encapsulamiento en una clase se logra a través del uso de niveles de visibilidad (también conocidos como modificadores de acceso). Con los temas vistos hasta el momento se pueden definir dos niveles de visibilidad6:
6
40
Además del nivel de visibilidad por defecto (el que se ha manejado en los ejemplos hasta el momento), existe otro que se presentará en la sección correspondiente a la herencia.
1. TÓPICOS BÁSICOS
Definición de nivel de visibilidad público Cuando se aplica a un atributo o método de una clase se especifica que dicha información o acción es visible; se puede acceder a aquel tanto en el interior como en el exterior de la clase donde se encuentra definido. Cuando se aplica a una clase5, se especifica esta clase puede ser utilizada desde cualquier clase de cualquier paquete
El siguiente diagrama puede facilitar la comprensión del nivel de visibilidad público. Las flechas que aparecen en él representan la utilización del elemento señalado por la punta de la flecha y el otro extremo (de la flecha), el sitio de donde es invocado el elemento. Suponga que todos los atributos y métodos de la clase Clase1 son públicos, por ende, ellos pueden ser accedidos tanto al interior (cuando un método de Clase1 invoca otro método de la misma clase, o cuando un método de Clase1 accede a un atributo de la propia clase) como al exterior de la clase donde se encuentra definido (cuando un método de Clase2 accede a un atributo de Clase1, o cuando un método de Clase3 invoca un método de Clase1).
Para especificar un atributo o método público en Java se le debe anteponer a su definición la palabra reservada public. Para especificar que una clase es pública se debe anteponer la palabra reservada public a la definición de la clase.
41
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
Definición de nivel de visibilidad privado Cuando se aplica a un atributo o método de una clase se especifica que dicha información o acción solo puede ser visible (accesible) al interior de la clase donde se encuentra definido.
El siguiente diagrama puede facilitar la comprensión del nivel de visibilidad privado. Las flechas que aparecen en él representan la utilización del elemento señalado por la punta de la flecha y el otro extremo (de la flecha), el sitio de donde es invocado el elemento. Para este caso suponga que todos los atributos y métodos de Clase1 son privados, por ende, se puede acceder a ellos solo desde el interior de la propia clase (cuando un método de Clase1 invoca otro método de la misma clase, o cuando un método de Clase1 accede a un atributo de la propia clase).
Para especificar un atributo o método privado en Java se le debe anteponer a su definición la palabra reservada private. No es posible definir una clase con un nivel de visibilidad privado.
42
1. TÓPICOS BÁSICOS
sobre los niveles de visibilidad públiCo y privado Considérese el siguiente código: class Empleado{ private String private String private String private double private double
cedula; apellido; nombre; horasTrabajadas; sueldoXHora;
Empleado(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora){ this.cedula = cedula; this.apellido = apellido; this.nombre = nombre; this.sueldoXHora = sueldoXHora; this.horasTrabajadas = horasTrabajadas; } double calcularSalario(){ return horasTrabajadas * sueldoXHora; } }
Al definir todos los atributos de la clase Empleado como privados se especifica que a estos solo se les puede acceder o modificar mediante instrucciones ubicadas en el interior de algún método de la clase; ninguna clase externa podrá acceder ni mucho menos modificar dichos valores. Por ejemplo, las siguientes instrucciones ubicadas en cualquier clase distinta a Empleado no solamente no se podrían ejecutar, sino que ni siquiera podrían ser compiladas por Java. El error en compilación se marcaría en la segunda instrucción porque no se puede modificar el valor de un atributo privado. Si los atributos fueran públicos, no se presentaría ningún tipo de error de compilación. Empleado e1 = new Empleado(“12345”, “Pérez”, “Pedro”, 20, 50); e1.apellido = “Pérez Sosa”;
¿Por qué la implementación de niveles de visibilidad en el código corrigen los problemas de protección de los datos? El problema a corregir radica en que en la programación estructurada cualquier subrutina puede alterar el valor de cualquier variable global. desde el paradigma de orientación a objetos y con la utilización del encapsulamiento se ha restringido esa situación porque ahora con la utilización del modificador de acceso privado solo pueden alterar los valores de los atributos los métodos ubicados al interior de la clase. Esto aumenta el control sobre los datos.
43
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
En este punto, cualquier programador debe estarse preguntando, ¿cómo se puede alterar el valor del atributo de algún objeto en caso de ser absolutamente requerido? A través de métodos públicos codificados especialmente para poder obtener el valor de un atributo (este tipo de métodos se conocen como métodos accesores) y métodos públicos que permitan modificar el valor del atributo (este tipo de métodos se conocen como métodos modificadores). Aunque no es una imposición del lenguaje Java, como recomendación general el nombre del método accesor para un atributo debe formarse anteponiendo la palabra ‘get’ (del inglés obtener) al nombre del atributo; de la misma forma, el nombre método modificador debe formarse anteponiendo la palabra ‘set’ (del inglés establecer) al nombre del atributo. El código de la clase Empleado queda de la siguiente forma: public class Empleado{ private String cedula; private String apellido; private String nombre; private double horasTrabajadas; private double sueldoXHora; public Empleado(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora){ this.cedula = cedula; this.apellido = apellido; this.nombre = nombre; this.sueldoXHora = sueldoXHora; this.horasTrabajadas = horasTrabajadas; } public double calcularSalario(){ return horasTrabajadas * sueldoXHora; } public String getCedula(){ return cedula; } public void setCedula(String cedula){ this.cedula = cedula; }
}
public String getApellido(){ return apellido; public void setApellido(String apellido){ this.apellido = apellido; } public String getNombre(){ return nombre; } public void setNombre(String nombre){ this.nombre = nombre; }
44
1. TÓPICOS BÁSICOS
public double getHorasTrabajadas(){ return horasTrabajadas; } public void setHorasTrabajadas(double horas ){ this.horasTrabajadas = horas; } public double getSueldoXHora(){ return sueldoXHora; } public void setSueldoXHora(double sueldo ){ this.sueldoXHora = sueldo; } }
Puntos convenientes a resaltar en el anterior código: • Note que la clase Empleado se ha especificado como pública. • Note que tanto el método constructor de la clase como el método para calcular el salario del empleado se encuentran definidos con un nivel de visibilidad público, puesto que se desea que cualquier otra clase pueda invocar este servicio que exhibe el objeto. • Para cada atributo de la clase se ha definido un método accesor y uno modificador. No es obligatorio que un atributo tenga ambos métodos. Un programador atento debe estar pensando lo siguiente, si uno de los objetivos de los niveles de visibilidad era no permitir que desde clases externas se alteren los valores de los atributos de una clase, al definir métodos accesores y modificadores se pierde todo lo que se había avanzado; y ahora se regresa a una posición aún peor que la inicial en la medida en que es necesario escribir una mayor cantidad de código7 (pues las instrucciones que se requieren para codificar estos métodos). Aun cuando aparentemente se ha terminado en un estado peor al inicial, la realidad es distinta. • Antes (en la programación estructurada), cualquiera podría alterar los valores de las variables globales. Ahora también es posible alterar el valor de los atributos a través de métodos modificadores; pero la diferencia es que se pueden escoger cuáles métodos modificadores exhibirá una clase a las restantes clases (piense que ahora si no se desea cambiar el valor de la cédula de un empleado, simplemente no se define como público el método setCedula). • Antes, a una variable global entera definida para aceptar únicamente valores positivos podía asignársele un valor fuera del rango en cualquier subrutina de la aplicación. Ahora también es posible hacerlo; la diferencia es que hoy la realización de dicha asignación se ejecuta invocando un método en el cual puede ubicarse
7
La mayoría de los entornos de desarrollo para Java brindan la capacidad de generar de forma automática (con unos cuantos clics) los métodos accesores y modificadores para los atributos de una clase.
45
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
el código de validación para dicho atributo. Por ejemplo, el código para el método setHorasTrabajadas puede definirse de la siguiente manera, de tal forma que el objeto no pueda aceptar valores negativos para dicho atributo: public void setHorasTrabajadas(double horas){ if (horas>=0) this.horasTrabajadas=horas; else this.horasTrabajadas=0; }
terCera aproximaCión a la apliCaCión del CálCulo de la nómina mediante programaCión orientada a obJetos
Para esta tercera aproximación los únicos cambios profundos a implementar se encuentran en la clase Empleado. La clase Nomina permanece casi que sin alteraciones. El código que aparece en negrilla resalta las modificaciones efectuadas con respecto a la anterior aproximación. public class Empleado{ private String cedula; private String apellido; private String nombre; private double horasTrabajadas; private double sueldoXHora; public Empleado(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora){ setCedula(cedula); setApellido(apellido); setNombre(nombre); setSueldoXHora(sueldoXHora); setHorasTrabajadas(horasTrabajadas); } public double calcularSalario(){ return horasTrabajadas * sueldoXHora; } public String getCedula(){ return cedula; } private void setCedula(String cedula){ if (cedula!=null) this.cedula = cedula; else this.cedula = “”; } public String getApellido(){ return apellido; }
46
1. TÓPICOS BÁSICOS
public void setApellido(String apellido){ if (apellido!=null) this.apellido = apellido; else this.apellido=””; } public String getNombre(){ return nombre; } public void setNombre(String nombre){ if (nombre!=null) this.nombre = nombre; else this.nombre=””; } public double getHorasTrabajadas(){ return horasTrabajadas; } public void setHorasTrabajadas(double horas ){ if (horas>=0) this.horasTrabajadas = horas; else this.horasTrabajadas = 0; } public double getSueldoXHora(){ return sueldoXHora; } public void setSueldoXHora(double sueldo ){ if (sueldo>=0) this.sueldoXHora = sueldo; else this.sueldoXHora = 0; } } import java.io.BufferedReader; import java.io.InputStreamReader; public class Nomina{ public static void main(String[] args) throws Exception{ int numeroEmpleados; Empleado[] losEmpleados = new Empleado[50]; String cedula, apellido, nombre; double horas, sueldo; double total = 0; BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) ); System.out.print(“Digite numero de empleados: “); numeroEmpleados = Integer.valueOf(br.readLine()).intValue(); for(int i=0;i
47
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
System.out.print(“Digite el nombre del empleado: “ ); nombre = br.readLine(); System.out.print(“Digite num de horas trabajadas del empleado: “); horas = Double.valueOf(br.readLine()).doubleValue(); System.out.print(“Digite sueldo por hora del empleado: “ ); sueldo = Double.valueOf(br.readLine()).doubleValue(); Empleado unEmpleado = new Empleado(cedula, apellido, nombre, horas, sueldo); }
losEmpleados[i]=unEmpleado;
for (int i=0;i
}
Puntos convenientes a resaltar en el anterior código: • •
•
•
•
48
Como se definen dos clases públicas estas deben crearse en archivos independientes. En Java no puede haber dos clases públicas definidas en el mismo archivo. Se ha agregado código de validaciones simples a cada uno de los métodos modificadores de los atributos. Se deja al lector la implementación de demás validaciones que considere convenientes. Note que el constructor de la clase ya no le asigna valores directamente a los atributos, sino que ahora utiliza los métodos modificadores; con esto se garantiza que a la hora de crear un nuevo objeto de la clase se cumplan las validaciones. Note que el método para alterar el valor del atributo cédula de la clase Empleado es privado. Al definirlo de esta manera, desde ninguna otra clase distinta a Empleado podrá ser invocado. Una situación que generalmente ocasiona problemas a los programadores que apenas comienzan a entender sobre la teoría de objetos corresponde a lo que debe hacerse en caso error cuando se suministre un valor para crear un objeto Empleado. La idea general que hasta el momento se va a manejar corresponde simplemente a asignar un valor por defecto que sea correcto sin indicarle al usuario que se ha presentado un error. Ideas como la creación de un ciclo para luego preguntar indefinidamente hasta que se pasen parámetros correctos no deben ni siquiera considerarse como alternativas de solución. Más adelante, en la sección de manejo de excepciones, se analizará cómo debe manejarse esta situación.
1. TÓPICOS BÁSICOS
•
Es conveniente notar que la clase Empleado simplemente describe la estructura y el comportamiento de un conjunto de empleados. Dentro de esta descripción es importante resaltar que no se encuentra definido código para leer o escribir información por pantalla. Note que todo el código de lectura y de escritura de los datos reside en la clase Nomina. Realizar este tipo de desacoplamiento corresponde a una muy buena práctica de programación porque incita y facilita la reutilización de código. Imagínese que existiera otra aplicación que requiera de trabajar con los objetos de la clase Empleado, pero a diferencia de la aplicación de la nómina, esta última trabaja en un ambiente gráfico con ventanas y botones; ¿se podría trabajar con la clase Empleado si estuviera codificada de tal manera que los datos se leen de pantalla preguntándoselos al usuario? La respuesta es un rotundo no.
La idea detrás del encapsulamiento es realmente sencilla, lo que se busca es proteger el estado interno de los atributos. Como consecuencia, cuando otra clase requiera trabajar con una clase encapsulada, lo hará sin acoplarse a su estructura interna. Al no hacerlo, los cambios dentro de la estructura de la clase encapsulada no repercutirán en cambios en el código de las clases que la utilicen. Analice cómo se encuentran definidas las cosas en el mundo real. Ponga por ejemplo una habitación que cuente con iluminación en una casa cualquiera. ¿Qué tan complejo es realizar el cambio para que la iluminación de la habitación deje de ser incandescente y se vuelva fluorescente? En realidad lo que se requiere es muy poco, simplemente basta con cambiar el tipo de foco que alumbra a la habitación. En el mundo de la programación, hay ocasiones en que la realización de un cambio que se cree sencillo requeriría, en términos del ejemplo anterior: quitar la antigua base del foco, reventar el techo, reventar la pared, quitar los alambres que conducen la corriente, instalar nuevos alambres, repellar la pared, arreglar el techo, instalar la nueva base y, finalmente, instalar el nuevo tipo de foco. El cambio del tipo de foco en una habitación no es una actividad traumática en la vida real porque de antemano los fabricantes se ponen de acuerdo en los servicios que exhibirá el foco y en lo que se necesita para su funcionamiento. De allí en adelante los fabricantes crean focos a partir de las convenciones establecidas (diámetro, profundidad, tipo de material, conductibilidad del material, etc.). Así debiera ser también el mundo del desarrollo de software, los fabricantes de componentes (software) debieran especificar detalladamente como son los servicios que ha de exhibir un elemento (se puede pensar en los servicios a exhibir cómo en los métodos públicos de la clase). Los clientes (otros programas o aplicaciones) se acoplan a los servicios y no a cómo es que encuentran implementados. Esta última idea corresponde a, quizás, la mejor de las prácticas que se pueden tener en el mundo de la programación. Analice el caso de ejemplo que se presenta a continuación. Imagine que Java carece de un componente que le permita manipular elementos de tipo fecha y que se encuentra interesado en la construcción de tal tipo de componente.
49
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
Los servicios que debe prestar el componente son: • El de conocer el valor del número correspondiente al mes de la fecha. • El de conocer el nombre del mes correspondiente a la fecha. • El de obtener la fecha en el siguiente formato “dd/mm/aaaa”. • El de obtener la fecha en el siguiente formato “dd de nombremes de aaaa”. Aclaraciones: Para efectos de mantener la simplicidad del ejemplo no se contemplan validaciones para garantizar que las fechas en realidad sean válidas; por ejemplo, que no exista un 30 de febrero, etc. Se deja al lector la implementación de estas y cualquier otra validación que considere conveniente. Los servicios que debe exhibir el componente pueden traducirse a métodos, para los cuales se presenta una posible definición: public public public public
int numeroMes() String nombreMes() String formato1() String formato2()
Analícese la definición de las clases que se presentan a continuación public class Fecha1{ private int dia; private int mes; private int año; private String[] nombresMeses; public Fecha1(int dia, int mes, int año){ this.dia = dia; this.mes = mes; this.año = año; this.nombresMeses = new String[13]; nombresMeses[1]=new String(“Enero”); nombresMeses[2]=new String(“Febrero”); nombresMeses[3]=new String(“Marzo”); nombresMeses[4]=new String(“Abril”); nombresMeses[5]=new String(“Mayo”); nombresMeses[6]=new String(“Junio”); nombresMeses[7]=new String(“Julio”); nombresMeses[8]=new String(“Agosto”); nombresMeses[9]=new String(“Septiembre”); nombresMeses[10]=new String(“Octubre”); nombresMeses[11]=new String(“Noviembre”); nombresMeses[12]=new String(“Diciembre”); } public int numeroMes(){ return mes; }
50
1. TÓPICOS BÁSICOS
public String nombreMes(){ return nombresMeses[mes]; } public String formato1(){ return dia+”/”+mes+”/”+año; } public String formato2(){ return dia + “ de “ + nombresMeses[mes] + “ de “ + año; } }
public class Fecha2{ private int dia; private int mes; private int año; public Fecha2(int dia, int mes, int año){ this.dia = dia; this.mes = mes; this.año = año; } public int numeroMes(){ return mes; } public String nombreMes(){ if (mes==1) return “Enero”; if (mes==2) return “Febrero”; if (mes==3) return “Marzo”; if (mes==4) return “Abril”; if (mes==5) return “Mayo”; if (mes==6) return “Junio”; if (mes==7) return “Julio”; if (mes==8) return “Agosto”; if (mes==9) return “Septiembre”; if (mes==10) return “Octubre”; if (mes==11) return “Noviembre”; if (mes==12) return “Diciembre”; return null; } public String formato1(){ return dia+”/”+mes+”/”+año; } public String formato2(){ return dia + “ de “ + nombreMes() + “ de “ + año; } }
51
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
Imagine que una aplicación empiece a trabajar con la primera implementación; más adelante, si dicha aplicación quiere cambiar a la segunda implementación, lo podrá hacer sin traumatismo alguno8. Esto se hace posible porque la primera clase exhibe unos métodos (servicios) bien definidos, que también los posee la segunda. En contraste, imagine que una aplicación utilice una versión modificada de la clase Fecha1. En dicha versión modificada, no se definen métodos, sino que simplemente se acceden a los atributos. El cambio de la clase Fecha1 por la clase Fecha2 será traumático si en la aplicación existen muchas llamadas al atributo nombresMeses, ya que dicho atributo no se encuentra presente en la segunda implementación.
1.8. AtributOs finAles Hasta el momento un atributo es una variable para un objeto que puede tomar distintos valores, o expresado de otra manera, cuyo valor puede variar en el tiempo. En el paradigma de programación procedimental era posible definir variables que fueran constantes, es decir, cuyo valor no podía ser modificado; en el paradigma de programación orientada a objetos existe también la misma noción enmarcada desde el concepto de atributo final. Definición de atributo final Corresponde a un atributo cuyo valor para un objeto no cambia durante la ejecución del programa. Toma el primer valor que le sea asignado.
Para definir un atributo como final se le debe colocar la palabra reservada ‘final antes de su declaración. Por ejemplo, para la clase Empleado se puede pensar en definir el atributo donde se almacena la cédula del empleado como un atributo final de tal forma que dicho valor no pueda ser alterado posteriormente durante la ejecución de un programa. Aplicando dicho cambio, el código quedaría de la siguiente manera: public class Empleado{ final private String cedula; private String apellido; private String nombre; private double horasTrabajadas; private double sueldoXHora; public Empleado(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora){ if (cedula!=null) this.cedula = cedula; else this.cedula = “”; setApellido(apellido);
8
52
Como ocurría en el ejemplo de la habitación y el tipo de iluminación.
1. TÓPICOS BÁSICOS
}
setNombre(nombre); setSueldoXHora(sueldoXHora); setHorasTrabajadas(horasTrabajadas);
public double calcularSalario(){ return horasTrabajadas * sueldoXHora; } public String getCedula(){ return cedula; } public String getApellido(){ return apellido; } public void setApellido(String apellido){ if (apellido!=null) this.apellido = apellido; else this.apellido=””; } public String getNombre(){ return nombre; } public void setNombre(String nombre){ if (nombre!=null) this.nombre = nombre; else this.nombre=””; } public double getHorasTrabajadas(){ return horasTrabajadas; } public void setHorasTrabajadas(double horas ){ if (horas>=0) this.horasTrabajadas = horas; else this.horasTrabajadas = 0; } public double getSueldoXHora(){ return sueldoXHora; } public void setSueldoXHora(double sueldo ){ if (sueldo>=0) this.sueldoXHora = sueldo; else this.sueldoXHora = 0; } }
Del anterior código es conveniente resaltar que el método modificador del atributo cédula ha desaparecido y, además, que el código de validación de dicho atributo ha pasado al método constructor. Al trabajar con atributos finales tenga en cuenta los siguientes puntos: • Es un grave error de programación definir métodos modificadores para los atributos finales. El error en tiempo de compilación que generan evita que se pueda ejecutar el código).
53
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
• En caso de definir un atributo final al que no se le asigna inmediatamente valor en su definición, es una obligación inicializar el valor que tendrá dentro del método constructor. No hacerlo corresponde a un error grave de programación que evitaría la compilación de la clase. • Si se define como final un atributo de tipo de dato primitivo, el primer valor que tome este atributo será el que se mantenga durante toda la ejecución del programa; en caso de definir como final un atributo de tipo de dato de referencia la referencia, inicialmente asignada no podrá ser alterada durante la ejecución del programa.
1.9. AtributOs y métOdOs estáticOs Al instanciar dos objetos de una misma clase, los valores de los atributos de estos dos objetos son totalmente independientes (es decir, la alteración del valor de uno de los atributos en un objeto no afecta al valor del mismo atributo en el otro objeto). El siguiente gráfico toma como ejemplo la clase Fecha1 y presenta dos posibles objetos que se pueden crear a partir de dicha clase.
En determinados escenarios sería deseable lograr que el valor de un atributo lo compartan por todos los objetos de una clase, por ejemplo, en el caso presentado en el anterior gráfico sería conveniente que todos los objetos de la clase Fecha1 compartieran los valores a almacenar en el atributo nombreMeses, así se evitaría tener que instanciar un nuevo array y una cantidad de objetos String (para los nombres de los meses) con cada nueva creación de un objeto Fecha1.
54
1. TÓPICOS BÁSICOS
Definición de atributo estático Corresponde a un atributo de la clase más que a un atributo para los objetos. Al ser un atributo de la clase no se crea un valor nuevo por cada nueva instancia de la clase. Este atributo toma valor, aunque no existan objetos de la clase. Todos los objetos de la clase comparten dicho atributo y pueden acceder y modificar su valor.
Para definir un atributo como estático basta con anteponerle la palabra reservada ‘static’ a su definición. Por ejemplo, se podría realizar la modificación a la clase Fecha1 para que trabaje con un atributo estático, el código de dicha clase quedaría de la siguiente manera: public class Fecha1{ private int dia; private int mes; private int año; static private String[] nombresMeses; public Fecha1(int dia, int mes, int año){ this.dia = dia; this.mes = mes; this.año = año; this.nombresMeses = new String[13]; nombresMeses[1]=new String(“Enero”); nombresMeses[2]=new String(“Febrero”); nombresMeses[3]=new String(“Marzo”); nombresMeses[4]=new String(“Abril”); nombresMeses[5]=new String(“Mayo”); nombresMeses[6]=new String(“Junio”); nombresMeses[7]=new String(“Julio”); nombresMeses[8]=new String(“Agosto”); nombresMeses[9]=new String(“Septiembre”); nombresMeses[10]=new String(“Octubre”); nombresMeses[11]=new String(“Noviembre”); nombresMeses[12]=new String(“Diciembre”); } public int numeroMes(){ return mes; } public String nombreMes(){ return nombresMeses[mes]; } public String formato1(){ return dia+”/”+mes+”/”+año; } public String formato2(){ return dia + “ de “ + nombresMeses[mes] + “ de “ + año; } }
55
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
La situación sería más clara de entender si se piensa que al instanciar dos objetos con esta nueva definición se obtiene una configuración en memoria parecida a la siguiente:
Es conveniente detenerse un momento y analizar cuidadosamente el anterior gráfico. En él aparecen dos instancias de la clase Fecha1 (una, con los valores de “1/1/2009” y la otra, con los valores de “21/2/2009”). Note que para ambos objetos la referencia para el atributo nombresMeses los conduce a otra posición en memoria. La definición ubicada en esta última posición de memoria no corresponde a un objeto, sino a la definición de la clase. Note que para la clase se encuentra la definición del atributo nombresMeses, que al final es el que apunta al objeto array en memoria donde se almacenan los nombres de los meses. Es de esperarse que aunque no existan objetos en memoria de la clase Fecha1, sí exista la definición de la clase.
56
1. TÓPICOS BÁSICOS
sobre la manipulaCión de los atributos estátiCos Considérese el siguiente código: class EjemploEstatico{ public static int a = 50; } EjemploEstatico ej1 = new EjemploEstatico(); EjemploEstatico ej2 = new EjemploEstatico();
Como los objetos ej1 y ej2 comparten el valor del atributo a y además este es público, se podría modificar dicho valor con cualquiera de las siguientes instrucciones: ej1.a = 200; ej2.a = 150;
Pero como este atributo se encuentra definido para la clase, su valor existe aun cuando no existan instancias de la clase. La pregunta que se podría estar formulando cualquier programador en estos momentos es, ¿cómo es posible conocer en tiempo de ejecución el valor del atributo si no existen objetos referenciados a los que se les pueda preguntar el valor? La respuesta es que sí es posible conocer o alterar dicho valor, y para ello se debe emplear el nombre de la clase, por ejemplo, el siguiente código imprime por pantalla el valor del atributo a System.out.println(EjemploEstatico.a);
Así como existen atributos estáticos, también es posible definir métodos estáticos. Definición de método estático Método definido para la clase. Generalmente opera sobre los atributos de clase.
Generalmente se define un método estático para manipular las variables estáticas que tenga una clase, o para crear librerías de métodos que puedan ser utilizados por varias clases. Para especificar que un método de una clase será estático, se debe anteponer a su propia definición la palabra reservada ‘static’. Considérese el siguiente ejemplo. public class Fecha1{ private int dia;
57
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
private int mes; private int año; static private String[] nombresMeses; public Fecha1(int dia, int mes, int año){ this.dia = dia; this.mes = mes; this.año = año; this.nombresMeses = new String[13]; nombresMeses[1]=new String(“Enero”); nombresMeses[2]=new String(“Febrero”); nombresMeses[3]=new String(“Marzo”); nombresMeses[4]=new String(“Abril”); nombresMeses[5]=new String(“Mayo”); nombresMeses[6]=new String(“Junio”); nombresMeses[7]=new String(“Julio”); nombresMeses[8]=new String(“Agosto”); nombresMeses[9]=new String(“Septiembre”); nombresMeses[10]=new String(“Octubre”); nombresMeses[11]=new String(“Noviembre”); nombresMeses[12]=new String(“Diciembre”); } public int numeroMes(){ return mes; } public String nombreMes(){ return nombresMeses[mes]; } public String formato1(){ return dia+”/”+mes+”/”+año; } public String formato2(){ return dia + “ de “ + nombresMeses[mes] + “ de “ + año; } public static String nombreMes(int numMes){ return nombresMeses[numMes]; } }
En el anterior código se definió un método estático que permite obtener el nombre de un mes pasando como parámetro el número de dicho mes. La diferencia con el otro método que tiene el mismo propósito es que uno recibe como parámetro el número del mes, mientras que el otro trabaja con el número de mes que guarda el objeto. Para ejecutar el método estático se puede emplear la siguiente instrucción. Fecha1.nombreMes(11);
58
1. TÓPICOS BÁSICOS
Al trabajar con métodos estáticos tenga en cuenta los siguientes puntos: • Al igual que los atributos estáticos, se puede acceder a un método estático puede ser accedido sin necesidad de haber instanciado la clase previamente. Se emplea el nombre de la clase como la referencia para la invocación del método. • Es un grave error de programación emplear variables no estáticas desde métodos estáticos. En caso de hacerlo, se generará un error en tiempo de compilación. Las variables que pueden utilizarse al interior de estos métodos son aquellas variables locales que han sido definidas dentro del método, variables locales que correspondan a parámetros del método o atributos estáticos de la clase. • Es un grave error de programación invocar métodos no estáticos desde métodos estáticos. En caso de hacerlo, se generará un error en tiempo de compilación. Desde un método estático solo pueden invocarse otros métodos estáticos de la clase. • Hay tener cuidado de no degenerar el uso tanto de los métodos como de los atributos estáticos. Si se analiza con cuidado, cualquier aplicación implementada bajo el paradigma procedimental puede ser codificada bajo el paradigma orientado a objeto como una serie de atributos y métodos estáticos, que distorsiona el sentido para el cual fue creado este modificador.
1.10. HerenciA Cuarta aproximaCión a la apliCaCión del CálCulo de la nómina mediante programaCión orientada a obJetos
En esta cuarta aproximación serán tenidos en cuenta los cambios sugeridos en la sección donde se trató los atributos finales; y adicionalmente se incluirá la definición de dos interfaces de usuario (una donde se pide el número de empleados de la empresa y otra donde se trabaja bajo un menú de opciones) El código que aparece en negrilla resalta las modificaciones efectuadas con respecto a la anterior aproximación. public class Empleado{ final private String cedula; private String apellido; private String nombre; private double horasTrabajadas; private double sueldoXHora;
59
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
public Empleado(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora){ if (cedula!=null) this.cedula = cedula; else this.cedula = “”; setApellido(apellido); setNombre(nombre); setSueldoXHora(sueldoXHora); setHorasTrabajadas(horasTrabajadas);
} public double calcularSalario(){ return horasTrabajadas * sueldoXHora; } public String getCedula(){ return cedula; } public String getApellido(){ return apellido; } public void setApellido(String apellido){ if (apellido!=null) this.apellido = apellido; else this.apellido=””; } public String getNombre(){ return nombre; } public void setNombre(String nombre){ if (nombre!=null) this.nombre = nombre; else this.nombre=””; } public double getHorasTrabajadas(){ return horasTrabajadas; } public void setHorasTrabajadas(double horas ){ if (horas>=0) this.horasTrabajadas = horas; else this.horasTrabajadas = 0; } public double getSueldoXHora(){ return sueldoXHora; } public void setSueldoXHora(double sueldo ){ if (sueldo>=0) this.sueldoXHora = sueldo; else this.sueldoXHora = 0; } }
60
1. TÓPICOS BÁSICOS
import java.io.BufferedReader; import java.io.InputStreamReader; public class Nomina1{ public static void main(String[] args) throws Exception{ int numeroEmpleados; Empleado[] losEmpleados = new Empleado[50]; String cedula, apellido, nombre; double horas, sueldo; double total = 0; BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) ); System.out.print(“Digite numero de empleados: “); numeroEmpleados = Integer.valueOf(br.readLine()).intValue(); for(int i=0;i
}
import java.io.BufferedReader; import java.io.InputStreamReader; class Nomina2{ public static void main(String[] args) throws Exception{ int numeroEmpleados=0, opcionMenu=0; Empleado[] losEmpleados = new Empleado[50]; String cedula, apellido, nombre;
61
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
double horas, sueldo; double total = 0; BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) ); while(opcionMenu!=3){ System.out.println(“Menu de opciones”); System.out.println(“1- Adicionar Empleado”); System.out.println(“2- Calcular nómina total”); System.out.println(“3- Salir”); System.out.print(“Escoja opción: “); opcionMenu = Integer.valueOf(br.readLine()).intValue(); if (opcionMenu==1){ System.out.print(“\nDigite la cedula del empleado: “ ); cedula = br.readLine(); System.out.print(“Digite el apellido del empleado: “ ); apellido = br.readLine(); System.out.print(“Digite el nombre del empleado: “ ); nombre = br.readLine(); System.out.print(“Digite num de horas trabajadas: “); horas = Double.valueOf(br.readLine()).doubleValue(); System.out.print(“Digite sueldo por hora del empleado: “ ); sueldo = Double.valueOf(br.readLine()).doubleValue(); Empleado unEmpleado = new Empleado(cedula, apellido, nombre, horas, sueldo); losEmpleados[numeroEmpleados]=unEmpleado; numeroEmpleados++; } else if (opcionMenu==2){ for (int i=0;i
}
}
}
Puntos convenientes a resaltar en el anterior código: •
62
Note que tanto la clase Nomina1, como Nomina2 trabajan con la misma definición de la clase Empleado. Tal y como se mencionó anteriormente, este grado de desacoplamiento es deseable dentro del mundo del desarrollo de aplicaciones.
1. TÓPICOS BÁSICOS
quinta aproximaCión a la apliCaCión del CálCulo de la nómina mediante programaCión orientada a obJetos
Existe una particularidad en el código presentado en la Cuarta aproximación a la aplicación del cálculo de la nómina. Las clases Nomina1 y Nomina2 presentan código que se encarga de la interacción con el usuario para poder realizar el cálculo de la nómina. Ambas clases tienen un estilo de interacción distinto, sin embargo, ambas comparten una gran cantidad de código. Analícese que ambas definen un array de empleados, ambas requieren de una variable que cuenta el número de empleados registrados, ambas realizan la misma operación para registrar un nuevo empleado y ambas tienen exactamente el mismo código para calcular la nómina total de la empresa. No sería de extrañar que estas mismas coincidencias aparecieran si se quisiera codificar una nueva clase que representa la interacción con el usuario. Esta situación da para pensar que el código presentado en esa cuarta aproximación puede ser mejorado aún más. Para hacerlo sería necesario incluir una nueva clase donde se pudieran incluir todas esas instrucciones que se repiten. Pero, ¿por qué ha ocurrido esto? Si supuestamente, hasta el momento, se ha estado siguiendo a cabalidad el enfoque de la orientación a objetos. Lo que ha sucedido es que desde el principio del modelamiento del problema se ha cometido un error, se ha obviado la definición de una clase. Si se lee detenidamente el enunciado del caso de ejemplo de la aplicación del cálculo de la nómina, puede darse cuenta que allí se hace referencia, además de los objetos empleados, al objeto que los agrupa: la empresa. En realidad el escenario debió ser imaginado de la siguiente manera:
63
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
La idea con la que se va a trabajar es la siguiente: un empleado por sí solo no tiene mucho sentido; para el problema en mención, él debe trabajar en una empresa. En una empresa trabaja un conjunto de empleados; si se calcula cuánto debe pagarle a cada empleado y totaliza dicha cantidad, se conocerá cuánto paga finalmente la empresa por concepto de nómina. Se hace necesario entonces codificar la clase Empresa. Para poder describir una empresa, se incluirán los atributos nit, nombre y dirección. Adicionalmente, como en una empresa trabajan empleados, entonces se hace necesario incluir como un atributo un array de objetos de la clase Empleado y su respectivo contador para conocer cuántos empleados posee dicha empresa. La alternativa que se adoptará es que un objeto Empresa al crearse no contenga empleados, y según se requiera pueden irse adicionando. Se requiere un método para esto último y, además, se debe definir un método para obtener el cálculo total de la nómina. public class Empresa{ final private String nit; private String nombre; private String direccion; private Empleado[] empleados; private int numeroEmpleados; public Empresa(String nit, String nombre, String direccion){ if (nit!=null) this.nit = nit; else this.nit = “”; setNombre(nombre); setDireccion(direccion); empleados = new Empleado[50]; numeroEmpleados = 0; } public String getNit(){ return nit; } public String getNombre(){ return nombre; } public void setNombre(String nombre){ if (nombre!=null) this.nombre = nombre; else this.nombre=””; } public String getDireccion(){ return direccion; } public void setDireccion(String direccion){ if (direccion!=null) this.direccion = direccion; else this.direccion=””; }
64
1. TÓPICOS BÁSICOS
public int getNumeroEmpleados(){ return numeroEmpleados; } public void adicionarEmpleado(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora){ Empleado e = new Empleado(cedula, apellido, nombre, horasTrabajadas, sueldoXHora); empleados[numeroEmpleados]=e; numeroEmpleados++; } public double calcularNominaTotal(){ double total = 0; for (int i=0; i
Puntos convenientes a resaltar en el anterior código: •
•
• •
• •
Para varios atributos definidos en la clase, no se pasan valores iniciales a través de parámetros. La idea es que cuando se cree un objeto de tipo Empresa, no contenga ningún Empleado; por eso el numeroEmpleados es 0 y el array de empleados empieza vacío. Para garantizar el encapsulamiento de la clase, no se permite acceder directamente al array de empleados que maneja la empresa ni tampoco alterar directamente el valor del atributo numeroEmpleados. El método accesor para el atributo numeroEmpleados devuelve el valor incrementándolo en una unidad. Esto se debe a que en Java los arrays inician en la posición 0. El método adicionarEmpleado requiere de toda la información del Empleado (porque no la conoce de antemano). Los parámetros que se utilizan son los mismos que define el constructor de la clase. El método calcularNominaTotal recorre todo el array y le invoca a cada Empleado el método que tiene dicha clase que permite calcular su salario. Al igual como se mencionó en el último punto, es conveniente resaltar en el apartado de la Tercera aproximación a la aplicación del cálculo de la nómina, la clase Empresa describe la estructura y comportamiento de conjunto de objetos Empresa, pero en ella no se incluyen instrucciones para leer los datos, o para escribir por pantalla información; todo esto para aumentar el desacoplamiento entre los componentes y facilitar su reutilización.
65
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
Permaneciendo inalterada la definición de la clase Empleado, y considerando la nueva definición de la clase Empresa, se podría reescribir las implementación de la clase Nomina1 de la siguiente manera (la reimplementación de la clase Nomina2 se deja como trabajo al lector). El código que aparece en negrilla resalta las modificaciones efectuadas con respecto a la anterior aproximación. import java.io.BufferedReader; import java.io.InputStreamReader; public class Nomina1{ public static void main(String[] args) throws Exception{ Empresa pyme = new Empresa(“123”,”Acme inc. “,”805 Silicon Valley”); String cedula, apellido, nombre; double horas, sueldo; double total = 0; BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) ); System.out.print(“Digite numero de empleados: “); int numeroEmpleados = Integer.valueOf(br.readLine()).intValue(); for(int i=0;i
pyme.adicionarEmpleado(cedula, apellido, nombre, horas, sueldo);
total = pyme.calcularNominaTotal(); System.out.println(“\nLa nómina total es: “+ total); }
66
}
1. TÓPICOS BÁSICOS
Puntos convenientes a resaltar del anterior código: •
•
• • •
Note que en esta nueva versión de la clase desaparece la definición del contador y del array de empleados. Todo eso será administrado ahora a través de un objeto de la clase Empresa. Para mantener simple el código, se han asignado unos valores iniciales para los atributos nit, nombre y dirección de la empresa. Se podría alterar la definición de la clase para hacer que estos datos también se le preguntarán al usuario al iniciarse la ejecución de la aplicación. Note que ahora la clase Nomina1 no instancia al objeto Empleado, simplemente invoca al método adicionarEmpleado de la clase Empresa que se encarga de dicha función. Todo el código del cálculo de la nómina desaparece y en su lugar ahora se encuentra la invocación al método calcularNominaTotal de la clase Empresa. La clase Nomina2 también puede escribirse en función de la clase Empresa. Esta nueva aproximación es una mejor solución al problema del cálculo de la nómina porque facilitaría la modificación de la aplicación, en caso de ser necesaria una actualización en la fórmula del cálculo de la nómina total de la empresa, para considerar todos los aportes parafiscales (como pensión, salud, entre otras). Ante dichas circunstancias, solo sería necesario modificar el código del método de una clase, y permanecerían sin cambios las clases Nomina1 y Nomina2.
En aras de analizar más características y conceptos de la programación orientada a objetos se aumenta la complejidad del enunciado del caso de ejemplo del cálculo de la nómina, que queda de la siguiente manera. Cierta compañía que paga a sus empleados de manera mensual desea ayuda para el desarrollo un programa a fin de efectuar el cálculo de su nómina. La empresa contrata empleados bajo las siguientes modalidades: • Existen empleados a quienes se les paga según el número de horas trabajadas y del valor por hora convenido previamente con cada uno. • Existen empleados a quienes se les paga al mes un sueldo básico más un porcentaje sobre las ventas efectuadas en el mes. El valor del sueldo básico y el porcentaje sobre las ventas son convenidos previamente con cada uno. • Existen empleados a quienes se les paga un sueldo fijo mensual Se mantendrán las restantes limitaciones iniciales contempladas en el enunciado original del caso de ejemplo.
67
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
sexta aproximaCión a la apliCaCión del CálCulo de la nómina mediante programaCión orientada a obJetos
Aunque se podría considerar la posibilidad de utilizar la clase Empleado para representar y trabajar con los demás tipos de Empleado, es más conveniente en este punto definir una serie de clases adicionales que ayuden en dicho sentido. Se creará una nueva clase para representar a los empleados cuyo salario se calcula de acuerdo a su sueldo básico y su comisión. El nombre que se le asignará a esta clase corresponde a EmpleadoXComision. public class EmpleadoXComision{ final private String cedula; private String apellido; private String nombre; private double sueldoBasico; private double porcentaje; private double ventasTotales; public EmpleadoXComision(String cedula, String apellido, String nombre, double sueldoBasico, double porcentaje, double ventasTotales){ if (cedula!=null) this.cedula = cedula; else this.cedula = “”;
}
setApellido(apellido); setNombre(nombre); setSueldoBasico(sueldoBasico); setPorcentaje(porcentaje); setVentasTotales(ventasTotales);
public double calcularSalario(){ return sueldoBasico + porcentaje * ventasTotales; } public String getCedula(){ return cedula; } public String getApellido(){ return apellido; } public void setApellido(String apellido){ if (apellido!=null) this.apellido = apellido; else this.apellido=””; }
68
1. TÓPICOS BÁSICOS
public String getNombre(){ return nombre; } public void setNombre(String nombre){ if (nombre!=null) this.nombre = nombre; else this.nombre=””; } public double getSueldoBasico(){ return sueldoBasico; } public void setSueldoBasico(double sueldo){ if (sueldo>=0) this.sueldoBasico = sueldo; else this.sueldoBasico = 0; } public double getPorcentaje(){ return porcentaje; } public void setPorcentaje(double porcentaje ){ if (porcentaje>=0 && porcentaje<=1) this.porcentaje = porcentaje; else this.porcentaje = 0; } public double getVentasTotales(){ return ventasTotales; } public void setVentasTotales(double ventas){ if (ventas>=0) this.ventasTotales = ventas; else this.ventasTotales = 0; } }
Se definirá una clase que represente a los empleados que devengan un sueldo fijo mensual. El nombre de esta clase será EmpleadoSueldoFijo. public class EmpleadoSueldoFijo{ final private String cedula; private String apellido; private String nombre; private double sueldoFijo; public EmpleadoSueldoFijo(String cedula, String apellido, String nombre, double sueldoFijo){ if (cedula!=null) this.cedula = cedula; else this.cedula = “”;
69
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
}
setApellido(apellido); setNombre(nombre); setSueldoFijo(sueldoFijo);
public double calcularSalario(){ return sueldoFijo; } public String getCedula(){ return cedula; } public String getApellido(){ return apellido; } public void setApellido(String apellido){ if (apellido!=null) this.apellido = apellido; else this.apellido=””; } public String getNombre(){ return nombre; } public void setNombre(String nombre){ if (nombre!=null) this.nombre = nombre; else this.nombre=””; } public double getSueldoFijo(){ return sueldoFijo; } public void setSueldoFijo(double sueldo){ if (sueldo>=0) this.sueldoFijo = sueldo; else this.sueldoFijo = 0; } }
Como la intención es la de asignar nombres representativos a las clases, a partir de esta aproximación a la aplicación de la nómina a la clase Empleado se le cambiará el nombre por EmpleadoXHora. La clase Empresa debe ser ajustada para que ahora se permita el trabajo con todos los tipos de empleados presentados anteriormente. El código que aparece en negrilla resalta las modificaciones efectuadas con respecto a la anterior aproximación.
70
1. TÓPICOS BÁSICOS
public class Empresa{ final private String nit; private String nombre; private String direccion; private EmpleadoXHora[] empleados1; private EmpleadoXComision[] empleados2; private EmpleadoSueldoFijo[] empleados3; private int numeroEmpleados1, numeroEmpleados2, numeroEmpleados3; public Empresa(String nit, String nombre, String direccion){ if (nit!=null) this.nit = nit; else this.nit=””; setNombre(nombre); setDireccion(direccion); empleados1 = new EmpleadoXHora[50]; empleados2 = new EmpleadoXComision[50]; empleados3 = new EmpleadoSueldoFijo[50]; numeroEmpleados1= 0; numeroEmpleados2= 0; numeroEmpleados3= 0; } public String getNit(){ return nit; } public String getNombre(){ return nombre; } public void setNombre(String nombre){ if (nombre!=null) this.nombre = nombre; else this.nombre=””; } public String getDireccion(){ return direccion; } public void setDireccion(String direccion){ if (direccion!=null) this.direccion = direccion; else this.direccion=””; } public int getNumeroEmpleados1(){ return numeroEmpleados1; } public int getNumeroEmpleados2(){ return numeroEmpleados2; } public int getNumeroEmpleados3(){ return numeroEmpleados3; }
71
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
public void adicionarEmpleadoXHora(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora){ empleados1[numeroEmpleados1] = new EmpleadoXHora(cedula, apellido, nombre, horasTrabajadas, sueldoXHora); numeroEmpleados1++; } public void adicionarEmpleadoXComision(String cedula, String apellido, String nombre, double sueldoBasico, double porcentaje, double ventasTotales){ empleados2[numeroEmpleados2] = new EmpleadoXComision(cedula, apellido, nombre, sueldoBasico, porcentaje, ventasTotales); numeroEmpleados2++; } public void adicionarEmpleadoSueldoFijo(String cedula, String apellido, String nombre, double sueldoFijo){ empleados3[numeroEmpleados3] = new EmpleadoSueldoFijo(cedula, apellido, nombre, sueldoFijo); numeroEmpleados3++; } public double calcularNominaTotal(){ double total = 0; for(int i=0;i
i=0;i
return total;
}
Puntos convenientes a resaltar en el código anterior: •
• • •
72
Note que en esta definición aparecen tres atributos de tipo array. Un array para almacenar referencias a objetos de tipo EmpleadoXHora, otro array para almacenar referencias a objetos de tipo EmpleadoXComision y, finalmente, otro para almacenar referencias a objetos tipo EmpleadoSueldoFijo. Cada array tiene su propio contador. Note que por cada tipo de empleado existe un método para adicionar empleados a la empresa. Note que el método calcularNominaTotal fue modificado. Ahora recorre todos los arrays de los objetos.
1. TÓPICOS BÁSICOS
La clase que administra la interacción con el usuario también requiere una serie de modificaciones. El código que aparece en negrilla resalta las modificaciones efectuadas con respecto a la anterior aproximación. import java.io.BufferedReader; import java.io.InputStreamReader; public class Nomina1{ public static void main(String[] args) throws Exception{ Empresa pyme = new Empresa(“123”,”Acme inc. “,”805 Silicon Valley”); String cedula, apellido, nombre; double total = 0; BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) ); System.out.print(“Digite numero de empleados sueldo por hora: “); int numeroEmpleados1 = Integer.valueOf(br.readLine()).intValue(); for(int i=0;i
}
pyme.adicionarEmpleadoXHora(cedula, apellido, nombre, horas, sueldo);
System.out.print(“Digite numero de empleados por comision: “); int numeroEmpleados2 = Integer.valueOf(br.readLine()).intValue(); for(int i=0;i
73
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
System.out.print(“Digite sueldo basico empleado: “); sueldo = Double.valueOf(br.readLine()).doubleValue(); System.out.print(“Digite porcentaje del empleado: “ ); porcentaje = Double.valueOf(br.readLine()).doubleValue(); System.out.print(“Digite ventas totales del empleado: “ ); ventas = Double.valueOf(br.readLine()).doubleValue();
}
pyme.adicionarEmpleadoXComision(cedula, apellido, nombre, sueldo, porcentaje, ventas);
System.out.print(“Digite numero de empleados sueldo fijo: “); int numeroEmpleados3 = Integer.valueOf(br.readLine()).intValue() for(int i=0;i
}
pyme.adicionarEmpleadoSueldoFijo(cedula, apellido, nombre, sueldo);
total = pyme.calcularNominaTotal(); System.out.println(“\nLa nómina total es: “+ total); }
}
La alternativa de solución escogida para la aplicación de la nómina requiere la codificación de tres clases distintas, una para cada tipo de empleado (Esta solución se presenta en el apartado Sexta aproximación a la aplicación del cálculo de la nómina mediante programación orientada a objetos). Es evidente que cada una de estas clases será diferente de las restantes, pero también es obvio que tendrán muchos códigos en común (la definición de algunos atributos, definición de algunos métodos accesores y modificadores, etc.). Existe una noción dentro del mundo de la programación orientada a objetos que podría evitar tener que escribir el mismo código varias veces para este caso en particular. Esta noción se conoce con el nombre de herencia.
74
1. TÓPICOS BÁSICOS
Definición de herencia Capacidad de crear clases que adquieren de manera automática los atributos y métodos de otras ya existentes, al mismo tiempo que añade atributos y métodos propios o modificar algunos de los existentes.
La idea detrás de la herencia es definir una clase en función de otra u otras. Para el caso que se analiza podría ser útil emplear esta noción, definiendo las clases EmpleadoXHora, EmpleadoXComision y EmpleadoSueldoFijo en función de alguna otra, de tal manera que las clases de los tipos de empleados ganen la definición de los atributos y métodos de otra clase más general. En esta clase general debe quedar lo que es similar a todas. A continuación se muestra una posible codificación para esta clase que ha sido denominada como Empleado. public class Empleado{ private String cedula; private String apellido; private String nombre; public String getCedula(){ return cedula; } public String getApellido(){ return apellido; } public void setApellido(String apellido){ if (apellido!=null) this.apellido = apellido; else this.apellido=””; } public String getNombre(){ return nombre; } public void setNombre(String nombre){ if (nombre!=null) this.nombre = nombre; else this.nombre=””; } }
Puntos convenientes a resaltar en el código anterior: • Para hacer más simple la explicación de la noción de herencia, por lo pronto se omitirá la definición del atributo cedula como final. • Aunque el método para realizar el cálculo del salario es común a todos los tipos de empleados, su código interno no fue incluido dentro de la definición de la clase Empleado porque es distinto en todas.
75
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
Teniendo la definición de la clase general, que en adelante será denominada superclase o clase padre, solo falta por conocer cómo debe ser la definición de las clases específicas, que desde ahora serán denominadas subclases o clases hijas, para ganen los atributos y métodos. La palabra reservada extends posibilita esta acción, la cual debe colocarse después de la instrucción de definición de la clase para entonces utilizarla. Después de esta definición se dice que la subclase extiende o hereda de la superclase. La relación existente entre una subclase y su superclase puede denotarse con la frase ‘es un tipo de’, de tal forma que se puede leer que una subclase es un tipo de una superclase (no es correcto leerlo en el sentido contrario). El siguiente gráfico busca facilitar la comprensión del concepto de herencia. ClaseHija1 y ClaseHija2 extienden de ClasePadre (denotado en la gráfica con la flecha que apunta hacia ClasePadre); al definir esta relación entre clases, automáticamente ClaseHija1 y ClaseHija2 ganan la definición de los atributos y métodos definidos en ClasePadre. Adicionalmente, ClaseHija1 y ClaseHija2 añaden más atributos y métodos a su definición.
La definición de las subclases para el ejemplo del cálculo de la nómina quedaría de la siguiente manera. public class EmpleadoXHora extends Empleado{ private double horasTrabajadas; private double sueldoXHora; public EmpleadoXHora(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora){ if (cedula!=null) this.cedula = cedula; else this.cedula = “”;
76
1. TÓPICOS BÁSICOS
}
setApellido(apellido); setNombre(nombre); setSueldoXHora(sueldoXHora); setHorasTrabajadas(horasTrabajadas);
public double calcularSalario(){ return horasTrabajadas * sueldoXHora; } public double getHorasTrabajadas(){ return horasTrabajadas; } public void setHorasTrabajadas(double horas ){ if (horas>=0) this.horasTrabajadas = horas; else this.horasTrabajadas = 0; } public double getSueldoXHora(){ return sueldoXHora; } public void setSueldoXHora(double sueldo ){ if (sueldo>=0) this.sueldoXHora = sueldo; else this.sueldoXHora = 0; } } public class EmpleadoXComision extends Empleado{ private double sueldoBasico; private double porcentaje; private double ventasTotales; public EmpleadoXComision(String cedula, String apellido, String nombre, double sueldoBasico, double porcentaje, double ventasTotales){ if (cedula!=null) this.cedula = cedula; else this.cedula = “”;
}
setApellido(apellido); setNombre(nombre); setSueldoBasico(sueldoBasico); setPorcentaje(porcentaje); setVentasTotales(ventasTotales);
public double calcularSalario(){ return sueldoBasico + porcentaje * ventasTotales; } public double getSueldoBasico(){ return sueldoBasico; } public void setSueldoBasico(double sueldo){ if (sueldo>=0) this.sueldoBasico = sueldo; else this.sueldoBasico = 0;
77
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
} public double getPorcentaje(){ return porcentaje; } public void setPorcentaje(double porcentaje ){ if (porcentaje>=0 && porcentaje<=1) this.porcentaje = porcentaje; else this.porcentaje = 0; }
}
public double getVentasTotales(){ return ventasTotales; } public void setVentasTotales(double ventas){ if (ventas>=0) this.ventasTotales = ventas; else this.ventasTotales = 0;
} public class EmpleadoSueldoFijo extends Empleado{ private double sueldoFijo; public EmpleadoSueldoFijo(String cedula, String apellido, String nombre, double sueldoFijo){ if (cedula!=null) this.cedula = cedula; else this.cedula = “”;
}
setApellido(apellido); setNombre(nombre); setSueldoFijo(sueldoFijo);
public double calcularSalario(){ return sueldoFijo; } public double getSueldoFijo(){ return sueldoFijo; } public void setSueldoFijo(double sueldo){ if (sueldo>=0) this.sueldoFijo = sueldo; else this.sueldoFijo = 0; } }
La cantidad de código se redujo considerablemente. Lastimosamente, la definición de las anteriores clases marca errores en tiempo de compilación con la manipulación del atributo cedula. Para explicar el porqué de este error hay que retomar lo analizado en la definición del nivel de visibilidad privado. Este tipo de visibilidad define que sólo al interior de la clase Empleado (donde se encuentra definido como privado) puede ser visible el contenido del atributo cedula. Como las clases EmpleadoXHora, EmpleadoXComision y EmpleadoSueldoFijo no son la clase Empleado, por eso se marca el error. El error no se presenta con los atributos
78
1. TÓPICOS BÁSICOS
apellido y nombre porque en los métodos constructores de las subclases no se hace referencia directa al valor del atributo, sino que se utilizan los métodos modificadores (públicos) de cada atributo.
Para poder trabajar con este tipo de situaciones, cuando existen atributos o métodos privados dentro de la superclase que necesitan ser manipulados por la subclase, se define un nuevo nivel de visibilidad. Definición de nivel de visibilidad protegido Cuando se aplica este nivel de visibilidad a un atributo o método, se especifica que dicha información o acción es visible al interior de la clase donde se encuentra definido y al interior de cualquier subclase que esta presente. Cuando se aplica a una clase, esta sólo puede ser utilizada en otra clase que pertenezca al mismo paquete.
Adicionalmente a todo lo mencionado para este nivel de visibilidad, Java permite que los atributos y métodos declarados como protegidos sean visibles para cualquier clase que se encuentre en el mismo paquete. Para especificar un atributo o método protegido en Java se le debe anteponer a su definición la palabra reservada protected. Después de presentado este nuevo nivel de visibilidad se disponen de herramientas para solucionar esta situación de dos maneras: • La primera alternativa consistiría en definir dentro de la clase Empleado el método modificador del atributo cedula. Este método debe ser utilizado por el constructor de todas las subclases para inicializar el valor de ese atributo. El problema con esta alternativa es que si se deseara definir el atributo cedula de los empleados como final, no se puede definir un método modificador para dicho atributo. • La segunda alternativa consistiría en modificar la definición de la clase Empleado y marcar al atributo cedula como protegido; asi las subclases pueden manipularlo. La desventaja con esta alternativa es que el código de validación de este campo debería repetirse en todos los constructores de las subclases. Mientras el código de las clases EmpleadoXHora, EmpleadoXComision y EmpleadoSueldoFijo permanece sin cambios, la clase Empleado quedaría de la siguiente manera: public class Empleado{ protected final String cedula; private String apellido; private String nombre; public String getCedula(){ return cedula; } public String getApellido(){ return apellido; }
79
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
public void setApellido(String apellido){ if (apellido!=null) this.apellido = apellido; else this.apellido=””; } public String getNombre(){ return nombre; } public void setNombre(String nombre){ if (nombre!=null) this.nombre = nombre; else this.nombre=””; } }
sobre la Clase obJeCt El lenguaje de programación Java tiene dentro de su definición una clase especial, la clase Object. Por definición, esta clase es la superclase de todas las clases en Java. Todas las clases que existen y que lleguen a existir (a excepción de ella misma) terminan extendiendo de forma directa o indirecta a esta clase. Esto quiere decir que todas las clases adquieren de forma automática todos los atributos y métodos de esta clase. Un lector perspicaz debe estar pensando, ¿cómo hace el lenguaje de programación para garantizar que todas las clases extiendan de la clase Object? Además, si ninguno de los ejemplos que han sido codificados hasta el momento incluyen la definición extends Object, ¿cómo pueden decir que estas clases heredan de esta superclase? Ambos cuestionamientos se solucionan con la misma respuesta; Java de forma automática, a toda clase que no tenga especificado que extiende de otra le incluye la definición extends Object; así se asegura, de forma directa o indirecta, que todas las clases extiendan Object. En otras palabras, da lo mismo realizar cualquiera de las siguientes definiciones para una clase:
public class Ejemplo{} public class Ejemplo extends Object{}
Antes de continuar examinando más cuestiones sobre la noción de herencia es conveniente tener claro lo presentado hasta el momento. Considérese el siguiente ejemplo: public class EstudiantePreescolar{ public void leer(){} public void escribir(){} }
80
1. TÓPICOS BÁSICOS
public class EstudiantePrimaria extends EstudiantePreescolar{ public void sumar(){} public void restar(){} public void multiplicar(){} public void dividir(){} } public class EstudianteBachillerato extends EstudiantePrimaria{ public void factorizar(){} public void resolverEcuaciones(){} } public class EstudianteIngenieria extends EstudianteBachillerato{ public void integrar(){} public void derivar(){} }
Puntos a tener en cuenta sobre el anterior código: • Sobre un objeto de la clase EstudiantePreescolar solo se pueden invocar los métodos leer y escribir. Sobre un objeto de la clase EstudiantePrimaria se pueden invocar los métodos sumar, restar, multiplicar, dividir, leer y escribir (estos dos últimos heredados de la clase EstudiantePreescolar). En general, cualquier objeto de una subclase tendrá, además de los métodos que tenga definidos, los métodos de su clase padre. • De lo anterior se puede concluir que un objeto de una clase hijo sabe hacer todo lo que puede hacer un objeto de una clase padre de él; sin embargo, no todo padre sabe hacer todo lo que puede uno de sus hijos. • Suponga que una definición como la presentada corresponde con exactitud a las habilidades y destrezas que deben poseer los diversos tipos de estudiantes. Suponga además, que en determinada situación se requiere de alguien con las habilidades de un estudiante de bachillerato para un curso básico de matemáticas y alfabetización. ¿Qué objetos, de qué clases, podrían suplir dicho requerimiento? Por lo comentado anteriormente, los objetos de las clases EstudianteBachillerato y EstudianteIngenieria lo cumplen. Esto se traduce en lo siguiente: suponga que se hace la siguiente definición de variable EstudianteBachillerato a.
¿Objetos de cuáles clases pueden ser referenciados empleando dicho apuntador? Nuevamente la respuesta es: EstudianteBachillerato y EstudianteIngenieria. Por ende, cualquiera de las dos siguientes definiciones es correcta: EstudianteBachillerato a = new EstudianteBachillerato(); EstudianteBachillerato a = new EstudianteIngenieria();
Considerando lo presentado anteriormente, ¿cuál sería la diferencia entre las siguientes dos definiciones?
81
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
EstudianteBachillerato a = new EstudianteIngenieria(); EstudianteIngenieria b = new EstudianteIngenieria();
La primera parte de la definición (antes del igual) especifica el tipo de la variable. Con esta especificación se establece qué servicios pueden ser invocados sobre dicha variable. La segunda parte de la definición (después del igual) especifica el objeto que va a brindar los servicios. En otras palabras, la primera parte de la definición especifica qué se le puede pedir a la referencia y la segunda es el objeto que va a cumplir con los servicios. Por consiguiente, la diferencia entre las definiciones presentadas anteriormente, es que sobre la referencia a solo se van a poder invocar los métodos definidos en la clase EstudianteBachillerato (y en cualquiera de sus superclases), mientras que en la referencia b se van a poder invocar todos los métodos definidos en la clase EstudianteIngenieria. En resumidas cuentas, con la referencia a no se podrá invocar el método integrar, porque dicho método pertenece a la clase EstudianteIngenieria, pero la referencia es de tipo EstudianteBachillerato. Generalmente, los programadores al llegar a este punto se preguntan si el objeto referenciado con el nombre a pierde los métodos que aparecen en la subclase, pero que no se encuentran en la superclase. La respuesta es que no los pierde, pero cuando se emplea esa referencia no pueden utilizarse. La siguiente pregunta que se hacen los programadores cuando llegan a este punto es, ¿por qué alguien preferiría realizar la primera definición en vez de la segunda?, ya que con la primera no se pueden invocar ciertos métodos de la clase. La respuesta es que en ocasiones trabajar con una referencia de un tipo de dato más general favorece la codificación (un ejemplo de esta situación se analizará más adelante). • Se presenta un error en tiempo de compilación definir un objeto de una clase y trabajarlo a través de un referencia cuyo tipo no sea la propia clase o alguna de sus superclases.
82
1. TÓPICOS BÁSICOS
sobre la instanCiaCión, la palabra reservada ‘super’ y el orden de invoCaCión de los ConstruCtores en las subClases
Cuando se explicaba el proceso de instanciación, se mencionó que Java separa un espacio en memoria con la estructura definida de la clase; un interrogante que puede surgir en estos momentos es cómo se realiza este proceso estando de por medio la herencia de clases. La situación es la siguiente: como un objeto de una subclase es un tipo de objeto de una superclase, al pedir instanciar un objeto de una clase específica, primero debe separarse memoria para un objeto de su superclase. Si la jerarquía de la herencia tiene muchos niveles, es decir, cuando la superclase a su vez tiene una superclase, entonces, primero debe reservarse un espacio en memoria para el objeto superior de la jerarquía (que en el caso de Java corresponde a la clase Object). Examínese el ejemplo de la jerarquía de herencia del caso de ejemplo de cálculo de la nómina; por simplicidad, nada más se presentará la definición de la clase y sus respectivos atributos para el caso de la clase EmpleadoXHora (la situación es análoga para cualquier tipo de jerarquía). public class Empleado{ protected final String cedula; private String apellido; private String nombre; } public class EmpleadoXHora extends Empleado{ private double horasTrabajadas; private double sueldoXHora; }
Suponiendo que se pide ejecutar la instrucción: new EmpleadoXHora(…)
Antes de que el lenguaje de programación separe un espacio en memoria con la estructura de la clase EmpleadoXHora (solo dos atributos), primero debe separar un espacio con la estructura de la clase Empleado (superclase de EmpleadoXHora), pero antes debe separar espacio para un objeto de la clase Object (superclase de EmpleadoXHora). Luego de separarse espacio para un Object (con todos los atributos que pueda definir esta clase), se separa un espacio en memoria para almacenar valores para los atributos cedula, apellido y nombre, y, finalmente, se separa espacio en memoria para almacenar valores para los atributos horasTrabajadas y sueldoXHora.
83
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
Si el orden de la separación de espacio en memoria es como se ha presentado anteriormente, ¿cómo debe realizarse la inicialización de cada atributo de la clase? La respuesta es que la inicialización de los atributos debe realizarse de acuerdo a lo establecido en el ente que determina cómo se efectúa dicho proceso: el constructor de la clase. Por ende, puede suponerse que conforme se va separando el espacio en memoria se va ejecutando el constructor de la clase correspondiente. Lo anterior significa que en el caso de instanciar un objeto de la clase EmpleadoXHora, como el primer espacio que se separa es para un objeto de tipo Object, el constructor de Object es el primero en ejecutarse; luego se separa espacio para un Objeto de tipo Empleado, ejecutándose el constructor de Empleado y, finalmente, después de separarse espacio para un EmpleadoXHora se ejecuta el constructor de EmpleadoXHora. ¿Cómo hace el lenguaje Java para garantizar que siempre se invoquen los métodos constructores de las superclases antes que de las subclases? Así como existe en Java una palabra reservada para hacer referencia al mismo objeto (this) también existe una palabra reservada para hacer referencia a su superclase. Esta palabra corresponde a ‘super’. A través de esta palabra se puede referenciar al objeto de la clase padre, a uno de sus atributos o a cualquiera de sus métodos. • • •
Empleando la instrucción super.atributo, se puede manipular los atributos públicos o protegidos del padre. Empleando la instrucción super.nombreMetodo, se puede invocar la ejecución de un método público o protegido del padre. Empleando la instrucción super() (en caso de ser necesario pueden especificarse parámetros), se invoca al constructor público o protegido de la clase padre.
El lenguaje de programación Java define de forma automática, y como primera instrucción de un constructor de una subclase, el llamado al constructor que no recibe parámetros de su superclase a través de la instrucción: super();
¿Qué sucede si la superclase no tiene un constructor que no reciba parámetros? Si la superclase no tiene este constructor, entonces es una obligación de la subclase especificar explícitamente el constructor de la superclase que debe invocarse cuando se utiliza la instrucción super y se pasan como parámetros los valores que requiere el constructor de la clase.
84
1. TÓPICOS BÁSICOS
Con todo lo presentado en el tema de herencia es posible recodificar las clases Empleado, EmpleadoXHora, EmpleadoXComision y EmpleadoSueldoFijo como se presenta a continuación. public class Empleado{ protected final String cedula; private String apellido; private String nombre; public Empleado(String cedula, String apellido, String nombre){ if (cedula!=null) this.cedula = cedula; else this.cedula = “”; setApellido(apellido); setNombre(nombre); } public String getCedula(){ return cedula; } public String getApellido(){ return apellido; } public void setApellido(String apellido){ if (apellido!=null) this.apellido = apellido; else this.apellido=””; } public String getNombre(){ return nombre; } public void setNombre(String nombre){ if (nombre!=null) this.nombre = nombre; else this.nombre=””; } } public class EmpleadoXHora extends Empleado{ private double horasTrabajadas; private double sueldoXHora; public EmpleadoXHora(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora){ super(cedula, apellido, nombre); setSueldoXHora(sueldoXHora); setHorasTrabajadas(horasTrabajadas); } public double calcularSalario(){ return horasTrabajadas * sueldoXHora; } public double getHorasTrabajadas(){ return horasTrabajadas;
85
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
} public void setHorasTrabajadas(double horas ){ if (horas>=0) this.horasTrabajadas = horas; else this.horasTrabajadas = 0; } public double getSueldoXHora(){ return sueldoXHora; } public void setSueldoXHora(double sueldo ){ if (sueldo>=0) this.sueldoXHora = sueldo; else this.sueldoXHora = 0; } } public class EmpleadoXComision extends Empleado{ private double sueldoBasico; private double porcentaje; private double ventasTotales; public EmpleadoXComision(String cedula, String apellido, String nombre, double sueldoBasico, double porcentaje, double ventasTotales){ super(cedula, apellido, nombre); setSueldoBasico(sueldoBasico); setPorcentaje(porcentaje); setVentasTotales(ventasTotales); } public double calcularSalario(){ return sueldoBasico + porcentaje * ventasTotales; } public double getSueldoBasico(){ return sueldoBasico; } public void setSueldoBasico(double sueldo){ if (sueldo>=0) this.sueldoBasico = sueldo; else this.sueldoBasico = 0; } public double getPorcentaje(){ return porcentaje; } public void setPorcentaje(double porcentaje ){ if (porcentaje>=0 && porcentaje<=1) this.porcentaje = porcentaje; else this.porcentaje = 0; } public double getVentasTotales(){ return ventasTotales; } public void setVentasTotales(double ventas){ if (ventas>=0) this.ventasTotales = ventas; else this.ventasTotales = 0; } }
86
1. TÓPICOS BÁSICOS
public class EmpleadoSueldoFijo extends Empleado{ private double sueldoFijo; public EmpleadoSueldoFijo(String cedula, String apellido, String nombre, double sueldoFijo){ super(cedula, apellido, nombre); setSueldoFijo(sueldoFijo); } public double calcularSalario(){ return sueldoFijo; } public double getSueldoFijo(){ return sueldoFijo; } public void setSueldoFijo(double sueldo){ if (sueldo>=0) this.sueldoFijo = sueldo; else this.sueldoFijo = 0; } }
Es conveniente resaltar en el anterior código: • La primera instrucción en cada uno de los constructores de las subclases es la invocación al constructor que recibe tres parámetros (cedula, apellido, y nombre) del padre. • Note cómo con esta implementación se puede mantener la definición de final del atributo cedula. La clase Empresa podría modificarse para intentar aprovechar el hecho de que se puede manipular un objeto de la subclase empleando una referencia de su superclase. public class Empresa{ final private String nit; private String nombre; private String direccion; private Empleado[] empleados; private int numeroEmpleados; public Empresa(String nit, String nombre, String direccion){ if (nit!=null) this.nit = nit; else this.nit=””; setNombre(nombre); setDireccion(direccion); empleados = new Empleado[50]; numeroEmpleados = 0; } public String getNombre(){ return nombre; }
87
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
public void setNombre(String nombre){ if (nombre!=null) this.nombre = nombre; else this.nombre=””; } public String getDireccion(){ return direccion; } public void setDireccion(String direccion){ if (direccion!=null) this.direccion = direccion; else this.direccion=””; } public int getNumeroEmpleados(){ return numeroEmpleados; } public void adicionarEmpleadoXHora(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora){ empleados[numeroEmpleados] = new EmpleadoXHora(cedula, apellido, nombre, horasTrabajadas, sueldoXHora); numeroEmpleados++; } public void adicionarEmpleadoXComision(String cedula, String apellido, String nombre, double sueldoBasico, double porcentaje, double ventasTotales){ empleados[numeroEmpleados] = new EmpleadoXComision(cedula, apellido, nombre, sueldoBasico, porcentaje, ventasTotales); numeroEmpleados++; } public void adicionarEmpleadoSueldoFijo(String cedula, String apellido, String nombre, double sueldoFijo){ empleados[numeroEmpleados] = new EmpleadoSueldoFijo(cedula, apellido, nombre, sueldoFijo); numeroEmpleados++; } }
Del código anterior es conveniente resaltar: • Que esta implementación no requiere de tres arrays ni de tres contadores. • La definición del array se hace en función de la superclase Empleado y no de sus subclases. De esta forma, se pueden almacenar dentro de éste referencias a objetos que sean de la clase Empleado o de cualquier subclase de ella. • Dentro de los métodos de la clase que permiten adicionar los diversos tipos de empleados siempre se almacenan los nuevos objetos en un único array • Aún queda pendiente la implementación del método calcularNominaTotal, pues su implementación no es tan sencilla como simplemente recorrer el array de empleados e invocar el método calcularSalario; porque como el array está declarado de
88
1. TÓPICOS BÁSICOS
tipo Empleado, no puede invocarse dicho método, pues no aparece definido en la superclase. La solución trivial sería la de incluir dicho método en la clase; sin embargo, quedaría pendiente la implementación que debería dársele, pues como la clase Empleado es una clase general no posee ninguno de los atributos (que sí poseen las subclases de Empleado) que ayudan al cálculo del salario. La solución más simple sería la de colocar una implementación por defecto para el método calcularSalario en empleado que retorne el valor de 0.
séptima aproximaCión a la apliCaCión del CálCulo de la nómina mediante programaCión orientada a obJetos
Por practicidad se vuelve a presentar la definición (parcial hasta el momento) de todas las clases que se han utilizado en la aplicación del cálculo de la nómina. El código que aparece en negrilla resalta las modificaciones efectuadas con respecto a la anterior aproximación. public class Empleado{ protected final String cedula; private String apellido; private String nombre; public Empleado(String cedula, String apellido, String nombre){ if (cedula!=null) this.cedula = cedula; else this.cedula = “”;
}
setApellido(apellido); setNombre(nombre);
public double calcularSalario(){ return 0; } public String getCedula(){ return cedula; }
}
public String getApellido(){ return apellido; public void setApellido(String apellido){ if (apellido!=null) this.apellido = apellido; else this.apellido= “”; } public String getNombre(){ return nombre; }
89
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
public void setNombre(String nombre){ if (nombre!=null) this.nombre = nombre; else this.nombre= “”; } }
public class EmpleadoXHora extends Empleado{ private double horasTrabajadas; private double sueldoXHora; public EmpleadoXHora(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora){ super(cedula, apellido, nombre); setSueldoXHora(sueldoXHora); setHorasTrabajadas(horasTrabajadas); } public double calcularSalario(){ return horasTrabajadas * sueldoXHora; } public double getHorasTrabajadas(){ return horasTrabajadas; } public void setHorasTrabajadas(double horas ){ if (horas>=0) this.horasTrabajadas = horas; else this.horasTrabajadas = 0; } public double getSueldoXHora(){ return sueldoXHora; } public void setSueldoXHora(double sueldo ){ if (sueldo>=0) this.sueldoXHora = sueldo; else this.sueldoXHora = 0; } }
public class EmpleadoXComision extends Empleado{ private double sueldoBasico; private double porcentaje; private double ventasTotales; public EmpleadoXComision(String cedula, String apellido, String nombre, double sueldoBasico, double porcentaje, double ventasTotales){
90
1. TÓPICOS BÁSICOS
}
super(cedula, apellido, nombre); setSueldoBasico(sueldoBasico); setPorcentaje(porcentaje); setVentasTotales(ventasTotales);
public double calcularSalario(){ return sueldoBasico + porcentaje * ventasTotales; } public double getSueldoBasico(){ return sueldoBasico; } public void setSueldoBasico(double sueldo){ if (sueldo>=0) this.sueldoBasico = sueldo; else this.sueldoBasico = 0; } public double getPorcentaje(){ return porcentaje; } public void setPorcentaje(double porcentaje ){ if (porcentaje>=0 && porcentaje<=1) this.porcentaje = porcentaje; else this.porcentaje = 0; } public double getVentasTotales(){ return ventasTotales;
} public void setVentasTotales(double ventas){ if (ventas>=0) this.ventasTotales = ventas; else this.ventasTotales = 0; } }
public class EmpleadoSueldoFijo extends Empleado{ private double sueldoFijo; public EmpleadoSueldoFijo(String cedula, String apellido, String nombre, double sueldoFijo){ super(cedula, apellido, nombre); setSueldoFijo(sueldoFijo); } public double calcularSalario(){ return sueldoFijo; }
91
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
public double getSueldoFijo(){ return sueldoFijo; } public void setSueldoFijo(double sueldo){ if (sueldo>=0) this.sueldoFijo = sueldo; else this.sueldoFijo = 0; } }
public class Empresa{ final private String nit; private String nombre; private String direccion; private Empleado[] empleados; private int numeroEmpleados; public Empresa(String nit, String nombre, String direccion){ if (nit!=null) this.nit = nit; else this.nit=””; setNombre(nombre); setDireccion(direccion); empleados = new Empleado[50]; numeroEmpleados= 0; } public String getNit(){ return nit; } public String getNombre(){ return nombre; } public void setNombre(String nombre){ if (nombre!=null) this.nombre = nombre; else this.nombre=””;
}
public String getDireccion(){ return direccion; } public void setDireccion(String direccion){ if (direccion!=null) this.direccion = direccion; else this.direccion=””; } public int getNumeroEmpleados(){ return numeroEmpleados; }
92
1. TÓPICOS BÁSICOS
public void adicionarEmpleadoXHora(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora){ empleados[numeroEmpleados] = new EmpleadoXHora(cedula, apellido, nombre, horasTrabajadas, sueldoXHora); numeroEmpleados++; } public void adicionarEmpleadoXComision(String cedula, String apellido, String nombre, double sueldoBasico, double porcentaje, double ventasTotales){ empleados[numeroEmpleados] = new EmpleadoXComision(cedula, apellido, nombre, sueldoBasico, porcentaje, ventasTotales); numeroEmpleados++; } public void adicionarEmpleadoSueldoFijo(String cedula, String apellido, String nombre, double sueldoFijo){ empleados[numeroEmpleados] = new EmpleadoSueldoFijo(cedula, apellido, nombre, sueldoFijo); numeroEmpleados++; } public double calcularNominaTotal(){ double total = 0; for (int i=0;i
Gracias al nivel de encapsulamiento con el que se ha trabajado la clase Empresa no es necesaria la modificación de ninguna línea de código de la clase Nomina1. Por simple practicidad se vuelve a mostrar este código: import java.io.BufferedReader; import java.io.InputStreamReader; public class Nomina1{ public static void main(String[] args) throws Exception{ Empresa pyme = new Empresa(“123”,”Acme inc.”,”805 Silicon Valley”); String cedula, apellido, nombre; double total = 0; BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) );
93
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
System.out.print(“Digite numero de empleados sueldo por hora: “); int numeroEmpleados1 = Integer.valueOf(br.readLine()).intValue(); for(int i=0;i
}
pyme.adicionarEmpleadoXHora(cedula, apellido, nombre, horas, sueldo);
System.out.print(“Digite numero de empleados por comision: “); int numeroEmpleados2 = Integer.valueOf(br.readLine()).intValue(); for(int i=0;i
}
pyme.adicionarEmpleadoXComision(cedula, apellido, nombre, sueldo, porcentaje, ventas);
System.out.print(“Digite numero de empleados sueldo fijo: “); int numeroEmpleados3 = Integer.valueOf(br.readLine()).intValue();
94
1. TÓPICOS BÁSICOS
for(int i=0;i
}
pyme.adicionarEmpleadoSueldoFijo(cedula, apellido, nombre, sueldo);
total = pyme.calcularNominaTotal();
}
}
System.out.println(“\nLa nómina total es: “+ total);
Al llegar a este punto todos los programadores que se encuentran habituados a la programación procedimental se realizan la misma pregunta, ¿cómo hace el programa para saber cuál de las tres implementaciones del método calcularSalario debe ejecutarse en cada caso? Antes de brindar solución a este cuestionamiento es conveniente entender la situación. Considérese el siguiente código: Empresa e = new Empresa(“123”,”Acme inc.”,”805 Silicon Valley”); e.adicionarEmpleadoXHora(“72303”, “García”, “Luis”, 100, 15); e.adicionarEmpleadoXComision(“98765”, “Sánchez”, “María”, 1000, 0.5, 1000); e.adicionarEmpleadoSueldoFijo(“12345”, “Pérez”, “Pedro”, 1200);
En memoria se obtendría una configuración como la que se muestra a continuación:
95
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
Efectivamente se han codificado cuatro métodos calcularSalario en las clases Empleado, EmpleadoXHora, EmpleadoXComision y en EmpleadoSueldoFijo. En el momento de ejecutarse el método calcularNominaTotal, del array empleados se extraen las referencias a todos los objetos subclases de Empleado que se han adicionado. Analícese el caso del primer objeto almacenado en el array que aparece en el ejemplo; la pregunta no es cuál de los cuatro métodos se ejecutan porque en la clase EmpleadoXHora solo se encuentran definidos dos métodos calcularSalario (una implementación, que ganó de su superclase por herencia, y otra que la misma clase tiene definida). La misma situación aplica para cualquier otro objeto de una subclase de Empleado. El cuestionamiento en este punto surge porque existen dos métodos para el cálculo del salario en la clase, y dentro del método calcularNominaTotal de Empresa se pide invocar dicho método. Para este caso se ejecutará la versión del método que haya sido definida en la subclase. Lo cual garantizaría la obtención del resultado adecuado durante su ejecución.
Cuando se trabaja con herencia se deben tener en cuenta los siguientes puntos: • Mediante la herencia se refina el comportamiento y estructura de una clase bien sea añadiendo nuevos atributos y métodos, o bien sobrescribiendo la definición de atributos y métodos de su superclase. • Como un objeto de una subclase posee todos los métodos de su superclase, entonces este objeto puede hacer todo lo que su superclase; por eso se dice que un objeto de una subclase puede reemplazar a cualquier objeto de su superclase. • Cuando una subclase redefine o sobrescribe un método de su superclase, al momento de invocarse la ejecución de dicho método prevalecerá la implementación de la subclase sobre la de la superclase.
1.11. métOdOs y clAses AbstrActAs Durante la modificación del código de la aplicación de la nómina para trabajar el concepto de herencia fue necesario incluir el método calcularSalario dentro de la clase Empleado. En dicho momento se explicó que era necesaria la inclusión de este método a fin de definir un único array dentro de la clase Empresa para manejar cualquier tipo de Empleado y realizar el cálculo total de la nómina de la empresa. Se asignó una implementación por defecto que simplemente retornaba el valor de 0, ya que la clase Empleado no posee información suficiente para realizar este cálculo. Existe una alternativa de solución más elegante en términos de programación para no tener que asignar esta implementación por defecto, por ello es necesario definir el método calcularSalario de la clase Empresa como un método abstracto.
96
1. TÓPICOS BÁSICOS
Definición de método abstracto Un método abstracto es un método cuya signatura se encuentra definida, pero no su implementación.
Analícese el siguiente ejemplo: public double calcularSalario(){ return 0; }
Debe entenderse como signatura del método el conjunto de caracteres donde se especifica el nivel de visibilidad, el tipo de retorno, el nombre y los parámetros que requiere el método para su funcionamiento. La implementación corresponde al conjunto de instrucciones que aparecen demarcadas entre la apertura y el cierre de llaves (‘{‘, ’}’). Un método abstracto define qué debe hacerse, pero no específica cómo se realiza la operación. Para definir un método abstracto se debe anteponer la palabra abstract a su definición, y colocar el carácter ‘;’ en vez de la apertura y cierre de llaves (‘{‘, ’}’). Ante estas consideraciones, el código de la clase Empleado debe quedar de la siguiente manera: public class Empleado{ protected final String cedula; private String apellido; private String nombre; public Empleado(String cedula, String apellido, String nombre){ if (cedula!=null) this.cedula = cedula; else this.cedula = “”; setApellido(apellido); setNombre(nombre); } public abstract double calcularSalario(); public String getCedula(){ return cedula; } public String getApellido(){ return apellido; } public void setApellido(String apellido){ if (apellido!=null) this.apellido = apellido; else this.apellido=””; }
97
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
public String getNombre(){ return nombre; } public void setNombre(String nombre){ if (nombre!=null) this.nombre = nombre; else this.nombre=””; } }
El manejo de esta clase requiere cuidado porque sería peligroso que se instanciará y se pidiera la ejecución del método calcularSalario. ¿Qué proceso ejecutaría un objeto de esta clase si no tiene definido implementación para el mismo? Para evitar situaciones como esta, es necesario introducir el concepto de clase abstracta. Definición de clase abstracta Una clase abstracta es una clase que no se encuentra completamente definida. Como no está completa, no es posible instanciar objetos de dicha clase.
Es paradójico que dentro del mundo de la programación orientada a objetos, donde los principales componentes son las clases y sus instancias, se especifique un concepto para evitar instanciar una clase. Sin embargo, tal y como se analizó previamente en algunas situaciones es conveniente definir este comportamiento. Al trabajar con clases abstractas se debe tener en cuenta lo siguiente: • Si en una clase se encuentra definido por lo menos un método abstracto, la clase donde debe convertirse en abstracta. • Se puede definir como abstracta una clase a pesar de que no tenga un método abstracto. ¿Para qué? Para evitar su instanciación. • Intentar definir un método abstracto dentro de una clase sin volver abstracta la clase generará un error en tiempo de compilación. • Una clase abstracta puede tener definido un método constructor. Obviamente este método no podrá hacer parte de una instrucción new, es decir no puede utilizarse para crear objetos pero si se puede utilizar para especificar cómo debe efectuarse la inicialización de los atributos al instanciarse un objeto de una de sus subclases (este tópico fue analizado en el apartado Sobre la instanciación, la palabra reservada ‘super’ y el orden de invocación de los constructores en las subclases) • Cuando se define una clase abstracta con uno o más métodos abstractos es obligación de las subclases de ésta brindar implementación a todos los métodos abstractos; estos métodos en las subclases deben poseer exactamente la misma definición (mismo nombre, mismo dato de retorno y mismos parámetros) que tienen en la superclase.
98
1. TÓPICOS BÁSICOS
Si una subclase que extiende una clase abstracta, que cuenta con varios métodos abstractos, no brinda implementación a todos los métodos abstractos se presenta un error en tiempo de compilación. Para establecer una clase como abstracta se le debe anteponer a su definición la palabra reservada ‘abstract’.
oCtava aproximaCión a la apliCaCión del CálCulo de la nómina empleando programaCión orientada a obJetos
Después de especificar cómo abstracta la clase Empleado el código quedaría de la siguiente manera (El código que aparece en negrilla resalta las modificaciones efectuadas con respecto a la anterior aproximación) : public abstract class Empleado{ protected final String cedula; private String apellido; private String nombre; public Empleado(String cedula, String apellido, String nombre){ if (cedula!=null) this.cedula = cedula; else this.cedula = “”;
}
setApellido(apellido); setNombre(nombre);
public abstract double calcularSalario(); public String getCedula(){ return cedula; } public String getApellido(){ return apellido; } public void setApellido(String apellido){ if (apellido!=null) this.apellido = apellido; else this.apellido=””; } public String getNombre(){ return nombre; } public void setNombre(String nombre){ if (nombre!=null) this.nombre = nombre; else this.nombre=””;
} }
99
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
El cambio implementado sólo implica modificaciones en la clase Empleado, en ninguna otra clase es necesario realizar algún tipo de modificación. Simplemente por practicidad se presenta nuevamente el código de las restantes clases. public class EmpleadoXHora extends Empleado{ private double horasTrabajadas; private double sueldoXHora; public EmpleadoXHora(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora){ super(cedula, apellido, nombre); setSueldoXHora(sueldoXHora); setHorasTrabajadas(horasTrabajadas); } public double calcularSalario(){ return horasTrabajadas * sueldoXHora; } public double getHorasTrabajadas(){ return horasTrabajadas; } public void setHorasTrabajadas(double horas ){ if (horas>=0) this.horasTrabajadas = horas; else this.horasTrabajadas = 0; } public double getSueldoXHora(){ return sueldoXHora; } public void setSueldoXHora(double sueldo ){ if (sueldo>=0) this.sueldoXHora = sueldo; else this.sueldoXHora = 0; } }
public class EmpleadoXComision extends Empleado{ private double sueldoBasico; private double porcentaje; private double ventasTotales; public EmpleadoXComision(String cedula, String apellido, String nombre, double sueldoBasico, double porcentaje, double ventasTotales){
100
1. TÓPICOS BÁSICOS
}
super(cedula, apellido, nombre); setSueldoBasico(sueldoBasico); setPorcentaje(porcentaje); setVentasTotales(ventasTotales);
public double calcularSalario(){ return sueldoBasico + porcentaje * ventasTotales; } public double getSueldoBasico(){ return sueldoBasico; } public void setSueldoBasico(double sueldo){ if (sueldo>=0) this.sueldoBasico = sueldo; else this.sueldoBasico = 0; } public double getPorcentaje(){ return porcentaje; } public void setPorcentaje(double porcentaje ){ if (porcentaje>=0 && porcentaje<=1) this.porcentaje = porcentaje; else this.porcentaje = 0; } public double getVentasTotales(){ return ventasTotales; } public void setVentasTotales(double ventas){ if (ventas>=0) this.ventasTotales = ventas; else this.ventasTotales = 0; } }
public class EmpleadoSueldoFijo extends Empleado{ private double sueldoFijo; public EmpleadoSueldoFijo(String cedula, String apellido, String nombre, double sueldoFijo){ super(cedula, apellido, nombre); setSueldoFijo(sueldoFijo); } public double calcularSalario(){ return sueldoFijo; }
101
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
public double getSueldoFijo(){ return sueldoFijo; } public void setSueldoFijo(double sueldo){ if (sueldo>=0) this.sueldoFijo = sueldo; else this.sueldoFijo = 0; } }
public class Empresa{ final private String nit; private String nombre; private String direccion; private Empleado[] empleados; private int numeroEmpleados; public Empresa(String nit, String nombre, String direccion){ if (nit!=null) this.nit = nit; else this.nit=””; setNombre(nombre); setDireccion(direccion); empleados = new Empleado[50]; numeroEmpleados = 0; } public String getNit(){ return nit; } public String getNombre(){ return nombre; } public void setNombre(String nombre){ if (nombre!=null) this.nombre = nombre; else this.nombre=””; } public String getDireccion(){ return direccion; } public void setDireccion(String direccion){ if (direccion!=null) this.direccion = direccion; else this.direccion=””; } public int getNumeroEmpleados(){ return numeroEmpleados; }
102
1. TÓPICOS BÁSICOS
public void adicionarEmpleadoXHora(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora){ empleados[numeroEmpleados] = new EmpleadoXHora(cedula, apellido, nombre, horasTrabajadas, sueldoXHora); numeroEmpleados++;
}
public void adicionarEmpleadoXComision(String cedula, String apellido, String nombre, double sueldoBasico, double porcentaje, double ventasTotales){ empleados[numeroEmpleados] = new EmpleadoXComision(cedula, apellido, nombre, sueldoBasico, porcentaje, ventasTotales); numeroEmpleados++; } public void adicionarEmpleadoSueldoFijo(String cedula, String apellido, String nombre, double sueldoFijo){ empleados[numeroEmpleados] = new EmpleadoSueldoFijo(cedula, apellido, nombre, sueldoFijo); numeroEmpleados++; } public double calcularNominaTotal(){ double total = 0; for (int i=0;i
import java.io.BufferedReader; import java.io.InputStreamReader; public class Nomina1{ public static void main(String[] args) throws Exception{ Empresa pyme = new Empresa(“123”,”Acme inc.”,”805 Silicon Valley”); String cedula, apellido, nombre; double total = 0; BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) ); System.out.print(“Digite numero de empleados sueldo por hora: “); int numeroEmpleados1 = Integer.valueOf(br.readLine()).intValue();
103
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
for(int i=0;i
}
pyme.adicionarEmpleadoXHora(cedula, apellido, nombre, horas, sueldo);
System.out.print(“Digite numero de empleados por comision: “); int numeroEmpleados2 = Integer.valueOf(br.readLine()).intValue(); for(int i=0;i
System.out.print(“Digite porcentaje del empleado: “ ); porcentaje = Double.valueOf(br.readLine()).doubleValue(); System.out.print(“Digite ventas totales del empleado: “ ); ventas = Double.valueOf(br.readLine()).doubleValue();
}
pyme.adicionarEmpleadoXComision(cedula, apellido, nombre, sueldo, porcentaje, ventas);
System.out.print(“Digite numero de empleados sueldo fijo: “); int numeroEmpleados3 = Integer.valueOf(br.readLine()).intValue(); for(int i=0;i
104
1. TÓPICOS BÁSICOS
}
pyme.adicionarEmpleadoSueldoFijo(cedula, apellido, nombre, sueldo);
total = pyme.calcularNominaTotal();
}
}
System.out.println(“\nLa nómina total es: “+ total);
1.12. cAsting Durante la presentación del concepto de herencia se trabajó el siguiente ejemplo: public class EstudiantePreescolar{ public void leer(){} public void escribir(){} } public class EstudiantePrimaria extends EstudiantePreescolar{ public void sumar(){} public void restar(){} public void multiplicar(){} public void dividir(){} } public class EstudianteBachillerato extends EstudiantePrimaria{ public void factorizar(){} public void resolverEcuaciones(){} } public class EstudianteIngenieria extends EstudianteBachillerato{ public void integrar(){} public void derivar(){} }
En ese punto se mencionó que son totalmente correctas cualquiera de las siguientes definiciones: EstudianteBachillerato a = new EstudianteIngenieria(); EstudianteIngenieria b = new EstudianteIngenieria();
La principal diferencia entre ambas definiciones corresponde a la cantidad de métodos que pueden ser ejecutados en ambas referencias; mientras con la referencia b se pueden invocar todos los métodos que estén definidos en EstudianteIngenieria (y, por ende, en cualquiera de sus superclases), con la referencia a solo podrán ser invocados los métodos que se encuentren definidos en la clase EstudianteBachillerato (y, por ende, en cualquiera de sus superclases). Según se comentó en dicha sección, el objeto referenciado a través de a es un
105
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
objeto de la clase EstudianteIngenieria y por ello posee todos los métodos definidos para esa clase, pero como está referenciado a través de una variable de tipo EstudianteBachillerato, sobre ella solo podrán ser invocados métodos que se encuentren definidos en esta última clase. En otras palabras: el objeto sí tiene los métodos, pero no pueden ser utilizados a través de esta referencia. En ocasiones es imperativo ejecutar estos métodos que no pueden ser invocados (porque el tipo de la referencia no los permite) y para lograr tal fin ayudará el concepto de casting. Definición de casting Casting hace referencia a forzar la conversión de una referencia de un tipo a otro.
En Java para forzar la conversión de la referencia basta con colocarle antes, entre paréntesis, el nombre del tipo de dato al que se desea realizar la conversión. Por ejemplo, para una referencia definida así: EstudianteBachillerato a = new EstudianteIngenieria();
Para realizar la conversión de la variable a del tipo EstudianteBachillerato al tipo EstudianteIngenieria y para poder ejecutar el método derivar se debe emplear la siguiente instrucción: ((EstudianteIngenieria)a).derivar();
Es conveniente notar de la anterior instrucción lo siguiente: • Cómo se toma la referencia y se le antepone entre paréntesis el tipo de dato al que se quiere forzar la conversión. • Cómo se encierra entre paréntesis toda la instrucción de transformación para después sí ejecutar el método en mención. Adicionalmente, es conveniente tener en cuenta los siguientes puntos al trabajar con el casting. • La transformación de la referencia solo ocurre durante la ejecución de la instrucción. Si lo que se requiere es cambiar permanentemente el tipo de la referencia de dicho objeto, es necesario definir una nueva variable y asignarle el resultado del casting. • Se marca como error en tiempo de compilación intentar convertir una referencia de un tipo a otra de otro tipo, si ambas no se encuentran relacionadas en la jerarquía de herencia como que una extiende de la otra. • Carece de sentido realizar conversiones de tal manera que una referencia de tipo de una subclase se transforme a una referencia de una superclase, por ejemplo,
106
1. TÓPICOS BÁSICOS
(EstudiantePreescolar)a. Este casting está demás y es totalmente trivial, pues un objeto de una subclase posee todos los métodos definidos en la superclase. Existen ocasiones en que el error solo puede detectarse en tiempo de compilación, por ejemplo, considere la siguiente situación: Object a = new String(“Ejemplo”); EstudiantePrimaria b = (EstudiantePrimaria)a;
Note que en este caso no se presenta error alguno en tiempo de compilación, pues los castings se realizan entre clases que se encuentran relacionadas en la jerarquía de herencia (pues Object es superclase tanto de String como de EstudiantePrimaria), pero cuando se ejecute este código se presentará un grave error al intentar convertir la referencia.
1.13. pOlimOrfismO Definición de polimorfismo El polimorfismo hace referencia a la propiedad de que un elemento (generalmente el nombre de un método) de adquirir muchas formas (implementaciones).
La definición presentada anteriormente corresponde a una definición muy general y amplia de la etimología de palabra. Aplicado al campo de la programación orientada a objetos existen básicamente dos formas de trabajar este concepto. • Sobrecarga: la sobrecarga hace referencia a la propiedad que permite definir varios métodos dentro de una clase que tengan el mismo nombre, pero con diferentes parámetros. A manera de ejemplo considérese el siguiente código. public class Empresa{ final private String nit; private String nombre; private String direccion; private Empleado[] empleados; private int numeroEmpleados; public Empresa(String nit, String nombre, String direccion){ if (nit!=null) this.nit = nit; else this.nit=””; setNombre(nombre); setDireccion(direccion); empleados = new Empleado[50]; numeroEmpleados = 0; }
107
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
public Empresa(String nit){ if (nit!=null) this.nit = nit; else this.nit=””;
}
setNombre(“”); setDireccion(“”); empleados = new Empleado[50]; numeroEmpleados = 0;
public Empresa(String nit, int numero){ if (nit!=null) this.nit = nit; else this.nit=””;
}
setNombre(“”); setDireccion(“”); empleados = new Empleado[numero]; numeroEmpleados = 0;
public Empresa(int numero, String nit){ if (nit!=null) this.nit = nit; else this.nit=””;
}
setNombre(“”); setDireccion(“”); empleados = new Empleado[numero]; numeroEmpleados = 0;
public Empresa(String nit, String nombre){ if (nit!=null) this.nit = nit; else this.nit=””;
}
setNombre(nombre); setDireccion(“”); empleados = new Empleado[50]; numeroEmpleados = 0;
public String getNombre(){ return nombre; } public void setNombre(String nombre){ if (nombre!=null) this.nombre = nombre; else this.nombre=””; } public String getDireccion(){ return direccion; } public void setDireccion(String direccion){ if (direccion!=null) this.direccion = direccion; else this.direccion=””; }
108
1. TÓPICOS BÁSICOS
public int getNumeroEmpleados(){ return numeroEmpleados; } public void adicionarEmpleadoXHora(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora){ empleados[numeroEmpleados] = new EmpleadoXHora(cedula, apellido, nombre, horasTrabajadas, sueldoXHora); numeroEmpleados++; } public void adicionarEmpleadoXComision(String cedula, String apellido, String nombre, double sueldoBasico, double porcentaje, double ventasTotales){ empleados[numeroEmpleados] = new EmpleadoXComision(cedula, apellido, nombre, sueldoBasico, porcentaje, ventasTotales); numeroEmpleados++; } public void adicionarEmpleadoSueldoFijo(String cedula, String apellido, String nombre, double sueldoFijo){ empleados[numeroEmpleados] = new EmpleadoSueldoFijo(cedula, apellido, nombre, sueldoFijo); numeroEmpleados++; }
}
public double calcularNominaTotal(){ double total = 0; for (int i=0;i
Note que en este caso el método constructor de la clase se encuentra sobrecargado. Existen cinco métodos que permiten instanciar objetos; todos poseen el mismo nombre, lo único que varía en ellos son los parámetros. Cabe resaltar que además existen dos métodos constructores que reciben los mismos parámetros (String e int), pero en distinto orden; por ello no existe problema alguno en su definición. Ninguno de los siguientes métodos polimórficos pueden ser añadidos a la definición antes presentada. public Empresa(String direccion){ this.nit = “”; setNombre(“”); setDireccion(direccion); empleados = new Empleado[50]; empleados = 0; } public Empresa(String nombre, String nit){ if (nit!=null) this.nit = nit; else this.nit=””; setNombre(nombre); setDireccion(“”);
109
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
}
empleados = new Empleado[50]; empleados = 0;
La razón del porqué no pueden ser incluidos es que para el primer constructor que se muestra ya existe un método que recibe exactamente un parámetro de tipo String, y para el segundo ya existe un método que recibe exactamente dos parámetros de tipo String. • Redefinición o sobrescritura: esta propiedad permite que una subclase redefina o sobrescriba el comportamiento (implementación) de un método que hereda de su superclase. La situación acá es que una subclase hereda todos los atributos y métodos de su superclase, pero en determinadas circunstancias la subclase estaría interesada en modificar la implementación de uno de esos métodos. Para ello es posible que la subclase redefina el comportamiento del método con otra serie de instrucciones. Esto conllevaría a la situación en la que la subclase tendría dos métodos polimórficos con el mismo nombre y los mismos parámetros, uno que hereda de su padre y otro que el mismo define. Cuando se presentó el concepto de herencia se dijo que si una subclase redefine un método de su superclase, al momento de invocarse la ejecución de dicho método prevalecerá la implementación de la subclase sobre la de la superclase. Para la presentación de un ejemplo de esta situación se retomará parte del código presentado anteriormente. public class Empleado{ protected final String cedula; private String apellido; private String nombre; public Empleado(String cedula, String apellido, String nombre){ if (cedula!=null) this.cedula = cedula; else this.cedula = “”;
}
setApellido(apellido); setNombre(nombre);
public double calcularSalario(){ return 0; } public String getCedula(){ return cedula; } public String getApellido(){ return apellido; } public void setApellido(String apellido){ if (apellido!=null) this.apellido = apellido; else this.apellido=””; }
110
1. TÓPICOS BÁSICOS
public String getNombre(){ return nombre; } public void setNombre(String nombre){ if (nombre!=null) this.nombre = nombre; else this.nombre=””; } } public class EmpleadoXHora extends Empleado{ private double horasTrabajadas; private double sueldoXHora; public EmpleadoXHora(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora){ super(cedula, apellido, nombre); setSueldoXHora(sueldoXHora); setHorasTrabajadas(horasTrabajadas); } public double calcularSalario(){ return horasTrabajadas * sueldoXHora; } public double getHorasTrabajadas(){ return horasTrabajadas; } public void setHorasTrabajadas(double horas ){ if (horas>=0) this.horasTrabajadas = horas; else this.horasTrabajadas = 0; } public double getSueldoXHora(){ return sueldoXHora; } public void setSueldoXHora(double sueldo ){ if (sueldo>=0) this.sueldoXHora = sueldo; else this.sueldoXHora = 0; } }
En el código que se presenta, la clase Empleado no es abstracta y posee una implementación por defecto para el método calcularSalario (retornar el valor de 0). Por su parte, la clase EmpleadoXHora extiende la clase Empleado y redefine el comportamiento de dicho método. Al instanciar un objeto de clase EmpleadoXHora e invocar el método calcularSalario, la respuesta que se obtendría sería el producto de los atributos horasTrabajadas y sueldoXHora, y no 0 como lo define la implementación heredada de su superclase. La pregunta que podría surgir en estos momentos corresponde a si una subclase puede cambiarle a un método heredado el nivel de visibilidad establecido por su superclase. La respuesta es que la subclase sí puede redefinir el nivel de visibilidad,
111
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
pero únicamente para ampliarlo, nunca restringiéndolo. El siguiente gráfico ilustra esta situación:
La flecha superior marca la dirección en que no es válido realizar la redefinición; es decir, si la superclase define un método público, la subclase no puede redefinirlo como privado. Por otro lado, la flecha inferior marca la dirección válida de la redefinición; es decir, si una superclase define un método protegido, la subclase puede redefinirlo como público.
sobre la ComparaCión de variables de tipos de referenCia En el apartado Sobre el operador lógico de comparación y su funcionamiento sobre las variables de tipo primitivo y de referencia se estuvo analizando la comparación de variables. En ese apartado se mencionó que la utilización del operador ‘==’ para tipos de datos de referencia tenía un comportamiento especial, pues no comparaba el contenido de los objetos, sino las posiciones en memoria. En ese momento se mencionó que para realizar la comparación del contenido era necesario cotejar uno a uno los valores de todos los atributos de los objetos. Como es conveniente que todas estas instrucciones de comparación sean definidas en un método y que este método se encuentre dentro de la clase a comparar, Java define un método para tal fin en la clase Object (por herencia es adquirido por todas las clases en Java). Este método tiene la siguiente definición en la clase Object. public boolean equals(Object obj){ return (this==obj); }
112
1. TÓPICOS BÁSICOS
Por defecto este método lo que hace es comparar las referencias de los objetos y si son la misma devuelve verdadero (true), en caso contrario devuelve falso (false). Corresponde a una clase especifica sobrescribir esta implementación y brindar una que posibilite el cotejar los valores de los atributos del objeto. Para el caso de análisis de la aplicación del cálculo de la nómina podría definirse la clase EmpleadoXHora de la siguiente manera: public class EmpleadoXHora extends Empleado{ private double horasTrabajadas; private double sueldoXHora; public EmpleadoXHora(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora){ super(cedula, apellido, nombre); setSueldoXHora(sueldoXHora); setHorasTrabajadas(horasTrabajadas); } public double calcularSalario(){ return horasTrabajadas * sueldoXHora; } public double getHorasTrabajadas(){ return horasTrabajadas; } public void setHorasTrabajadas(double horas ){ if (horas>=0) this.horasTrabajadas = horas; else this.horasTrabajadas = 0; } public double getSueldoXHora(){ return sueldoXHora; } public void setSueldoXHora(double sueldo ){ if (sueldo>=0) this.sueldoXHora = sueldo; else this.sueldoXHora = 0; } public boolean equals(Object o){ if (!(o instanceof EmpleadoXHora)) return false; EmpleadoXHora temp = (EmpleadoXHora)o; if if if if if }
( !this.getCedula().equals(temp.getCedula()) ) return false; ( !this.getApellido().equals(temp.getApellido()) ) return false; ( !this.getNombre().equals(temp.getNombre()) ) return false; ( this.getSueldoXHora()!=temp.getSueldoXHora() ) return false; ( this.getHorasTrabajadas()!=temp.getHorasTrabajadas() ) return false; return true;
}
113
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
Sobre la anterior implementación es conveniente destacar: •
•
•
•
•
•
El método equals recibe como parámetro el objeto contra el cual se compara a otro. Esta situación debe pensarse de la siguiente manera: suponga que tiene dos referencias de tipo EmpleadoXHora a y b, para compararlas da lo mismo utilizar la instrucción a.equals(b) que emplear b.equals(a). El resultado del método corresponde a un dato de tipo booleano. En la primera instrucción definida dentro del método equals aparece la utilización de la palabra reservada instanceof. Con esta instrucción se puede determinar si una variable es una instancia de una determinada clase. Como el método equals recibe un parámetro de tipo Object, y puesto que Object es la superclase de todas las demás clases, se puede recibir en este parámetro una referencia a cualquier objeto (sea o no de la clase EmpleadoXHora). El propósito de la primera instrucción de este método es corroborar que efectivamente se van a comparar dos objetos de la misma clase (en este caso EmpleadoXHora). En caso de que los objetos no sean de la misma clase, se devuelve falso. En la siguiente línea se define una variable temp (de temporal), se toma el objeto o que se recibe como parámetro se le hace casting y se le asigna el resultado de este a la variable temp. Las siguientes instrucciones comparan uno a uno los valores de los atributos de cada objeto. Cabe resaltar que la comparación de los atributos cedula, apellido y nombre se hace empleado el método equals en este caso de la clase String. Todo esto porque String es una clase y, por lo tanto, se maneja como tipo de dato de referencia. Los atributos sueldoXHora y horasTrabajadas si se comparan empleando el operador de desigualdad ‘!=’. Si algún valor de un atributo no se corresponde con el valor del atributo en el otro objeto finaliza el método y se devuelve el valor lógico de falso (false) como resultado. En caso de que ninguna de esas condiciones se cumpla entonces los dos objetos son iguales y, por ende, se devuelve el valor lógico de verdadero (true).
1.14. métOdOs y clAses finAles Hasta el momento una subclase puede cambiar la implementación de un método que hereda de su superclase, sin embargo existe un concepto que ayuda a que dicha situación. Definición de método final Corresponde a un método que no puede ser redefinido o sobrescrito por las subclases.
114
1. TÓPICOS BÁSICOS
En Java para declarar un método final se le debe anteponer la palabra ‘final’ a la definición. Intentar sobrescribir un método final de una clase genera un error en tiempo de compilación.
novena aproximaCión a la apliCaCión del CálCulo de la nómina mediante programaCión orientada a obJetos
Los cambios más relevantes en esta novena implementación de la aplicación de la nómina son la inclusión de constructores polimórficos para la clase Empresa, la definición de métodos finales en la clase Empleado y la definición de los métodos equals en las subclases de Empleado. El código que aparece en negrilla resalta las modificaciones efectuadas con respecto a la anterior aproximación. public abstract class Empleado{ protected final String cedula; private String apellido; private String nombre; public Empleado(String cedula, String apellido, String nombre){ if (cedula!=null) this.cedula = cedula; else this.cedula = “”;
}
setApellido(apellido); setNombre(nombre);
public abstract double calcularSalario(); public final String getCedula(){ return cedula; } public final String getApellido(){ return apellido; } public final void setApellido(String apellido){ if (apellido!=null) this.apellido = apellido; else this.apellido=””; } public final String getNombre(){ return nombre; } public final void setNombre(String nombre){ if (nombre!=null) this.nombre = nombre; else this.nombre=””; } }
115
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
public class EmpleadoXHora extends Empleado{ private double horasTrabajadas; private double sueldoXHora; public EmpleadoXHora(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora){ super(cedula, apellido, nombre); setSueldoXHora(sueldoXHora); setHorasTrabajadas(horasTrabajadas); } public double calcularSalario(){ return horasTrabajadas * sueldoXHora; } public double getHorasTrabajadas(){ return horasTrabajadas; } public void setHorasTrabajadas(double horas ){ if (horas>=0) this.horasTrabajadas = horas; else this.horasTrabajadas = 0; } public double getSueldoXHora(){ return sueldoXHora; } public void setSueldoXHora(double sueldo ){ if (sueldo>=0) this.sueldoXHora = sueldo; else this.sueldoXHora = 0; } public boolean equals(Object o){ if (!(o instanceof EmpleadoXHora)) return false; EmpleadoXHora temp = (EmpleadoXHora)o; if if if if if } }
116
( !this.getCedula().equals(temp.getCedula()) ) return false; ( !this.getApellido().equals(temp.getApellido()) ) return false; ( !this.getNombre().equals(temp.getNombre()) ) return false; ( this.getSueldoXHora()!=temp.getSueldoXHora() ) return false; ( this.getHorasTrabajadas()!=temp.getHorasTrabajadas() ) return false; return true;
1. TÓPICOS BÁSICOS
public class EmpleadoXComision extends Empleado{ private double sueldoBasico; private double porcentaje; private double ventasTotales; public EmpleadoXComision(String cedula, String apellido, String nombre, double sueldoBasico, double porcentaje, double ventasTotales){
}
super(cedula, apellido, nombre); setSueldoBasico(sueldoBasico); setPorcentaje(porcentaje); setVentasTotales(ventasTotales);
public double calcularSalario(){ return sueldoBasico + porcentaje * ventasTotales; } public double getSueldoBasico(){ return sueldoBasico; } public void setSueldoBasico(double sueldo){ if (sueldo>=0) this.sueldoBasico = sueldo; else this.sueldoBasico = 0; } public double getPorcentaje(){ return porcentaje; } public void setPorcentaje(double porcentaje ){ if (porcentaje>=0 && porcentaje<=1) this.porcentaje = porcentaje; else this.porcentaje = 0; } public double getVentasTotales(){ return ventasTotales; } public void setVentasTotales(double ventas){ if (ventas>=0) this.ventasTotales = ventas; else this.ventasTotales = 0; }
117
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
public boolean equals(Object o){ if (!(o instanceof EmpleadoXComision)) return false; EmpleadoXComision temp = (EmpleadoXComision)o; if if if if
( !this.getCedula().equals(temp.getCedula()) ) return false; ( !this.getApellido().equals(temp.getApellido()) ) return false; ( !this.getNombre().equals(temp.getNombre()) ) return false; ( !this.getSueldoBasico()==temp.getSueldoBasico() ) return false; if ( !this.getPorcentaje()==temp.getPorcentaje() ) return false; if ( !this.getVentasTotales()==temp.getVentasTotales() ) return false; }
return true;
}
public class EmpleadoSueldoFijo extends Empleado{ private double sueldoFijo; public EmpleadoSueldoFijo(String cedula, String apellido, String nombre, double sueldoFijo){ super(cedula, apellido, nombre); setSueldoFijo(sueldoFijo); } public double calcularSalario(){ return sueldoFijo; } public double getSueldoFijo(){ return sueldoFijo; } public void setSueldoFijo(double sueldo){ if (sueldo>=0) this.sueldoFijo = sueldo; else this.sueldoFijo = 0; } public boolean equals(Object o){ if (!(o instanceof EmpleadoSueldoFijo)) return false; EmpleadoSueldoFijo temp = (EmpleadoSueldoFijo)o; if ( !this.getCedula().equals(temp.getCedula()) ) return false; if ( !this.getApellido().equals(temp.getApellido()) ) return false; if ( !this.getNombre().equals(temp.getNombre()) ) return false;
118
1. TÓPICOS BÁSICOS
if ( !this.getSueldoFijo()!=temp.getSueldoFijo() ) return false; }
return true;
}
public class Empresa{ final private String nit; private String nombre; private String direccion; private Empleado[] empleados; private int numeroEmpleados; public Empresa(String nit, String nombre, String direccion){ if (nit!=null) this.nit = nit; else this.nit=””; setNombre(nombre); setDireccion(direccion); empleados = new Empleado[50]; numeroEmpleados = 0; } public Empresa(String nit){ if (nit!=null) this.nit = nit; else this.nit=””;
}
setNombre(“”); setDireccion(“”); empleados = new Empleado[50]; numeroEmpleados = 0;
public Empresa(String nit, int numero){ if (nit!=null) this.nit = nit; else this.nit=””;
}
setNombre(“”); setDireccion(“”); empleados = new Empleado[numero]; numeroEmpleados = 0;
public String getNit(){ return nit; }
119
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
public String getNombre(){ return nombre; } public void setNombre(String nombre){ if (nombre!=null) this.nombre = nombre; else this.nombre=””; } public String getDireccion(){ return direccion; } public void setDireccion(String direccion){ if (direccion!=null) this.direccion = direccion; else this.direccion=””; } public int getNumeroEmpleados(){ return numeroEmpleados; } public void adicionarEmpleadoXHora(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora){ empleados[numeroEmpleados] = new EmpleadoXHora(cedula, apellido, nombre, horasTrabajadas, sueldoXHora); numeroEmpleados++; } public void adicionarEmpleadoXComision(String cedula, String apellido, String nombre, double sueldoBasico, double porcentaje, double ventasTotales){ empleados[numeroEmpleados] = new EmpleadoXComision(cedula, apellido, nombre, sueldoBasico, porcentaje, ventasTotales); numeroEmpleados++; } public void adicionarEmpleadoSueldoFijo(String cedula, String apellido, String nombre, double sueldoFijo){ empleados[numeroEmpleados] = new EmpleadoSueldoFijo(cedula, apellido, nombre, sueldoFijo); numeroEmpleados++; } public double calcularNominaTotal(){ double total = 0; for (int i=0;i
120
1. TÓPICOS BÁSICOS
import java.io.BufferedReader; import java.io.InputStreamReader; public class Nomina1{ public static void main(String[] args) throws Exception{ Empresa pyme = new Empresa(“123”,”Acme inc.”,”805 Silicon Valley”); String cedula, apellido, nombre; double total = 0; BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) ); System.out.print(“Digite numero de empleados sueldo por hora: “); int numeroEmpleados1 = Integer.valueOf(br.readLine()).intValue(); for(int i=0;i
}
pyme.adicionarEmpleadoXHora(cedula, apellido, nombre, horas, sueldo);
System.out.print(“Digite numero de empleados por comision: “); int numeroEmpleados2 = Integer.valueOf(br.readLine()).intValue(); for(int i=0;i
121
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
}
pyme.adicionarEmpleadoXComision(cedula, apellido, nombre, sueldo, porcentaje, ventas);
System.out.print(“Digite numero de empleados sueldo fijo: “); int numeroEmpleados3 = Integer.valueOf(br.readLine()).intValue(); for(int i=0;i
}
pyme.adicionarEmpleadoSueldoFijo(cedula, apellido, nombre, sueldo);
total = pyme.calcularNominaTotal(); System.out.println(“\nLa nómina total es: “+ total); }
}
Definición de clase final Corresponde a una clase que no puede ser extendida.
Para declarar una clase final se debe anteponer la palabra reservada ‘final’ a su definición. Intentar crear una clase que extienda una clase final generará un error en tiempo de compilación. A manera de ejemplo suponga que se desea definir la clase Empresa como final; lo anterior se logra a través de la siguiente definición (se obvia el resto de código que hace parte de la definición de la clase): public final class Empresa
122
1. TÓPICOS BÁSICOS
1.15. HerenciA simple y múltiple Suponga que la codificación de un juego requiere la definición de las siguientes clases. public public public public public
class class class class class
Vehiculo … VehiculoCivil extends Vehiculo … VehiculoMilitar extends Vehiculo … VehiculoTerrestre extends Vehiculo … VehiculoAcuatico extends Vehiculo …
Los ejemplos presentados anteriormente (y todos los demás presentados hasta el momento en este libro) donde se emplea el concepto de herencia muestran de lo que se conoce como herencia simple. Definición de herencia simple Capacidad de definir una clase de tal manera de que esta extienda de forma directa a una sola superclase.
Cualquier lector podría estar pensando en estos momentos que la clase VehiculoTerrestre tiene como superclase a la clase Vehiculo, y también por transitividad (de forma indirecta) a la clase Object (por ser la superclase de Vehiculo). Esto efectivamente es así, pero note que dentro de la definición de herencia simple se menciona la palabra directa, por ende, de forma directa la única superclase de VehiculoTerrestre es Vehiculo. Suponga que para el juego en mención adicionalmente se requiere la codificación de una clase que represente a tanques de guerra; se podría pensar en realizar la siguiente definición para aprovechar las características definidas en clases previas. public class TanqueGuerra extends VehiculoMilitar, VehiculoTerrestre …
En el código presentado anteriormente se ejemplifica un caso de herencia múltiple. Definición de herencia múltiple Capacidad de definir una clase de tal manera que esta tenga más de una superclase directa y, por ende, pueda así adquirir de manera automática todos los atributos y métodos definidos en todas sus superclases.
La diferencia entre este tipo de herencia es el número de superclases directa que tiene la subclase. Es conveniente mencionar en este punto que Java no permite la utilización de
123
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
la herencia múltiple para la definición de nuevas clases (como sí es permitido en otros lenguajes orientados a objetos); sin embargo, sí posibilita la utilización de un concepto extremadamente importante como las interfaces, que posibilita la simulación de ciertas características de la herencia múltiple.
1.16. interfAces El lenguaje de programación Java permite la definición de dos tipos de componentes básicos para las aplicaciones orientadas a objetos: la clase y la interface. Definición de interfaces Una interface define la signatura de un conjunto de servicios que deben ser exhibidos por una clase. Corresponde a la definición señalar qué debe saber hacer una clase, pero no cómo debe realizar la implementación de dichos servicios.
La sintaxis para la definición en Java de una interface no varía mucho respecto a la definición de una clase. Para la presentación de un ejemplo se retomará el caso presentado en la sección de encapsulamiento referente a la codificación de un componente para la representación de una fecha, en donde, pudo haberse encapsulado la funcionalidad requerida que debía ser exhibida por el componente en una interface, por ejemplo: public interface ServiciosFecha{ public int numeroMes(); public String nombreMes(); public String formato1(); public String formato2(); }
Del anterior código cabe notar lo siguiente: • La línea de definición de la interface es similar a la de la definición de una clase, lo único que varía es que en esta aparece la palabra interface. • La definición del nivel de visibilidad de la interface, que para este caso es público. • Aparece como contenido de la interface la definición de varios métodos, mas no sus respectivas implementaciones. Como una interface es simplemente la definición de un contrato de servicio, es decir, qué debe saber hacerse, es necesario una contraparte donde se defina el cómo va a ser la implementación del servicio. Para esto las interfaces se ayudan con las clases, es decir, al definir una clase esta puede especificar explícitamente que va a dar implementación a una interface. Esto se logra a través de la utilización de la palabra reservada implements en la definición de la clase. Por ejemplo, considere la siguiente definición:
124
1. TÓPICOS BÁSICOS
public class Fecha1 implements ServiciosFecha
Los siguientes puntos deben ser tenidos en cuenta al trabajar y manipular interfaces. • Los niveles de visibilidad permitidos para la definición de una interface son público o el nivel de visibilidad por defecto. • Dentro de una interfaz pueden definirse atributos, pero únicamente finales. El valor inicial de estos atributos debe ser asignado en la misma declaración. • El único nivel de visibilidad permitido para la definición de métodos o atributos en las interfaces es público. Si no se define explícitamente a través de la utilización de la palabra reservada ‘public’, Java lo asume de forma automática. • Los métodos definidos dentro de una interface deben ser abstractos obligatoriamente. Si no se definen explícitamente a través de la utilización de la palabra reservada ‘abstract’, Java lo asume de forma automática. • Es posible declarar interfaces donde no se definan métodos. • Cuando en la definición de una clase se declara que esta implementará una interface, es una obligación que esta clase implemente todos los métodos definidos en la interface. En caso de que la clase solo pueda implementar algunos cuantos, la clase debe declararse como abstracta. • Al igual que como sucede con la relación de herencia, la relación de implementación de una interface puede denotarse con la frase ‘es un tipo de’, de tal manera que la clase (que implementa) es un tipo de elemento de la interface. • Una clase puede implementar cualquier número de interfaces; es por ello que se mencionó anteriormente que con el uso de interfaces se pueden simular determinadas características de la herencia múltiple. Para definirse en Java, solo se necesita colocar la distintas interfaces separadas por comas después de la definición de implements. Por ejemplo: public class Ejemplo implements Interface1, Interface2, Interface3
En este caso, la clase Ejemplo debe proporcionar implementación para todos los métodos definidos en todas las interfaces. En caso de que existan métodos con la misma signatura en diferentes clases, solo será necesario implementarlo una vez en la clase. • Una interface puede extender varias interfaces; en otras palabras una interface puede definirse en función de varias interfaces, por consiguiente, en Java se encuentra permitida la herencia múltiple entre interfaces. Considere el siguiente ejemplo. public interface Nadador{ public void bracear(); public void respirar(); } public interface Ciclista{
125
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
public void pedalear(); public void respirar();
} public interface Corredor{ public void trotar(); public void respirar(); }
Con la declaración de las anteriores interfaces se podría pensar en la definición de la interface TriAtleta de la siguiente forma: public interface TriAtleta extends Nadador, Ciclista, Corredor{}
• No habría necesidad de definir ningún método dentro de la interface TriAtleta, y cuando una clase declare que implementará esta interface será necesario que le asigne código a todos los métodos definidos en las interfaces Nadador, Ciclista y Corredor (en realidad solo se deberán implementar cuatro métodos que corresponden a bracear, pedalear, trotar y respirar; este último unicamente debe implementarse una sola vez). • Es posible definir referencias a objetos cuyo tipo de dato sea una interface, por ejemplo, la siguiente definición sería totalmente válida: TriAtleta a = null; • Al igual que como sucedía con las clases abstractas, no se puede utilizar una interface como parte de una instrucción new, a menos que en la misma declaración se le asigne implementación a los métodos abstractos9.
déCima aproximaCión a la apliCaCión del CálCulo de la nómina empleando programaCión orientada a obJetos
En esta implementación de la aplicación de la nómina se incluye la definición y el trabajo con una nueva interface. El código que aparece en negrilla resalta las modificaciones efectuadas con respecto a la anterior aproximación. public interface PersonaAsalariada{ public double calcularSalario(); }
public abstract class Empleado implements PersonaAsalariada{ protected final String cedula; private String apellido; private String nombre;
9
126
Esta última modalidad corresponde a tópicos avanzados que serán analizados en capítulos posteriores.
1. TÓPICOS BÁSICOS
public Empleado(String cedula, String apellido, String nombre){ if (cedula!=null) this.cedula = cedula; else this.cedula = “”;
}
setApellido(apellido); setNombre(nombre);
public final String getCedula(){ return cedula; } public final String getApellido(){ return apellido; } public final void setApellido(String apellido){ if (apellido!=null) this.apellido = apellido; else this.apellido=””; } public final String getNombre(){ return nombre; } public final void setNombre(String nombre){ if (nombre!=null) this.nombre = nombre; else this.nombre=””; } }
Note cómo en la clase Empleado no aparece la definición del método calcularSalario. La razón es que este método se encuentra definido en la interface, y se sobreentiende que la clase Empleado es abstracta porque aún no le ha brindado implementación a dicho método. En las subclases de Empleado no se ha producido cambio alguno. public class EmpleadoXHora extends Empleado{ private double horasTrabajadas; private double sueldoXHora; public EmpleadoXHora(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora){ super(cedula, apellido, nombre); setSueldoXHora(sueldoXHora); setHorasTrabajadas(horasTrabajadas); }
127
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
public double calcularSalario(){ return horasTrabajadas * sueldoXHora; } public double getHorasTrabajadas(){ return horasTrabajadas; } public void setHorasTrabajadas(double horas ){ if (horas>=0) this.horasTrabajadas = horas; else this.horasTrabajadas = 0; } public double getSueldoXHora(){ return sueldoXHora; } public void setSueldoXHora(double sueldo ){ if (sueldo>=0) this.sueldoXHora = sueldo; else this.sueldoXHora = 0;
}
public boolean equals(Object o){ if (!(o instanceof EmpleadoXHora)) return false; EmpleadoXHora temp = (EmpleadoXHora)o; if if if if if }
( !this.getCedula().equals(temp.getCedula()) ) return false; ( !this.getApellido().equals(temp.getApellido()) ) return false; ( !this.getNombre().equals(temp.getNombre()) ) return false; ( this.getSueldoXHora()!=temp.getSueldoXHora() ) return false; ( this.getHorasTrabajadas()!=temp.getHorasTrabajadas() ) return false; return true;
}
public class EmpleadoXComision extends Empleado{ private double sueldoBasico; private double porcentaje; private double ventasTotales; public EmpleadoXComision(String cedula, String apellido, String nombre, double sueldoBasico, double porcentaje, double ventasTotales){
}
128
super(cedula, apellido, nombre); setSueldoBasico(sueldoBasico); setPorcentaje(porcentaje); setVentasTotales(ventasTotales);
1. TÓPICOS BÁSICOS
public double calcularSalario(){ return sueldoBasico + porcentaje * ventasTotales; } public double getSueldoBasico(){ return sueldoBasico; } public void setSueldoBasico(double sueldo){ if (sueldo>=0) this.sueldoBasico = sueldo; else this.sueldoBasico = 0; } public double getPorcentaje(){ return porcentaje; } public void setPorcentaje(double porcentaje ){ if (porcentaje>=0 && porcentaje<=1) this.porcentaje = porcentaje; else this.porcentaje = 0; } public double getVentasTotales(){ return ventasTotales; } public void setVentasTotales(double ventas){ if (ventas>=0) this.ventasTotales = ventas; else this.ventasTotales = 0; } public boolean equals(Object o){ if (!(o instanceof EmpleadoXComision)) return false; EmpleadoXComision temp = (EmpleadoXComision)o; if ( !this.getCedula().equals(temp.getCedula()) ) return false; if ( !this.getApellido().equals(temp.getApellido()) ) return false; if if if if
( !this.getNombre().equals(temp.getNombre()) ) return false; ( this.getSueldoBasico()!=temp.getSueldoBasico() ) return false; ( this.getPorcentaje()!=temp.getPorcentaje() ) return false; ( this.getVentasTotales()!=temp.getVentasTotales() ) return false;
}
return true;
}
129
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
public class EmpleadoSueldoFijo extends Empleado{ private double sueldoFijo; public EmpleadoSueldoFijo(String cedula, String apellido, String nombre, double sueldoFijo){ super(cedula, apellido, nombre); setSueldoFijo(sueldoFijo); } public double calcularSalario(){ return sueldoFijo; } public double getSueldoFijo(){ return sueldoFijo; } public void setSueldoFijo(double sueldo){ if (sueldo>=0) this.sueldoFijo = sueldo; else this.sueldoFijo = 0; } public boolean equals(Object o){ if (!(o instanceof EmpleadoSueldoFijo)) return false; EmpleadoSueldoFijo temp = (EmpleadoSueldoFijo)o; if if if if } }
130
( ( ( (
!this.getCedula().equals(temp.getCedula()) ) return false; !this.getApellido().equals(temp.getApellido()) ) return false; !this.getNombre().equals(temp.getNombre()) ) return false; this.getSueldoFijo()!=temp.getSueldoFijo() ) return false;
return true;
1. TÓPICOS BÁSICOS
public class Empresa{ final private String nit; private String nombre; private String direccion; private Empleado[] empleados; private int numeroEmpleados; public Empresa(String nit, String nombre, String direccion){ if (nit!=null) this.nit = nit; else this.nit=””; setNombre(nombre); setDireccion(direccion); empleados = new Empleado[50]; numeroEmpleados = 0; } public Empresa(String nit){ if (nit!=null) this.nit = nit; else this.nit=””; setNombre(“”); setDireccion(“”); empleados = new Empleado[50]; numeroEmpleados = 0;
} public Empresa(String nit, int numero){ if (nit!=null) this.nit = nit; else this.nit=””;
}
setNombre(“”); setDireccion(“”); empleados = new Empleado[numero]; numeroEmpleados = 0;
public String getNit(){ return nit; } public String getNombre(){ return nombre; } public void setNombre(String nombre){ if (nombre!=null) this.nombre = nombre; else this.nombre=””; } public String getDireccion(){ return direccion;
131
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
} public void setDireccion(String direccion){ if (direccion!=null) this.direccion = direccion; else this.direccion=””; } public int getNumeroEmpleados(){ return numeroEmpleados; } public void adicionarEmpleadoXHora(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora){ empleados[numeroEmpleados] = new EmpleadoXHora(cedula, apellido, nombre, horasTrabajadas, sueldoXHora); numeroEmpleados++; } public void adicionarEmpleadoXComision(String cedula, String apellido, String nombre, double sueldoBasico, double porcentaje, double ventasTotales){ empleados[numeroEmpleados] = new EmpleadoXComision(cedula, apellido, nombre, sueldoBasico, porcentaje, ventasTotales); numeroEmpleados++;
}
public void adicionarEmpleadoSueldoFijo(String cedula, String apellido, String nombre, double sueldoFijo){ empleados[numeroEmpleados] = new EmpleadoSueldoFijo(cedula, apellido, nombre, sueldoFijo); numeroEmpleados++; } public double calcularNominaTotal(){ double total = 0; for (int i=0;i
import java.io.BufferedReader; import java.io.InputStreamReader; public class Nomina1{ public static void main(String[] args) throws Exception{ Empresa pyme = new Empresa(“123”,”Acme inc.”,”805 Silicon Valley”); String cedula, apellido, nombre; double total = 0;
132
1. TÓPICOS BÁSICOS
BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) ); System.out.print(“Digite numero de empleados sueldo por hora: “); int numeroEmpleados1 = Integer.valueOf(br.readLine()).intValue(); for(int i=0;i
}
pyme.adicionarEmpleadoXHora(cedula, apellido, nombre, horas, sueldo);
System.out.print(“Digite numero de empleados por comision: “); int numeroEmpleados2 = Integer.valueOf(br.readLine()).intValue(); for(int i=0;i
}
pyme.adicionarEmpleadoXComision(cedula, apellido, nombre, sueldo, porcentaje, ventas);
System.out.print(“Digite numero de empleados sueldo fijo: “); int numeroEmpleados3 = Integer.valueOf(br.readLine()).intValue(); for(int i=0;i
133
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
cedula = br.readLine(); System.out.print(“Digite el apellido del empleado: “ ); apellido = br.readLine(); System.out.print(“Digite el nombre del empleado: “ ); nombre = br.readLine(); System.out.print(“Digite sueldo basico empleado: “); sueldo = Double.valueOf(br.readLine()).doubleValue();
}
pyme.adicionarEmpleadoSueldoFijo(cedula, apellido, nombre, sueldo);
total = pyme.calcularNominaTotal(); System.out.println(“\nLa nómina total es: “+ total); }
134
}
2.
TÓPICOS AVANZADOS
La décima aproximación a la aplicación del cálculo de la nómina es un buen ejemplo de una aplicación orientada a objetos. En esencia el problema a resolver es muy sencillo, pero en su interior se han podido trabajar varias de las nociones de la programación orientada a objetos. Analizándola en profundidad, esta aplicación posee un alto nivel de organización y delimitación funcional del código; sin embargo posee dos grandes problemas: • El primero de ellos tiene que ver con la cantidad de objetos de tipo Empleado que pueden registrarse dentro de un objeto Empresa. La clase Empresa cuenta con tres constructores, dos de ellos definen un máximo de 50 empleados por empresa, y el restante constructor maneja como número máximo un valor especificado por el usuario. Sin importar el constructor que se utilice, todos presentan el mismo problema: el tamaño de un array no puede modificarse, ni para incrementarse, ni para decrementarse. En otras palabras: qué pasa si después de haber registrado empleados en los primeros 50 espacios del array es necesario ingresar el empleado número 51 (la misma situación aplica si se utilizase el otro constructor). • El segundo problema se encuentra relacionado con el manejo que se da a las situaciones anormales, por ejemplo, al instanciar la clase EmpleadoXHora si se suministra un valor negativo para el atributo horasTrabajadas se crea el objeto y se le asigna el valor por defecto de 0 a dicho campo. En ocasiones este tipo de comportamiento es adecuado y suficiente, pero existen escenarios donde sería recomendable informarle al usuario que se ha producido un error y explicarle en qué consiste. La décima aproximación a la aplicación de la nómina no posee código que permita avisarle a un usuario que se ha presentado una situación excepcional.
135
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
Los temas que se presentan en este capítulo están dirigidos a mejorar la forma en que se desarrollan las aplicaciones con el fin de hacerlas más completas y robustas.
2.1. cOlecciOnes Como cualquier otro lenguaje de programación, Java permite trabajar con estructuras de datos básicas como arrays y matrices. La gran limitante que existe al trabajar con estas estructuras (no solo en Java, sino en la mayoría de lenguajes de programación) es que su tamaño es estático10, es decir, una vez que se crea una estructura con un tamaño determinado no se le puede alterar. Como ejemplo, considere el siguiente código y el diagrama que representa una posible distribución de los objetos en memoria (se trabaja con la definición de clases presentada en la décima aproximación a la aplicación del cálculo de la nómina). Empresa e = new Empresa(“123”,3); e.adicionarEmpleadoXHora(“72303”,”García”,”Luis”,100,15); e.adicionarEmpleadoXComision(“98765”,”Sánchez”,”María”,1000,0.5,1000); e.adicionarEmpleadoSueldoFijo(“12345”,”Pérez”,”Pedro”,1200);
Si se adicionase un nuevo empleado a la empresa, se generaría un error porque no hay espacio en el array donde incluirlo. No puede intentar corregirse el código porque no existe una instrucción en Java que permita cambiarle el tamaño al array para volverlo más grande. Para resolver este problema existen dos alternativas11: • La primera consiste en incluir en el código un artilugio de tal manera que cuando se llene el array se cree uno nuevo con mayor capacidad, se copien las referencias del 10 11
136
En este contexto la palabra estático hace referencia a que su estado no cambia. No tiene relación con las definiciones que sobre métodos y atributos estáticos se presentaron anteriormente. La alternativa de simplemente definir un array de gran tamaño no se contempla por el enorme gasto de recursos que se presentaría cuando no se requiera utilizarse tanto espacio en memoria.
2. TÓPICOS AVANZADOS
antiguo array al nuevo y, finalmente, que el programa continúe trabajando con el nuevo array. Esta operación deberá efectuarse cada vez que se llene el array con el que trabaja el objeto empresa. • La segunda consiste en no emplear estructuras estáticas como los arrays, sino dinámicas como las listas. Teniendo en mente la reutilización de código, sería más conveniente no codificar una solución particular para este problema en la clase Empresa, sino más bien idear una solución general que pueda ser reutilizada en otros escenarios. Si se analiza detenidamente, esta misma situación se repite constantemente en una infinidad de problemas, como, por ejemplo, al registrar los libros que han sido prestados en una biblioteca por un usuario, o al registrar los carros vendidos en una agencia, o al registrar los correos que le llegan a una persona; en realidad la lista de ejemplos es interminable. Como existen dos alternativas de solución (una que emplea artilugios de programación con arrays y otra, listas) se podría pensar en definir una clase para cada una de las posibles soluciones. Sin embargo, como ambas clases servirían para el mismo propósito podría pensarse en la definición de los métodos que deben poseer ambas clases en una interface. public interface ServiciosColeccionElementos{ public void agregar(Object o); public Object obtener(int i); public int getCantidadElementos(); }
De la anterior definición es conveniente resaltar los siguientes puntos: • Como se está intentando construir una solución general no se hace referencia a un tipo de dato específico, sino que más bien se manejan referencias de tipo general como Object. • Existe un método agregar que permite adicionar un elemento a la colección. • Existe un método obtener que devuelve el i-ésimo elemento almacenado en la colección. Este método retorna el elemento, mas no lo elimina de la colección. • Existe un método getCantidadElementos que retorna el número de elementos que han sido almacenados en la colección. • Quedaría pendiente la definición de otros métodos útiles: como permitir eliminar elementos de la colección o mover un elemento de la colección de una posición a otra. Estos métodos no serán tenidos en cuenta por el momento para facilitar y simplificar la presentación del tema.
137
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
CodifiCaCión de la estruCtura de datos para maneJar una ColeCCión de elementos empleando arrays
Se presenta a continuación una posible implementación para la estructura de datos en mención. public class ColeccionElementosConArrays implements ServiciosColeccionElementos
{
private Object[] losElementos; private int indice, tamano; public ColeccionElementosConArrays() { losElementos = new Object[10]; indice = 0; tamano=10; } public void agregar(Object o){ if (indice >= tamano){ Object[] elnuevoarray = new Object[tamano+10]; for (int i=0;i
=0 && i
}
}
Del anterior código es conveniente resaltar los siguientes puntos: •
138
La clase define tres atributos, un atributo de tipo array de Object, que guarda la referencia al array donde se almacenan los datos; un atributo indice, que registra la cantidad de elementos que han sido almacenados en el array; y, finalmente, un atributo tamano, que guarda el tamaño máximo de elementos que se pueden registrar en el array.
2. TÓPICOS AVANZADOS
•
•
•
Cuando el índice del nuevo elemento a registrar es igual (o mayor) a la cantidad máxima de elementos que se pueden registrar en el array, se crea un nuevo array que es más grande que el inicial en 10 posiciones. Se copian las referencias del antiguo array al nuevo; y, finalmente, el objeto ColeccionElementosConArrays cambia la referencia del atributo losElementos del antiguo array al nuevo. Cuando se desea obtener un elemento empleando el método obtener, si el parámetro suministrado es menor que 0 o mayor a la cantidad de elementos que se han registrado en el array, se devuelve null. Hay que tener en cuenta que el primer elemento se almacena en la posición cero del array, por ende, si se desea extraer el elemento n, debe pasarse como parámetro el valor de n-1.
CodifiCaCión de la estruCtura de datos para maneJar una ColeCCión de elementos empleando listas
La codificación de una solución alternativa mediante el empleo de listas requiere inicialmente definir la clase de los elementos que harán las veces de nodos. class Nodo{ private Nodo siguiente; private Object info; public Nodo(Object o){ this.info = o; } public Nodo getSiguiente(){ return siguiente; } public void setSiguiente(Nodo n){ this.siguiente = n; }
}
public Object getInfo(){ return info; }
139
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
public class ColeccionElementosConListas implements ServiciosColeccionElementos
{
private Nodo elPrimero; private Nodo elUltimo; private int numero; public void agregar(Object o){ Nodo nuevo = new Nodo(o); if (elPrimero==null) elPrimero=nuevo; else elUltimo.setSiguiente(nuevo); elUltimo = nuevo; numero = numero+1; } public Object obtener(int i){ if (i<0 || i>=numero) return null; if (i==0) return elPrimero.getInfo(); if (i==numero-1) return elUltimo.getInfo(); Nodo temp=elPrimero; for (int contador=0;contador
Note que en el método obtener el código se encuentra optimizado de tal forma que cuando se pide el último elemento su referencia se devuelve inmediatamente sin necesidad de recorrer la lista.
140
2. TÓPICOS AVANZADOS
eJemplo de utilizaCión de las estruCturas de datos que posibilitan la manipulaCión de ColeCCiones de elementos
A continuación se presenta un ejemplo de utilización de objetos de las clases definidas en los apartados anteriores. ServiciosColeccionElementos e = new ColeccionElementosConArrays(); e.agregar( new EmpleadoXHora(“72303”, “García”, “Luis”, 100, 15) ); e.agregar( new EmpleadoXComision(“98765”, “Sánchez”, “María”, 1000, 0.5, 1000) ); e.agregar( new EmpleadoSueldoFijo(“12345”, “Pérez”, “Pedro”, 1200) ); EmpleadoXHora e1 = (EmpleadoXHora)e.obtener(0); System.out.println( e1.calcularSalario() );
Del anterior código es conveniente resaltar los siguientes puntos: •
•
Note que el objeto e tiene por tipo de dato ServiciosColeccionElementos, es decir, una interface. Esta definición es muy útil porque si se desea trabajar con la estructura de datos que maneja listas, solo es necesario cambiar la instrucción new ColeccionElementosConArrays() por new ColeccionElementosConListas(). No se requiere alterar ni una sola instrucción del restante código. El método obtener retorna una referencia almacenada en la colección, solo requiere suministrarse la posición. Este método tiene una particularidad; como fue definido para devolver referencias de tipo Object, debe realizarse un casting a lo que retorne antes de poder trabajar con la referencia almacenada.
Es interesante analizar que aunque ambas implementaciones para las colecciones son diferentes, los métodos que satisfacen son los mismos, y que para utilizar una de estas implementaciones solo se requiere alterar una línea de código. Conviene en este punto analizar cómo sería la estructura de los objetos en memoria en caso de ejecutar el código con cada una de las implementaciones.
141
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
Con las implementaciones y ejemplos presentados previamente es posible brindar una definición al concepto de colección.
142
2. TÓPICOS AVANZADOS
Definición de colección Estructura de datos que permite guardar y manipular referencias a otros objetos.
Programar empleando este tipo de estructuras de datos corresponde a una buena práctica de este tipo, ya que hace que el programador se desentienda de las operaciones de bajo nivel (administración de arrays, trabajo con listas, código de corrimiento de información) y se centre en la lógica propia de la aplicación. Afortunadamente, Java cuenta con una estructura unificada (framework) para el trabajo con colecciones12. Este marco de trabajo para las colecciones en Java, al igual que en los ejemplos presentados previamente, encapsula en la interface Collection todos los métodos que deben ser exhibidos por estas estructuras de datos. A continuación se presentan algunos de los más relevantes: • int size(). Retorna el número de elementos almacenados en la colección. • boolean isEmpty(). Retorna verdadero (true) si la colección no tiene elementos. • boolean contains(Object o). Retorna verdadero (true) si la colección contiene el elemento especificado. Requiere que la clase del elemento que se pasa por parámetro tenga redefinido apropiadamente el método equals y hashCode. Del método equals se ha hablado anteriormente en este libro; por su parte, el método hashCode lo poseen todos los objetos ya que lo heredan de la clase Object. La finalidad del método hashCode es el de retornar un valor hash para un objeto; este valor es de gran utilidad al trabajar con tablas hash. Si dos objetos son iguales de acuerdo al método equals, entonces la invocación del método hashCode en cada uno de ellos debe producir el mismo valor entero. • boolean add(Object o). Adiciona a la colección el elemento que se pasa como parámetro. En caso de que no sea posible adicionar el elemento en la colección (posiblemente porque ya fue adicionado), retorna falso (false); en caso contrario, retorna verdadero (true). • boolean remove(Object o). Remueve un elemento de la colección que sea igual al suministrado por parámetro (la comparación se realiza empleando el método equals). Retorna verdadero(true) si la colección contenía dicho elemento. • boolean containsAll(Collection c). Retorna verdadero (true) si la colección contiene todos los elementos que contiene la colección que se pasa por parámetro.
12
Mayor información sobre las colecciones y el framework de trabajo en Java puede ser consultada en la siguiente referencia: http://java.sun.com/docs/books/tutorial/collections/intro/index.html
143
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
• boolean addAll(Collection c). Adiciona a la colección todos los elementos que contenga la colección que se pasa por parámetro. Devuelve verdadero (true) si la colección original cambia como resultado de la ejecución de la instrucción. • boolean removeAll(Collection c). Remueve todos los elementos de la colección que se encuentren contenidos en la colección que se pasa por parámetro. Devuelve verdadero (true) si la colección original cambia como resultado de la ejecución de la instrucción. • boolean retainAll(Collection c). Mantiene dentro de la colección solamente los objetos que se encuentren contenidos en la colección que se pasa por parámetro. Devuelve verdadero (true) si la colección original cambia como resultado de la ejecución de la instrucción. • void clear(). Remueve todos los elementos que tiene la colección. El framework de colecciones de Java define básicamente tres tipos estructuras de datos (listas, conjuntos y colas13) y, adicionalmente, define una estructura de datos (mapas), que no es una colección, sino que se construye con base en las colecciones. Cada uno de los elementos antes definidos posee una interface (donde se definen puntualmente los servicios que se van a prestar) y, adicionalmente, tiene variadas implementaciones a dichas interfaces.
2.1.1. Listas Definición de lista Estas estructuras de datos corresponden a colecciones ordenadas y pueden poseer elementos repetidos. Al encontrarse ordenadas se hace posible que un usuario pueda manipular los elementos empleando sus posiciones.
Java define la interface List donde se especifican los métodos que debe proveer una implementación de este tipo de estructura de datos. Esta interface extiende Collection por lo cual también gana la definición de todos los métodos definidos en la superinterface. A continuación se presentan algunos de los métodos más relevantes de esta interface. • Object get(int pos). Retorna el elemento que se encuentra en la posición especificada por el parámetro que se pasa. • Object set(int pos, Object o). Reemplaza el elemento que se encuentra en la posición indicada en el parámetro por el elemento que tambien se pasa por parámetro. Retorna el elemento que inicialmente se encontraba en dicha posición.
13
144
Esta colección no será analizada detalladamente como las restantes colecciones. Mayor información sobre esta puede ser consultada en la siguiente referencia: http://java.sun.com/docs/books/tutorial/collections/interfaces/queue.html
2. TÓPICOS AVANZADOS
• void add(int pos, Object o). Inserta el elemento especificado por parámetro en la posición especificada. • Object remove(int pos). Remueve el elemento que se encuentra en la posición especificada por parámetro. Retorna el elemento que se ha eliminado de la colección. • boolean addAll(int pos, Collection c). Adiciona los elementos especificados en la colección que se pasa por parámetro a partir de la posición que se especifique. • int indexOf(Object o). Retorna el índice o posición de la primera ocurrencia dentro de la colección, del objeto que se pasa por parámetro. Retorna el valor de -1 en caso de que el objeto no se encuentre en la colección. • int lastIndexOf(Object o). Retorna el índice o posición de la última ocurrencia dentro de la colección del objeto que se pasa por parámetro. • List subList(int inicial, int final). Retorna una porción de la lista especificada a partir de una posición inicial (incluida) hasta una posición final (excluida). Los elementos que se retornan se encapsulan dentro de un objeto, que debe cumplir con la interface List. Java brinda tres implementaciones para esta interface, ellas son ArrayList, LinkedList y
Vector.
• ArrayList. Corresponde a una implementación donde se trabaja internamente con arrays (en esencia es similar a la implementación presentada en el apartado Codificación de la estructura de datos para manejar una colección de elementos empleando arrays). Es la implementación más popular y la más frecuentemente utilizada. Por las características de su implementación, es capaz de acceder a un elemento dentro de ella a una tasa constante en el tiempo. • LinkedList. Corresponde a una implementación donde se trabaja internamente con listas (en esencia es similar a la implementación presentada en el apartado Codificación de la estructura de datos para manejar una colección de elementos empleando listas). Se recomienda su utilización si generalmente se estarán adicionando elementos al principio de la lista. Por las características de implementación su tasa de acceso a los elementos es lineal en el tiempo. • Vector. Vector es una de las primeras implementaciones en Java que buscaba trabajar el concepto de colecciones. En la actualidad se mantiene por cuestiones de compatibilidad con antiguas versiones de Java.
145
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
deCimoprimera
aproximaCión a la apliCaCión del CálCulo de la nómina
mediante programaCión orientada a obJetos
En esta implementación de la aplicación se incluyen los cambios requeridos en la clase Empresa para que deje de trabajar con arrays y se pase a trabajar con un objeto de tipo ArrayList. Las restantes clases permanecen invariantes. El código que aparece en negrilla resalta las modificaciones efectuadas con respecto a la anterior aproximación. public interface PersonaAsalariada{ public double calcularSalario(); }
public abstract class Empleado implements PersonaAsalariada{ protected final String cedula; private String apellido; private String nombre; public Empleado(String cedula, String apellido, String nombre){ if (cedula!=null) this.cedula = cedula; else this.cedula = “”;
}
setApellido(apellido); setNombre(nombre);
public final String getCedula(){ return cedula; } public final String getApellido(){ return apellido; } public final void setApellido(String apellido){ if (apellido!=null) this.apellido = apellido; else this.apellido=””; } public final String getNombre(){ return nombre; } public final void setNombre(String nombre){ if (nombre!=null) this.nombre = nombre; else this.nombre=””; } }
146
2. TÓPICOS AVANZADOS
public class EmpleadoXHora extends Empleado{ private double horasTrabajadas; private double sueldoXHora; public EmpleadoXHora(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora){ super(cedula, apellido, nombre); setSueldoXHora(sueldoXHora); setHorasTrabajadas(horasTrabajadas); } public double calcularSalario(){ return horasTrabajadas * sueldoXHora; } public double getHorasTrabajadas(){ return horasTrabajadas; } public void setHorasTrabajadas(double horas ){ if (horas>=0) this.horasTrabajadas = horas; else this.horasTrabajadas = 0; } public double getSueldoXHora(){ return sueldoXHora; } public void setSueldoXHora(double sueldo ){ if (sueldo>=0) this.sueldoXHora = sueldo; else this.sueldoXHora = 0; } public boolean equals(Object o){ if (!(o instanceof EmpleadoXHora)) return false; EmpleadoXHora temp = (EmpleadoXHora)o; if if if if if }
( !this.getCedula().equals(temp.getCedula()) ) return false; ( !this.getApellido().equals(temp.getApellido()) ) return false; ( !this.getNombre().equals(temp.getNombre()) ) return false; ( this.getSueldoXHora()!=temp.getSueldoXHora() ) return false; ( this.getHorasTrabajadas()!=temp.getHorasTrabajadas() ) return false; return true;
}
147
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
public class EmpleadoXComision extends Empleado{ private double sueldoBasico; private double porcentaje; private double ventasTotales; public EmpleadoXComision(String cedula, String apellido, String nombre, double sueldoBasico, double porcentaje, double ventasTotales){ super(cedula, apellido, nombre); setSueldoBasico(sueldoBasico); setPorcentaje(porcentaje); setVentasTotales(ventasTotales); } public double calcularSalario(){ return sueldoBasico + porcentaje * ventasTotales; } public double getSueldoBasico(){ return sueldoBasico; } public void setSueldoBasico(double sueldo){ if (sueldo>=0) this.sueldoBasico = sueldo; else this.sueldoBasico = 0; } public double getPorcentaje(){ return porcentaje; } public void setPorcentaje(double porcentaje ){ if (porcentaje>=0 && porcentaje<=1) this.porcentaje = porcentaje; else this.porcentaje = 0; } public double getVentasTotales(){ return ventasTotales; } public void setVentasTotales(double ventas){ if (ventas>=0) this.ventasTotales = ventas; else this.ventasTotales = 0; }
148
2. TÓPICOS AVANZADOS
public boolean equals(Object o){ if (!(o instanceof EmpleadoXComision)) return false; EmpleadoXComision temp = (EmpleadoXComision)o; if if if if if if
( !this.getCedula().equals(temp.getCedula()) ) return false; ( !this.getApellido().equals(temp.getApellido()) ) return false; ( !this.getNombre().equals(temp.getNombre()) ) return false; ( this.getSueldoBasico()!=temp.getSueldoBasico() ) return false; ( this.getPorcentaje()!=temp.getPorcentaje() ) return false; ( this.getVentasTotales()!=temp.getVentasTotales() ) return false;
}
return true;
}
public class EmpleadoSueldoFijo extends Empleado{ private double sueldoFijo; public EmpleadoSueldoFijo(String cedula, String apellido, String nombre, double sueldoFijo){ super(cedula, apellido, nombre); setSueldoFijo(sueldoFijo); } public double calcularSalario(){ return sueldoFijo; } public double getSueldoFijo(){ return sueldoFijo; } public void setSueldoFijo(double sueldo){ if (sueldo>=0) this.sueldoFijo = sueldo; else this.sueldoFijo = 0; } public boolean equals(Object o){ if (!(o instanceof EmpleadoSueldoFijo)) return false; EmpleadoSueldoFijo temp = (EmpleadoSueldoFijo)o;
149
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
if if if if }
( ( ( (
!this.getCedula().equals(temp.getCedula()) ) return false; !this.getApellido().equals(temp.getApellido()) ) return false; !this.getNombre().equals(temp.getNombre()) ) return false; this.getSueldoFijo()!=temp.getSueldoFijo() ) return false;
return true;
}
import java.util.ArrayList; public class Empresa{ final private String nit; private String nombre; private String direccion; private ArrayList empleados; public Empresa(String nit, String nombre, String direccion){ if (nit!=null) this.nit = nit; else this.nit=””; setNombre(nombre); setDireccion(direccion); empleados = new ArrayList(); } public Empresa(String nit){ if (nit!=null) this.nit = nit; else this.nit=””;
}
setNombre(“”); setDireccion(“”); empleados = new ArrayList();
public Empresa(String nit, int numero){ if (nit!=null) this.nit = nit; else this.nit=””;
}
setNombre(“”); setDireccion(“”); empleados = new ArrayList(numero);
public String getNit(){ return nit; }
150
2. TÓPICOS AVANZADOS
public String getNombre(){ return nombre; } public void setNombre(String nombre){ if (nombre!=null) this.nombre = nombre; else this.nombre=””; } public String getDireccion(){ return direccion; } public void setDireccion(String direccion){ if (direccion!=null) this.direccion = direccion; else this.direccion=””; } public int getNumeroEmpleados(){ return empleados.size(); } public void adicionarEmpleadoXHora(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora){ empleados.add (new EmpleadoXHora(cedula, apellido, nombre, horasTrabajadas, sueldoXHora) ); } public void adicionarEmpleadoXComision(String cedula, String apellido, String nombre, double sueldoBasico, double porcentaje, double ventasTotales){ empleados.add( new EmpleadoXComision(cedula, apellido, nombre, sueldoBasico, porcentaje, ventasTotales) ); } public void adicionarEmpleadoSueldoFijo(String cedula, String apellido, String nombre, double sueldoFijo){ empleados.add( new EmpleadoSueldoFijo(cedula, apellido, nombre, sueldoFijo) ); } public double calcularNominaTotal(){ double total = 0; for (int i=0;i
151
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
Sobre la implementación de la clase Empresa es conveniente resaltar los siguientes puntos: •
•
• • •
Como la estructura de datos es la que maneja y conoce la cantidad de elementos que posee, desaparece de la clase el atributo donde se registraba la cantidad de empleados de la empresa. Cuando se requiera trabajar con este valor, se utilizará la invocación al método size() de la colección. La clase ArrayList, por defecto, tiene un constructor que no recibe parámetros. Este constructor trabaja con una estructura interna de tamaño inicial 10; en caso de requerirse, esta estructura se incrementa. Esta clase también tiene un constructor que recibe la cantidad de elementos con los que el usuario sugiere la creación inicial de la estructura interna del objeto. Este constructor polimórfico también es utilizado en la clase Empresa. Note que para adicionar elementos en la colección se invoca el método add. Para obtener el elemento en una determinada posición se invoca el método get. Note que para poder invocar el método calcularSalario en los objetos almacenados en el ArrayList de nombre empleados, es necesario primero realizar un casting.
import java.io.BufferedReader; import java.io.InputStreamReader; public class Nomina1{ public static void main(String[] args) throws Exception{ Empresa pyme = new Empresa(“123”,”Acme inc.”,”805 Silicon Valley”); String cedula, apellido, nombre; double total = 0; BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) ); System.out.print(“Digite numero de empleados sueldo por hora: “); int numeroEmpleados1 = Integer.valueOf(br.readLine()).intValue(); for(int i=0;i
152
2. TÓPICOS AVANZADOS
}
pyme.adicionarEmpleadoXHora(cedula, apellido, nombre, horas, sueldo);
System.out.print(“Digite numero de empleados por comision: “); int numeroEmpleados2 = Integer.valueOf(br.readLine()).intValue(); for(int i=0;i
}
pyme.adicionarEmpleadoXComision(cedula, apellido, nombre, sueldo, porcentaje, ventas);
System.out.print(“Digite numero de empleados sueldo fijo: “); int numeroEmpleados3 = Integer.valueOf(br.readLine()).intValue(); for(int i=0;i
} total = pyme.calcularNominaTotal();
System.out.println(“\nLa nómina total es: “+ total); }
}
153
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
2.1.2. Conjuntos Definición de conjunto Esta colección modela el concepto matemático de conjunto, su principal característica es que dentro de ella no puede haber elementos repetidos.
Java define la interface Set para la implementación de los conjuntos, sin embargo, como extiende Collection y no adiciona ningún método, los métodos que se exhiben en ambas interfaces son los mismos. Adicionalmente, Java define una interface para trabajar implementaciones de conjuntos donde se guarde algún tipo de ordenamiento, para ello ha sido declarada la interface SortedSet; esta interface extiende a Set. A continuación se presentan algunos de los métodos más relevantes de esta interface. Set
• Comparator comparator(). Retorna el objeto Comparator que utiliza el conjunto para establecer el criterio de ordenamiento. Retorna null si se está trabajando el ordenamiento natural de los elementos (para trabajar con este tipo de ordenamiento es necesario que las clases de los objetos que se almacenan en la estructura de datos implementen la interface Comparable). • Object first(). Retorna el primer (menor) elemento de la colección. • SortedSet headSet(Object o). Retorna la porción del conjunto cuyos elementos sean estrictamente menores al objeto pasado por parámetro. Los objetos que se devuelven se encapsulan dentro de un objeto que debe cumplir también la interface SortedSet. • Object last(). Retorna el último (mayor) elemento de la colección. • SortedSet subset(Object inicial, Object final). Retorna una porción del conjunto comprendida entre el objeto inicial y el final. Los objetos que se devuelven se encapsulan dentro de un objeto que debe cumplir también la interface SortedSet. • SortedSet tailSet(Object o). Retorna la porción del conjunto cuyos elementos sean mayores o iguales al objeto pasado por parámetro. Los objetos que se devuelven se encapsulan dentro de un objeto que debe cumplir también la interface SortedSet. Java brinda tres implementaciones para conjuntos (HashSet, LinkedHashSet y TreeSet), pero solo una de ellas es también implementación de la interface SortedSet (TreeSet). • HashSet. Esta implementación basa su estructura en una tabla hash. Es una de las implementaciones más empleadas gracias a su desempeño y eficiencia, pero al no implementar la interface SortedSet no hay garantía de que los elementos se almacenen bajo algún criterio de ordenamiento.
154
2. TÓPICOS AVANZADOS
• TreeSet. Esta implementación trabaja internamente con una estructura de datos de tipo árbol para mantener los elementos ordenados; por esa razón, su desempeño no es tan elevado como la implementación HashSet. • LinkedHashSet. Corresponde a una implementación intermedia a las anteriormente presentadas. Su estructura interna trabaja con una lista doblemente enlazada.
eJemplo de utilizaCión de una implementaCión de la interfaCe set El ejemplo que se presenta a continuación trabaja con la clase EmpleadoSueldoFijo definida en la decimoprimera aproximación de la aplicación del cálculo de la nómina. Anteriormente se mencionó que las implementaciones de Set requieren redefinir los métodos equals y hashCode de las clases de los objetos que se almacenarán en ellas. public interface PersonaAsalariada{ public double calcularSalario(); }
public abstract class Empleado implements PersonaAsalariada{ protected final String cedula; private String apellido; private String nombre; public Empleado(String cedula, String apellido, String nombre){ if (cedula!=null) this.cedula = cedula; else this.cedula = “”;
}
setApellido(apellido); setNombre(nombre);
public final String getCedula(){ return cedula; } public final String getApellido(){ return apellido; } public final void setApellido(String apellido){ if (apellido!=null) this.apellido = apellido; else this.apellido=””; } public final String getNombre(){
155
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
return nombre; } public final void setNombre(String nombre){ if (nombre!=null) this.nombre = nombre; else this.nombre=””; } }
public class EmpleadoSueldoFijo extends Empleado{ private double sueldoFijo; public EmpleadoSueldoFijo(String cedula, String apellido, String nombre, double sueldoFijo){ super(cedula, apellido, nombre); setSueldoFijo(sueldoFijo); } public double calcularSalario(){ return sueldoFijo; } public double getSueldoFijo(){ return sueldoFijo; } public void setSueldoFijo(double sueldo){ if (sueldo>=0) this.sueldoFijo = sueldo; else this.sueldoFijo = 0; } public boolean equals(Object o){ if (!(o instanceof EmpleadoSueldoFijo)) return false; EmpleadoSueldoFijo temp = (EmpleadoSueldoFijo)o; if ( !this.getCedula().equals(temp.getCedula()) ) return false; if ( !this.getApellido().equals(temp.getApellido()) ) return false; if ( !this.getNombre().equals(temp.getNombre()) ) return false; if ( this.getSueldoFijo()!=temp.getSueldoFijo() ) return false; }
return true;
public int hashCode(){ return cedula.hashCode(); } }
156
2. TÓPICOS AVANZADOS
Sobre la clase anterior es conveniente resaltar la implementación del método hashCode, que simplemente pide retornar el valor del hashCode de la cedula del empleado. Finalmente considere el siguiente código: Set conjunto = new HashSet(); conjunto.add(new EmpleadoSueldoFijo(“12345”,”Pérez”,”Pedro”,1000)); conjunto.add(new EmpleadoSueldoFijo(“12345”,”Pérez”,”Pedro”,1000)); conjunto.add(new EmpleadoSueldoFijo(“2345”,”Sanchez”,”Maria”,1000)); System.out.println(conjunto.size());
Al ejecutar el anterior código la respuesta es 2. Esto indica que a pesar de haberse definido 3 instrucciones de inserción dentro del conjunto sólo dos fueron efectivas. La segunda no lo fue porque ya existía dentro de la estructura de datos un objeto con la misma información. Note además que no se incluyo código particular para evitar ingresar dos veces el mismo objeto, ya que dicha implementación se gana por la definición de conjunto (Set).
2.1.3. Mapas Definición de mapa Un mapa es una estructura de datos que mapea llaves en valores. Al registrar un objeto dentro de esta estructura de datos, además del objeto o valor a almacenar, se debe suministrar un objeto que haga las veces de llave, y al extraer objetos de esta se suministra el objeto llave para que se retorne el objeto almacenado. Un mapa no puede poseer llaves repetidas (se logra a través de la utilización de un conjunto) y solo se almacena un valor para cada llave.
Java define la interface map donde se especifican los métodos que debe proveer una implementación de este tipo de estructura de datos. Esta interface no extiende Collection. A continuación se presentan algunos de los métodos más relevantes de esta interface. • Object put (Object llave, Object valor). Inserta el objeto que se recibe en el parámetro valor bajo el valor de la llave especificada. Retorna el antiguo valor asociado a la llave, o null, si no había mapeo para dicha llave. • Object get(Object llave). Retorna el valor que se encuentra asociado a la llave que se pasa por parámetro.
157
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
• Object remove(Object llave). Remueve el mapeo para la llave que se pasa por parámetro. Retorna el antiguo valor asociado a la llave, o null, si no había mapeo para dicha llave. • boolean containsKey(Object llave). Devuelve verdadero (true), si el mapa contiene como llave al objeto especificado como parámetro. • boolean containsValue(Object valor). Retorna verdadero (true), si el mapa contiene como uno de sus valores al objeto especificado como parámetro. • int size(). Retorna el número de mapeos llave-valor almacenados en la estructura de datos. • boolean isEmpty(). Retorna verdadero (true), si el mapa no tiene registrados mapeos llave-valor. • void putAll(Map m). Copia todos los mapeos del objeto pasado como parámetro. • void clear(). Remueve todos los mapeos del mapa. • Set keySet(). Retorna un Set con todas las llaves registradas en el mapa. • Collection values(). Retorna una Collection con todos los valores almacenados en el mapa. Java brinda varias implementaciones a la interface map. De todas ellas la más eficiente en término de tiempo corresponde a HashMap. Esta implementación basa su estructura en una tabla hash, y provee un desempeño constante en el tiempo para las operaciones básicas de inserción y extracción.
eJemplo de utilizaCión de una implementaCión de la interfaCe map Se presenta a continuación el ejemplo de implementación de una agenda telefónica simple. En aras de hacer más sencillo el ejemplo y su entendimiento se definen las siguientes restricciones:
• De un contacto de la agenda telefónica solo se registrará un único número telefónico, •
y, adicionalmente, se guardará información de una única dirección. No existen dentro del directorio dos contactos que tengan el mismo nombre.
La siguiente clase representa los contactos de la agenda. public class Contacto{ private final String nombre; private String telefono; private String direccion;
158
2. TÓPICOS AVANZADOS
public Contacto(String nombre, String telefono, String direccion){ this.nombre = nombre; this.telefono = telefono; this.direccion = direccion; } public String getNombre(){ return nombre; } public String getTelefono(){ return telefono; }
}
public String getDireccion(){ return direccion; }
Considere el siguiente código: HashMap directorio = new HashMap(); directorio.put( “Homero Simpson”, new Contacto(“Homero Simpson”, “000-1245”,”Avenida Siempre Viva 742”) ); directorio.put( “Montgomery Burns”, new Contacto(“Montgomery Burns”, “000-2467”,”Mansion Burns”) ); System.out.println( ((Contacto)directorio.get(“Homero Simpson”)).getTelefono() );
Sobre el anterior código es conveniente resaltar:
• Note como para ingresar valores dentro del mapa directorio se emplea el método put.
•
•
Este método recibe la llave y el valor. Para el ejemplo, la llave corresponde al nombre del contacto, mientras que el valor es el propio contacto. Para extraer elementos del mapa directorio se emplea el método get. Este método recibe únicamente la llave y retorna el valor almacenado en el mapa. Para el ejemplo, la llave que se suministra corresponde al nombre del contacto. Como el tipo de retorno del método es Object, se hace necesaria la realización de un casting para convertir la referencia al tipo Contacto con el fin de poder invocar el método getTelefono().
2.1.4. Genéricos Un lector atento habrá podido notar que hasta el momento cada vez que se pide obtener una referencia almacenada en cualquier estructura de datos ya presentada se hace necesaria la utilización de la operación de casting. Esto se debe a que seguramente la estructura interna de la estructura de datos trabaja con referencias de tipo Object para permitir almacenar cualquier tipo de objeto en ella.
159
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
Existen ocasiones en que el programador conoce exactamente el tipo de referencias que se estarán almacenando dentro de la estructura de datos. Desde la versión 5 (JDK5.0) de Java, este lenguaje de programación permite al programador que cuando declare una estructura de datos del framework de colecciones, indique el tipo de dato a registrar en ella. Para hacerlo simplemente se requiere colocar entre los símbolos de menor y mayor que (‘<>’) el tipo de referencia de los objetos (bien puede ser una clase o una interface). Por ejemplo, para declarar un ArrayList donde solo se almacenarán referencias de tipo Empleado, se requiere la siguiente instrucción: ArrayList losEmpleados = new ArrayList();
Permitir este tipo de definiciones le trae muchos beneficios al programador. • En primer lugar, ya no se requiere código de validación para garantizar que dentro de la estructura de datos solo se registren objetos de un determinado tipo de dato. Un intento de almacenar objetos dentro de una estructura de datos de un tipo de dato distinto al definido causará un error. • En segundo lugar, como la estructura de datos ya conoce el tipo de referencia que está almacenando, ya no se hace necesaria la operación de casting.
deCimosegunda
aproximaCión a la apliCaCión del CálCulo
de la nómina mediante programaCión orientada a obJetos
En esta implementación de la aplicación se modifica la clase Empresa para que trabaje con ArrayList genéricos. Las restantes clases permanecen invariantes. El código que aparece en negrilla resalta las modificaciones efectuadas con respecto a la anterior aproximación. public interface PersonaAsalariada{ public double calcularSalario(); }
public abstract class Empleado implements PersonaAsalariada{ protected final String cedula; private String apellido; private String nombre; public Empleado(String cedula, String apellido, String nombre){ if (cedula!=null) this.cedula = cedula; else this.cedula = “”;
160
2. TÓPICOS AVANZADOS
}
setApellido(apellido); setNombre(nombre);
public final String getCedula(){ return cedula; } public final String getApellido(){ return apellido; } public final void setApellido(String apellido){ if (apellido!=null) this.apellido = apellido; else this.apellido=””; } public final String getNombre(){
return nombre; } public final void setNombre(String nombre){ if (nombre!=null) this.nombre = nombre; else this.nombre=””; } }
public class EmpleadoXHora extends Empleado{ private double horasTrabajadas; private double sueldoXHora; public EmpleadoXHora(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora){ super(cedula, apellido, nombre); setSueldoXHora(sueldoXHora); setHorasTrabajadas(horasTrabajadas); } public double calcularSalario(){ return horasTrabajadas * sueldoXHora; } public double getHorasTrabajadas(){ return horasTrabajadas; } public void setHorasTrabajadas(double horas ){ if (horas>=0) this.horasTrabajadas = horas; else this.horasTrabajadas = 0; } public double getSueldoXHora(){ return sueldoXHora; } public void setSueldoXHora(double sueldo ){
161
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
}
if (sueldo>=0) this.sueldoXHora = sueldo; else this.sueldoXHora = 0;
public boolean equals(Object o){ if (!(o instanceof EmpleadoXHora)) return false; EmpleadoXHora temp = (EmpleadoXHora)o; if if if if if }
( !this.getCedula().equals(temp.getCedula()) ) return false; ( !this.getApellido().equals(temp.getApellido()) ) return false; ( !this.getNombre().equals(temp.getNombre()) ) return false; ( this.getSueldoXHora()!=temp.getSueldoXHora() ) return false; ( this.getHorasTrabajadas()!=temp.getHorasTrabajadas() ) return false; return true;
}
public class EmpleadoXComision extends Empleado{ private double sueldoBasico; private double porcentaje; private double ventasTotales; public EmpleadoXComision(String cedula, String apellido, String nombre, double sueldoBasico, double porcentaje, double ventasTotales){
}
super(cedula, apellido, nombre); setSueldoBasico(sueldoBasico); setPorcentaje(porcentaje); setVentasTotales(ventasTotales);
public double calcularSalario(){ return sueldoBasico + porcentaje * ventasTotales; } public double getSueldoBasico(){ return sueldoBasico; } public void setSueldoBasico(double sueldo){ if (sueldo>=0) this.sueldoBasico = sueldo; else this.sueldoBasico = 0; } public double getPorcentaje(){ return porcentaje; }
162
2. TÓPICOS AVANZADOS
public void setPorcentaje(double porcentaje ){ if (porcentaje>=0 && porcentaje<=1) this.porcentaje = porcentaje; else this.porcentaje = 0; } public double getVentasTotales(){ return ventasTotales; } public void setVentasTotales(double ventas){ if (ventas>=0) this.ventasTotales = ventas; else this.ventasTotales = 0; } public boolean equals(Object o){ if (!(o instanceof EmpleadoXComision)) return false; EmpleadoXComision temp = (EmpleadoXComision)o; if if if if if
( ( ( ( (
!this.getCedula().equals(temp.getCedula()) ) return false; !this.getApellido().equals(temp.getApellido()) ) return false; !this.getNombre().equals(temp.getNombre()) ) return false; this.getSueldoBasico()!=temp.getSueldoBasico() ) return false; this.getPorcentaje()!=temp.getPorcentaje() ) return false;
if ( this.getVentasTotales()!=temp.getVentasTotales() ) return false; }
return true;
}
public class EmpleadoSueldoFijo extends Empleado{ private double sueldoFijo; public EmpleadoSueldoFijo(String cedula, String apellido, String nombre, double sueldoFijo){ super(cedula, apellido, nombre); setSueldoFijo(sueldoFijo); } public double calcularSalario(){ return sueldoFijo; }
163
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
public double getSueldoFijo(){ return sueldoFijo; } public void setSueldoFijo(double sueldo){ if (sueldo>=0) this.sueldoFijo = sueldo; else this.sueldoFijo = 0; } public boolean equals(Object o){ if (!(o instanceof EmpleadoSueldoFijo)) return false; EmpleadoSueldoFijo temp = (EmpleadoSueldoFijo)o; if if if if }
( ( ( (
!this.getCedula().equals(temp.getCedula()) ) return false; !this.getApellido().equals(temp.getApellido()) ) return false; !this.getNombre().equals(temp.getNombre()) ) return false; this.getSueldoFijo()!=temp.getSueldoFijo() ) return false;
return true;
}
import java.util.ArrayList; public class Empresa{ final private String nit; private String nombre; private String direccion; private ArrayList empleados; public Empresa(String nit, String nombre, String direccion){ if (nit!=null) this.nit = nit; else this.nit=””; setNombre(nombre); setDireccion(direccion); empleados = new ArrayList(); } public Empresa(String nit){ if (nit!=null) this.nit = nit; else this.nit=””;
}
setNombre(“”); setDireccion(“”); empleados = new ArrayList ();
public Empresa(String nit, int numero){ if (nit!=null) this.nit = nit; else this.nit=””;
164
2. TÓPICOS AVANZADOS
}
setNombre(“”); setDireccion(“”); empleados = new ArrayList(numero);
public String getNit(){ return nit; } public String getNombre(){ return nombre; } public void setNombre(String nombre){ if (nombre!=null) this.nombre = nombre; else this.nombre=””; } public String getDireccion(){ return direccion; } public void setDireccion(String direccion){ if (direccion!=null) this.direccion = direccion; else this.direccion=””; } public int getNumeroEmpleados(){ return empleados.size(); } public void adicionarEmpleadoXHora(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora){ empleados.add (new EmpleadoXHora(cedula, apellido, nombre, horasTrabajadas, sueldoXHora) ); } public void adicionarEmpleadoXComision(String cedula, String apellido,
}
String nombre, double sueldoBasico, double porcentaje, double ventasTotales){ empleados.add( new EmpleadoXComision(cedula, apellido, nombre, sueldoBasico, porcentaje, ventasTotales) );
public void adicionarEmpleadoSueldoFijo(String cedula, String apellido, String nombre, double sueldoFijo){ empleados.add( new EmpleadoSueldoFijo(cedula, apellido, nombre, sueldoFijo) ); }
165
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
public double calcularNominaTotal(){ double total = 0; for (int i=0;i
Sobre la implementación de la clase Empresa es conveniente resaltar los siguientes puntos: • •
Note cómo se realiza la definición e instanciación del array de empleados. En el método calcularNominaTotal ya no es necesario el casting.
import java.io.BufferedReader; import java.io.InputStreamReader; public class Nomina1{ public static void main(String[] args) throws Exception{ Empresa pyme = new Empresa(“123”,”Acme inc.”,”805 Silicon Valley”); String cedula, apellido, nombre; double total = 0; BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) ); System.out.print(“Digite numero de empleados sueldo por hora: “); int numeroEmpleados1 = Integer.valueOf(br.readLine()).intValue(); for(int i=0;i
} System.out.print(“Digite numero de empleados por comision: “); int numeroEmpleados2 = Integer.valueOf(br.readLine()).intValue(); for(int i=0;i
166
2. TÓPICOS AVANZADOS
System.out.print(“\nDigite la cedula del empleado: “ ); cedula = br.readLine(); System.out.print(“Digite el apellido del empleado: “ ); apellido = br.readLine(); System.out.print(“Digite el nombre del empleado: “ ); nombre = br.readLine(); System.out.print(“Digite sueldo basico empleado: “); sueldo = Double.valueOf(br.readLine()).doubleValue(); System.out.print(“Digite porcentaje del empleado: “ ); porcentaje = Double.valueOf(br.readLine()).doubleValue(); System.out.print(“Digite ventas totales del empleado: “ ); ventas = Double.valueOf(br.readLine()).doubleValue();
}
pyme.adicionarEmpleadoXComision(cedula, apellido, nombre, sueldo, porcentaje, ventas);
System.out.print(“Digite numero de empleados sueldo fijo: “); int numeroEmpleados3 = Integer.valueOf(br.readLine()).intValue(); for(int i=0;i
}
pyme.adicionarEmpleadoSueldoFijo(cedula, apellido, nombre, sueldo);
total = pyme.calcularNominaTotal(); System.out.println(“\nLa nómina total es: “+ total); }
}
167
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
eJemplo de uso para una implementaCión de la interfaCe set que emplea estruCturas genériCas
El ejemplo que se presenta a continuación trabaja con la clase EmpleadoSueldoFijo definida en la décimo segunda aproximación de la aplicación del cálculo de la nómina. public interface PersonaAsalariada{ public double calcularSalario(); }
public abstract class Empleado implements PersonaAsalariada{ protected final String cedula; private String apellido; private String nombre; public Empleado(String cedula, String apellido, String nombre){ if (cedula!=null) this.cedula = cedula; else this.cedula = “”;
}
setApellido(apellido); setNombre(nombre);
public final String getCedula(){ return cedula; } public final String getApellido(){ return apellido; } public final void setApellido(String apellido){ if (apellido!=null) this.apellido = apellido; else this.apellido=””; } public final String getNombre(){ return nombre; } public final void setNombre(String nombre){ if (nombre!=null) this.nombre = nombre; else this.nombre=””; } }
168
2. TÓPICOS AVANZADOS
public class EmpleadoSueldoFijo extends Empleado{ private double sueldoFijo; public EmpleadoSueldoFijo(String cedula, String apellido, String nombre, double sueldoFijo){ super(cedula, apellido, nombre); setSueldoFijo(sueldoFijo); } public double calcularSalario(){ return sueldoFijo; } public double getSueldoFijo(){ return sueldoFijo; } public void setSueldoFijo(double sueldo){ if (sueldo>=0) this.sueldoFijo = sueldo; else this.sueldoFijo = 0; } public boolean equals(Object o){ if (!(o instanceof EmpleadoSueldoFijo)) return false; EmpleadoSueldoFijo temp = (EmpleadoSueldoFijo)o; if if if if }
( ( ( (
!this.getCedula().equals(temp.getCedula()) ) return false; !this.getApellido().equals(temp.getApellido()) ) return false; !this.getNombre().equals(temp.getNombre()) ) return false; this.getSueldoFijo()!=temp.getSueldoFijo() ) return false;
return true;
public int hashCode(){ return cedula.hashCode(); } }
Sobre la clase anterior es conveniente resaltar la implementación del método hashCode, simplemente se pide retornar el valor del hashCode de la cedula del empleado. Finalmente, considere el siguiente código: Set conjunto = new HashSet(); conjunto.add(new EmpleadoSueldoFijo(“12345”,”Pérez”,”Pedro”,1000)); conjunto.add(new EmpleadoSueldoFijo(“12345”,”Pérez”,”Pedro”,1000)); conjunto.add(new EmpleadoSueldoFijo(“2345”,”Sanchez”,”Maria”,1000)); System.out.println(conjunto.size());
169
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
eJemplo de uso para una implementaCión de la interfaCe map que emplea estruCturas genériCas
Aqui se trabajará con el ejemplo que se utilizó en el apartado; ejemplo de uso para una implementación de la interface map. La diferencia radica en que para este ejemplo se estará utilizando una estructura de datos mapa genérico. public class Contacto{ private final String nombre; private String telefono; private String direccion; public Contacto(String nombre, String telefono, String direccion){ this.nombre = nombre; this.telefono = telefono; this.direccion = direccion; } public String getNombre(){ return nombre; } public String getTelefono(){ return telefono; }
}
public String getDireccion(){ return direccion; }
Considere el siguiente código: HashMap directorio = new HashMap(); directorio.put( “Homero Simpson”, new Contacto(“Homero Simpson”, “000-1245”,”Avenida Siempre Viva 742”) ); directorio.put( “Montgomery Burns”, new Contacto(“Montgomery Burns”, “000-2467”,”Mansion Burns”) ); System.out.println( directorio.get(“Homero Simpson”).getTelefono() );
Sobre el anterior código es conveniente resaltar: •
•
170
Note que para definir el HashMap fue necesario establecer cuál es el tipo de dato de las llaves y cuál es el tipo de dato de los valores que se estarán almacenando. Dicha definición se enmarca dentro de los símbolos de mayor y menor que y se separan con coma. La última línea de código no requiere el casting del objeto que se extrae de la estructura de datos.
2. TÓPICOS AVANZADOS
2.2. mAnejO de excepciOnes Una aplicación de software no se encuentra exenta a errores, por ello, y sin lugar a dudas, una buena aplicación debe advertir la aparición y sobre todo el manejo de este tipo de situaciones excepcionales. Por ejemplo, si una persona debe tener una cédula se debe prever qué debe suceder en caso de que se suministre para dicho atributo un valor inválido, como que la cadena esté vacía o que esta contenga un carácter no numérico. Para estos casos, el manejo del error podría consistir simplemente en informarle al usuario dicha situación para que este se encargue de corregirlo. Analice cómo podría incluirse dentro de la aplicación del cálculo de la nómina la validación para no permitir que el atributo cedula de un Empleado tome como valor la cadena vacía. Aunque todo el análisis que se efectúe va dirigido a validar este punto específico, este análisis puede ser extendido fácilmente para la validación de cualquier situación. Considérense las siguientes alternativas de solución. • La primera alternativa consistiría en redefinir el código de la clase Nomina1 (definición presentada en la décimo segunda aproximación de la aplicación del cálculo de la nómina) La idea general es la de colocar código de validación cuando se recibe el valor de la cédula, si el valor no es válido, simplemente no se ejecuta la instrucción de adicionar un tipo de empleado específico a la empresa. El código quedaría de la siguiente manera. import java.io.BufferedReader; import java.io.InputStreamReader; public class Nomina1{ public static void main(String[] args) throws Exception{ Empresa pyme = new Empresa(“123”,”Acme inc.”,”805 Silicon Valley”); String cedula, apellido, nombre; double total = 0; BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) ); System.out.print(“Digite numero de empleados sueldo por hora: “); int numeroEmpleados1 = Integer.valueOf(br.readLine()).intValue(); for(int i=0;i
171
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
}
if (cedula.trim().equals(“”)){ System.out.println(“Valor de cédula no válida.”); i--; } else { pyme.adicionarEmpleadoXHora(cedula, apellido, nombre, horas, sueldo); }
System.out.print(“Digite numero de empleados por comision: “); int numeroEmpleados2 = Integer.valueOf(br.readLine()).intValue(); for(int i=0;i
}
}
pyme.adicionarEmpleadoXComision(cedula, apellido, nombre, sueldo, porcentaje, ventas);
System.out.print(“Digite numero de empleados sueldo fijo: “); int numeroEmpleados3 = Integer.valueOf(br.readLine()).intValue(); for(int i=0;i
}
172
if (cedula.trim().equals(“”)){ System.out.println(“Valor de cédula no válida.”); i--; } else { pyme.adicionarEmpleadoSueldoFijo(cedula, apellido, nombre, sueldo); }
2. TÓPICOS AVANZADOS
total = pyme.calcularNominaTotal(); System.out.println(“\nLa nómina total es: “+ total); }
}
El método trim() de la clase String devuelve una copia de la cadena de caracteres original de la que elimina los espacios en blanco que se encuentren al principio y al final de la misma. Está claro que la ubicación de este código en esta clase prevé la aparición de este error, y, además, lo maneja. Sin embargo, se hace necesario duplicar este código a lo largo de esta clase (al validar el registro de un nuevo EmpleadoXHora, o un EmpleadoXComision o un EmpleadoSueldoFijo) y en cualquier otra clase que funcione como interfaz de usuario (por ejemplo, Nomina2). Esto sugiere que debe encontrarse otro lugar para ubicar este código de validación.
• La segunda alternativa consistiría en ubicar el código de validación no en la clase Nomina01 (con el fin de evitar la redundancia de código), sino en la clase Empresa (ya que la interfaz de usuario utiliza dicha clase). El código en dicha clase quedaría de la siguiente manera (para evitar la inclusión de demasiadas líneas de código que puedan despistar al lector y alejarlo del punto de la explicación solo se mostrará el código del método adicionarEmpleadoXHora de la clase Empresa, pero dichas instrucciones deberían extenderse también a los métodos adicionarEmpleadoXComision y adicionarEmpleadoSueldoFijo): public void adicionarEmpleadoXHora(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora){ if (cedula.trim().equals(“”)){ System.out.println(“Valor de cédula no valida”); }else{ empleados.add (new EmpleadoXHora(cedula, apellido, nombre, horasTrabajadas, sueldoXHora) );
•
}
}
La ubicación del código de validación en este método presenta varios inconvenientes. Uno de ellos sería que en caso de reutilizar la clase Empleado (o alguna de sus subclases) en otra aplicación se requiere duplicar el código de validación de este método. Otro inconveniente que se presentaría corresponde a la duplicación del código al interior de la propia clase, ya que debe ubicarse también en los restantes métodos adicionarEmpleadoXComision y adicionarEmpleadoSueldoFijo. Finalmente, el último inconveniente que se podría presentar, pero no por ello el menos importante, tiene que ver con el hecho de que la clase Empresa no tiene por qué conocer qué tipo de interfaz de usuario está utilizando sus servicios; es decir, puede que la interfaz sea de tipo consola (como la que presenta la clase Nomina01) o puede que sea de tipo gráfica (con ventanas, botones y demás), luego allí estaría mal desplegar el mensaje que le informa al usuario dicha situación empleando un mensaje en consola. En realidad el problema que presenta este último inconveniente tiene que ver con que no existe una forma clara
173
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
de informarle a la clase de la interfaz de usuario que hubo un error para que tome la acción correspondiente. • La tercera alternativa permitiría corregir uno de los inconvenientes que se presentan en la alternativa anterior. Esta alternativa consistiría en ubicar el código de validación en la propia clase EmpleadoXHora (o incluso sería mejor hacerlo en el método constructor de la clase Empleado, ya que por herencia lo ganarían todas las subclases de ella). Al ubicarse allí el código de validación no importaría si la clase es reutilizada por alguna otra aplicación. El interrogante que surge es qué hacer en caso de que el valor de la cédula no sea válido. Si el problema permite crear un objeto EmpleadoXHora con un valor de cédula desconocido, no hay mayor inconveniente; pero sí existiría un gran problema en caso de que esta última situación no sea una alternativa válida. A manera de resumen con la tercera alternativa, que sería la más apropiada desde el punto de vista de reutilización de código, se presentarían los siguientes problemas: o Cuando no se permita la creación de un objeto con valores inválidos, cómo hacer para detener la ejecución de un método como el método constructor. o No hay una forma clara de informarle a los objetos de la clase de la interfaz de usuario que se ha presentado un error de tal manera que estos tomen las medidas necesarias. El marco de trabajo para el manejo de excepciones que brinda Java contempla las soluciones para ambos problemas presentados anteriormente.
Definición de excepción Una excepción es un evento que ocurre durante la ejecución de un programa con el cual se interrumpe el flujo de ejecución normal de las instrucciones del mismo.
Este evento será representado dentro de la aplicación a través de un objeto; en él, además, se podrá almacenar información explicatoria adicional del evento. En Java existe una clase predefinida para la representación de una Excepción, que es la clase Exception, y se encuentra dentro del paquete java.lang. Esta clase posee un atributo llamado message cuya finalidad es la de registrar un mensaje relativo al evento; adicionalmente, esta clase posee el método accesor getMessage() y dos métodos constructores: uno que no recibe parámetros y otro que recibe un String con el mensaje relativo a la Excepción.
2.2.1. Generación y lanzamiento de excepciones La idea detrás del manejo de excepciones es la de crear un objeto Exception por cada posible evento excepcional que se pueda presentar durante la ejecución de un programa. El código
174
2. TÓPICOS AVANZADOS
para realizar la validación del atributo cedula en el constructor de la clase Empleado quedaría de la siguiente manera: public Empleado(String cedula, String apellido, String nombre){ if (cedula.trim().equals(“”)) new Exception(“Valor de la cédula no válido”); this.cedula = cedula;
}
setApellido(apellido); setNombre(nombre);
El código anterior prevé la posible condición que generaría una excepción, pero aún no se ha encontrado respuesta a los dos problemas que se mencionaron en la tercera alternativa de solución. Para detener la ejecución de un método donde se ha presentado alguna excepción y dar a conocer esta situación (la que se denomina lanzar una Excepción) se debe emplear la instrucción throw. Esta instrucción requiere, además, del objeto Exception a lanzar. El código del método constructor de la clase Empleado quedaría de la siguiente manera: public Empleado(String cedula, String apellido, String nombre){ if (cedula.trim().equals(“”)) throw new Exception(“Valor de la cédula no válido”);
}
this.cedula = cedula; setApellido(apellido); setNombre(nombre);
Una vez generada la excepción sólo queda definir el código que la manejara; para este caso particular el manejo simplemente consistirá en informarle la situación al usuario. En este momento debe realizarse la siguiente pregunta: tiene la clase Empleado la información suficiente para hacer el manejo del error, es decir, puede decidir la clase Empleado si presenta el error en consola o en una ventana. La respuesta es obvia: no; como esta clase puede ser empleada por cualquier otra, desconoce el tipo de interfaz de usuario que la puede terminar utilizando. Si la clase no puede hacer el manejo del error, deberá delegarle esta función al objeto de la clase que invocó el método constructor de Empleado, es decir, se debe delegar el manejo del error al método constructor de cada una de las subclases de Empleado. En Java, la delegación del procesamiento del error se logra definiendo el lanzamiento de los errores que en el mismo método se presenten, más la adición a la declaración del método de la instrucción throws seguida del tipo de error que se estará lanzando. Teniendo en cuenta lo anterior, el código del método constructor de la clase Empleado quedaría de la siguiente manera: public Empleado(String cedula, String apellido, String nombre) throws Exception{ if (cedula.trim().equals(“”)) throw new Exception(“Valor de la cédula no válido”);
175
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
this.cedula = cedula;
}
setApellido(apellido); setNombre(nombre);
Del anterior código cabe resaltar que la instrucción throws se define a nivel del método que no va a hacer el manejo de la excepción, no a nivel de la definición de la clase. La teoría de lanzamiento de excepciones define que ahora la excepción será lanzada al método constructor de las subclases de Empleado. En este momento se debe volver a repetir la pregunta: ¿tiene la subclase EmpleadoXHora (se toma como ejemplo esta clase, pero el análisis puede extenderse tanto a EmpleadoXComision como a EmpleadoSueldoFijo) la información suficiente para el manejo del error? La respuesta vuelve a ser no, por consiguiente este método debe seguir lanzando el error, hasta que llegue al método de la clase que pueda hacer su manejo. Las definiciones de los métodos constructores de las subclases de Empleado deberían quedar de la siguiente manera: public EmpleadoXHora(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora) throws Exception {
}
super(cedula, apellido, nombre); setSueldoXHora(sueldoXHora); setHorasTrabajadas(horasTrabajadas);
public EmpleadoXComision(String cedula, String apellido, String nombre, double sueldoBasico, double porcentaje, double ventasTotales) throws Exception {
}
super(cedula, apellido, nombre); setSueldoBasico(sueldoBasico); setPorcentaje(porcentaje); setVentasTotales(ventasTotales);
public EmpleadoSueldoFijo(String cedula, String apellido, String nombre, double sueldoFijo) throws Exception {
}
super(cedula, apellido, nombre); setSueldoFijo(sueldoFijo);
Es conveniente resaltar del anterior código las instrucciones super que invocan al constructor de la superclase de cada una de las clases que allí aparecen. Es este método el que eventualmente, y dependiendo de los valores que se pasen, el que puede generar una excepción. Como ninguno de los constructores de las subclases puede manejar la excepción esta será lanzada a los métodos de los objetos que invocan dichos métodos; en este caso, los métodos adicionarEmpleadoXHora, adicionarEmpleadoXComision y adicionarEmpleadoSueldoFijo de la clase Empresa. Como esta clase tampoco posee información para realizar un correcto manejo del error, será necesario que estos métodos también lancen las excepciones.
176
2. TÓPICOS AVANZADOS
Con todo el código definido, ahora la excepción llegará a cualquier método de la clase que haya invocado alguno de los métodos adicionar de la clase Empresa, es decir, ahora la excepción ha llegado a la clase Nomina01. Al repetir la pregunta de si está clase tiene la información suficiente para hacer el manejo de la excepción, la respuesta sería sí, en este caso. Es conveniente hacer un alto en este punto y analizar que con este mecanismo de manejo de excepciones se ha obtenido la ventaja que ofrecía la tercera alternativa de solución (presentada anteriormente) y, además, se han corregido las dos grandes deficiencias que esta poseía.
2.2.2. Captura de excepciones El proceso contrario a la generación y lanzamiento de excepciones es conocido como la captura de la excepción. A través de este proceso una clase maneja el error y, además, le permite reanudar la ejecución del programa. Se requiere la utilización de varios tipos de bloques de código para llevar a cabo este proceso. Hasta el momento se conoce que durante la ejecución de un programa puede haber métodos que ante determinadas situaciones lancen excepciones, es decir, son métodos que pueden o no fallar. El mecanismo de manejo de errores requiere que el objeto de la clase que vaya a hacer el manejo del error incluya la invocación del método (o métodos) dentro de un bloque de código que debe ser nombrado con la palabra reservada try. La intención de este bloque de código es definir una o un conjunto de instrucciones que el programa debe intentar ejecutar (se menciona como un intento porque no es seguro que siempre se puedan ejecutar). El método main de la clase Nomina01 debería quedar de la siguiente manera (se presentará únicamente el código de la validación para los EmpleadosXHora con el fin de no despistar la atención del lector con tanto código; sin embargo, las instrucciones que se definen deben ser extendidas para los restantes tipos de empleados): for(int i=0;i
Dentro del bloque de código try, pueden incluirse, además, instrucciones que no lancen
177
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
excepciones. En caso de que alguna de las instrucciones definidas dentro del try falle, no se ejecutará el resto de instrucciones que dentro de él se encuentren definidas. Lo que debe tenerse pendiente es que este bloque de código como tal no especifica de qué manera se realizará el manejo de la excepción. Después de que un método lanza una excepción dentro de un bloque try, el flujo de control intentará encontrar un bloque catch que maneje el error. El bloque catch es el bloque de código directo responsable del manejo de la excepción. Este tipo de bloque de código debe definirse con la inclusión del tipo de excepción que capturará y, además, asignando un nombre de referencia al objeto excepción (recordemos que este objeto representa el error y que contiene información acerca del mismo) para permitir su manipulación. El código debe modificarse como se muestra a continuación: for(int i=0;i
Del anterior código es conveniente resaltar los siguientes puntos: • Note la definición del bloque de código catch. Dicho bloque captura objetos Exception y el objeto que se capture queda disponible al interior del bloque bajo la referencia de nombre e. • Esta clase sí interactúa directamente con el usuario de la aplicación y puede decidir qué hacer con esa situación excepcional; en ese caso, el manejo es simple: se le despliega el mensaje por pantalla al usuario y, además, se decrementa el contador del for para que vuelva la petición de introducir los datos del empleado. • El mensaje que se le despliega al usuario se extrae del objeto Exception capturado empleando el método getMessage(). • Después de ejecutado el código del manejo del error, la aplicación supone que se recuperó de este mismo y continúa realizando de las instrucciones que se encuentren después del bloque de código catch. La ejecución no se reanuda en el punto donde se suspendió.
178
2. TÓPICOS AVANZADOS
Existe un bloque de código adicional que puede definirse dentro del manejo de excepciones. El bloque de código finally permite definir instrucciones que se ejecutan, bien sea que se lancen o no excepciones. Dentro de este bloque de código se definen instrucciones que siempre deben ejecutarse. Considere el siguiente ejemplo: for(int i=0;i
Sin importar si las instrucciones dentro del try lanzan o no excepciones, siempre se imprimirá por pantalla el conjunto de asteriscos para delimitar los datos de los distintos empleados.
2.2.3. Definición de excepciones personales En Java, el mecanismo de manejo de excepciones permite que en caso de ser requerido un usuario defina sus propios tipos de excepciones. Esto se puede lograr definiendo una clase que extienda a Exception. Para el ejemplo que se está trabajando suponga que se desea manejar en una excepción no sólo el mensaje del error, sino también un atributo donde se pueda explicar el motivo del error que lo originan: Considere el siguiente código: public class ExcepcionAmpliada extends Exception{ private String descripcion; public ExcepcionAmpliada(String mensaje){ super(mensaje); this.descripcion = “”; } public ExcepcionAmpliada(String mensaje, String descripción){ super(mensaje); this.descripcion = descripcion;
179
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
}
}
public String getDescripcion(){ return descripción; }
Del anterior código es conveniente resaltar la definición de dos constructores, que reciben ambos como parámetro el mensaje de la excepción e invocan el constructor de la superclase Exception, pasando dicha información. La generación y lanzamiento de las excepciones definidas por el propio usuario sigue el mismo mecanismo definido para las excepciones generales, es decir, se requiere primero crear un objeto del tipo de la excepción específica y luego lanzarlo con la instrucción throw. Si un método no va a hacer el manejo de la excepción, debe especificar lo empleando el lanzamiento del tipo de la excepción a través de la palabra reservada throws. Suponga que para el caso en análisis se desea además validar que el número de la cedula no sea un valor negativo. Esto se podría lograr a través de la definición del siguiente código en el constructor de la clase Empleado. public Empleado(String cedula, String apellido, String nombre) throws Exception{ if (cedula.trim().equals(“”)) throw new Exception(“Valor de la cédula no válido”); if (Integer.parseInt(cedula)<0) throw new ExcepcionAmpliada(“Valor de cedula no valido”, “La cedula no puede tener un valor negativo”); this.cedula = cedula;
}
180
setApellido(apellido); setNombre(nombre);
2. TÓPICOS AVANZADOS
sobre el lanzamiento de exCepCiones En caso de que un segmento de código lance varios tipos de excepciones se deben especificar en la instrucción throws separadas por coma; es decir la definición del método debió haber sido la siguiente: public Empleado(String cedula, String apellido, String nombre) throws Exception, ExcepcionAmpliada
Sin embargo, la definición que se estableció en el código que se presenta antes de este apartado fue: public Empleado(String cedula, String apellido, String nombre) throws Exception
Para el caso de análisis, la anterior definición es correcta, porque el método está especificando que se lanzarán objetos de la clase Exception, y debido a la herencia todo objeto de la clase
ExcepcionAmpliada es un tipo de objeto de la clase Exception. En este caso se está utilizando
una de las propiedades de la herencia que define que un objeto de una subclase puede reemplazar a un objeto de una superclase.
Considérese la siguiente definición del método: public Empleado(String cedula, String apellido, String nombre) throws ExcepcionAmpliada
Esta definición sí presenta problemas debido a que no todas las excepciones que lanza el método son de tipo ExcepcionAmpliada. Si el código del método lanzase un objeto de tipo
Exception, se escenifica el caso en el que un objeto de una superclase trata de reemplazar a un
objeto de la subclase.
181
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
sobre la definiCión de varios bloques CatCh Así como a través de la instrucción throws se puede especificar el lanzamiento de uno o varios tipos de excepciones, el manejo de excepciones en Java posibilita, también, definir código de manejo de error particular para cada uno de los tipos de errores. Para implementar esta situación es necesario añadir más bloques de código catch específicos para cada tipo de excepción. A manera de ejemplo, examine el siguiente fragmento de código modificado de la clase Nomina1: for(int i=0;i
Del anterior código es conveniente mencionar los siguientes puntos: •
•
182
En caso de presentarse el lanzamiento de alguna excepción en la ejecución del método adicionarEmpleadoXHora, el mecanismo de manejo de errores buscará el primer catch en donde pueda hacerse este manejo; después de ejecutado ese bloque no se ejecutará ningún otro de los allí definidos. Nótese que ambos bloques catch definidos nombran a la excepción que capturan con el nombre de variable e. El ámbito de esta variable está supeditado al bloque de código donde fue definida, es decir, que fuera de dicho bloque de código la variable no existe.
2. TÓPICOS AVANZADOS
•
•
•
En caso de presentarse una excepción de tipo ExcepcionAmpliada, se le desplegará al usuario el mensaje del error y, además, su propia descripción. Esto puede realizarse debido a que este tipo de excepción maneja dicha información. En caso de presentarse una excepción de tipo Exception, como por herencia un objeto de una superclase no puede reemplazar a un objeto de una subclase, no se entraría en el primer catch. En este escenario el manejo de la excepción sería realizado por el segundo bloque catch. En caso de no presentarse excepciones en la ejecución del método adicionarEmpleadoXHora, ninguna de las instrucciones dentro del bloque catch se ejecutarían.
La forma en que fue descrito el mecanismo de manejo de errores podría plantear dudas en caso de que se tuvieran las siguientes líneas de código: for(int i=0;i
¿Cómo funcionaría el mecanismo de manejo de errores en caso de que el método adicionarEmpleadoXHora lanzara una excepción de tipo ExcepcionAmpliada? Al lanzarse la excepción, el mecanismo buscará el primer bloque catch que pueda manejar el error, en este caso, y aunque existe un catch específico para objetos de tipo ExcepcionAmpliada, el manejo del error sería efectuado por el primer bloque catch, debido a que por herencia un objeto de una subclase es también un tipo de objeto de la superclase.
183
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
Esta situación plantearía el siguiente problema, el código del segundo catch no se ejecutaría en ningún caso. Java detecta y marca estos errores en tiempo de compilación. Como el orden de estos bloques sí influye en el mecanismo de manejo de excepciones, entonces la norma general es la de ubicar los bloques catch de las excepciones más específicas antes de los bloques catch de las excepciones más generales.
sobre la Captura y posterior lanzamiento de exCepCiones Considérese la siguiente implementación alternativa para el método adicionarEmpleadoXHora de la clase Empresa. public void adicionarEmpleadoXHora(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora) throws Exception{
}
try{ empleados.add (new EmpleadoXHora(cedula, apellido, nombre, horasTrabajadas, sueldoXHora) ); }catch(ExcepcionAmpliada e){ throw new Exception(e.getMessage()); }
Este código especifica que en caso de producirse una excepción de tipo ExcepcionAmpliada, esta será capturada, se creará y lanzará un nuevo objeto de tipo Exception. En este caso, simplemente se ha cambiado una excepción por otra. El mecanismo de manejo de excepciones de Java posibilita la definición de este tipo acciones, así como también permite capturar una excepción y volver a lanzar el mismo objeto dentro del propio bloque catch. En ocasiones, este tipo de instrucciones se emplean para realizar algún proceso de control cuando se presenta una excepción pero sin concluir el manejo del error de la misma.
184
2. TÓPICOS AVANZADOS
sobre la funCionalidad del bloque finally El lector cuidadoso y perspicaz debe estarse preguntando cuál es la funcionalidad del bloque finally, si al final resulta lo mismo ubicar el código de dicho bloque fuera de los bloques catch. Para ejemplificar, esta situación considere el siguiente código: for(int i=0;i
Al final se obtiene el mismo resultado definiendo el código anterior o definiendo el siguiente: for(int i=0;i
185
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
try{ pyme.adicionarEmpleadoXHora(cedula, apellido, nombre, horas,sueldo); }catch(Exception e){ System.out.println(e.getMessage()); i--; } System.out.println(“*******************”); }
¿Cuál es la finalidad del bloque finally, si todo el código que en el se ubique puede perfectamente existir fuera de el? La respuesta a esta pregunta se encuentra relacionada con lo presentado en el apartado sobre la captura y posterior lanzamiento de las excepciones. Aunque en la mayoría de los casos se obtiene el mismo resultado al ubicar el código fuera del bloque finally, existen algunos donde el resultado es totalmente distinto. Por ejemplo, considere el siguiente código: for(int i=0;i
En caso de presentarse una excepción en la ejecución del método adicionarEmpleadoXHora de la clase Empresa, esta será capturada en el catch respectivo cuya única acción define su nuevo lanzamiento. Como la excepción se vuelve a lanzar no se ejecuta ninguna otra instrucción dentro de dicho método, es decir, no se imprime el conjunto de asteriscos. Pero si el código del método fuera como el que se presenta a continuación,
186
2. TÓPICOS AVANZADOS
for(int i=0;i
En este caso, al presentarse un error en el método adicionarEmpleadoXHora el error se captura y se relanza, pero antes de activar el mecanismo de lanzamiento se ejecuta el código definido dentro del bloque finally, es decir, se imprimen por pantalla el conjunto de asteriscos.
187
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
Contraste entre apliCaCiones Con y sin maneJo de errores sobre la Captura y posterior lanzamiento de exCepCiones
Se podría pensar que el manejo de las situaciones excepcionales bajo este mecanismo puede aumentar la complejidad del código, pero la realidad es totalmente opuesta. Considere el siguiente ejemplo, se desea definir un método en una clase que permita copiar el contenido de un archivo a otro. El código que se presenta a continuación presenta parte de las instrucciones de dicho método; por facilidad se encapsula cada uno de los comportamientos necesarios en métodos adicionales con nombres sumamente descriptivos. abrirDeLectura(archivo1); copiarContenidoDeArchivo1AMemoria(); abrirDeEscritura(archivo2); copiarContenidoDeMemoriaAArchivo2(); cerrarArchivo(archivo2); cerrarArchivo(archivo1);
El anterior código supone que el método debe conocer dos variables de tipo String correspondientes a archivo1 y archivo2 en donde se guarda el nombre del archivo a copiar y el nombre del nuevo archivo, respectivamente. Ahora bien, el código que se muestra no tiene en cuenta ninguna situación anormal. Generalmente, los desarrolladores tienden a manejar estas situaciones modificando la signatura de los métodos para que estos devuelvan valores numéricos que indiquen el error que ha sucedido7. Existen algunas variantes a este mecanismo de manejo de errores, pero al final la esencia de la solución es la misma. Aplicado al ejemplo que se está trabajando, se puede suponer lo siguiente: •
•
•
188
El método abrirDeLectura devuelve un valor entero. Si el valor que devuelve es 0, significa que el método se ejecutó sin contratiempos; si el valor que devuelve es 1, significa que el archivo especificado no se pudo abrir de lectura porque no existe en el sistema de archivos; si el valor que devuelve el método es 2, significa que no se tienen permisos suficientes en el sistema de archivo para abrir dicho archivo. El método abrirDeEscritura devuelve un valor entero. Si el valor que devuelve es 0, significa que el método se ejecutó sin contratiempos; si el valor que devuelve es 1 significa que el archivo no pudo crearse porque ya existe en el sistema de archivos, un archivo con el mismo nombre; si devuelve 2, significa que no se tienen permisos suficientes en el sistema para crear un nuevo archivo. El método copiarContenidoDeArchivo1AMemoria devuelve un valor entero. Si el valor que devuelve es 0, significa que el método se ejecutó sin inconvenientes; si el valor que devuelve es 1, significa que hubo algún tipo de problema de entrada o salida con la lectura del contenido del archivo.
2. TÓPICOS AVANZADOS
•
El método copiarContenidoDeMemoriaAArchivo2 devuelve un valor entero. Si el valor que devuelve es 0 significa que el método se ejecutó sin inconvenientes; si el valor que devuelve es 1, significa que se presentó algún tipo de problema de entrada o salida con la escritura del archivo.
Con las consideraciones mencionadas sería necesario redefinir el código del método de la siguiente manera: int v1 = abrirDeLectura(archivo1); if (v1==1) System.out.println(“El archivo no existe”); else if (v1==2) System.out.println(“Permisos insuficientes”); else if (v1==0){ int v2 = copiarContenidoDeArchivo1AMemoria(); if (v2==1) System.out.println(“Problemas de entrada/salida”); else if (v2==0){ int v3 = abrirDeEscritura(archivo2); if (v3==1) System.out.println(“Ya existe archivo con el nombre especificado”); else if (v3==2) System.out.println(“Permisos insuficientes”); else if (v3==0){ int v4 = copiarContenidoDeMemoriaAArchivo2(); if (v4==1) System.out.println(“Problemas de entrada/salida”); cerrarArchivo(archivo2); } } cerrarArchivo(archivo1); }
Ahora cuando se contempla el manejo de situaciones anormales el método es más complejo de entender y, por ende, en caso de ser necesario, más difícil de corregir. Como simple ejercicio, considérese qué tanto se vería afectado el código en caso de que el método cerrarArchivo también retornara un valor indicando si el método se ejecutó bien o no. ¿Cómo sería la implementación del mismo problema si se trabajara con el mecanismo de manejo de excepciones? La solución que se muestra a continuación supone que los métodos lanzan excepciones para cada uno de los casos anormales descritos en párrafos anteriores.
189
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
try{ abrirDeLectura(archivo1); copiarContenidoDeArchivo1AMemoria(); abrirDeEscritura(archivo2); copiarContenidoDeMemoriaAArchivo2(); }catch(Exception e){ System.out.println(e.getMessage()); }finally{ cerrarArchivo(archivo2); cerrarArchivo(archivo1); }
Obviamente, la solución que emplea el mecanismo de manejo de excepciones es más simple, más fácil de entender y por ende más fácil de corregir.
sobre las exCepCiones no Chequeadas en Java Java tiene una clase especial de excepciones, que así se denominan, no chequeadas. Estas excepciones se definen a partir de la clase RuntimeExcepction que, es, a su vez, una subclase de Exception. Objetos creados a partir de esta clase representan errores en la programación, de los cuales no se espera que la aplicación se pueda recuperar. La particularidad de este tipo de excepciones es que no es necesario especificarlas como parte de la instrucción throws en la declaración del método, pero aun así cumplen con todo el proceso definido en el mecanismo de manejo de excepciones; en otras palabras, los métodos las lanzan, pero no hay necesidad de especificarlas en la signatura del método. Existe una gran variedad de este tipo de excepciones, entre las que se encuentran: • • • •
NullPointerException: se produce al intentar ejecutar un método o acceder a una propiedad empleando una referencia que no apunta a ningún objeto. IndexOutofBoundsException: se produce al intentar acceder a una posición inexistente dentro de un array. ClassCastException: se produce al intentar convertir una referencia de un tipo a otro, cuando la conversión no es válida. NumberFormatException: se produce al especificar un formato de número no válido, por ejemplo, cuando se desea convertir el String con la cadena vacía a un número.
Generalmente cuando se producen errores de este tipo, es necesario detener la aplicación y corregir la lógica de codificación de la misma.
190
2. TÓPICOS AVANZADOS
deCimoterCera
aproximaCión a la apliCaCión del CálCulo de la
nómina empleando programaCión orientada a obJetos
En esta implementación de la aplicación se adiciona el manejo de excepciones, lo que repercute en cambios en la mayoría de las clases, más que todo en las signaturas de algunos de los métodos. La clase Nomina01 si se ve fuertemente modificada ya que se adiciona código para el manejo de errores. El código que aparece en negrilla resalta las modificaciones efectuadas con respecto a la anterior aproximación. public interface PersonaAsalariada{ public double calcularSalario(); }
public abstract class Empleado implements PersonaAsalariada{ protected final String cedula; private String apellido; private String nombre; public Empleado(String cedula, String apellido, String nombre) throws Exception{ if (cedula.trim().equals(“”)) throw new Exception(“valor de cedula invalido”); if (cedula.matches(“.*[a-zA-Z].*”)) throw new Exception(“La cedula no puede tener caracteres”); this.cedula = cedula;
}
setApellido(apellido); setNombre(nombre);
public final String getCedula(){ return cedula; } public final String getApellido(){ return apellido; } public final void setApellido(String apellido){ if (apellido!=null) this.apellido = apellido; else this.apellido=””; }
191
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
public final String getNombre(){ return nombre; } public final void setNombre(String nombre){ if (nombre!=null) this.nombre = nombre; else this.nombre=””; } }
public class EmpleadoXHora extends Empleado{ private double horasTrabajadas; private double sueldoXHora; public EmpleadoXHora(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora) throws Exception { super(cedula, apellido, nombre); setSueldoXHora(sueldoXHora); setHorasTrabajadas(horasTrabajadas); } public double calcularSalario(){ return horasTrabajadas * sueldoXHora; } public double getHorasTrabajadas(){ return horasTrabajadas; } public void setHorasTrabajadas(double horas ){
}
if (horas>=0) this.horasTrabajadas = horas; else this.horasTrabajadas = 0;
public double getSueldoXHora(){ return sueldoXHora; } public void setSueldoXHora(double sueldo ){ if (sueldo>=0) this.sueldoXHora = sueldo; else this.sueldoXHora = 0; } public boolean equals(Object o){ if (!(o instanceof EmpleadoXHora)) return false; EmpleadoXHora temp = (EmpleadoXHora)o;
192
2. TÓPICOS AVANZADOS
if if if if if }
( !this.getCedula().equals(temp.getCedula()) ) return false; ( !this.getApellido().equals(temp.getApellido()) ) return false; ( !this.getNombre().equals(temp.getNombre()) ) return false; ( this.getSueldoXHora()!=temp.getSueldoXHora() ) return false; ( this.getHorasTrabajadas()!=temp.getHorasTrabajadas() ) return false; return true;
}
public class EmpleadoXComision extends Empleado{ private double sueldoBasico; private double porcentaje; private double ventasTotales; public EmpleadoXComision(String cedula, String apellido, String nombre, double sueldoBasico, double porcentaje, double ventasTotales) throws Exception {
}
super(cedula, apellido, nombre); setSueldoBasico(sueldoBasico); setPorcentaje(porcentaje); setVentasTotales(ventasTotales);
public double calcularSalario(){ return sueldoBasico + porcentaje * ventasTotales; } public double getSueldoBasico(){ return sueldoBasico; } public void setSueldoBasico(double sueldo){ if (sueldo>=0) this.sueldoBasico = sueldo; else this.sueldoBasico = 0; } public double getPorcentaje(){ return porcentaje; } public void setPorcentaje(double porcentaje ){ if (porcentaje>=0 && porcentaje<=1) this.porcentaje = porcentaje; else this.porcentaje = 0; }
193
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
public double getVentasTotales(){ return ventasTotales; } public void setVentasTotales(double ventas){ if (ventas>=0) this.ventasTotales = ventas; else this.ventasTotales = 0; } public boolean equals(Object o){ if (!(o instanceof EmpleadoXComision)) return false; EmpleadoXComision temp = (EmpleadoXComision)o; if if if if if if
( !this.getCedula().equals(temp.getCedula()) ) return false; ( !this.getApellido().equals(temp.getApellido()) ) return false; ( !this.getNombre().equals(temp.getNombre()) ) return false; ( this.getSueldoBasico()!=temp.getSueldoBasico() ) return false; ( this.getPorcentaje()!=temp.getPorcentaje() ) return false; ( this.getVentasTotales()!=temp.getVentasTotales() ) return false;
}
return true;
}
public class EmpleadoSueldoFijo extends Empleado{ private double sueldoFijo; public EmpleadoSueldoFijo(String cedula, String apellido, String nombre, double sueldoFijo) throws Exception { super(cedula, apellido, nombre); setSueldoFijo(sueldoFijo); } public double calcularSalario(){ return sueldoFijo; } public double getSueldoFijo(){ return sueldoFijo; } public void setSueldoFijo(double sueldo){ if (sueldo>=0) this.sueldoFijo = sueldo; else this.sueldoFijo = 0; }
194
2. TÓPICOS AVANZADOS
public boolean equals(Object o){ if (!(o instanceof EmpleadoSueldoFijo)) return false; EmpleadoSueldoFijo temp = (EmpleadoSueldoFijo)o; if if if if }
( ( ( (
!this.getCedula().equals(temp.getCedula()) ) return false; !this.getApellido().equals(temp.getApellido()) ) return false; !this.getNombre().equals(temp.getNombre()) ) return false; this.getSueldoFijo()!=temp.getSueldoFijo() ) return false;
return true;
}
import java.util.ArrayList; public class Empresa{ final private String nit; private String nombre; private String direccion; private ArrayList empleados; public Empresa(String nit, String nombre, String direccion){ if (nit!=null) this.nit = nit; else this.nit=””; setNombre(nombre); setDireccion(direccion); empleados = new ArrayList(); } public Empresa(String nit){ if (nit!=null) this.nit = nit; else this.nit=””;
}
setNombre(“”); setDireccion(“”); empleados = new ArrayList ();
public Empresa(String nit, int numero){ if (nit!=null) this.nit = nit; else this.nit=””;
}
setNombre(“”); setDireccion(“”); empleados = new ArrayList(numero);
195
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
public String getNit(){ return nit; } public String getNombre(){ return nombre; } public void setNombre(String nombre){ if (nombre!=null) this.nombre = nombre; else this.nombre=””; } public String getDireccion(){ return direccion; } public void setDireccion(String direccion){ if (direccion!=null) this.direccion = direccion; else this.direccion=””; } public int getNumeroEmpleados(){ return empleados.size(); } public void adicionarEmpleadoXHora(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora) throws Exception { empleados.add (new EmpleadoXHora(cedula, apellido, nombre, horasTrabajadas, sueldoXHora) ); } public void adicionarEmpleadoXComision(String cedula, String apellido, String nombre, double sueldoBasico, double porcentaje, double ventasTotales) throws Exception { empleados.add( new EmpleadoXComision(cedula, apellido, nombre, sueldoBasico, porcentaje, ventasTotales) ); } public void adicionarEmpleadoSueldoFijo(String cedula, String apellido, String nombre, double sueldoFijo) throws Exception { empleados.add( new EmpleadoSueldoFijo(cedula, apellido, nombre, sueldoFijo) ); } public double calcularNominaTotal(){ double total = 0; for (int i=0;i
196
2. TÓPICOS AVANZADOS
import java.io.BufferedReader; import java.io.InputStreamReader; public class Nomina1{ public static void main(String[] args) throws Exception{ Empresa pyme = new Empresa(“123”,”Acme inc.”,”805 Silicon Valley”); String cedula, apellido, nombre; double total = 0; BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) ); System.out.print(“Digite numero de empleados sueldo por hora: “); int numeroEmpleados1 = Integer.valueOf(br.readLine()).intValue(); for(int i=0;i
}
try{ pyme.adicionarEmpleadoXHora(cedula, apellido, nombre, horas, sueldo); }catch(Exception e){ System.out.println(e.getMessage()); i--; } System.out.println(“*******************”);
System.out.print(“Digite numero de empleados por comision: “); int numeroEmpleados2 = Integer.valueOf(br.readLine()).intValue(); for(int i=0;i
197
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
double sueldo, porcentaje, ventas; System.out.print(“\nDigite la cedula del empleado: “ ); cedula = br.readLine(); System.out.print(“Digite el apellido del empleado: “ ); apellido = br.readLine(); System.out.print(“Digite el nombre del empleado: “ ); nombre = br.readLine(); System.out.print(“Digite sueldo basico empleado: “); sueldo = Double.valueOf(br.readLine()).doubleValue(); System.out.print(“Digite porcentaje del empleado: “ ); porcentaje = Double.valueOf(br.readLine()).doubleValue(); System.out.print(“Digite ventas totales del empleado: “ ); ventas = Double.valueOf(br.readLine()).doubleValue();
}
try{ pyme.adicionarEmpleadoXComision(cedula, apellido, nombre, sueldo, porcentaje, ventas); }catch(Exception e){ System.out.println(e.getMessage()); i--; } System.out.println(“*******************”);
System.out.print(“Digite numero de empleados sueldo fijo: “); int numeroEmpleados3 = Integer.valueOf(br.readLine()).intValue(); for(int i=0;i
}
try{ pyme.adicionarEmpleadoSueldoFijo(cedula, apellido, nombre, sueldo); }catch(Exception e){ System.out.println(e.getMessage()); i--; } System.out.println(“*******************”);
total = pyme.calcularNominaTotal(); System.out.println(“\nLa nómina total es: “+ total); }
198
}
3.
CREACIÓN DE INTERFACES GRÁFICAS DE USUARIO
La interfaz de usuario para la aplicación del cálculo de la nómina es hasta ahora supremamente sencilla. Todo el procesamiento se realiza a través de la línea de comandos, allí se captura y se le despliega la información al usuario. El propósito de este capítulo es presentar características básicas de creación de interfaces gráficas de usuario en Java empleando Swing. La tecnología Swing corresponde a un amplio conjunto de herramientas para la construcción de interfaces gráficas de usuario (GUI, del inglés Graphic User Interface) que las vuelve interactivas. Swing corresponde a la evolución de la tecnología AWT que se empleaba anteriormente en Java.
199
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
sobre el diseño y desarrollo de interfaCes gráfiCas de usuario (gui) La mayoría de lenguajes de programación brindan características para la definición de las GUI. Para facilitar el diseño y desarrollo de este tipo de elementos, los lenguajes de programación trabajan con avanzadas herramientas gráficas donde el usuario solamente requiere arrastrar los elementos a un formulario para después asignarle comportamiento como respuesta a los eventos de estos (por ejemplo, cuando se hace clic en ellos, cuando se presionen teclas, etc.).
Este tipo de forma de desarrollo de las GUI generalmente da origen a dos problemas:
• Duplicación de código. Imagine el siguiente escenario: un programador necesita desarrollar dos interfaces de usuario que en esencia son muy parecidas (suponga que una de ellas se diferencia de la otra en que adiciona un campo extra). El desarrollador seguramente empleará la herramienta de diseño para crear la primera de las interfaces y luego copiará y pegará la distribución gráfica en la otra. En la segunda interfaz, adicionará los elementos que sean necesarios. Prácticamente el código de la primera interfaz se ha duplicado en la segunda.
200
3. CREACIÓN DE INTERFACES GRÁFICAS DE USUARIOS
La duplicación de código no es inherentemente mala; el problema comienza cuando se requiere realizarle algún tipo de ajuste o modificación, pues esta misma modificación debe ser extendida a los restantes lugares donde se haya duplicado el código. Generalmente, lo que termina sucediendo es que un programador olvida realizar el cambio en el código en alguno de los lugares.
•
Generación de interfaces gráficas de componentes estáticos. Después de finalizar el desarrollo de una GUI bajo el enfoque que se está presentando, un diseñador se enfrenta ante el dilema de si bloquea o no el tamaño de la interfaz gráfica. Si no lo hace, cuando el usuario cambie el tamaño de la ventana aparecerá un gran espacio desaprovechado (como se muestra en la siguiente gráfica).
La alternativa de permitir la redimensión del cambio reajustando los elementos de la ventana no es ni siquiera considerada por los desarrolladores debido al gran trabajo que este demandaría realizarlo en cada ventana. La tecnología para el desarrollo de las GUI en Java busca corregir ambas situaciones.
3.1. cOmpOnentes gráficOs En Java todos los elementos que se utilizan son objetos (a excepción de los tipos de datos primitivos) por lo que no queda muy difícil deducir que los componentes gráficos que se requieren para la creación de las GUI también lo son. Solo basta por conocer los nombres de las clases y paquetes que contienen dichas definiciones, y lo que representan cada una de ellas.
201
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
Este libro se centra en los componentes básicos que se muestran en la siguiente tabla14. Nombre de la clase javax.swing.JFrame
Descripción Esta clase modela al componente gráfico ventana.
Esta clase modela al componente gráfico etiqueta de texto. Un objeto javax.swing.JLabel de esta clase le despliega información al usuario, pero no permite que este la modifique directamente. Esta clase modela al componente gráfico cuadro de texto. Un objeto javax.swing.JTextField de esta clase permite desplegar y, además, acepta información suministrada por el usuario. javax.swing.JButton
javax.swing.JPanel
Esta clase modela al componente gráfico botón. Un JPanel no es un componente gráfico como tal, más bien es un agrupador o contenedor de elementos gráficos. Además, posibilita establecer la posición y tamaño de los componentes registrados a través del uso de un objeto de la interface java.awt. LayoutManager
No tiene sentido el despliegue de componentes gráficos en pantalla de forma aislada sin que estos se encuentren incluidos dentro de una ventana (por ejemplo, desplegar solo un botón que ande flotando por el espacio del escritorio). Por eso uno de los componentes más importantes en el desarrollo de las GUI es JFrame. Analice el siguiente código de definición de una ventana: JFrame f = new JFrame(); f.setSize(300,300); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setVisible(true);
El código anterior crea un nuevo objeto JFrame (ventana); a través del método setSize le establece un tamaño de 300 pixeles de largo y 300 pixeles de ancho; en la siguiente instrucción se establece que al presionar el botón cerrar se debe terminar la aplicación (este método recibe una constante que es suministrada a través del atributo público, estático y final de la clase JFrame que lleva por nombre EXIT_ON_CLOSE), y, por último, la instrucción final hace visible al objeto en la pantalla. Como regla general la última operación de manipulación de una ventana debería ser la invocación al método setVisible. Al ejecutarse el anterior código aparece en la pantalla del usuario una ventana, como la que se muestra en la siguiente gráfica:
14
202
Mayor información sobre componentes gráficos puede ser consultada en la siguiente referencia: http://java.sun.com/ docs/books/tutorial/uiswing/index.html
3. CREACIÓN DE INTERFACES GRÁFICAS DE USUARIOS
Note como solo con la instrucción new JFrame se ha podido crear una ventana que posee un icono (que se muestra en la esquina superior izquierda), una barra de título, los botones para minimizar, maximizar y cerrar, además, la ventana también posee sus bordes; y si se hace clic en alguno de ellos y se reajusta la ventana, esta cambia de tamaño.
sobre la instruCCión de finalizaCión de la apliCaCión Si no se establece a través de la instrucción setDefaultCloseOperation el botón cerrar de una ventana no finaliza la aplicación sino que simplemente vuelve invisible una ventana. Muchas aplicaciones de software basan su funcionamiento en este tipo de comportamiento, como, por ejemplo, considere un programa de reproducción de archivos de sonido. El siguiente código ejemplifica esta situación. JFrame reproductor = new JFrame(“Reproductor”); reproductor.setSize(300,300); reproductor.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); reproductor.setVisible(true); JFrame ecualizador = new JFrame(“Ecualizador”); ecualizador.setSize(300,300); ecualizador.setVisible(true); JFrame listaReproduccion = new JFrame(“Lista de Reproducción”); listaReproduccion.setSize(300,600); listaReproduccion.setVisible(true);
203
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
Sobre el anterior código es conveniente mencionar: •
•
Se toma como supuesto que el software de reproducción además de presentar una ventana para controlar la reproducción (detener, pausar, continuar la reproducción), trabaja con una ventana donde se muestra el ecualizador gráfico y otra ventana donde se despliega la lista de reproducción de los archivos. El código hace uso de un constructor polimórfico de la clase JFrame que recibe como parámetro el nombre a desplegar en la ventana en la barra de título.
Al presionar el botón cerrar en las ventanas denominadas lista de reproducción y ecualizador, simplemente se ocultan los objetos, pues estos no desaparecen de la memoria. Al presionar el botón cerrar de la ventana identificada como reproductor, se cierra la aplicación. La siguiente gráfica muestra el resultado obtenido de la ejecución del código.
204
3. CREACIÓN DE INTERFACES GRÁFICAS DE USUARIOS
sobre el Componente Jpanel Previamente se ha mencionado que un JPanel no es un componente gráfico, sino que más bien hace las veces de un agrupador o contenedor de elementos gráficos. Un ejemplo ayuda a clarificar esta situación; suponga por un momento que un desarrollador requiere diseñar e implementar dos ventanas, como las que se presentan a continuación.
En el apartado sobre el diseño y desarrollo de interfaces gráficas de usuario (GUI) se mencionó que generalmente los desarrolladores optan por duplicar el código que genera dicha interfaz gráfica. Una mejor aproximación a esta situación sería definir una nueva clase que modelara esta porción de la ventana, de tal manera que si se requiere utilizar en una ventana, se instancia un objeto de la clase y se añade.
La clase JPanel se utiliza para representar a un conjunto de elementos gráficos; pero de nada sirve un contenedor de elementos gráficos si no se puede especificar el tamaño y la ubicación de dichos elementos.
205
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
un eJemplo senCillo de la utilizaCión del Componente Jpanel Para entender cómo es el funcionamiento de los JFrame con JPanel considere el código que se presenta a continuación: JFrame f = new JFrame(); JPanel p = new JPanel(); p.add(new JButton(“Boton 1”)); p.add(new JButton(“Boton 2”)); p.add(new JButton(“Boton 3”)); f.getContentPane().add(p); f.setSize(300,300); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setVisible(true);
Al ejecutar el anterior código se obtiene una interfaz gráfica como la que se presenta a continuación:
La mayoría del código de la ventana se ha presentado en anteriores ejemplos, por lo cual no volverá a ser explicado. Las instrucciones que vale la pena resaltar son: • • •
206
Existe una línea de código que crea un nuevo objeto JPanel. Como apenas se acaba de crear, este objeto no almacena referencias a ningún otro componente gráfico. Se le adicionan componentes gráficos a través del método add. En este caso particular se han añadido tres objetos de la clase JButton. No solamente es necesario crear un nuevo objeto JPanel, sino que también es necesario incluirlo dentro de la ventana. Esto se logra utilizando la instrucción getContentPane. add() y si se pasa como referencia el JPanel a desplegar en la ventana.
3. CREACIÓN DE INTERFACES GRÁFICAS DE USUARIOS
sobre la definiCión de Clases que extiendan Jpanel En el apartado anterior, un ejemplo sencillo de la utilización del componente JPanel, se codificó un ejemplo de una interfaz gráfica de usuario; sin embargo, dicho ejemplo puede ser codificado también de la siguiente manera: public class PanelInterno extends JPanel{ public PanelInterno(){ add(new JButton(“Boton 1”)); add(new JButton(“Boton 2”)); add(new JButton(“Boton 3”)); } }
JFrame f = new JFrame(); JPanel p = new PanelInterno(); f.getContentPane().add(p); f.setSize(300,300); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setVisible(true);
La configuración gráfica que se obtiene al ejecutar el anterior código es igual a la que se obtuvo con el código del apartado anterior. La diferencia radical de esta implementación es que se ha definido la clase PanelInterno, que extiende a la clase JPanel en donde se ha encapsulado la configuración gráfica de la ventana. Esta forma de implementación beneficia la reutilización ya que es más sencillo emplear la configuración gráfica encapsulada en la clase PanelInterno en otras ventanas (situación analizada en el apartado Sobre el component JPanel). Por ejemplo, el siguiente código crea dos ventanas que tienen configuraciones gráficas similares sin necesidad de la duplicación del código. JFrame f1 = new JFrame(“Ventana 1”); JPanel p1 = new PanelInterno(); f1.getContentPane().add(p1); f1.setSize(300,300); f1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
207
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
f1.setVisible(true); JFrame f2 = new JFrame(“Ventana 2”); JPanel p2 = new PanelInterno(); f2.getContentPane().add(p2); f2.setSize(300,300); f2.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f2.setVisible(true);
3.2. lAyOuts Uno de los problemas mencionados en el apartado sobre el diseño y desarrollo de interfaces gráficas de usuario (GUI) encuentra solución con la utilización de objetos JPanel. El punto referente a la generación de las GUI de componentes estáticas encuentra solución a través de la utilización de objetos que implementen la interface LayoutManager. Estos objetos son conocidos como Layouts. Definición de Layout Un Layout es un algoritmo que establece ubicación y tamaño de una serie de componentes gráficos.
Java brinda una amplia gama de Layouts. Este libro se ocupará de detallar tres de ellos15: FlowLayout, GridLayout y BorderLayout; y, adicionalmente, se presentará cómo combinar estos elementos para obtener resultados mejores y más atractivos. 15
208
Mayor información sobre los restantes Layouts puede ser consultada en la siguiente referencia: http://java.sun.com/ docs/books/tutorial/uiswing/layout/visual.html
3. CREACIÓN DE INTERFACES GRÁFICAS DE USUARIOS
Antes de entrar a analizar cada uno de los Layouts mencionados es conveniente tener claro lo siguiente. • Un JPanel puede tener definido un solo algoritmo de asignación de tamaño y posición de sus componentes, es decir, un solo Layout. • Para especificar que un JPanel trabajará con un determinado Layout es necesario emplear el método setLayout definido en la clase JPanel. Pasando como parámetro el valor null se especificará que el JPanel no trabajará con un Layout, entonces será necesario definir para cada componente gráfico su ubicación y su respectivo tamaño a través de la utilización del método setBounds . • Para adicionar elementos gráficos a un JPanel se emplea el método add y se pasa como parámetro la referencia al objeto gráfico que se desea añadir. • Para definir que una ventana debe desplegar un conjunto de componentes gráficos especificados en un JPanel debe emplearse el método getContentPane().add y debe pasarse cómo parámetro la referencia al JPanel que se desea desplegar.
3.2.1. FlowLayout Este es uno de los algoritmos de asignación de ubicación más simple y sencillo de utilizar. Como tal no modifica el tamaño de los objetos gráficos, sino que simplemente establece la ubicación de los mismos en un JPanel, y se obtiene a partir de la instanciación de la clase java.awt.FlowLayout. Considérese el siguiente ejemplo: JFrame f = new JFrame(); JPanel p = new JPanel(); p.setLayout(new FlowLayout()); p.add(new JLabel(“Cedula”)); p.add(new JTextField(20)); p.add(new JButton(“Haga clic aca”)); f.setSize(300,200); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.getContentPane().add(p); f.setVisible(true);
Después de ejecutar el anterior código se obtiene la siguiente configuración gráfica:
209
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
Al redimensionar la ventana se obtiene la siguiente configuración gráfica.
La forma en que organiza los componentes este Layout es evidente. Ubica los elementos uno después del otro, teniendo en cuenta el orden en que fueron añadidos al JPanel. Si hay suficiente espacio en la ventana para mostrar los componentes, estos se muestran; en caso contrario, los componentes para los que no hay suficiente espacio se despliegan en la parte de abajo. El comportamiento se repite nuevamente en caso de que no exista suficiente espacio para todos los componentes. Este Layout posee los siguientes tres constructores: • FlowLayout(int alineacion). Crea un nuevo objeto FlowLayout, el parámetro que se suministra indica cómo se alinearán los componentes que se adicionen. Se puede especificar que los componentes queden centrados en el JPanel, o que se alineen hacia la derecha o hacia la izquierda. • FlowLayout(int alineacion, int espacioHorizontal, int espacioVertical). Crea un nuevo objeto FlowLayout, el primer parámetro indica cómo se alinearán los componentes, el segundo parámetro especifica la cantidad de pixeles que separarán a los componentes en forma horizontal y el tercer parámetro especifica la cantidad de pixeles que separarán a los componentes de forma vertical. • FlowLayout(). Crea un nuevo objeto FlowLayout; por defecto, los objetos se centrarán en el JPanel. Considere el siguiente código que presenta una pequeña modificación al ejemplo anterior. JFrame f = new JFrame();
210
3. CREACIÓN DE INTERFACES GRÁFICAS DE USUARIOS
JPanel p = new JPanel(); p.setLayout(new FlowLayout(FlowLayout.RIGHT,5,5)); p.add(new JLabel(“Cedula”)); p.add(new JTextField(20)); p.add(new JButton(“Haga clic aca”)); f.setSize(300,200); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.getContentPane().add(p); f.setVisible(true);
Al ejecutar el código anterior se obtiene la siguiente configuración gráfica. Adicionalmente se muestra la configuración que se obtiene al redimensionar la ventana.
Note como los componentes se encuentran un poco más espaciados y alineados hacia la derecha.
3.2.2. GridLayout Este algoritmo asigna la ubicación y el tamaño a los componentes y se obtiene a partir de la instanciación de la clase java.awt.GridLayout. El algoritmo divide el área del JPanel en una cuadrícula o tabla cuyas dimensiones deben ser especificadas por el usuario. Los componentes gráficos que se añadan al JPanel se ubican en cada una de las celdas de esta cuadrícula y toman todo el espacio disponible de la celda. Considere el siguiente ejemplo: JFrame f = new JFrame(); JPanel p = new JPanel(); p.setLayout(new GridLayout(3,3)); for (int i=1;i<10;i++) p.add(new JButton(i+””)); f.setSize(300,200); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.getContentPane().add(p); f.setVisible(true);
Al ejecutar el anterior código se obtiene la siguiente configuración gráfica. Adicionalmente se muestra la configuración que se obtiene al redimensionar la ventana.
211
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
Note como cada uno de los componentes añadidos adquiere todo el espacio de la celda de la cuadrícula. Este Layout posee los siguientes constructores: • GridLayout(int numFilas, int numCols). Crea un nuevo objeto GridLayout, los parámetros que se reciben especifican la cantidad de filas y columnas que tendrá la cuadrícula. • GridLayout(int
espacioVertical).
numFilas,
int
numCols, int espacioHorizontal, int objeto GridLayout, los parámetros que se reciben
Crea un nuevo especifican la cantidad de filas y columnas, y, adicionalmente, la cantidad de pixeles que separarán a los componentes de forma horizontal y vertical.
• GridLayout(). Crea un nuevo objeto GridLayout por defecto de una fila con una columna. Considere el siguiente código que presenta una pequeña modificación al ejemplo anterior: JFrame f = new JFrame(); JPanel p = new JPanel(); p.setLayout(new GridLayout(3,3,5,5)); for (int i=1;i<10;i++) p.add(new JButton(i+””)); f.setSize(300,200); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.getContentPane().add(p); f.setVisible(true);
212
3. CREACIÓN DE INTERFACES GRÁFICAS DE USUARIOS
Al ejecutar el código anterior se obtiene la siguiente configuración gráfica:
Note el espaciamiento entre las celdas de la cuadrícula.
3.2.3. BorderLayout Este algoritmo asigna la ubicación y el tamaño de los componentes; se obtiene a partir de la instanciación de la clase java.awt.BorderLayout. El algoritmo divide el área del JPanel en cinco regiones: norte, sur, este, oeste y centro. El usuario puede utilizar cualquiera o todas estas regiones; cuando el usuario adicione un componente, debe especificar en cuál de las regiones desea hacerlo. Considere el siguiente ejemplo: JFrame f = new JFrame(); JPanel p = new JPanel(); p.setLayout(new BorderLayout()); p.add(new JButton(“Norte”), BorderLayout.NORTH); p.add(new JButton(“Sur”), BorderLayout.SOUTH); p.add(new JButton(“Este”), BorderLayout.EAST); p.add(new JButton(“Oeste”), BorderLayout.WEST); p.add(new JButton(“Centro”), BorderLayout.CENTER); f.setSize(300,200); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.getContentPane().add(p); f.setVisible(true);
213
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
Al ejecutar el anterior código se obtiene la siguiente configuración gráfica. Adicionalmente se muestra la configuración que se obtiene al redimensionar la ventana. Note cómo se especifica la región donde debe ubicarse un componente a través del uso de atributos estáticos y finales de la clase BorderLayout.
Este algoritmo separa espacio de la ventana para los componentes que inicialmente se ubiquen en las regiones norte y sur, si sobra espacio se lo asigna a los componentes ubicados en las regiones oeste y este, y finalmente el espacio restante es asignado para la región central. Este Layout posee los siguientes dos constructores: • BorderLayout(). Crea un nuevo objeto BorderLayout. • BorderLayout(int espacioHorizontal, int espacioVertical). Crea un nuevo objeto BorderLayout, los parámetros que se reciben especifican la cantidad de pixeles que separarán a los componentes de forma horizontal (espaciamiento entre los componentes ubicados en la región oeste, central y este) y vertical (espaciamiento entre los componentes ubicados en la región norte, central y sur). Considere el siguiente código que presenta una pequeña modificación al ejemplo anterior:
214
3. CREACIÓN DE INTERFACES GRÁFICAS DE USUARIOS
JFrame f = new JFrame(); JPanel p = new JPanel(); p.setLayout(new BorderLayout(5,5)); p.add(new JButton(“Norte”), BorderLayout.NORTH); p.add(new JButton(“Sur”), BorderLayout.SOUTH); p.add(new JButton(“Este”), BorderLayout.EAST); p.add(new JButton(“Oeste”), BorderLayout.WEST); p.add(new JButton(“Centro”), BorderLayout.CENTER); f.setSize(300,200); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.getContentPane().add(p); f.setVisible(true);
Al ejecutar el código anterior se obtiene la siguiente configuración gráfica.
Note el espaciamiento de los componentes del Layout.
215
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
sobre la Jerarquía de organizaCión de las Clases que representan a Componentes gráfiCos
Resulta muy provechoso conocer cómo están organizadas jerárquicamente las clases de los componentes gráficos en Java. La estructura es la que se muestra en el siguiente gráfico. Object
Component
Container add ( Component )
JComponent listenerList:EventListenerList
Window Frame JTextComponent JFrame
JTextField addActionListener(ActionListener)
AbstractButton addActionListener( ActionListener )
JLabel
JPanel
JButton
Los rectángulos representan las clases, la línea que sale de una clase a otra que finaliza en una flecha blanca representa la relación de herencia (debe leerse de la siguiente manera: la clase Object es la superclase de Component, la cual a su vez es la superclase de Container, etc.) y la información que se presenta en algunos rectángulos corresponde a algún atributo o método que posee la clase y que es conveniente resaltar (según la definición de un atributo en el caso de la clase JComponent; en las restantes clases se da conforme a la definición de método). De la anterior gráfica es conveniente resaltar los siguientes puntos:
216
•
La clase JFrame extiende la clase Frame, que a su vez extiende a la clase Window, que a su vez extiende a la clase Container, que a su vez extiende a la clase Component. Esto significa que JFrame es un tipo de Frame, que a su vez es un tipo de Window, que a su vez es un tipo de Container, que a su vez es un tipo de Component.
•
Al extender el anterior análisis a las restantes clases, se puede inferir la siguiente situación: tanto JFrame, como JTextField, como JButton, como JLabel, como JPanel terminan siendo tipos de Component.
3. CREACIÓN DE INTERFACES GRÁFICAS DE USUARIOS
•
La clase Container de forma indirecta termina siendo superclase de JFrame, JTextField, JButton, JLabel y JPanel; por eso si la clase Container define el método add, que recibe como parámetro una referencia de tipo Component, todas las subclases restantes también lo poseen.
•
En la mayoría de códigos de ejemplos presentados en este capítulo para adicionar componentes gráficos a los JPanel se emplea el método add. Este método es precisamente el que gana JPanel de su superclase indirecta Container. Este método recibe como parámetro una referencia de tipo Component, es decir, que se puede adicionar en un JPanel cualquier objeto de una subclase de Component. Eso es lo que hace posible adicionar cualquier tipo de elemento gráfico (como un JLabel, JButton, JTextField, etc.) a un JPanel.
•
El anterior análisis hace pensar en la posibilidad, totalmente válida, de poder incluir un JPanel en otro JPanel. Sacarle provecho a esta situación es lo que flexibiliza el desarrollo de las GUI atractivas visualmente.
•
También podría pensarse en la posibilidad de incluir dentro de un JPanel un objeto de la clase JFrame (relación contraria a la que se ha trabajado en los ejemplos presentados). Este tipo de acciones aunque son válidas a nivel de la estructura jerárquica de las clases, son restringidas a nivel del código en el método add. Si se intenta efectuar una acción de este tipo, se generará el lanzamiento de una excepción.
217
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
sobre la utilizaCión de varios layout La utilización de forma independiente de los Layout presentados anteriormente ayuda a un diseñador a crear interfaces gráficas sencillas, pero a la vez poco atractivas visualmente. Se mejora la calidad de las interfaces si se utilizan todos estos algoritmos de forma conjunta. El problema es que un JPanel sólo puede tener definido un único Layout. La solución pasa por definir la inclusión de un JPanel dentro de otro, y jugar en la definición de los Layouts para cada uno de estos. A manera de ejemplo considere desarrollar una interfaz gráfica para la aplicación del cálculo de la nómina como la que se muestra a continuación.
Obviamente, la anterior interfaz gráfica no puede construirse empleando un solo JPanel. El siguiente gráfico muestra la estructura de la solución; en ella se han utilizado 7 JPanel cada uno con su propio Layout. PanelPrincipal y PanelFormulario emplean un BorderLayout; mientras que PanelInferior, PanelEtiquetas y PanelCampos emplean un GridLayout (sus respectivas dimensiones pueden ser inferidas en la imagen); finalmente, PanelBotones1 y PanelBoton emplean un FlowLayout. El gráfico, además, presenta las regiones que se utilizan de cada BorderLayout y qué componente se inserta en cada una de ellas. En PanelBotones1, se añadirán tres botones para adicionar los diferentes tipos de empleados; en PanelBoton, se ubicará el botón para calcular la nómina de la empresa; en PanelEtiquetas, se ubicarán las etiquetas descriptivas de los campos; y en PanelCampos, se ubicarán los campos.
218
3. CREACIÓN DE INTERFACES GRÁFICAS DE USUARIOS
A continuación se presenta el código de la solución. import import import import import import import import
java.awt.BorderLayout; java.awt.FlowLayout; java.awt.GridLayout; javax.swing.JButton; javax.swing.JFrame; javax.swing.JLabel; javax.swing.JPanel; javax.swing.JTextField;
public class PanelPrincipal extends JPanel { private JTextField txtCedula, txtApellido, txtNombre, txtSueldoXHora, txtHorasTrabajadas, txtSueldoBasico, txtPorcentaje, txtVentasTotales, txtSueldoFijo; private JButton btnAdicionarEmpleadoXHora, btnAdicionarEmpleadoXComision, btnAdicionarEmpleadoSueldoFijo,
219
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
btnCalcularNomina; public PanelPrincipal(){ JPanel panelFormulario = new JPanel(); JPanel panelEtiquetas = new JPanel(); JPanel panelCampos = new JPanel(); JPanel panelInferior = new JPanel(); JPanel panelBotones1 = new JPanel(); JPanel panelBoton = new JPanel(); setLayout(new BorderLayout()); add(panelFormulario, BorderLayout.NORTH); panelFormulario.setLayout(new BorderLayout(10,0)); panelFormulario.add( panelEtiquetas, BorderLayout.WEST ); panelFormulario.add( panelCampos, BorderLayout.CENTER); //------------------------------------------------panelEtiquetas.setLayout(new GridLayout(9,1)); panelEtiquetas.add(new JLabel(“Cédula”)); panelEtiquetas.add(new JLabel(“Apellido”)); panelEtiquetas.add(new JLabel(“Nombre”)); panelEtiquetas.add(new JLabel(“Sueldo X Hora”)); panelEtiquetas.add(new JLabel(“Horas Trabajadas”)); panelEtiquetas.add(new JLabel(“Sueldo Básico”)); panelEtiquetas.add(new JLabel(“Porcentaje”)); panelEtiquetas.add(new JLabel(“Ventas Totales”)); panelEtiquetas.add(new JLabel(“Sueldo Fijo”)); //------------------------------------------------panelCampos.setLayout(new GridLayout(9,1)); panelCampos.add(txtCedula=new JTextField(“”)); panelCampos.add(txtApellido=new JTextField(“”)); panelCampos.add(txtNombre=new JTextField(“”)); panelCampos.add(txtSueldoXHora=new JTextField(“”)); panelCampos.add(txtHorasTrabajadas=new JTextField(“”)); panelCampos.add(txtSueldoBasico=new JTextField(“”)); panelCampos.add(txtPorcentaje=new JTextField(“”)); panelCampos.add(txtVentasTotales=new JTextField(“”)); panelCampos.add(txtSueldoFijo=new JTextField(“”)); add(panelInferior, BorderLayout.SOUTH); panelInferior.setLayout(new GridLayout(2,1)); panelInferior.add(panelBotones1); panelInferior.add(panelBoton); //------------------------------------------------panelBotones1.setLayout(new FlowLayout()); panelBotones1.add(btnAdicionarEmpleadoXHora= new JButton(“Adicionar Empleado X Hora”)); panelBotones1.add(btnAdicionarEmpleadoXComision= new JButton(“Adicionar Empleado X Comision”)); panelBotones1.add(btnAdicionarEmpleadoSueldoFijo= new JButton(“Adicionar Empleado Sueldo Fijo”));
220
3. CREACIÓN DE INTERFACES GRÁFICAS DE USUARIOS
//------------------------------------------------panelBoton.setLayout(new FlowLayout()); panelBoton.add(btnCalcularNomina= new JButton(“Calcular Nomina”)); } }
JFrame f = new JFrame(); f.setSize(670,315); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.getContentPane().add(new PanelPrincipal()); f.setVisible(true);
Sobre el anterior código es conveniente resaltar: La clase PanelPrincipal extiende la clase JPanel, por ello adquiere toda su estructura y comportamiento. PanelPrincipal define como atributos todos los elementos JTextField y JButton de las GUI. Más adelante, cuando en este capítulo se esté trabajando la asignación de comportamiento al presionar los botones, estos atributos serán realmente útiles.
•
Las instrucciones para crear los componentes JTextField y JButton, y adicionarlas al JPanel respectivo se resumen en una instrucción, por ejemplo, panelCampos. add(txtCedula=new JTextField(“”));
•
Para poder lograr una separación entre PanelEtiquetas y PanelCampos se ha utilizado un constructor polimórfico de la clase BorderLayout, que recibe un valor para el espacio horizontal.
•
La ventana que se obtiene al ejecutar el anterior código reajusta sus componentes al cambiar de tamaño. Al aumentar el tamaño de la ventana, se amplía también el espacio de los JTextField para facilitar el ingreso de información.
3.3. bOrdes Las GUI desarrolladas que emplea la teoría presentada anteriormente son capaces de reajustar el tamaño y la posición de los componentes como respuesta a alteraciones en las dimensiones del JPanel. El solo hecho de poder especificar la separación que deben tener los componentes al interior de un JPanel mejora la apariencia gráfica, sin embargo, este espaciamiento no aplica para los límites exteriores del JPanel. Considere la gráfica que se muestra a continuación. Note cómo los óvalos señalan la situación antes mencionada.
221
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
Lograr un espaciamiento en dichas áreas mejoraría aún más la apariencia gráfica de la ventana. Este puede lograrse a través de la utilización de bordes. Los bordes se utilizan no solamente para espaciar los componentes de las orillas, sino que también permiten dibujar líneas; sin embargo, en este libro solo se trabajarán los bordes como elementos para insertar espacio en márgenes externos de un JPanel16. Un borde funciona de la manera como se muestra en el siguiente gráfico:
Suponga que el área que posee el color gris oscuro corresponde al área del JPanel, el área gris claro corresponde al área asignada para el borde. Considere el siguiente ejemplo donde se trabaja con un borde: JFrame f = new JFrame(); JPanel p = new JPanel(); p.setLayout(new GridLayout(1,1)); p.add( new JButton() ); p.setBorder( BorderFactory.createEmptyBorder(15,15,15,15) );
16
222
Mayor información sobre los restantes tipos de bordes y su utilización puede ser consultada en la siguiente referencia: http://java.sun.com/docs/books/tutorial/uiswing/components/border.html
3. CREACIÓN DE INTERFACES GRÁFICAS DE USUARIOS
f.setSize(300,200); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.getContentPane().add(p); f.setVisible(true);
La ejecución del anterior código genera la siguiente ventana:
Es conveniente resaltar en este ejemplo lo siguiente: • Para establecerle un borde a un JPanel se emplea el método setBorder que posee esta clase que recibe como parámetro una referencia de tipo Border. • La creación de un objeto Border no se hace a través de la invocación de un constructor; para hacerlo, se debe invocar un método estático de la clase javax. swing.BorderFactory que permite crear el borde. • El borde que se crea depende del método que se invoque, en el ejemplo se crea un borde vacío (EmptyBorder)17 utilizando el método createEmptyBorder. Este método recibe cuatro parámetros de tipo entero: el primero especifica el espaciamiento en la parte superior, el segundo especifica el espaciamiento hacia la izquierda, el tercero especifica el espaciamiento en la parte inferior y, finalmente, el cuarto especifica el espaciamiento en la parte derecha para el componente.
17
Mayor información sobre los métodos estáticos que pueden invocarse en la clase BorderFactory consulte la siguiente referencia: http://java.sun.com/javase/6/docs/api/javax/swing/BorderFactory.html
223
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
aproximaCión a las gui para la apliCaCión del CálCulo de la nómina En el apartado sobre la utilización de varios Layouts se propuso un prototipo a una GUI para la aplicación del cálculo de la nómina empleando programación orientada a objetos. El código que se presenta a continuación mejora la apariencia gráfica de la ventana adicionando espacios mediante el empleo de bordes. import import import import import import import import import
javax.swing.BorderFactory; java.awt.BorderLayout; java.awt.FlowLayout; java.awt.GridLayout; javax.swing.JButton; javax.swing.JFrame; javax.swing.JLabel; javax.swing.JPanel; javax.swing.JTextField;
public class PanelPrincipal extends JPanel { private JTextField txtCedula, txtApellido, txtNombre, txtSueldoXHora, txtHorasTrabajadas, txtSueldoBasico, txtPorcentaje, txtVentasTotales, txtSueldoFijo; private JButton btnAdicionarEmpleadoXHora, btnAdicionarEmpleadoXComision, btnAdicionarEmpleadoSueldoFijo, btnCalcularNomina; public PanelPrincipal(){ JPanel panelFormulario = new JPanel(); JPanel panelEtiquetas = new JPanel(); JPanel panelCampos = new JPanel(); JPanel panelInferior = new JPanel(); JPanel panelBotones1 = new JPanel(); JPanel panelBoton = new JPanel(); setLayout(new BorderLayout()); add(panelFormulario, BorderLayout.NORTH); panelFormulario.setLayout(new BorderLayout(10,0)); panelFormulario.setBorder( BorderFactory.createEmptyBorder(15,20,0,20) ); panelFormulario.add( panelEtiquetas, BorderLayout.WEST ); panelFormulario.add( panelCampos, BorderLayout.CENTER); //------------------------------------------------panelEtiquetas.setLayout(new GridLayout(9,1)); panelEtiquetas.add(new JLabel(“Cédula”)); panelEtiquetas.add(new JLabel(“Apellido”)); panelEtiquetas.add(new JLabel(“Nombre”)); panelEtiquetas.add(new JLabel(“Sueldo X Hora”)); panelEtiquetas.add(new JLabel(“Horas Trabajadas”)); panelEtiquetas.add(new JLabel(“Sueldo Básico”)); panelEtiquetas.add(new JLabel(“Porcentaje”));
224
3. CREACIÓN DE INTERFACES GRÁFICAS DE USUARIOS
panelEtiquetas.add(new JLabel(“Ventas Totales”)); panelEtiquetas.add(new JLabel(“Sueldo Fijo”)); //------------------------------------------------panelCampos.setLayout(new GridLayout(9,1)); panelCampos.add(txtCedula=new JTextField(“”)); panelCampos.add(txtApellido=new JTextField(“”)); panelCampos.add(txtNombre=new JTextField(“”)); panelCampos.add(txtSueldoXHora=new JTextField(“”)); panelCampos.add(txtHorasTrabajadas=new JTextField(“”)); panelCampos.add(txtSueldoBasico=new JTextField(“”)); panelCampos.add(txtPorcentaje=new JTextField(“”)); panelCampos.add(txtVentasTotales=new JTextField(“”)); panelCampos.add(txtSueldoFijo=new JTextField(“”)); add(panelInferior, BorderLayout.SOUTH); panelInferior.setLayout(new GridLayout(2,1)); panelInferior.setBorder(BorderFactory.createEmptyBorder(0,0,5,0)); panelInferior.add(panelBotones1); panelInferior.add(panelBoton); //------------------------------------------------panelBotones1.setLayout(new FlowLayout()); panelBotones1.add(btnAdicionarEmpleadoXHora= new JButton(“Adicionar Empleado X Hora”)); panelBotones1.add(btnAdicionarEmpleadoXComision= new JButton(“Adicionar Empleado X Comision”)); panelBotones1.add(btnAdicionarEmpleadoSueldoFijo= new JButton(“Adicionar Empleado Sueldo Fijo”)); //------------------------------------------------panelBoton.setLayout(new FlowLayout()); panelBoton.add(btnCalcularNomina= new JButton(“Calcular Nomina”)); } }
JFrame f = new JFrame(); f.setSize(670,315); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.getContentPane().add(new PanelPrincipal()); f.setVisible(true);
225
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
La ejecución del anterior código genera la siguiente interfaz gráfica:
Del código es conveniente resaltar las dos instrucciones que se han agregado para definirles bordes a los paneles PanelInferior y PanelFormulario.
3.4. mAnejO de eventOs La información presentada sobre el diseño y desarrollo de GUI hasta el momento sólo ha permitido definir cómo será la presentación gráfica de la misma y cómo deben reajustarse los componentes ante cambios en el tamaño de la ventana, pero no se ha efectuado la definición de qué instrucciones deben ejecutarse cuando se presione un determinado botón. Esta sección presentará toda la teoría necesaria para poder tratar dicha situación. Así se debe estar al tanto de cuándo un determinado botón es presionado por el usuario. Existen básicamente dos alternativas para realizar esto: • Crear un objeto que periódicamente esté chequeando el estado del botón para saber si ha sido presionado o no. • Que el propio botón le avise a un objeto cuando el usuario lo haya presionado18. Obviamente, esta segunda opción corresponde a una mejor alternativa de solución.
18
226
Esta alternativa de solución guarda relación con el propósito definido en el patrón de diseño observador (Observer).
3. CREACIÓN DE INTERFACES GRÁFICAS DE USUARIOS
sobre el patrón de diseño observador La idea general detrás del patrón observador puede ser entendida a través del siguiente ejemplo: suponga que en una pareja de novios la chica desea estar enterada de cualquier suceso o evento que le ocurra a su novio (al igual que sucede con los botones en las GUI); suponga, además, que la pareja cuenta con teléfonos celulares que posibilitan y facilitan la intercomunicación. La alternativa de que la chica esté llamando continuamente a su novio (a través del teléfono celular), obviamente, no es una muy buena idea. Suponga, que la chica establece 24 horas como período de tiempo entre llamadas, en ese lapso de tiempo puede que al chico le ocurran muchas cosas de las cuales la novia no se enteraría. Sí ella decide cambiar el tiempo entre llamadas a 12, o 6, o 3, o 1 hora, aun así podrían ocurrirle varios eventos al chico en ese tiempo de los cuales ella no se enteraría. Al llevar esta situación al extremo, ella puede optar por llamar cada 30 minutos, o cada 10 minutos, esto solo conllevaría a saturar el canal de comunicación y en últimas hasta a fastidiar al novio con semejante intensidad. La mejor alternativa es que el novio llame a su chica cada vez que le ocurra algo fuera de lo común. La situación podría complejizarse aún más si se añaden al problema la mamá del novio (otra persona interesada en lo que le suceda al muchacho) y, además, el conductor del bus que ocasionalmente toma el novio para ir a la universidad (este último no tiene relación alguna con el chico y, por ende, no se encuentra interesado en lo que a él le suceda). Para que todo este mecanismo suceda se necesita que previamente tanto la mamá como la novia del muchacho le expresen explícitamente su deseo de que este les avise cualquier suceso que le ocurra. El novio debe anotar en un listado los números telefónicos de estas dos personas para poder comunicarse con ellas. Es obvio que simplemente no puede informarles a todos los objetos los eventos que le ocurran porque existen entes que no se encuentran interesados en ellos (por ejemplo, el conductor del bus). Luego de haberse registrado la dependencia hay que esperar que algún evento extraordinario le pase al muchacho para que este les dé aviso a las personas interesadas llamándolas a todas a los teléfonos que tenga registrados en su listado. La aplicación de esta situación en objetos requiere inicialmente que ellos mismos, como interesados en lo que le suceda al botón, se suscriban en calidad de observadores (también llamados escuchas). Posteriormente, si algo le pasa al botón, este deberá avisarles a todos los objetos que se han suscrito como escuchas; pero, para poder lograrlo en el mundo de los
227
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
objetos es necesaria la invocación de un método. A fin de garantizar la invocación de un método común a todos los escuchas se hace necesario que estos sean objetos de clases que implementen una interface específica, todo esto porque puede haber escuchas de diversos tipos de clases. Generalmente, estas interfaces definen en los métodos a implementar parámetros donde se detalla el evento ocurrido.
En Java, para que un objeto se pueda declarar como interesado en conocer los eventos que le ocurren a un botón, su clase debe implementar la interface java.awt.event. ActionListener, y debe suscribirse explícitamente como escucha del botón, empleando el método addActionListener de la clase JButton. Esta interface se encuentra definida como se muestra en el siguiente código: public interface ActionListener{ public void actionPerformed(ActionEvent e); }
La interface solo posee un método que se ejecutará cuando se presione el botón, el parámetro que se recibe java.awt.event.ActionEvent guarda información sobre el evento19.
19
228
Mayor información sobre la clase ActionEvent puede ser consultada en la siguiente referencia: http://java.sun.com/ javase/6/docs/api/java/awt/event/ActionEvent.html
3. CREACIÓN DE INTERFACES GRÁFICAS DE USUARIOS
sobre las alternativas de implementaCión de la interfaCe aCtionlistener Para definir las instrucciones que se ejecuten como respuesta a lo que le ocurra a un JButton se debe definir una clase que implemente ActionListener y suscribirlo como escucha del JButton. A continuación se presentan cada una de las distintas alternativas que se manejan en Java para implementar esta situación. Se utilizará como ejemplo la siguiente GUI, donde se buscará colocar un texto en el JTextField cuando sea presionado el JButton.
Alternativa 1. Que el JFrame o JPanel donde se ubica el botón implemente ActionListener. Considere el código que se muestra a continuación: import import import import import import import
java.awt.FlowLayout; java.awt.event.ActionEvent; java.awt.event.ActionListener; javax.swing.JButton; javax.swing.JFrame; javax.swing.JPanel; javax.swing.JTextField;
public class Ejemplo extends JPanel implements ActionListener { private JTextField texto; private JButton boton; public Ejemplo() { setLayout( new FlowLayout() ); add( texto = new JTextField(20) ); add( boton = new JButton(“clic acá”) ); boton.addActionListener( this ); } public void actionPerformed(ActionEvent e) { texto.setText( “Se presionó el botón” ); } }
229
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
JFrame f = new JFrame(); f.setSize(300,150); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.getContentPane().add(new Ejemplo()); f.setVisible(true);
Sobre la anterior implementación conviene resaltar. • La clase Ejemplo, que extiende JPanel, también define qué va a implementar la interface ActionListener. • Con motivo de esta definición dentro de la clase debe aparecer la definición e implementación del método actionPerformed. La implementación de este método toma el objeto texto y le establece el mensaje a desplegar. • Además, se requiere que el JPanel se suscriba como escucha del JButton, para ello se utiliza la instrucción boton.addActionListener(this). Sin duda alguna, esta es la alternativa más sencilla de implementar y la más fácilmente entendible; presenta como desventaja que al trabajar GUI con varios botones y al suscribir al JPanel como escucha de todos ellos, se debe modificar el código en el método actionPerformed para que primero identifique el botón al que se le hizo clic (esto se logra invocando el método getSource de la clase ActionEvent). Alternativa 2. Que una clase externa implemente ActionListener. Considere el siguiente código que se muestra a continuación: import import import import import import
java.awt.FlowLayout; java.awt.event.ActionEvent; java.awt.event.ActionListener; javax.swing.JButton; javax.swing.JFrame; javax.swing.JPanel;
import javax.swing.JTextField; public class Ejemplo extends JPanel{ private JTextField texto; private JButton boton; public Ejemplo() { setLayout( new FlowLayout() ); add( texto = new JTextField(20) ); add( boton = new JButton(“clic acá”) ); boton.addActionListener( new ManejadorBoton(this) );
230
3. CREACIÓN DE INTERFACES GRÁFICAS DE USUARIOS
}
}
public void cambiarTexto(String cadena){ texto.setText(cadena); }
import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JPanel; public class ManejadorBoton implements ActionListener{ JPanel elPanel; public ManejadorBoton(JPanel panel){ elPanel=panel; }
}
public void actionPerformed(ActionEvent e) { ((Ejemplo)elPanel).cambiarTexto( “Se presionó el botón” ); }
JFrame f = new JFrame(); f.setSize(300,150); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.getContentPane().add(new Ejemplo()); f.setVisible(true);
Sobre el anterior código es conveniente resaltar lo siguiente: •
La clase externa ManejadorBoton es la que implementa la interface ActionListener, por ende, el método actionPerformed se encuentra definido en su interior. Este método debe alterar el texto presentado en el JTextField ubicado en la clase Ejemplo; para ello se requiere que ManejadorBoton conozca la referencia al JPanel para que pueda invocarle un método, por eso se requiere definirlo como atributo de la clase e inicializarlo adecuadamente en el constructor.
•
La clase Ejemplo debe brindar una forma de permitir acceder al JTextField para que se modifique su valor, para ello define el método cambiarTexto.
•
Esta alternativa de solución no es muy sencilla, requiere muchas más líneas de código y tampoco es muy fácil de entender.
231
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
Alternativa 3. Que se utilice una clase interna que implemente el ActionListener. En Java, dentro de la definición de una clase es permitido definir nuevas clases, a este último conjunto de clases se le conoce como clases internas. Este tipo de clases no pueden marcarse como públicas, y poseen la gran ventaja de que tienen acceso a todos los atributos y métodos de la clase donde se encuentran contenidas, aunque estos sean privados o protegidos. El código que se presenta a continuación saca ventaja de este último punto. import import import import import import import
java.awt.FlowLayout; java.awt.event.ActionEvent; java.awt.event.ActionListener; javax.swing.JButton; javax.swing.JFrame; javax.swing.JPanel; javax.swing.JTextField;
public class Ejemplo extends JPanel{ private JTextField texto; private JButton boton; public Ejemplo() { setLayout( new FlowLayout() ); add( texto = new JTextField(20) ); add( boton = new JButton(“clic acá”) ); boton.addActionListener( new ManejadorBoton() ); } class ManejadorBoton implements ActionListener{ public void actionPerformed(ActionEvent e) { texto.setText( “Se presionó el botón” ); } } }
JFrame f = new JFrame(); f.setSize(300,150); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.getContentPane().add(new Ejemplo()); f.setVisible(true);
Sobre el código presentado es conveniente notar que la definición de la clase ManejadorBoton se efectúa al interior de la clase Ejemplo. Esta clase interna utiliza atributos de la clase Ejemplo como texto sin restricción alguna.
232
3. CREACIÓN DE INTERFACES GRÁFICAS DE USUARIOS
Alternativa 4. Que se utilice una clase interna y anónima. La alternativa 3 presentaba la utilización de clases internas. En el ejemplo allí presentado se definía una nueva clase a la que se le asignada un nombre. Esta última alternativa plantea la posibilidad de definir nuevamente una clase interna pero anónima, es decir, sin nombre. Para definir este tipo de clases basta con especificar la instrucción new junto con el tipo del elemento a crear (que bien puede ser una clase o bien una interface) y ubicar entre llaves la definición de los métodos o atributos que sean necesarios. Considere como ejemplo el código que se presenta a continuación: import import import import import import import
java.awt.FlowLayout; java.awt.event.ActionEvent; java.awt.event.ActionListener; javax.swing.JButton; javax.swing.JFrame; javax.swing.JPanel; javax.swing.JTextField;
public class Ejemplo extends JPanel{ private JTextField texto; private JButton boton;
}
public Ejemplo() { setLayout( new FlowLayout() ); add( texto = new JTextField(20) ); add( boton = new JButton(“clic acá”) ); boton.addActionListener( new ActionListener(){ public void actionPerformed(ActionEvent e) { texto.setText( “Se presionó el botón” ); } } ); }
JFrame f = new JFrame(); f.setSize(300,150); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.getContentPane().add(new Ejemplo()); f.setVisible(true);
Esta alternativa es la más utilizada por algunas herramientas que facilitan el desarrollo de GUI debido, quizás, a la forma como organiza el código de la clase.
233
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
deCimoCuarta aproximaCión a la apliCaCión del CálCulo de la nómina mediante programaCión orientada a obJetos
Esta implementación es la última para estimar del cálculo de la nómina; en esta se adiciona una nueva clase donde se define la interfaz gráfica de usuario presentada en el apartado Aproximación a la GUI para la aplicación del cálculo de la nómina. Las restantes clases no han sufrido modificación alguna (ya que uno de los propósitos con las que fueron codificadas fue la de facilitar la creación de nuevas interfaces de usuario) y se presentan acá por simple conveniencia. El código que aparece en negrilla resalta las modificaciones efectuadas con respecto a la anterior aproximación: public interface PersonaAsalariada{ public double calcularSalario(); }
public abstract class Empleado implements PersonaAsalariada{ protected final String cedula; private String apellido; private String nombre; public Empleado(String cedula, String apellido, String nombre) throws Exception{ if (cedula.trim().equals(“”)) throw new Exception(“valor de cedula inválido”); if (cedula.matches(“.*[a-zA-Z].*”)) throw new Exception(“La cedula no puede tener caracteres”); this.cedula = cedula;
}
setApellido(apellido); setNombre(nombre);
public final String getCedula(){ return cedula; } public final String getApellido(){ return apellido; } public final void setApellido(String apellido){ if (apellido!=null) this.apellido = apellido; else this.apellido=””; }
234
3. CREACIÓN DE INTERFACES GRÁFICAS DE USUARIOS
public final String getNombre(){ return nombre; } public final void setNombre(String nombre){ if (nombre!=null) this.nombre = nombre; else this.nombre=””; } }
public class EmpleadoXHora extends Empleado{ private double horasTrabajadas; private double sueldoXHora; public EmpleadoXHora(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora) throws Exception { super(cedula, apellido, nombre); setSueldoXHora(sueldoXHora); setHorasTrabajadas(horasTrabajadas); } public double calcularSalario(){ return horasTrabajadas * sueldoXHora; } public double getHorasTrabajadas(){ return horasTrabajadas; } public void setHorasTrabajadas(double horas ){ if (horas>=0) this.horasTrabajadas = horas; else this.horasTrabajadas = 0; } public double getSueldoXHora(){ return sueldoXHora; } public void setSueldoXHora(double sueldo ){ if (sueldo>=0) this.sueldoXHora = sueldo; else this.sueldoXHora = 0; } public boolean equals(Object o){ if (!(o instanceof EmpleadoXHora)) return false; EmpleadoXHora temp = (EmpleadoXHora)o; if if if if
( ( ( (
!this.getCedula().equals(temp.getCedula()) ) return false; !this.getApellido().equals(temp.getApellido()) ) return false; !this.getNombre().equals(temp.getNombre()) ) return false; this.getSueldoXHora()!=temp.getSueldoXHora() ) return false;
235
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
}
if ( this.getHorasTrabajadas()!=temp.getHorasTrabajadas() ) return false; return true;
}
public class EmpleadoXComision extends Empleado{ private double sueldoBasico; private double porcentaje; private double ventasTotales; public EmpleadoXComision(String cedula, String apellido, String nombre, double sueldoBasico, double porcentaje, double ventasTotales) throws Exception {
}
super(cedula, apellido, nombre); setSueldoBasico(sueldoBasico); setPorcentaje(porcentaje); setVentasTotales(ventasTotales);
public double calcularSalario(){ return sueldoBasico + porcentaje * ventasTotales; } public double getSueldoBasico(){ return sueldoBasico; } public void setSueldoBasico(double sueldo){ if (sueldo>=0) this.sueldoBasico = sueldo; else this.sueldoBasico = 0; } public double getPorcentaje(){ return porcentaje; } public void setPorcentaje(double porcentaje ){ if (porcentaje>=0 && porcentaje<=1) this.porcentaje = porcentaje; else this.porcentaje = 0; } public double getVentasTotales(){ return ventasTotales; } public void setVentasTotales(double ventas){ if (ventas>=0) this.ventasTotales = ventas; else this.ventasTotales = 0; }
236
3. CREACIÓN DE INTERFACES GRÁFICAS DE USUARIOS
public boolean equals(Object o){ if (!(o instanceof EmpleadoXComision)) return false; EmpleadoXComision temp = (EmpleadoXComision)o; if if if if if if
( !this.getCedula().equals(temp.getCedula()) ) return false; ( !this.getApellido().equals(temp.getApellido()) ) return false; ( !this.getNombre().equals(temp.getNombre()) ) return false; ( this.getSueldoBasico()!=temp.getSueldoBasico() ) return false; ( this.getPorcentaje()!=temp.getPorcentaje() ) return false; ( this.getVentasTotales()!=temp.getVentasTotales() ) return false;
}
return true;
} public class EmpleadoSueldoFijo extends Empleado{ private double sueldoFijo; public EmpleadoSueldoFijo(String cedula, String apellido, String nombre, double sueldoFijo) throws Exception { super(cedula, apellido, nombre); setSueldoFijo(sueldoFijo); } public double calcularSalario(){ return sueldoFijo; } public double getSueldoFijo(){ return sueldoFijo; } public void setSueldoFijo(double sueldo){ if (sueldo>=0) this.sueldoFijo = sueldo; else this.sueldoFijo = 0; } public boolean equals(Object o){ if (!(o instanceof EmpleadoSueldoFijo)) return false; EmpleadoSueldoFijo temp = (EmpleadoSueldoFijo)o; if if if if
( ( ( (
!this.getCedula().equals(temp.getCedula()) ) return false; !this.getApellido().equals(temp.getApellido()) ) return false; !this.getNombre().equals(temp.getNombre()) ) return false; this.getSueldoFijo()!=temp.getSueldoFijo() ) return false;
return true; } }
237
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
import java.util.ArrayList; public class Empresa{ final private String nit; private String nombre; private String direccion; private ArrayList empleados; public Empresa(String nit, String nombre, String direccion){ if (nit!=null) this.nit = nit; else this.nit=””; setNombre(nombre); setDireccion(direccion); empleados = new ArrayList(); } public Empresa(String nit){ if (nit!=null) this.nit = nit; else this.nit=””;
}
setNombre(“”); setDireccion(“”); empleados = new ArrayList ();
public Empresa(String nit, int numero){ if (nit!=null) this.nit = nit; else this.nit=””;
}
setNombre(“”); setDireccion(“”); empleados = new ArrayList(numero);
public String getNit(){ return nit; } public String getNombre(){ return nombre; } public void setNombre(String nombre){ if (nombre!=null) this.nombre = nombre; else this.nombre=””; }
238
3. CREACIÓN DE INTERFACES GRÁFICAS DE USUARIOS
public String getDireccion(){ return direccion; } public void setDireccion(String direccion){ if (direccion!=null) this.direccion = direccion; else this.direccion=””; } public int getNumeroEmpleados(){ return empleados.size(); } public void adicionarEmpleadoXHora(String cedula, String apellido, String nombre, double horasTrabajadas, double sueldoXHora) throws Exception { empleados.add (new EmpleadoXHora(cedula, apellido, nombre, horasTrabajadas, sueldoXHora) ); } public void adicionarEmpleadoXComision(String cedula, String apellido, String nombre, double sueldoBasico, double porcentaje, double ventasTotales) throws Exception { empleados.add( new EmpleadoXComision(cedula, apellido, nombre, sueldoBasico, porcentaje, ventasTotales) ); } public void adicionarEmpleadoSueldoFijo(String cedula, String apellido, String nombre, double sueldoFijo) throws Exception { empleados.add( new EmpleadoSueldoFijo(cedula, apellido, nombre, sueldoFijo) ); } public double calcularNominaTotal(){ double total = 0; for (int i=0;i
239
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
import java.io.BufferedReader; import java.io.InputStreamReader; public class Nomina1{ public static void main(String[] args) throws Exception{ Empresa pyme = new Empresa(“123”,”Acme inc.”,”805 Silicon Valley”); String cedula, apellido, nombre; double total = 0; BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) ); System.out.print(“Digite numero de empleados sueldo por hora: “); int numeroEmpleados1 = Integer.valueOf(br.readLine()).intValue(); for(int i=0;i
}
try{ pyme.adicionarEmpleadoXHora(cedula, apellido, nombre, horas, sueldo); }catch(Exception e){ System.out.println(e.getMessage()); i--; } System.out.println(“*******************”);
System.out.print(“Digite numero de empleados por comision: “); int numeroEmpleados2 = Integer.valueOf(br.readLine()).intValue(); for(int i=0;i
240
3. CREACIÓN DE INTERFACES GRÁFICAS DE USUARIOS
System.out.print(“Digite el nombre del empleado: “ ); nombre = br.readLine(); System.out.print(“Digite sueldo basico empleado: “); sueldo = Double.valueOf(br.readLine()).doubleValue(); System.out.print(“Digite porcentaje del empleado: “ ); porcentaje = Double.valueOf(br.readLine()).doubleValue(); System.out.print(“Digite ventas totales del empleado: “ ); ventas = Double.valueOf(br.readLine()).doubleValue();
}
try{ pyme.adicionarEmpleadoXComision(cedula, apellido, nombre, sueldo, porcentaje, ventas); }catch(Exception e){ System.out.println(e.getMessage()); i--; } System.out.println(“*******************”);
System.out.print(“Digite numero de empleados sueldo fijo: “); int numeroEmpleados3 = Integer.valueOf(br.readLine()).intValue(); for(int i=0;i
}
try{ pyme.adicionarEmpleadoSueldoFijo(cedula, apellido, nombre, sueldo); }catch(Exception e){ System.out.println(e.getMessage()); i--; } System.out.println(“*******************”);
total = pyme.calcularNominaTotal(); System.out.println(“\nLa nómina total es: “+ total); }
}
241
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
import import import import import import import import import import import import
java.awt.event.ActionEvent; java.awt.event.ActionListener; javax.swing.BorderFactory; java.awt.BorderLayout; java.awt.FlowLayout; java.awt.GridLayout; javax.swing.JButton; javax.swing.JFrame; javax.swing.JLabel; javax.swing.JOptionPane; javax.swing.JPanel; javax.swing.JTextField;
public class PanelPrincipal extends JPanel { private JTextField txtCedula, txtApellido, txtNombre, txtSueldoXHora, txtHorasTrabajadas, txtSueldoBasico, txtPorcentaje, txtVentasTotales, txtSueldoFijo; private JButton btnAdicionarEmpleadoXHora, btnAdicionarEmpleadoXComision, btnAdicionarEmpleadoSueldoFijo, btnCalcularNomina; private Empresa pyme; public PanelPrincipal(){ JPanel panelFormulario = new JPanel(); JPanel panelEtiquetas = new JPanel(); JPanel panelCampos = new JPanel(); JPanel panelInferior = new JPanel(); JPanel panelBotones1 = new JPanel(); JPanel panelBoton = new JPanel(); pyme = new Empresa(“123”,”Acme inc.”,”805 Silicon Valley”); setLayout(new BorderLayout()); add(panelFormulario, BorderLayout.NORTH); panelFormulario.setLayout(new BorderLayout(10,0)); panelFormulario.setBorder( BorderFactory.createEmptyBorder(15,20,0,20) ); panelFormulario.add( panelEtiquetas, BorderLayout.WEST ); panelFormulario.add( panelCampos, BorderLayout.CENTER); //------------------------------------------------panelEtiquetas.setLayout(new GridLayout(9,1)); panelEtiquetas.add(new JLabel(“Cédula”)); panelEtiquetas.add(new JLabel(“Apellido”)); panelEtiquetas.add(new JLabel(“Nombre”)); panelEtiquetas.add(new JLabel(“Sueldo X Hora”)); panelEtiquetas.add(new JLabel(“Horas Trabajadas”)); panelEtiquetas.add(new JLabel(“Sueldo Básico”));
242
3. CREACIÓN DE INTERFACES GRÁFICAS DE USUARIOS
panelEtiquetas.add(new JLabel(“Porcentaje”)); panelEtiquetas.add(new JLabel(“Ventas Totales”)); panelEtiquetas.add(new JLabel(“Sueldo Fijo”)); //------------------------------------------------panelCampos.setLayout(new GridLayout(9,1)); panelCampos.add(txtCedula=new JTextField(“”)); panelCampos.add(txtApellido=new JTextField(“”)); panelCampos.add(txtNombre=new JTextField(“”)); panelCampos.add(txtSueldoXHora=new JTextField(“”)); panelCampos.add(txtHorasTrabajadas=new JTextField(“”)); panelCampos.add(txtSueldoBasico=new JTextField(“”)); panelCampos.add(txtPorcentaje=new JTextField(“”)); panelCampos.add(txtVentasTotales=new JTextField(“”)); panelCampos.add(txtSueldoFijo=new JTextField(“”)); add(panelInferior, BorderLayout.SOUTH); panelInferior.setLayout(new GridLayout(2,1)); panelInferior.setBorder(BorderFactory.createEmptyBorder(0,0,5,0)); panelInferior.add(panelBotones1); panelInferior.add(panelBoton); //------------------------------------------------panelBotones1.setLayout(new FlowLayout()); panelBotones1.add(btnAdicionarEmpleadoXHora= new JButton(“Adicionar Empleado X Hora”)); panelBotones1.add(btnAdicionarEmpleadoXComision= new JButton(“Adicionar Empleado X Comision”)); panelBotones1.add(btnAdicionarEmpleadoSueldoFijo= new JButton(“Adicionar Empleado Sueldo Fijo”)); //------------------------------------------------panelBoton.setLayout(new FlowLayout()); panelBoton.add(btnCalcularNomina= new JButton(“Calcular Nomina”)); btnAdicionarEmpleadoXHora.addActionListener( new ActionListener(){ public void actionPerformed(ActionEvent e){ try{ pyme.adicionarEmpleadoXHora(txtCedula.getText(), txtApellido.getText(), txtNombre.getText(), Double.parseDouble(txtHorasTrabajadas.getText()), Double.parseDouble(txtSueldoXHora.getText()) ); limpiarCampos(); }catch(Exception ex){ JOptionPane.showMessageDialog( null, ex.getMessage() ); }
243
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
} } ); btnAdicionarEmpleadoXComision.addActionListener( new ActionListener(){ public void actionPerformed(ActionEvent e){ try{ pyme.adicionarEmpleadoXComision(txtCedula.getText(), txtApellido.getText(), txtNombre.getText(), Double.parseDouble(txtSueldoBasico.getText()), Double.parseDouble(txtPorcentaje.getText()), Double.parseDouble(txtVentasTotales.getText()) ); limpiarCampos(); }catch(Exception ex){ JOptionPane.showMessageDialog( null, ex.getMessage() ); } } } ); btnAdicionarEmpleadoSueldoFijo.addActionListener( new ActionListener(){ public void actionPerformed(ActionEvent e){ try{ pyme.adicionarEmpleadoSueldoFijo(txtCedula.getText(), txtApellido.getText(), txtNombre.getText(), Double.parseDouble(txtSueldoFijo.getText() )); limpiarCampos(); }catch(Exception ex){ JOptionPane.showMessageDialog( null, ex.getMessage() ); } } } );
}
244
btnCalcularNomina.addActionListener( new ActionListener(){ public void actionPerformed(ActionEvent e){ JOptionPane.showMessageDialog( null, “La nómina total es: “+pyme.calcularNominaTotal()); } } );
3. CREACIÓN DE INTERFACES GRÁFICAS DE USUARIOS
private void limpiarCampos(){ txtCedula.setText(“”); txtApellido.setText(“”); txtNombre.setText(“”); txtSueldoXHora.setText(“”); txtHorasTrabajadas.setText(“”); txtSueldoBasico.setText(“”); txtPorcentaje.setText(“”); txtVentasTotales.setText(“”); txtSueldoFijo.setText(“”); }
}
public static void main(String[] args){ JFrame f = new JFrame(); f.setSize(670,315); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.getContentPane().add(new PanelPrincipal()); f.setVisible(true); }
Es conveniente resaltar los siguientes puntos del anterior código: •
Note que se añadió a la clase un objeto de la clase Empresa que lleva por nombre pyme.
•
Las últimas instrucciones del método constructor de la clase PanelPrincipal definen las clases internas de los escuchas para cada uno de los objetos de tipo JButton que existen en el JPanel.
•
La implementación que se coloca en los métodos actionPerformed para los escuchas de los botones btnAdicionarEmpleadoXHora, adicionarEmpleadoXComision y adicionarEmpleadoSueldoFijo define el código para realizar el manejo de excepciones. En los bloques try de estos métodos se ubica la invocación al método limpiarCampos, el cual borra toda la información que contengan los cuadros de texto después de adicionarse los distintos tipos de empleado a la empresa.
•
En el código se usa repetidamente la instrucción JOptionPane.showMessageDialog, esta instrucción despliega una ventana donde se presenta el mensaje que se pasa como parámetro. El primer parámetro corresponde al componente con respecto al cual se centrará la ventana. El código que se presenta no pide centrar la ventana emergente.
245
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
Al ejecutar el código de la clase PanelPrincipal se despliega una ventana como la que se muestra a continuación.
Esta aplicación funciona de la siguiente manera: el usuario debe ingresar la información de un empleado, de cualquier tipo, y luego debe presionar el botón que permite adicionar el tipo de empleado correspondiente; al adicionarlo, la información de los campos se limpia. Si se presenta algún tipo de excepción, el mensaje de la misma se le despliega al usuario. Finalmente, cuando el usuario desea obtener el cálculo de la nómina total de la empresa debe presionar el botón identificado por Calcular Nómina.
246
ÍNDICE DE TEMAS 1. Tópicos básicos ............................................. 1 Aproximación alternativa de la aplicación del cálculo de la nómina empleando programación orientada a objetos ..............................24 Codificación en Java del algoritmo para el cálculo de la nómina mediante programación estructurada .............................................. 3 Cuarta aproximación a la aplicación del cálculo de la nómina mediante programación orientada a objetos ............................................................59 Décima aproximación a la aplicación del cálculo de la nómina mediante programación orientada a objetos ......................................................... 126 Novena aproximación a la aplicación del cálculo de la nómina mediante programación orientada a objetos ......................................................... 115 Octava aproximación a la aplicación del cálculo de la nómina mediante programación orientada a objetos ............................................................99 Primera aproximación a la aplicación del cálculo de la nómina mediante programación orientada a objetos ............................................................22 Quinta aproximación a la aplicación del cálculo de la nómina mediante programación orientada a objetos ............................................................63 Segunda aproximación a la aplicación del cálculo de la nómina mediante programación orientada a objetos ............................................................36 Séptima aproximación a la aplicación del cálculo de la nómina mediante programación orientada a objetos ............................................................89
Sexta aproximación a la aplicación del cálculo de la nómina mediante programación orientada a objetos ............................................................68 Sobre el ámbito de las variables y la palabra reservada ‘this’......................................................................30 Sobre el operador lógico de comparación y su funcionamiento sobre las variables de tipo primitivo y de referencia ...................................16 Sobre la clase Object .................................................................80 Sobre la comparación de variables de tipos de referencia ........................................................... 112 Sobre la composición de objetos ........................................... 9 Sobre la creación de instancia, la palabra reservada ‘super’ y el orden de invocación de los constructores en las subclases.......................................83 Sobre la definición y manipulación de los tipos de datos primitivos y los de referencia .......................11 Sobre la instanciación y utilización de arrays...................19 Sobre la reutilización de las variables de referencia ......18 Sobre la utilización de paquetes...........................................38 Sobre la manipulación de los atributos estáticos ...........57 Sobre los niveles de visibilidad público y privado .........43 Sobre los parámetros en los métodos ................................28 Tercera aproximación a la aplicación del cálculo de la nómina mediante programación orientada a objetos ............................................................46 Un ejemplo ampliado sobre la instanciación...................15
247
Todo lo básico que debería saber sobre Programación orientada a objetos en Java
2. Tópicos avanzados.................................. 135 Codificación de la estructura de datos para manejar una colección de elementos mediante listas.................................................................. 139 Codificación de la estructura de datos para manejar una colección de mediante empleando arrays ............................................................ 138 Contraste entre aplicaciones con y sin manejo de errores sobre la captura y posterior lanzamiento de excepciones ....................................... 188 Decimoprimera aproximación a la aplicación del cálculo de la nómina mediante programación orientada a objetos ........................... 146 Decimosegunda aproximación a la aplicación del cálculo de la nómina mediante programación orientada a objetos ........................... 160 Decimotercera aproximación a la aplicación del cálculo de la nómina empleando programación orientada a objetos ........................... 191 Ejemplo de una implementación de la interface Map mediante estructuras genéricas .... 170 Ejemplo de una implementación de la interface Set mediante estructuras genéricas....... 168 Ejemplo de utilización de las estructuras de datos que posibilitan la manipulación de colecciones de elementos. ........................................... 141 Ejemplo de utilización de una implementación de la interface Map ......................................................... 158 Ejemplo de utilización de una implementación de la interface Set ............................................................ 155 Sobre el lanzamiento de excepciones ............................. 181 Sobre la definición de varios bloques catch .................. 182 Sobre la funcionalidad del bloque finally ....................... 185 Sobre las excepciones no chequeadas en Java ............ 190
248
3. Creación de interfaces gráficas de usuario ................................ 199 Aproximación a la GUI para la aplicación del cálculo de la nómina ............................................... 224 Decimocuarta aproximación a la aplicación del cálculo de la nómina mediante programación orientada a objetos ........................... 234 Sobre el diseño y desarrollo de interfaces gráficas de usuario (GUI) ............................................... 200 Sobre el componente JPanel .............................................. 205 Sobre el patrón de diseño observador ............................ 227 Sobre la definición de clases que extiendan JPanel .............................................................. 207 Sobre la instrucción de finalización de la aplicación ................................................................. 203 Sobre la jerarquía de organización de las clases que representan a componentes gráficos .............. 216 Sobre la utilización de varios Layouts .............................. 218 Sobre las alternativas de implementación de la interface ActionListener ..................................... 229 Un ejemplo sencillo de la utilización del componente JPanel ................................................. 206
Esta obra, editada en Barranquilla por Ediciones Uninorte, se terminó de imprimir en los talleres de X-press Proceso Gráfico en agosto de 2010. Se compuso en Book Antiqua y Courier New.