Módulo 4 Arreglos, String, Polimorfismo y Excepciones en Java
Programación Orientada a Objetos De las Heras, Sebastián
4.1- Arreglos y la clase Vector 4.1.1- Arreglos El objetivo que perseguiremos en esta sección será el aprendizaje de arreglos en Java. Estudiaremos qué es un arreglo y la sintaxis utilizada para declarar, crear, inicializar y manipular un arreglo. Conceptualmente, los arreglos en Java son objetos que pueden almacenar múltiples variables del mismo tipo de dato. A un arreglo también se lo suele denominar “array” por su denominación en inglés. Los arreglos pueden mantener referencias a tipos de datos primitivos o tipos de de datos por referencia (objetos), pero el arreglo propiamente dicho es siempre un objeto. Los arreglos se clasifican en: Unidimensionales: almacenan datos indicando un solo índice debido a que están compuestos por una única dimensión. A estos tipos de arreglos se los denomina también vectores. La siguiente figura representa un arreglo unidimensional que almacena seis valores numéricos enteros: Índice Elementos
0 24
1 12
2 14
3 28
4 9
5 2
Para referenciar al primer elemento de un arreglo, se utiliza el índice cero, es decir el conteo del índice comienza en cero. Bidimensionales: están compuestos por dos dimensiones, razón por la cual para manipularlos se deben indicar dos índices. Imaginemos que tenemos un arreglo en el cual los elementos de la primera dimensión, almacenan a su vez otros arreglos:
Dimensión 1
Dimensión 2
La figura anterior representa un arreglo bidimensional cuya primera dimensión contiene tres elementos que almacenan nuevos arreglos de cuatro elementos.
Programación Orientada a Objetos – De las Heras, Sebastián | 2
Filas
Otra representación más intuitiva es considerar que los datos de los arreglos bidimensionales se almacenan en una estructura con forma de matriz. La siguiente figura representa el mismo arreglo definido anteriormente pero para su representación se utiliza una matriz:
0 1 2
0 43 3 6
Columnas 1 2 21 7 67 34 2 56
3 25 67 87
Debido a que la primera dimensión del arreglo contiene tres elementos, entonces se puede considerar que la matriz que representa al arreglo contiene tres filas. También se puede considerar que la matriz contiene cuatro columnas debido a que la segunda dimensión del arreglo contiene cuatro elementos. Multidimensionales: tienen más de dos dimensiones. Para manipular a este tipo de arreglo se tiene que utilizar un índice para cada una de las dimensiones que componen al arreglo. La siguiente figura representa un ejemplo de un arreglo multidimensional de tres dimensiones que almacena elementos de tipo char: -
La primera dimensión puede almacenar tres elementos. Los elementos que se almacenan son nuevos arreglos (dimensión 2).
-
Los arreglos de la segunda dimensión pueden almacenar dos elementos. Los elementos que se almacenan son nuevos arreglos (dimensión 3).
-
Los arreglos de la tercera dimensión pueden almacenar tres elementos. Se almacenan valores de tipo char. Dimensión 1
Dimensión 2
Dimensión 3
Debido a que el arreglo representado contiene tres dimensiones, entonces también se lo puede representar mediante la figura de un cubo como se muestra a continuación:
Programación Orientada a Objetos – De las Heras, Sebastián | 3
A modo de ejemplo, podemos observar que el elemento que se encuentra en la posición [0,1,1] tiene almacenado el valor “k”. Cuando indicamos la posición [0,1,1] hacemos referencia a la primera fila de la dimensión 1 (índice 0), la segunda fila de la dimensión 2 (índice 1) y la segunda fila de la dimensión 3 (índice 1). Es importante recordar que el conteo de los índices comienza en cero.
Para aprender a utilizar un arreglo, es fundamental conocer los siguientes conceptos:
Declaración de un arreglo.
Creación de un arreglo.
Inicialización de un arreglo.
Cada uno de estos puntos tiene distintas variantes y estudiaremos las más utilizadas.
Declaración de un arreglo Un arreglo se declara indicando el tipo de dato de los elementos que el arreglo almacenará, pudiendo ser tipos de datos primitivos o tipos de datos por referencia (objeto), seguido de un par de corchetes que se pueden ubicar a la izquierda o a la derecha del identificador del arreglo. A continuación se declaran dos arreglos para ejemplificar: int []numeros; String palabras[]; El primer arreglo es un objeto cuyo identificador es “numeros” y almacena valores numéricos enteros (int). El segundo arreglo es un objeto cuyo identificador es “palabras” y almacena cadenas de caracteres de tipo String.
Programación Orientada a Objetos – De las Heras, Sebastián | 4
Los corchetes se pueden colocar indistintamente a la izquierda o a la derecha del identificador. En la declaración del primer arreglo, los corchetes se colocaron a la izquierda del identificador mientras que para el segundo arreglo se optó por colocarlos a la derecha del identificador. Si deseamos declarar un arreglo de más de una dimensión, entonces se deben colocar tantos pares de corchetes como número de dimensiones contenga el arreglo. Por ejemplo, declararemos un arreglo de tres dimensiones que contendrá objetos de tipo Persona. Persona[][][] listadoPersonas; También se lo podría haber declarado de la siguiente forma: Persona listadoPersonas[][][]; Por el momento, solamente hemos definido una variable que representa un arreglo pero no hemos creado ningún objeto. Para crear o construir un arreglo es necesario invocar al operador “new”.
Creación de un arreglo La creación de un arreglo significa invocar a su constructor para que esté disponible en memoria junto a todos los objetos creados por la Máquina Virtual Java. Para la creación de un arreglo, Java debe conocer cuánto espacio de memoria debe reservar, motivo por el cual se debe especificar el tamaño del arreglo durante la creación del mismo. El tamaño del arreglo hace referencia a la cantidad de elementos que el arreglo podrá almacenar. Hay diferentes alternativas para construir un arreglo. La formas más estandarizada es utilizando la palabra reservada “new”, seguida del tipo de dato del arreglo con un par de corchetes que indican la cantidad de elementos del arreglo. Veamos el siguiente ejemplo: int[] calificaciones;
// declaración de un arreglo // que contendrá números enteros
calificaciones = new int[3];
// Creación de un // arreglo unidimensional // de 3 elementos
También se puede declarar y crear un arreglo en una sola sentencia: int[] calificaciones = new int[3]; Para crear un arreglo de objetos también se procede de la misma manera: Alumno[] listadoAlumnos = new Alumno[3]; No hay que confundir la utilización del operador “new”. Si nos fijamos en el ejemplo anterior, cuando construimos el arreglo “listadoAlumnos” utilizando el operador “new”, no estamos invocando al constructor de la clase Alumno, no estamos creando instancias de tipo Alumno, sino que estamos construyendo o instanciando un objeto arreglo.
Programación Orientada a Objetos – De las Heras, Sebastián | 5
También es importante tener en cuenta que en la creación de un arreglo siempre se debe indicar el tamaño del mismo, de lo contrario obtendremos un error de compilación. En la siguiente línea de código, se intenta crear un arreglo sin especificar su tamaño, y por consiguiente obtenemos un error de compilación: Alumno[] listadoAlumnos = new Alumno[]; // No compila Esto se debe a que la Máquina Virtual Java necesita conocer cuánto espacio de memoria debe reservar para almacenar el arreglo. Para la construcción de arreglos de más de una dimensión, se debe seguir la misma lógica como lo muestra el ejemplo a continuación: Persona listadoPersonas[][][]; // // // //
declaración de un arreglo de 3 dimensiones de objetos de tipo Persona
listadoPersonas = new Persona[3][2][2]; En el ejemplo anterior, hemos declarado un arreglo de tipo Persona de tres dimensiones. Posteriormente hemos construido dicho arreglo e indicamos que la primera dimensión contendrá tres elementos, y la segunda y tercera dimensiones contendrán dos elementos cada una.
Inicialización de un arreglo Hasta el momento, hemos podido declarar y crear un arreglo, pero no hemos indicado el almacenamiento de algún elemento en el mismo. Los elementos que se pueden almacenar pueden ser valores primitivos u objetos mediante variables de referencia. Los elementos de un arreglo pueden ser accedidos mediante un índice que indica una posición exacta en el arreglo. Este índice es un número que comienza en cero. Si por ejemplo, tenemos un arreglo de seis elementos, los índices del arreglo comenzarán en cero y llegarán hasta el valor cinco.
Programación Orientada a Objetos – De las Heras, Sebastián | 6
Supongamos que tenemos definida la clase Persona, y declaramos un arreglo que contiene elementos de tipo Persona: Persona[] empleados = new Persona[3]; El objeto “empleados” puede almacenar elementos que representan referencias a objetos de tipo Persona, pero hasta el momento solamente tenemos tres referencias de tipo Persona pero nulas, es decir, no hemos inicializado cada uno de los elementos de tipo Persona del arreglo. La siguiente figura es una representación del arreglo “empleados” luego de haber sido declarado y creado:
Si observamos cada uno de los elementos del arreglo, todos ellos pueden almacenar objetos de tipo Persona, pero por el momento tienen referencias nulas. El próximo paso entonces, es crear objetos de tipo Persona y asignar estos objetos a las posiciones del arreglo “empleados”. Esto lo podemos realizar de la siguiente manera: empleados[0] = new Persona(“Juan”); empleados[1] = new Persona(“Lucas”); empleados[2] = new Persona(“Florencia”); De esta forma, para cada posición del arreglo “empleados”, hemos inicializado un objeto de tipo Persona.
Programación Orientada a Objetos – De las Heras, Sebastián | 7
Persona Juan
Persona Lucas
Persona Florencia
Hay que tener en cuenta que si intentamos acceder a una posición del arreglo inexistente entonces obtendremos un error en tiempo de ejecución (ArrayIndexOutOfBoundsException). Por ejemplo, si en el caso anterior intentamos inicializar la posición del índice 3 del arreglo, obtendremos un error en tiempo de ejecución. empleados[3] = new Persona(“Pedro”); El índice 3 se encuentra fuera de rango, no existe, ya que el tamaño del arreglo es tres y por consiguiente los posibles valores de los índices son 0, 1 y 2. Para inicializar un arreglo de más de una dimensión hay que aplicar la misma lógica; se debe referenciar a alguna posición específica del arreglo y asignar un objeto o valor en dicha posición. El siguiente código es un ejemplo de inicialización de un arreglo bidimensional que almacena valores boolean. boolean[][] a = new boolean[2][2]; a[0][0] = true; a[0][1] = false; a[1][0] = false; a[1][1] = true; Algo importante a tener en cuenta, es que si declaramos y construimos un arreglo de tipo primitivo, entonces sus elementos quedan inicializados con los valores por defecto correspondientes al tipo de dato del arreglo. int[] a = new int[4]; boolean[][] b = new boolean[2][3]; En el ejemplo anterior, los elementos del arreglo “a” están inicializados con el valor cero ya que este es el valor por defecto o predeterminado para el
Programación Orientada a Objetos – De las Heras, Sebastián | 8
tipo de dato int. Los elementos del arreglo “b” están inicializados con el valor “false” ya que este es el valor por defecto del tipo de dato boolean. Un arreglo también puede ser inicializado utilizando sentencias iterativas, otorgando de esta forma una alternativa más sencilla para inicializar un arreglo cuando su tamaño es grande. Supongamos que tenemos un arreglo de String de cien elementos y deseamos inicializar cada uno de ellos con una cadena de caracteres vacía “”. De acuerdo a lo que acabamos de analizar anteriormente, una de las formas de hacerlo es la siguiente: String[] s = new String[100]; s[0] = new String(“”); s[1] = new String(“”); s[2] = new String(“”); . . . s[98] = new String(“”); s[99] = new String(“”); Pero como podemos ver, esta forma de inicializar un arreglo resulta bastante tediosa si el número de elementos que contiene el arreglo es alto. Para facilitar esta tarea podemos hacer uso de las sentencias iterativas. Los arreglos tienen un atributo denominado “length” que representa el número de elementos que puede almacenar un arreglo, es decir, su tamaño. Entonces por ejemplo, en el arreglo anterior, el valor de “s.length” es igual a 100. Si hacemos uso de este atributo y de las sentencias iterativas entonces podemos inicializar el arreglo. Una de las formas más utilizadas es mediante la sentencia for: String[] s = new String[100]; for (int i=0; i
Programación Orientada a Objetos – De las Heras, Sebastián | 9
2. for (int a=0 ; a
La línea de código 2 del ejemplo anterior, recorre los elementos de la primera dimensión del arreglo, la línea de código 3 recorre los elementos de la segunda dimensión y la línea de código 4 recorre los elementos de la tercera dimensión. También se puede declarar, crear e inicializar un arreglo en una única línea: int[] c = {22,8,53,45,2}; La línea anterior realiza las siguientes acciones:
Declara un arreglo que almacenará valores int cuya variable de referencia es “c”.
Construye un arreglo de valores int que podrá almacenar cinco elementos.
Almacena los valores “22”, “8”, “53”, “45” y “2” en el arreglo “c”.
Es decir, la sentencia de código anterior es equivalente a la siguiente: int[] c; c = new int[5]; c[0] = 22; c[1] = 8; c[2] = 53; c[3] = 45; c[4] = 2; Ahora veremos otro ejemplo, pero aplicado a un arreglo de objetos: Persona juan = new Persona(“Juan”); Persona[] listadoPersonas = {juan, new Persona(“Lucas”), new Persona(“Florencia”)};
El código anterior realiza las siguientes acciones:
Declara un arreglo que almacenará objetos de tipo Persona cuya variable de referencia es “listadoPersonas”.
Construye un arreglo de elementos de tipo Persona de tres elementos.
Asigna el objeto “juan” de tipo Persona creado con anterioridad al primer elemento del arreglo.
Crea dos objetos de tipo Persona y los asigna al segundo y tercer elementos del arreglo.
Por último, veremos otra alternativa que podemos utilizar para construir e inicializar un arreglo:
Programación Orientada a Objetos – De las Heras, Sebastián | 10
char[] d; d = new char[]{'p','o','o'}; Esta forma de crear e inicializar arreglos se denomina creación anónima de arreglos. Esta alternativa es poco utilizada, pero no deja de ser una posibilidad con la que nos encontremos alguna vez. Una vez que tenemos un arreglo declarado, creado e inicializado, para utilizarlo simplemente se deben referenciar a sus elementos utilizando el índice correspondiente a la posición del elemento con el que se desea trabajar. A continuación se muestra un ejemplo de una aplicación que genera instancias de la clase Usuario, las asigna a un arreglo de cinco elementos y por último realiza una iteración sobre el arreglo para imprimir en consola los objetos almacenados. // Clase GeneracionDeUsuarios package programacion; public class GeneracionDeUsuarios { public static void main(String[] args) { Usuario[] listadoUsuarios = new Usuario[5]; for (int i=0; i
Programación Orientada a Objetos – De las Heras, Sebastián | 11
4.1.2- Vector En la sección anterior estudiamos los conceptos asociados a los arreglos en Java y vimos que los mismos son útiles para almacenar varios elementos de un mismo tipo de dato. Para ello debemos conocer de antemano la cantidad de elementos que se podrán almacenar en el arreglo. Sin embargo, hay algunos escenarios en los cuales no sabemos con precisión la cantidad de elementos que serán almacenados en el arreglo. Una posible solución a este problema es crear un arreglo que pueda almacenar un número de elementos mucho mayor a la cantidad de elementos que estimamos que podemos llegar a guardar. El inconveniente de esta solución, es que cada posición del arreglo ocupa un lugar en memoria, y de esta forma estaremos ocupando espacio adicional en memoria en forma innecesaria. Java provee la clase Vector que permite representar arreglos que pueden aumentar o disminuir la cantidad de elementos posibles a almacenar en forma dinámica. La clase Vector se encuentra en el paquete “java.util” y cada vez que queramos hacer uso de esta clase será necesario importar dicho paquete. Vector nombres = new Vector(5); La línea de código anterior crea un objeto de tipo Vector con una capacidad inicial de cinco elementos. En el caso que hayamos agregado cinco elementos al vector y agreguemos un elemento adicional, entonces su capacidad será duplicada en forma automática, es decir se incrementará a diez elementos. Si en un futuro esta capacidad es superada, entonces nuevamente su capacidad será duplicada automáticamente, en este caso a veinte elementos. En otras palabras, si utilizamos este constructor su capacidad inicial será aquella indicada por medio del parámetro que se envía en su constructor, y por cada vez que su capacidad sea superada, entonces la misma será duplicada en forma automática. Hay que tener precaución en la utilización de este constructor especialmente cuando se debe almacenar una gran cantidad de elementos, ya que en el punto en el que superemos la capacidad, la misma será duplicada y por consiguiente se requerirá mayor cantidad de memoria disponible. Si deseamos tener un mayor control sobre cómo se incrementa la capacidad del vector cada vez que la misma es superada entonces podemos utilizar el siguiente constructor: Vector nombres = new Vector(5, 5); El constructor anterior recibe dos parámetros: el primero de ellos indica la capacidad inicial del vector, y el segundo indica cuánto debe aumentar la capacidad por cada vez que se la supere. En el ejemplo anterior, la capacidad inicial del vector es cinco y por cada vez que la misma sea superada, entonces será incrementada de a cinco unidades (5, 10, 15, 20, 25…). También se puede utilizar un tercer constructor que no recibe ningún parámetro: Vector nombres = new Vector();
Programación Orientada a Objetos – De las Heras, Sebastián | 12
En este caso, la capacidad inicial del vector es diez y será duplicada por cada vez que la misma sea superada. No se deben confundir los conceptos de capacidad y tamaño. La capacidad tiene una relación con la cantidad de espacio en memoria reservado por un vector, mientras que el tamaño de un vector hace referencia a la cantidad de elementos almacenados en un momento determinado.
Sus métodos A continuación, analizaremos los métodos que provee la clase Vector que nos permiten manipular objetos de este tipo. Si deseamos añadir un elemento a un vector podemos utilizar los métodos addElement(Object o) o add(Object o): Vector nombres = new Vector(5, 5); nombres.add(“Juan”); nombres.add(“María”); nombres.add(“Rodrigo”); nombres.addElement(“Florencia”); Se pueden utilizar los métodos addElement() y add() en forma indistinta ya que proveen la misma funcionalidad. Estos métodos agregan un elemento al final del vector. Si deseamos insertar un elemento en una determinada posición entonces podemos hacer uso del método insertElementAt(): nombres.insertElementAt(“Darío”, 2); Si utilizamos el método insertElementAt(), entonces debemos enviar un primer parámetro que representa el objeto que deseamos insertar y un segundo parámetro que indica la posición en la cual deseamos insertar el objeto dentro del vector. A la posición la debemos indicar mediante un índice y el conteo del mismo comienza en cero. En el ejemplo anterior, agregamos un objeto de tipo String con la cadena de caracteres “Darío” en la tercera posición (índice 2). También se puede utilizar en forma indistinta el método add(int index, Object element): nombres.add(4, “Paula”); A diferencia del método insertElementAt(), este método recibe como primer parámetro un índice que hace referencia a la posición en la que se desea insertar el objeto, y recibe como segundo parámetro el objeto que se desea insertar. En el ejemplo anterior, agregamos un objeto de tipo String con la cadena de caracteres “Paula” en la quinta posición (índice 4). Cuando se utiliza un índice para referenciar una posición específica para insertar un elemento en un vector, el mismo debe ser igual o mayor a cero (el conteo comienza en cero) y menor o igual al tamaño del vector. Recordemos que el tamaño de un vector hace referencia a la cantidad de
Programación Orientada a Objetos – De las Heras, Sebastián | 13
elementos almacenados. Si hacemos referencia a una posición inexistente por el momento entonces obtendremos un error (ArrayIndexOutOfBoundsException). En el siguiente ejemplo, se intenta insertar un elemento en la séptima posición (índice 6), pero la misma es inexistente ya que el tamaño del vector en el momento que se inserta el objeto es tres. import java.util.Vector; public class EjemplosVector { public static void main(String[] args) { Vector numeros = new Vector(); numeros.add(“uno”); numeros.add(“dos”); numeros.add(“tres”); /* Insertamos el objeto “siete” * en la séptima posición(índice 6) */ numeros.insertElementAt(“siete”, 6); } } Si ejecutamos el código anterior obtenemos el siguiente error: Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 6 > 3 at java.util.Vector.insertElementAt(Vector.java:551) at programacion.EjemplosVector.main(EjemplosVector.java :15) Java Result: 1 Si deseamos conocer el tamaño de un vector se puede utilizar el método size() y para conocer su capacidad se puede utilizar el método capacity(): Vector numeros = new Vector(5); numeros.add(“uno”); numeros.add(“dos”); numeros.add(“tres”); System.out.println(“Tamaño del vector: ” + numeros.size()); System.out.println(“Capacidad del vector: ” + numeros.capacity()); El código anterior produce la siguiente salida en consola: Tamaño del vector: 3 Capacidad del vector: 5 Esto se debe a que solamente hemos agregado tres elementos al vector y lo hemos inicializado con una capacidad inicial de cinco.
Programación Orientada a Objetos – De las Heras, Sebastián | 14
Un vector se considera vacío si su tamaño es cero, es decir, si no tiene ningún elemento almacenado. Para saber si un vector está vacío, podemos obtener su tamaño mediante el método size() y verificar si el mismo es igual a cero; o también podemos hacer uso de un método más directo denominado isEmpty(). Este método retorna el valor boolean “true” si el vector se encuentra vacío, y en caso contrario retorna el valor boolean “false”. Vector numeros = new Vector(5); /* Por el momento, el vector no contiene * ningún elemento, entonces está vacío. * La siguiente sentencia imprime “true” */ System.out.println(“¿Vector vacío? ” + numeros.isEmpty()); numeros.add(“uno”); numeros.add(“dos”); numeros.add(“tres”); /* Se han agregado elementos al vector. * En consecuencia, la siguiente sentencia * imprime “false” */ System.out.println(“¿Vector vacio? ” + numeros.isEmpty()); Si deseamos eliminar todos los elementos de un vector podemos utilizar los métodos removeAllElements() o clear() en forma indistinta: Vector numeros = new Vector(5); numeros.add(“uno”); numeros.add(“dos”); numeros.add(“tres”); System.out.println(“Tamaño del vector: ” + numeros.size()); // Imprime 3 // Se remueven todos los elementos del vector numeros.removeAllElements(); System.out.println(“Tamaño del vector: ” + numeros.size()); //Imprime 0 Si pretendemos remover algún elemento específico del vector entonces podemos utilizar los métodos removeElement() o remove() enviando como parámetro el objeto que estamos interesados en remover: Vector numeros = new Vector(5); numeros.add(“uno”); numeros.add(“dos”); numeros.add(“tres”); System.out.println(“Tamaño del vector: ” + numeros.size()); // Imprime 3
Programación Orientada a Objetos – De las Heras, Sebastián | 15
/* Se remueve el objeto de tipo String * con la cadena de caracteres “dos” */ numeros.removeElement(“dos”); System.out.println(“Tamaño del vector: ” + numeros.size()); // Imprime 2 También podemos remover un elemento en particular indicando la posición en la que se encuentra el elemento. Para ello se utiliza el método removeElementAt() y se envía como parámetro el índice correspondiente a la posición en la que se encuentra el objeto que deseamos remover: Vector numeros = new Vector(5); numeros.add(“uno”); numeros.add(“dos”); numeros.add(“tres”); System.out.println(“Tamaño del vector: ” + numeros.size()); // Imprime 3 /* Se remueve el elemento que se encuentra * en la tercera posición (índice 2) */ numeros.removeElementAt(2); System.out.println(“Tamaño del vector: ” + numeros.size()); // Imprime 2 En el caso que queramos remplazar un elemento del vector por otro, podemos hacer uso del método setElementAt(). Para utilizarlo, debemos enviar como parámetros el nuevo objeto que deseamos almacenar y un índice que hace referencia a la posición del objeto que deseamos remplazar. Vector numeros = new Vector(5); numeros.add(“uno”); numeros.add(“dos”); numeros.add(“nueve”); numeros.add(“cuatro”); /* Remplazamos el objeto que se encuentra * en la posición del índice 2 por el * objeto String “tres” */ numeros.setElementAt(“tres”, 2); Los métodos firstElement() y lastElement() devuelven el primer y último elemento del vector respectivamente: Vector numeros = new Vector(5); numeros.add(“uno”); numeros.add(“dos”); numeros.add(“tres”); numeros.add(“cuatro”); /* Se imprime el primer elemento del vector,
Programación Orientada a Objetos – De las Heras, Sebastián | 16
* en este caso “uno” */ System.out.println(numeros.firstElement()); /* Se imprime el último elemento del vector, * en este caso “cuatro” */ System.out.println(numeros.lastElement()); Si deseamos conocer el índice correspondiente a un elemento en particular, entonces podemos hacer uso del método indexOf(). A este método se le debe enviar como parámetro el objeto sobre el cual nos interesa conocer su índice correspondiente en el vector. Vector numeros = new Vector(5); numeros.add(“uno”); numeros.add(“dos”); numeros.add(“tres”); numeros.add(“cuatro”); /* Se imprime el índice correspondiente * a la posición en la que se encuentra * el objeto “tres” */ System.out.println(numeros.indexOf(“tres”)); En el ejemplo anterior, si hubiéramos enviado como parámetro un objeto inexistente al método indexOf(), entonces el valor devuelto por el método es “-1”. Para saber si un elemento en particular se encuentra almacenado o no en un vector, podemos utilizar el método contains(). Este método recibe como parámetro el objeto del cual se desea conocer su existencia o no dentro del vector. En caso que el objeto esté contenido en el vector, entonces el método contains() retorna un valor booleano “true”, y en caso contrario, retorna un valor booleano “false”. Vector numeros = new Vector(5); numeros.add(“uno”); numeros.add(“dos”); numeros.add(“tres”); numeros.add(“cuatro”); /* El objeto “tres” SÍ existe en el vector, * en consecuencia se imprime “true” */ System.out.println(numeros.contains(“tres”)); /* El objeto “ocho” NO existe en el vector, * en consecuencia se imprime “false” */ System.out.println(numeros.contains(“ocho”)); Para acceder a los elementos de un vector, se pueden utilizar los métodos elementAt() o get() en forma indistinta. Para ello, estos métodos reciben como parámetro un índice que hace referencia a una posición determinada del vector y retornan el objeto que se encuentra en dicha posición. Vector numeros = new Vector(5);
Programación Orientada a Objetos – De las Heras, Sebastián | 17
numeros.add(“uno"); numeros.add(“dos”); numeros.add(“tres”); numeros.add(“cuatro”); System.out.println(numeros.elementAt(0)); System.out.println(numeros.elementAt(1)); System.out.println(numeros.get(2)); System.out.println(numeros.get(3)); Las últimas cuatro sentencias del código anterior imprimen en consola los elementos del vector correspondientes a los índices 0, 1, 2 y 3. Si en algún momento intentamos acceder a un elemento que se encuentra fuera de rango, obtendremos un error en tiempo de ejecución. En el ejemplo anterior, el último índice válido es 3; si intentamos utilizar un índice superior a este último entonces obtendremos una excepción de tipo ArrayIndexOutOfBoundsException. Al igual que los arreglos, también es posible utilizar sentencias iterativas para manipular los elementos de un objeto de tipo Vector. Vector numeros = new Vector(5); numeros.add(“uno”); numeros.add(“dos”); numeros.add(“tres”); numeros.add(“cuatro”); for (int i = 0; i
Programación Orientada a Objetos – De las Heras, Sebastián | 18
En el código anterior, se utiliza la sentencia iterativa while. Mientras se cumpla la condición de que haya elementos para recorrer en el vector “numeros” entonces se ejecuta el ciclo iterativo, es decir, mientras la función hasMoreElements() retorne el valor “true”, entonces se ejecutará un nuevo ciclo del bloque while(). Dentro de este bloque de código, se utiliza el método nextElement() para obtener una referencia al siguiente elemento del vector. De esta forma, este método permite avanzar secuencialmente sobre los elementos de un vector. A medida que avanzamos, antes de ejecutar un nuevo ciclo, se evalúa que el vector tenga más elementos para recorrer utilizando el método hasMoreElements(). Una vez que lleguemos al último elemento del vector, el método hasMoreElements() retornará un valor boolean “false” debido a que no hay más elementos para recorrer, y en consecuencia, la ejecución del ciclo while se detendrá. Cuando se utilicen estos métodos hay que tener en cuenta que el método nextElement() retorna una referencia a un objeto de la clase Object. Esto quiere decir que en la mayoría de los casos será necesario aplicar casting a la clase adecuada con la que tengamos que trabajar.
Ventajas Vectores
y
desventajas
de
arreglos
y
Los arreglos y vectores en Java permiten disponer de estructuras de datos para almacenar elementos que contienen información. Dependiendo del escenario en el que nos encontremos, es conveniente utilizar un arreglo o una instancia de la clase Vector. Un arreglo representa una estructura de datos estática que puede almacenar un determinado número de variables. Estas variables son almacenadas en una posición específica del arreglo. Cuando construimos un arreglo, se debe especificar la cantidad de variables que se podrán almacenar. int[] arreglo = new int[5]; En el ejemplo anterior, se declara un arreglo y en su creación se reservan cinco espacios en memoria. Una vez que un arreglo es creado, el tamaño del mismo no puede ser alterado. A diferencia de un arreglo, un Vector (instancia de la clase Vector) tiene una estructura de datos dinámica. La capacidad de almacenamiento de un vector puede cambiar en forma dinámica. Cuando un Vector es instanciado, el mismo es inicializado con una capacidad inicial, y cuando esta es superada, la misma es incrementada en forma automática, e incluso podemos configurar cómo debe incrementarse la capacidad en caso que sea necesario. La clase Vector nos ofrece muchos métodos que nos permiten manipular un Vector y todos ellos brindan una alternativa más sencilla para desarrollar aplicaciones. Un objeto de la clase Vector utiliza arreglos internamente. La principal ventaja de un Vector sobre un arreglo es que un Vector puede aumentar su capacidad en forma automática; en cambio, un arreglo una vez creado con un tamaño determinado, no se puede modificar, por lo tanto, si en
Programación Orientada a Objetos – De las Heras, Sebastián | 19
el escenario que estamos trabajando, necesitamos una estructura de datos para almacenar información cuyo tamaño puede ser cambiante, entonces es conveniente la utilización de la clase Vector. En el caso que estemos trabajando en un escenario en el que necesitemos almacenar información en una estructura de datos pero la misma tiene un tamaño fijo y no cambiante en el tiempo, entonces es aconsejable utilizar arreglos. Esto se debe principalmente a que un arreglo tiene mejor performance que un Vector ya que sus operaciones y el acceso a sus elementos tienen mejor performance. Los métodos definidos en la clase Vector están basados en operaciones entre objetos y tienen menor eficiencia en comparación a los algoritmos utilizados para manipular arreglos. Es decir, un Vector nos puede ofrecer mayor facilidad para su manipulación en comparación a un arreglo, pero este último tiene mejor performance en cuantos a sus operaciones. Si la performance del escenario en el que estamos trabajando es un punto crítico a tener en cuenta, entonces es recomendable la utilización de arreglos en lugar de Vectores. El siguiente cuadro expone las ventajas y desventajas de Arreglos y Vectores. Estructura
Arreglo
Vector
Ventajas
Las operaciones sobre arreglos tienen mejor performance que las de un Vector.
Su estructura es dinámica. Su tamaño puede incrementarse o reducirse.
Desventajas
Su tamaño es fijo y no puede cambiar.
Define operaciones a nivel de objetos, por lo tanto son más ineficientes que los algoritmos de los arreglos.
Programación Orientada a Objetos – De las Heras, Sebastián | 20
4.2- Clase String 4.2.1- Utilización de la clase String La manipulación de texto o cadena de caracteres es una parte fundamental en la gran mayoría de los lenguajes de programación. En Java, las cadenas de caracteres son objetos. Para trabajar con estos objetos, Java provee una clase para tal fin. Dicha clase se llama “String” y pertenece al paquete “java.lang”. Mediante la utilización de esta clase podremos crear instancias que representan cadenas de caracteres. Al igual que cualquier otro objeto, se puede instanciar un objeto de tipo String utilizando el operador “new” como se muestra a continuación: String s1 = new String(); La línea de código anterior crea una instancia de la clase String y se la asigna a la variable “s1”. En este caso, la variable “s1” representa una cadena de caracteres vacía. Esto se debe a que en el momento de su inicialización, no se envió por parámetro ninguna cadena de caracteres específica. Si deseáramos inicializar un objeto de tipo String con una cadena de caracteres en particular, podemos utilizar el constructor de la clase String que recibe una cadena de texto como parámetro de la siguiente manera: String s2 = new String(“Ejemplo de un objeto String”); La clase String también provee otros constructores que permiten crear un objeto por medio de distintas alternativas. El código de ejemplo siguiente construye un objeto String utilizando un constructor que recibe un arreglo de elementos char: char[] javaArray = {'¡','J','a','v','a','!'}; String javaString = new String(javaArray); System.out.println(javaString); El código anterior imprimirá en consola la cadena de caracteres “¡Java!”. La utilización de cadena de caracteres es bastante frecuente en la programación, y por este motivo, Java nos ofrece una forma más sencilla de inicializar un objeto de tipo String. String s3 = “Ejemplo de un objeto String”; La línea de código anterior también crea un objeto de tipo String que contiene la cadena de caracteres “Ejemplo de un objeto String”. Algo importante que debemos conocer acerca de la utilización de objetos de tipo String es que los mismos son inmutables. Esto quiere decir que una vez que asignamos una cadena de caracteres a un objeto de tipo String, dicha cadena de caracteres representada por el objeto no podrá ser modificada. Analicemos este concepto mediante un ejemplo: String s4 = “Programación”;
Programación Orientada a Objetos – De las Heras, Sebastián | 21
s4.concat(“ Orientada a Objetos”); System.out.println(“s4 = ” + s4); //la salida en //consola es “Programación”
En la primera línea se crea un objeto “s4” de tipo String y se le asigna el valor “Programación”. En la siguiente línea se utiliza el método “concat()” con la intención de concatenar la cadena de caracteres “ Orientada a Objetos” al objeto “s4”. En la tercera línea imprimimos en consola el valor del objeto “s4”, pero los resultados muestran que en lugar de imprimir “Programación Orientada a Objetos”, se imprime solamente “Programación”. Esto se debe a la inmutabilidad de los objetos de tipo String. En el ejemplo anterior, cuando utilizamos el método “concat()”, este método devuelve un objeto de tipo String con las cadenas de caracteres “Programación Orientada a Objetos” como resultado de la concatenación entre los valores “Programación” y “ Orientada a Objetos” pero no hay ninguna variable que referencie a este nuevo objeto. La variable “s4” sigue referenciando al objeto original con el valor “Programación”. Analicemos este nuevo ejemplo: String s5 = “java”; s5.toUpperCase(); System.out.println(“s5 = ” + s5); // la salida en // consola es “java” El método “toUpperCase()” devuelve un objeto String cuya cadena de caracteres se encuentra en mayúscula, pero la cadena de caracteres representada por el objeto “s5” permanece inmutable. Este es el motivo por el cual se imprime la cadena de caracteres “java” en minúscula en lugar de mayúscula cuando imprimimos el valor del objeto “s5”. Para imprimir la cadena de caracteres en mayúscula, debemos referenciar al objeto String devuelto por el método “toUpperCase()” en una variable y luego imprimir dicha variable: String s5 = “java”; String s6 = s5.toUpperCase(); System.out.println(“s5 = ” + s5); // // System.out.println(“s6 = ” + s6) ;// //
la salida en consola es “java” la salida en consola es “JAVA”
Las siguientes figuras describen las sentencias ejecutadas del código anterior: String s5 = “java”; Objetos en memoria
s5
“java”
Variable de tipo String
Programación Orientada a Objetos – De las Heras, Sebastián | 22
String s6 = s5.toUpperCase(); Objetos en memoria
“java”
s5
toUpperCase()
Variable de tipo String
“JAVA”
s6 Variable de tipo String
Otra consideración a tener en cuenta de la clase String es que la misma contiene el modificador final en su declaración, lo cual implica que no puede ser derivada.
4.2.2- Métodos de la clase String La clase String provee muchos métodos que pueden ser utilizados para la manipulación de cadena de caracteres y son de gran ayuda para los programadores de aplicaciones. A continuación analizaremos los métodos más utilizados que provee esta clase.
public char charAt(int index) Devuelve el carácter que se encuentra en la posición indicada por la variable “index” recibida por parámetro. Los índices utilizados por la clase String comienzan en cero. String java = "Java"; System.out.println(java.charAt(1)); //La salida es ‘a’ public String concat(String s) Devuelve un objeto String compuesto por la unión de la cadena de caracteres del objeto que invoca el método y la cadena de caracteres del objeto que se recibe por parámetro. String lenguaje = "Lenguaje"; System.out.println(lenguaje.concat(" Java")); También se pueden utilizar los operadores “+” y “+=” como si se tratara de un tipo de dato primitivo. La utilización de estos operadores sobre un objeto de tipo String permite también concatenar cadenas de caracteres.
Programación Orientada a Objetos – De las Heras, Sebastián | 23
Ejemplo utilizando operador “+”: String lenguaje = "Lenguaje"; System.out.println(lenguaje + " Java"); Ejemplo utilizando operador “=+”: String lenguaje = "Lenguaje"; lenguaje += " Java"; System.out.println(lenguaje); public boolean endsWith(String suffix) Devuelve un valor boolean (true o false) que indica si la cadena de caracteres del objeto recibido por parámetro (suffix) es sufijo de la cadena de caracteres del objeto que invoca el método. String poo = "Programación Orientada a Objetos"; System.out.println(poo.endsWith("a Objetos")); //true System.out.println(poo.endsWith("a Objetosss")); //false public boolean equals(Object anObject) Devuelve un valor boolean (true o false) que indica si la cadena de caracteres que representa el objeto que invoca el método es igual a la cadena de caracteres representada por el objeto recibido por parámetro. Este método tiene en cuenta la distinción entre minúsculas y mayúsculas. String JAVA = "JAVA"; String c = "c"; System.out.println(JAVA.equals("JAVA")); System.out.println(JAVA.equals("java")); System.out.println(JAVA.equals("jAvA")); System.out.println(JAVA.equals(c));
// // // //
true false false false
Este método es heredado de la clase java.lang.Object y lo analizaremos con mayor detalle en el apartado 4.4 de la lectura (“Comparación de Objetos”).
public boolean equalsIgnoreCase(String s) Devuelve un valor boolean (true o false) que indica si la cadena de caracteres que representa el objeto que invoca el método es igual a la cadena de caracteres recibida por parámetro. A diferencia del método “equals()”, este método no tiene en cuenta la distinción entre minúsculas y mayúsculas. String JAVA = "JAVA"; String c = "c"; System.out.println(JAVA.equalsIgnoreCase("JAVA")); System.out.println(JAVA.equalsIgnoreCase("java")); System.out.println(JAVA.equalsIgnoreCase("jAvA")); System.out.println(JAVA.equalsIgnoreCase(c));
// // // //
true true true false
Programación Orientada a Objetos – De las Heras, Sebastián | 24
public int indexOf(int ch) Devuelve un valor entero que indica la posición de la primera ocurrencia del carácter recibido por parámetro. Si no hay ninguna ocurrencia, entonces el valor devuelto es “-1”. String poo = "Programación Orientada a Objetos"; System.out.println(poo.indexOf('t')); System.out.println(poo.indexOf('k')); El código anterior, primero imprime en consola el valor “18”, ya que la primera ocurrencia del carácter “t” se da en la posición 18 (la primera posición es cero). Luego imprime en consola “-1” ya que no hay ninguna ocurrencia del carácter “k” en la cadena de caracteres “Programación Orientada a Objetos”.
public int indexOf(String str) Devuelve un valor entero que indica la posición de la primera ocurrencia de la cadena de caracteres recibida por parámetro. Si no hay ninguna ocurrencia, entonces el valor devuelto es “-1”. String poo = "Programación Orientada a Objetos"; System.out.println(poo.indexOf("Orientada")); System.out.println(poo.indexOf("No Orientada")); El código anterior imprime en consola el valor “13”, ya que la primera ocurrencia de la subcadena de caracteres “Orientada” se da en la posición 13 (la primera posición es cero). Luego imprime en consola “-1” ya que no hay ninguna ocurrencia de la subcadena de caracteres “No Orientada”.
public int length() Devuelve un entero que indica el largo de la cadena de caracteres, es decir indica cuántos caracteres contiene la cadena de caracteres del objeto. String java = "Java"; System.out.println(java.length()); // Imprime el // valor ‘4’ public String replace(char old, char new) Este método remplaza todas las ocurrencias del carácter “old” recibido por parámetro por el carácter “new” también recibido por parámetro y devuelve un objeto String como resultado. Este método distingue mayúsculas y minúsculas. String JaVa = "JaVa"; System.out.println(JaVa.replace('a', 'A')); //Imprime ‘JAVA’
Programación Orientada a Objetos – De las Heras, Sebastián | 25
public boolean startsWith(String prefix) Devuelve un valor un boolean (true o false) que indica si la cadena de caracteres del objeto recibido por parámetro (prefix) es prefijo de la cadena de caracteres del objeto que invoca el método. Este método distingue mayúsculas y minúsculas. String poo = "Programación Orientada a Objetos"; System.out.println(poo.startsWith("Programación")); System.out.println(poo.startsWith("Programación No Orientada"));
// true // false
public String substring(int begin) Este método es utilizado para devolver una subcadena de caracteres que representa una parte de la cadena de caracteres del objeto sobre el que se invoca el método. El parámetro recibido indica a la posición inicial en la que inicia la subcadena de caracteres a devolver. El índice comienza en cero. String poo = "Programación Orientada a Objetos"; System.out.println(poo.substring(13)); El código anterior imprime en consola la cadena de caracteres “Orientada a Objetos”.
public String substring(int begin, int end) Este método es utilizado para devolver una cadena de caracteres que representa una parte de la cadena de caracteres del objeto sobre el que se invoca el método. A diferencia del método anterior, este último recibe dos parámetros. El primer parámetro recibido (begin) indica la posición inicial en la que inicia la subcadena de caracteres a devolver y el segundo parámetro recibido (end) indica la posición final en la que debe terminar la subcadena de caracteres a devolver. Hay que tener en cuenta que en la determinación de la posición inicial de la subcadena de caracteres, se considera que la primera posición comienza en cero, mientras que para la determinación de la posición final de la subcadena de caracteres se considera que la primera posición comienza en 1. String poo = "Programación Orientada a Objetos"; System.out.println(poo.substring(13,22)); El código anterior imprime en consola la subcadena de caracteres “Orientada”. public String toLowerCase() Devuelve un objeto String constituido por la cadena de caracteres del objeto que invocó al método pero con todos sus caracteres en minúscula. String JAVA = "JAVA"; System.out.println(JAVA.toLowerCase()); //Imprime ‘java’
Programación Orientada a Objetos – De las Heras, Sebastián | 26
public String toString() Devuelve un objeto String con la cadena de caracteres que representa el objeto. String Java = "Java"; System.out.println(Java.toString()); //Imprime ‘Java’ public String toUpperCase() Devuelve un objeto String constituido por la cadena de caracteres del objeto que invocó al método pero con todos sus caracteres en mayúscula. String Java = "Java"; System.out.println(JAVA.toUpperCase()); //Imprime ‘JAVA’
public String trim() Devuelve un objeto String compuesto por la cadena de caracteres del objeto que invocó al método, pero si dicha cadena de caracteres tiene espacios en blancos tanto al comienzo como al final, entonces los mismos son removidos. String lenguajeJava = " Lenguaje System.out.println(lenguajeJava.trim());
Java
";
El método anterior imprime en consola la cadena de caracteres “Lenguaje Java” en lugar de “ Lenguaje Java ”, ya que el método trim() elimina los espacios en blanco que se encuentran al comienzo y al final de la cadena de caracteres.
Programación Orientada a Objetos – De las Heras, Sebastián | 27
4.3- Polimorfismo Cuando declaramos una variable de referencia de un tipo de clase que se corresponde a una clase base de una jerarquía, dicha variable puede hacer referencia a cualquier subclase de dicha jerarquía. Esta propiedad se denomina Polimorfismo. Si definimos una variable “Figura f”, dicha variable puede hacer referencia a objetos de la clase Figura o también puede hacer referencia a objetos de la clase Rectangulo o de la clase Triangulo: Figura figura = new Figura();
// Suponiendo que la // clase Figura no // fuera abstracta Figura rectangulo = new Rectangulo(); Figura triangulo = new Triangulo(); En todos los casos hemos declarado variables de tipo Figura, pero en el caso de las variables “rectangulo” y “triangulo”, ambas hacen referencia a una instancia de la clase Rectangulo y una instancia de la clase Triangulo respectivamente. En el momento que se declara una variable de un cierto tipo de clase, cuando se crea efectivamente el objeto usando el operador new, la variable puede apuntar a cualquier instancia de una subclase de la clase definida en la variable. Esta propiedad se conoce como Polimorfismo. Tenemos una variable que apunta a un objeto distinto al que se esperaría por el tipo de dato de la variable. Cuando se invoca un método desde una variable con una referencia polifórmica, el método invocado será aquel que se encuentra definido en la clase de la referencia polifórmica. Veamos un ejemplo que explique estos conceptos: En este ejemplo vamos a usar el método “toString()”, razón por la cual lo vamos a redefinir en la clase Rectangulo y Triangulo de la misma forma que lo hicimos en su momento para la clase Figura. Método toString() para la clase Rectangulo: public String toString() { String s = super.toString() + "¿Es Cuadrado?: " + isCuadrado; return s; }
Método toString() para la clase Triangulo: public String toString() { String s = super.toString() + "Tipo de triángulo: " + tipoTriangulo; return s; }
Programación Orientada a Objetos – De las Heras, Sebastián | 28
En la siguiente clase definiremos un método “main()” en el que declararemos dos variables con referencias polifórmicas e invocaremos al método “toString()” para ambas variables. package programacion; public class Polimorfismo { public static void main(String args[]) { Figura rectangulo = new Rectangulo("Rectángulo", 4, 4, true); System.out.println("Datos de la variable rectangulo"); System.out.println(rectangulo.toString()); System.out.println("\n"); Figura triangulo = new Triangulo("Triángulo", 4, 6, "Escaleno"); System.out.println("Datos de la variable triangulo"); System.out.println(triangulo.toString()); } }
Si ejecutamos el método “main()”, obtendremos los siguientes resultados en consola: Datos de la variable rectangulo Nombre: Rectángulo Alto: 4 Ancho: 4 ¿Es Cuadrado?: true Datos de la variable triangulo Nombre: Triángulo Alto: 4 Ancho: 6 Tipo de triángulo: Escaleno Como podemos ver, en el caso de la variable “rectangulo”, cuando hemos invocado al método “toString()”, el método ejecutado es el que se encuentra en la clase Rectangulo porque efectivamente el objeto creado y al que se hace referencia es una instancia de esta clase. La misma lógica se aplica para la variable “triangulo”. Esta variable es de tipo Figura pero hace referencia a una instancia de la clase Triangulo, en consecuencia, el método “toString()” a ejecutar será el de la clase Triangulo. Vamos a mencionar otro ejemplo pero esta vez usando el método “calcularArea()”. Como bien sabemos, el método “calcularArea()” es un método abstracto en la clase Figura, pero tanto en la clase Rectangulo como en la clase Triangulo, el método ha sido redefinido con su propia implementación particular para cada clase.
Programación Orientada a Objetos – De las Heras, Sebastián | 29
package programacion; public class Polimorfismo { public static void main(String args[]) { Figura[] figuras = new Figura[2]; figuras[0] = new Rectangulo("Rectángulo", 4, 4, true); figuras[1] = new Triangulo("Triángulo", 4, 6, "Escaleno"); for (int i=0 ; i
En este caso, hemos definido un arreglo de referencias a objetos de tipo Figura. Nuestro interés en este caso es agregar en este arreglo todo tipo de figuras sin importar su tipo específico (rectángulos o triángulos) para luego recorrer este arreglo y mostrar en pantalla el cálculo del área de las figuras almacenadas. Para ello, cuando recorremos el arreglo, sabemos que obtendremos cualquier objeto de tipo Figura, ya sea un Rectangulo o un Triangulo, pero no nos importa el tipo específico al que pertenece el objeto al que se refiere la variable, sólo nos interesa saber que estas referencias ya sean de tipo Rectangulo o Triangulo tienen implementado el método “calcularArea()”. Cualquier objeto que obtengamos del arreglo figuras, será una instancia de la clase Figura y podremos invocar al método “calcularArea()”. La Máquina Virtual de Java determinará en tiempo de ejecución la implementación correspondiente que debe invocar de acuerdo a la clase a la que pertenece la referencia polifórmica. En la primera iteración del ciclo for, estamos trabajando con una variable que en tiempo de ejecución hace referencia a una instancia de la clase Rectangulo, por ende el método “calcularArea()” que se ejecuta es el que se corresponde al de la clase Rectangulo. En la segunda iteración del ciclo for, estamos trabajando con una variable que en tiempo de ejecución hace referencia a una instancia de la clase Triangulo, en consecuencia, el método “calcularArea()” que se ejecuta es el que se corresponde al de la clase Triangulo. En todos estos casos entra en juego el concepto de Polimorfismo. Se trata de una propiedad que permite obtener distintas respuestas ante un mismo mensaje dependiendo de quién reciba el mensaje. En el ejemplo anterior, el mensaje “calcularArea()” es siempre el mismo, pero la respuesta será distinta si quien recibe el mensaje es una instancia de la clase Rectangulo o una instancia de la clase Triangulo. Cuando utilizamos variables con referencias polifórmicas, solamente podemos invocar aquellos métodos definidos en la clase base a la que pertenece la variable. Es decir, si declaramos una variable de la siguiente forma Figura figura = new Triangulo();
Programación Orientada a Objetos – De las Heras, Sebastián | 30
La variable “figura” solamente podrá invocar a los métodos definidos en la clase Figura. Aunque la variable “figura” esté referenciando a una instancia de la clase Triangulo, no podrá invocar los métodos definidos en esta última clase. Cualquier intento de invocar un método definido en la subclase ocasionará un error de compilación: Figura figura = new Triangulo(); figura.getTipoTriangulo(); // ¡Error de compilación! En tiempo de compilación, se debe invocar a los métodos definidos en la clase base de la variable. Esto se debe a que una variable de tipo Figura puede referenciar a instancias de las subclases Rectangulo o Triangulo y hay métodos que están definidos en la clase Rectangulo pero no en la clase Triangulo y viceversa. Sin embargo, hay una forma de invocar a los métodos definidos en la clase a la que la instancia hace referencia. En estos casos se debe realizar un casting a la clase polifórmica. Triangulo t = (Triangulo) figura; t.getTipoTriangulo(); Mediante el empleo de casting, le estamos diciendo al compilador que la variable “figura” en realidad se trata de una instancia de la clase Triangulo. Si la variable figura no hiciera referencia a una instancia de la clase Triangulo, obtendríamos un error en tiempo de ejecución (ClassCastException). Una forma de saber la clase a la que pertenece la instancia referida por una variable es mediante el operador “instanceof”. Figura figura = new Triangulo(); if (figura instanceof Triangulo) { System.out.println("La variable figura hace referencia a una instancia de la clase Triangulo"); } Lo importante a tener en cuenta con respecto al Polimorfismo es que una variable de cierta clase puede referirse a una subclase. Cuando se invocan los métodos sobre dicha variable, solamente pueden invocarse los métodos definidos en la clase base. Pero en tiempo de ejecución, el método invocado será el que está definido en la subclase a la que hace referencia.
Programación Orientada a Objetos – De las Heras, Sebastián | 31
4.4- Comparación de objetos Para comprender la comparación de objetos en Java primero debemos entender algunos conceptos relacionados a la forma en que Java almacena una variable en memoria. Una variable en Java puede ser primitiva o por referencia. Ambas nos sirven para manipular información, pero difieren en su complejidad y el manejo que hace la Máquina Virtual de Java para almacenarlas en memoria. Cuando declaramos una variable local de tipo primitiva en Java, la misma es almacenada en un sector de la memoria denominado Stack. Una de las características de este sector de la memoria es su rápido acceso. Cada variable local primitiva declarada, cuenta con su espacio de memoria reservado en el Stack. int int int int
a b c d
= = = =
10; 6; 2; a;
Suponiendo las variables anteriores declaradas son locales, cada una de ellas es almacenada en el Stack. La declaración de la variable “d” indica que se asigne como valor de la misma el valor de la variable “a” pero esto no quiere decir que ambas variables compartan el mismo espacio de memoria. Se crea una copia del valor de la variable “a”, en este caso “10” y se lo asigna a la variable “d”.
Stack
d c b a Cuando se trata de objetos, su creación difiere en cuanto a la forma en la que se crean variables primitivas, ya que para crear un objeto se debe primero declarar una variable y luego se debe indicar la construcción de un objeto que será referenciado por la variable creada: Auto a1;
Programación Orientada a Objetos – De las Heras, Sebastián | 32
En el ejemplo anterior, se declara únicamente una variable de tipo Auto, pero por el momento esta variable no referencia a ningún objeto, solamente contamos con la variable que apunta a una referencia nula. Para que esta variable referencie a un objeto, se debe primero construir un objeto y luego asignar este objeto a la variable “a1”. Para ello, se utiliza el operador new y el operador de asignación (=). a1 = new Auto(“Ford Fiesta”, “LAR123”); En la línea de código anterior, primero se crea un objeto de tipo Auto utilizando el operador new y luego dicho objeto es asignado a la variable de referencia “a1”, es decir, la variable “a1” referencia al objeto creado de tipo Auto. Una variable de referencia se almacena en el Stack pero el objeto al que referencia se almacena en un sector de la memoria denominado Heap. En el ejemplo anterior, una vez creado el objeto, el mismo es almacenado en el Heap. Este sector de memoria es más lento en comparación al sector de memoria Stack, por lo tanto, por el momento contamos con una variable de referencia y un objeto referenciado por dicha variable. Una variable de referencia es una variable que indica mediante una dirección de memoria la ubicación donde se encuentra el objeto que referencia. Auto Auto Auto Auto
a1; a2; a3; a4;
En las líneas de código anteriores, se declaran cuatro variables de referencia de tipo Auto. Supongamos que la clase Auto tiene definidos los atributos “modelo” y “patente” y su constructor recibe dos variables de tipo String para inicializar dichos atributos. 1. 2. 3. 4.
a1 a2 a3 a4
= = = =
new Auto("Ford Fiesta", "LAR123"); new Auto("Volkswagen Gol", "HIJ583"); new Auto("Ford Fiesta", "KUS392"); a1;
La línea de código 1 crea un objeto de tipo Auto y se asigna a la variable “a1”. La línea de código 2 crea un objeto de tipo Auto y se asigna a la variable “a2”. La línea de código 3 crea un objeto de tipo Auto y se asigna a la variable “a3”. La línea de código 4, indica que la referencia indicada por la variable “a1” sea asignada a la variable “a4”, es decir, se indica que la variable “a4” referencie al mismo objeto referenciado por la variable “a1”. La siguiente figura ilustra una representación de las variables declaradas y sus respectivas referencias.
Programación Orientada a Objetos – De las Heras, Sebastián | 33
Heap Instancia de Auto
“Ford Fiesta” “LAR123” Instancia de Auto
“Ford Fiesta” “KUS392” Instancia de Auto
“Vokswagen Gol” “HIJ583”
Como se puede ver en la figura, un objeto puede ser referenciado por más de una variable. Luego de comprender los conceptos relacionados a la asignación y creación de objetos en memoria, y siguiendo con el ejemplo anterior, nos podemos plantear la siguiente pregunta:
¿Son las variables “a1” y “a2” iguales?
¿Son las variables “a1” y “a3” iguales?
¿Son las variables “a1” y “a4” iguales?
Para responder a estas preguntas, en primer lugar debemos tener en cuenta el criterio que se aplica para definir cuándo dos variables son iguales o no. Un criterio podría considerar que dos variables de referencia son iguales si ambas referencian al mismo objeto. Si seguimos este criterio, entonces las únicas variables que son iguales entre sí serían las variables “a1” y “a4”, ya que ambas hacen referencia al mismo objeto y el resto de las variables referencian a objetos distintos. El operador == permite aplicar este tipo de comparación entre dos objetos. Este operador devuelve como resultado el valor lógico “true” si ambas variables referencian al mismo objeto y devuelve el valor lógico “false” si ambas variables referencian a objetos distintos. A continuación se muestra un ejemplo en el que se hace uso de este operador: Auto Auto Auto Auto
a1 a2 a3 a4
= = = =
new Auto("Ford Fiesta", "LAR123"); new Auto("Volkswagen Gol", "HIJ583"); new Auto("Ford Fiesta", "KUS392"); a1;
Programación Orientada a Objetos – De las Heras, Sebastián | 34
// Comparación 1 if (a1 == a2) { System.out.println("Las variables a1 y a2 hacen referencia al mismo objeto"); } else { System.out.println("Las variables a1 y a2 hacen referencia a objetos distintos"); } // Comparación 2 if (a1 == a3) { System.out.println("Las variables a1 y a3 hacen referencia al mismo objeto"); } else { System.out.println("Las variables a1 y a3 hacen referencia a objetos distintos"); } // Comparación 3 if (a1 == a4) { System.out.println("Las variables a1 y a4 hacen referencia al mismo objeto"); } else { System.out.println("Las variables a1 y a4 hacen referencia a objetos distintos"); } En “Comparación 1”, se comparan las referencias de las variables “a1” y “a2”. El resultado de dicha comparación es “false” ya que ambas variables apuntan a distintos objetos. La misma lógica se aplica en “Comparación 2” pero aplicado a las variables “a1” y “a3”. Estas últimas hacen referencia a objetos distintos y en consecuencia el resultado de la comparación es “false”. En “Comparación 3” se comparan las referencias de las variables “a1” y “a4” y ambas variables referencian al mismo objeto, y por consiguiente el resultado de la comparación es “true”. Por el momento, el criterio de igualdad que aplicamos es el de considerar que dos variables de referencia son iguales si ambas referencian al mismo objeto. Para aplicar este criterio de comparación se utiliza el operador ==. A su vez, el operador != es utilizado en sentido inverso, es decir, para determinar si dos variables de referencia no son iguales. En el caso que las variables de referencia no sean iguales entonces el operador != retorna “true” y en caso que sean iguales retorna “false”. Otro criterio de comparación podría considerar que dos variables de referencia son iguales, si los objetos a los que referencian son “conceptualmente iguales”. Siguiendo con el ejemplo anterior, los atributos que contiene una instancia de la clase Auto son “modelo” y “patente”. Podríamos considerar que dos objetos de tipo Auto son iguales conceptualmente si son del mismo modelo y tienen la misma patente, pero a modo de ejemplo, vamos a considerar que dos objetos de tipo Auto son conceptualmente iguales si ambos tienen definido el mismo modelo. Siguiendo este criterio, entonces las variables “a1” y “a3” son las únicas variables “conceptualmente iguales” ya que los objetos referenciados por ambas variables tienen el mismo modelo definido (“Ford Fiesta”).
Programación Orientada a Objetos – De las Heras, Sebastián | 35
Para definir este criterio de igualdad conceptual entre objetos de tipo Auto debemos redefinir un método especial que tienen todos los objetos denominado “equals()”. Este método se encuentra definido en la clase java.lang.Object, por lo tanto todos los objetos heredan este método, pero para definir nuestro propio criterio conceptual de igualdad es necesario redefinir este método con una implementación acorde a nuestro criterio de igualdad. A continuación se redefine el método “equals()” de la clase Auto: public boolean equals(Object o) { if ((o instanceof Auto) && (((Auto)o).getModelo().equalsIgnoreCase(this.modelo)) { return true; } else { return false; } }
El método “equals()” recibe un objeto como parámetro. Luego se aplica el operador “instanceof” para comprobar que el objeto recibido sea una instancia de la clase Auto para verificar que se comparan objetos del mismo tipo. A continuación se compara el “modelo” del objeto recibido por parámetro y el “modelo” del objeto actual y en caso que sean iguales, se retorna el valor lógico “true” y en caso contrario se retorna el valor lógico “false”. En el siguiente código, se utiliza el método “equals()” para comparar las variables de referencia “a1”, “a2”, “a3” y “a4”. Auto Auto Auto Auto
a1 a2 a3 a4
= = = =
new Auto("Ford Fiesta", "LAR123"); new Auto("Volkswagen Gol", "HIJ583"); new Auto("Ford Fiesta", "KUS392"); a1;
// Comparación 4 if (a1.equals(a2)) { System.out.println("Las variables a1 y a2 son iguales conceptualmente"); } else { System.out.println("Las variables a1 y a2 no son iguales conceptualmente"); } // Comparación 5 if (a1.equals(a3)) { System.out.println("Las variables a1 y a3 son iguales conceptualmente"); } else { System.out.println("Las variables a1 y a3 no son iguales conceptualmente"); } // Comparación 6 if (a1.equals(a4)) { System.out.println("Las variables a1 y a4 son iguales conceptualmente");
Programación Orientada a Objetos – De las Heras, Sebastián | 36
} else { System.out.println("Las variables a1 y a4 no son iguales conceptualmente"); } En “Comparación 4”, se comparan conceptualmente las referencias de las variables “a1” y “a2”. El resultado de dicha comparación es “false” ya que los objetos referenciados por ambas variables tienen modelos distintos de autos. En “Comparación 5”, se comparan conceptualmente las referencias de las variables “a1” y “a3”. Ambas variables referencian objetos distintos, pero tienen el mismo modelo de auto, por lo tanto conceptualmente son iguales y en consecuencia el valor devuelto por el método “equals()” es “true”. En “Comparación 6”, se comparan conceptualmente las referencias de las variables “a1” y “a4”, pero ambas variables referencian al mismo objeto y en consecuencia se está comparando conceptualmente al mismo objeto y en consecuencia el resultado de la comparación es “true”. De acuerdo a lo analizado anteriormente, siempre que queramos verificar la igualdad entre dos objetos, debemos tener en cuenta si lo que queremos verificar es si las variables de referencia hacen referencia al mismo objeto (operador ==), o si los objetos referenciados por ambas variables son conceptualmente iguales (métodos equals()).
Programación Orientada a Objetos – De las Heras, Sebastián | 37
4.5- Sobrecarga de métodos La sobrecarga de un método hace referencia a la definición de distintas versiones del mismo método en una clase. Para sobrecargar un método, debe mantenerse el mismo nombre pero debe modificarse la lista de parámetros del mismo. No se toma como sobrecarga la diferenciación solamente en el tipo de salida del método. Ejemplos de métodos sobrecargados: void ejemplo(int a, String b) { . . } int ejemplo(int a) { . . } int ejemplo(int a, int b) { . . } int ejemplo() { . . } Se trata de métodos distintos pero todos ellos tienen el mismo nombre. La diferencia que existe entre ellos radica en que tienen distinto número y/o tipo de argumentos en su declaración. Algo a tener en cuenta es que los métodos sobrecargados pueden cambiar el tipo de retorno y se pueden definir también en subclases. En el momento en el que se invoca a un método sobrecargado, el compilador busca el método que tenga la definición que se corresponde con el número y/o tipo de parámetros en la invocación del método. Si el compilador no encuentra ningún método que se corresponda con la invocación, entonces obtendremos un error de compilación. Otro claro ejemplo del uso de métodos sobrecargados es cuando definimos distintos constructores para una misma clase.
Programación Orientada a Objetos – De las Heras, Sebastián | 38
4.6- La palabra clave this La palabra clave “this” hace referencia a la instancia u objeto actual. Por medio de ella podemos referirnos a cualquier miembro del objeto actual. Veamos a continuación un ejemplo que permita clarificar este concepto: En la clase “Ejemplo”, tenemos un constructor con dos parámetros (int c, int d) que son asignados a las variables de instancia “a” y “b”. public class Ejemplo { public int a = 0; public int b = 0; //constructor con 2 parámetros public Ejemplo(int c, int d) { a = c; b = d; } } A continuación vamos a escribir la misma clase “Ejemplo” pero haciendo uso de la palabra clave "this": public class Ejemplo { public int a = 0; public int b = 0; //constructor con 2 parámetros public Ejemplo(int c, int d) { this.a = c; this.b = d; } } Podemos ver que lo único que cambió en el constructor fue el agregado de la palabra "this". Ésta se puede utilizar para evitar ambigüedades en el código. Veamos el siguiente ejemplo: public class Ejemplo { public int a = 0; // variable de instancia public int b = 0; // variable de instancia //constructor con 2 parámetros public Ejemplo(int a, int b) { a = a; // variable local b = b; // variable local } }
Programación Orientada a Objetos – De las Heras, Sebastián | 39
En este caso, el constructor define dos parámetros del mismo nombre que las variables de instancia (“a” y “b”) ¿Cuál es el valor de las variables de instancia “a” y “b” luego de llamar al constructor? La respuesta es “a = 0”, y “b = 0”, porque el compilador está asignando la variable “a” que viene por parámetro a la misma variable “a” que viene por parámetro, y lo mismo se aplica para la variable “b”, es decir, en el constructor nunca hacemos referencia a las variables de instancia, sino que siempre nos estamos refiriendo a las variables locales. Este problema de ambigüedad se soluciona agregando la palabra clave "this" antes de las variables “a” y “b”, como se muestra a continuación: public class Ejemplo { public int a = 0;//variable de instancia public int b = 0;//variable de instancia //constructor con 2 parámetros public Ejemplo(int a, int b) { this.a = a; this.b = b; } } Supongamos que invocamos al constructor de la clase “Ejemplo” y como parámetros enviamos los valores “1” y “2”. Como resultado de la ejecución del constructor, los valores de las variables de instancia son “a = 1” y “b = 2”. Hay que tener en cuenta que la palabra clave “this” no puede ser utilizada desde un método estático debido a que estos métodos no se refieren a ninguna instancia en particular y al usar la palabra clave “this” pretenderíamos obtener la instancia actual, lo cual es contradictorio. public class Ejemplo { int a; public static void main(String args[]) { System.ou.println(this.a);//Error de compilación } } La utilización de la palabra clave “this” se considera una buena práctica de programación para evitar ambigüedades.
Programación Orientada a Objetos – De las Heras, Sebastián | 40
4.7- La palabra clave super La palabra clave “super” se utiliza cuando se quiere acceder a los miembros de la clase padre, desde la clase hija. Si se redefine un método de la clase padre, en la clase hija, y se quiere acceder al método de la clase padre, la forma de hacerlo es con la palabra clave “super”. public class EjemploPadre { public void escribir() { System.out.println("Estoy en la clase padre"); } } public class EjemploHija extends EjemploPadre { public void escribir() { System.out.println("Estoy en la clase hija"); } public static void main(String[] args) { EjemploHija ejemploHija = new EjemploHija(); ejemploHija.escribir(); } } Cuando ejecutamos el método “main()” de la clase hija, vemos por la consola el siguiente mensaje: Estoy en la clase hija Vemos este mensaje en la consola, porque el método escribir() de la clase padre, fue sobrescrito en la clase hija. Para poder ejecutar desde la clase hija el método “escribir()” de la clase padre, agregamos la palabra clave “super” como vemos a continuación: public class EjemploHija extends EjemploPadre { public void escribir() { super.escribir();//Se utiliza super SIN paréntesis. System.out.println("Estoy en la clase hija"); } public static void main(String[] args) { EjemploHija ejemploHija = new EjemploHija(); ejemploHija.escribir(); } }
Programación Orientada a Objetos – De las Heras, Sebastián | 41
Cuando ejecutamos el método “main()” de la clase hija, vemos por la consola el siguiente mensaje: Estoy en la clase padre Estoy en la clase hija Si tenemos en la clase hija una variable de instancia “int a=8”, y queremos acceder a una variable de instancia “int a=6” que se encuentra en la clase padre, lo podemos hacer de la siguiente manera: public class EjemploPadre { public int a = 6;//variable de instancia } public class EjemploHija extends EjemploPadre { public int a = 8;//variable de instancia public void escribir() { System.out.println("El valor de a en la clase padre es: " + super.a); System.out.println("El valor de a en la clase hija es: " + a); } public static void main(String[] args) { EjemploHija ejemploHija = new EjemploHija(); ejemploHija.escribir(); } } Luego de ejecutar el método “main()” de la clase hija, vemos por la consola el siguiente mensaje: El valor de a en la clase padre es: 6 El valor de a en la clase hija es: 8
Programación Orientada a Objetos – De las Heras, Sebastián | 42
4.8- Manejo de excepciones 4.8.1- Concepto de excepción En los lenguajes de programación, escribir código que verifique y haga un correcto manejo de las situaciones anómalas que podría tener una aplicación puede resultar tedioso. Sin embargo, la detección de posibles errores y la determinación de cómo debería reaccionar la aplicación ante esos errores es un aspecto fundamental que definirá cuán robusta y confiable es la aplicación. Para ello, Java provee los mecanismos necesarios para el manejo de eventos anormales. Definiremos entonces a una excepción como un evento anormal o inesperado que ocurre durante la ejecución de un programa y altera su flujo normal. El objetivo del manejo o gestión de excepciones es el de proveer un contexto acorde para que la aplicación pueda resolver las situaciones anómalas de la forma en que nosotros creamos más conveniente. Para entender mejor estos conceptos, analicemos el siguiente código de ejemplo: import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class Division { public static void main(String args[]) throws IOException { System.out.println("División entre dos números enteros"); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.println("Ingrese un dividendo entero"); int dividendo = Integer.parseInt(br.readLine()); System.out.println("Ingrese un divisor entero"); int divisor = Integer.parseInt(br.readLine()); int cociente = dividendo/divisor; System.out.println("El resultado de la división es: " + cociente); } }
El código anterior solicita al usuario el ingreso de dos números enteros para realizar el cálculo de su cociente. Si ejecutamos la aplicación podemos ver los siguientes mensajes en la consola: División entre dos números enteros Ingrese un dividendo entero
Programación Orientada a Objetos – De las Heras, Sebastián | 43
8 Ingrese un divisor entero 4 El resultado de la división es: 2 Veamos qué sucede si ingresamos como divisor el valor “cero”: División entre dos números enteros Ingrese un dividendo entero 2 Ingrese un divisor entero 0 Exception in thread "main" java.lang.ArithmeticException: / by zero at Division.main(Division.java:18) Java Result: 1 En consola podemos ver que ha ocurrido una “excepción” y la aplicación ha terminado abruptamente: Exception in thread "main" java.lang.ArithmeticException: / by zero at Division.main(Division.java:18) Esta información se la conoce como stack trace o rastreo de la pila de la excepción. Esta información incluye el nombre de la excepción que ha ocurrido (java.lang.ArithmeticException) junto a algún mensaje descriptivo de la misma y la cadena de llamadas a métodos al momento en que ocurrió la excepción. En este caso, se intentó hacer una división por cero en la siguiente línea de código y la misma arrojó una excepción: int cociente = dividendo/divisor; El nombre de la excepción que se obtuvo es “java.lang.ArithmeticException” y se informa que se intentó hacer una división por cero (“/ by zero”). La división por cero en el conjunto de los números enteros es una operación no permitida, siendo éste el motivo por el cual obtuvimos la excepción. En el código de ejemplo que vimos, el flujo normal de la aplicación sería esperar que el usuario siempre ingrese valores enteros válidos, pero como vimos, se puede dar la ocasión en la que el usuario ingrese valores no esperados, dando a producir situaciones anómalas conocidas como excepciones. Una aplicación puede arrojar varios tipos de excepciones. Intentemos ejecutar la aplicación e ingresar alguna letra en lugar de un número. El resultado obtenido es el siguiente: División entre dos números enteros Ingrese un dividendo entero a Exception in thread "main" java.lang.NumberFormatException: For input string: "a"
Programación Orientada a Objetos – De las Heras, Sebastián | 44
at java.lang.NumberFormatException.forInputString(NumberFo rmatException.java:48) at java.lang.Integer.parseInt(Integer.java:449) at java.lang.Integer.parseInt(Integer.java:499) at Division.main(Division.java:13) Java Result: 1 Como podemos ver, la consola nos informa que se ha lanzado una excepción de tipo “java.lang.NumberFormatException” y la aplicación finaliza abruptamente. Esto se debe a que la aplicación espera que siempre se ingresen números enteros, pero darse la situación en la que un usuario se equivoque e ingrese una letra en lugar de un número. En dicho caso la siguiente línea de código arrojará una excepción: int dividendo = Integer.parseInt(br.readLine()); Esto se debe a que la función “Integer.parseInt()” intenta obtener el número entero que representa la cadena de caracteres ingresada, pero si ingresamos como cadena de caracteres alguna letra, esta función no podrá convertir dicha letra en un número entero y arroja una excepción.
Mediante el manejo de excepciones, los desarrolladores pueden detectar posibles eventos anormales sin tener que escribir demasiados códigos e indicar cómo se debe comportar la aplicación ante estos eventos. Una excepción es representada por un objeto que la Máquina Virtual de Java instancia en tiempo de ejecución si algún evento anómalo ha sucedido, permitiendo al programador que intervenga en dicha situación si así lo deseare para recuperarse del estado anormal y evitar que la aplicación finalice en forma abrupta.
4.8.2- Captura de excepciones Como bien se mencionó anteriormente, el término excepción hace referencia a un “evento anormal” que puede alterar el flujo normal de una aplicación. Muchas pueden ser las causas que pueden originar una excepción: fallas en el hardware, no disponibilidad de recursos, situaciones no contempladas, errores matemáticos, invocación de un método sobre un puntero nulo, índices fuera de rango, entre otros. Cuando una excepción ocurre, se dice que la misma ha sido arrojada y el código responsable de hacer algo con dicha excepción se denomina gestor de la excepción. Se dice que dicho código captura la excepción arrojada. De esta forma, la ejecución del programa se transfiere al manejador de la excepción apropiado. Entonces, por ejemplo, si llamamos a un método que intenta abrir un archivo pero por algún motivo, éste no puede ser abierto, la ejecución de dicho método se detendrá y el código que hayamos definido para que
Programación Orientada a Objetos – De las Heras, Sebastián | 45
controle esta situación será ejecutado. Pero para que este modelo funcione, necesitamos indicarle de alguna forma a la Máquina Virtual Java qué código se debe ejecutar cuando alguna excepción en particular ocurre. Para hacer esta indicación, se utilizan las palabras claves “try” y “catch”. La palabra clave “try” se utiliza para definir un bloque de código en donde alguna excepción puede ocurrir. La palabra clave “catch” se utiliza para capturar alguna excepción en particular o grupo de excepciones para luego ejecutar algún bloque de código. Analicemos la utilización de las palabras claves “try” y “catch” siguiendo el escenario del ejemplo anterior: 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29.
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class Division { public static void main(String args[]) throws IOException { boolean reprocesarCiclo = true; do { try { System.out.println("División entre dos números enteros"); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.println("Ingrese un dividendo entero"); int dividendo = Integer.parseInt(br.readLine()); System.out.println("Ingrese un divisor entero"); int divisor = Integer.parseInt(br.readLine()); int cociente = dividendo/divisor; System.out.println("El resultado de la división es: " + cociente); reprocesarCiclo = false; } catch (ArithmeticException e) { System.out.println("No se puede dividir por cero!"); } catch (NumberFormatException e) { System.out.println("El valor ingresado no es un número entero!"); } } while (reprocesarCiclo);
30. 31. 32. 33. System.out.println("Fin de la aplicación"); 34. } 36. }
Programación Orientada a Objetos – De las Heras, Sebastián | 46
En el ejemplo, el código que se encuentra entre las líneas “11” y “25” constituye una región de código en donde alguna excepción puede ocurrir ya que dicho código se encuentra en un bloque “try”. Es nuestra forma de decirle al compilador que en dicho bloque de código puede producirse alguna excepción. El código que se encuentra entre las líneas “26” y “27” se corresponde con un bloque “catch” y le indica a la aplicación qué acciones debe realizar en el caso que el bloque de código “try” indicado con anterioridad arroje una excepción de tipo “ArithmeticException”. El código que se encuentra entre las líneas “28” y “29” se corresponde con un bloque “catch” y le indica a la aplicación qué acciones debe realizar en el caso que el bloque de código “try” indicado con anterioridad arroje una excepción de tipo “NumberFormatException”. Como pudimos ver, cada bloque “catch” debe especificar entre paréntesis un parámetro que indica el tipo de excepción que manipulará dicho bloque de código “catch”. Esto permite trabajar con el objeto que representa la excepción que ha ocurrido para obtener mayor información de la misma si así se desea. Cuando ocurre una excepción en un bloque de código “try” entonces inmediatamente se ejecutará el bloque de código “catch” correspondiente a la excepción que ocurrió. Se debe tener en cuenta que los bloques de código “catch” se deben colocar inmediatamente después del bloque de código “try”. A su vez, si hay varios bloques de código “catch”, los mismos deben colocarse consecutivamente, es decir, no se puede colocar alguna sentencia intermedia entre los bloques “catch”. La ejecución del código protegido por el bloque “try” comienza en la línea “11”. Si la aplicación logra ejecutarse hasta llegar a la línea “25” sin arrojar ninguna excepción, es decir si ningún evento anormal sucede, entonces la ejecución de la aplicación continuará en la línea “32” hacia abajo. Pero si en algún momento durante la ejecución del código que se encuentra entre las líneas “11” y “25” ocurre alguna excepción de tipo “ArithmeticException”, entonces la ejecución se transferirá directamente a la línea “26” y en consecuencia se ejecutará el bloque de código “catch” correspondiente a la línea “27”. Una vez que el bloque de código “catch” finaliza, la ejecución del programa continúa en la línea “32”. Algo importante a tener en cuenta también es que si por ejemplo, una excepción se produce en la línea “16”, entonces el resto de las líneas sucesivas que se encuentran en el bloque de código “try” no serán ejecutadas, es decir, una vez que el control de un programa se encuentra en un bloque de código “catch” entonces el control nunca regresará al bloque de código “try” para completar su ejecución. Uno de los beneficios de este modelo de manejo de excepciones es que el código necesario para manejar alguna excepción en particular que pueda ser arrojada en cualquier sección de un bloque de código “try” debe ser escrita una única vez. En el ejemplo anterior, una excepción de tipo “NumberFormatException” puede ocurrir tanto en la línea “16” y “19”, pero sin importar en qué línea se arroje la excepción, el mismo bloque de código “catch” capturará la excepción.
Programación Orientada a Objetos – De las Heras, Sebastián | 47
4.8.3- Lanzamiento de excepciones En la sección anterior vimos de qué forma se podía escribir un bloque de código “catch” para capturar alguna excepción en particular y determinar cómo queremos que se comporte el programa ante dicha excepción. En algunos casos es apropiado capturar una excepción en el mismo método en donde ocurre dicha excepción mediante los bloques de código “try” y “catch”, pero también hay otros casos en los cuales puede resultar más conveniente que la excepción sea capturada por algún método superior o previo en pila de llamadas (stack trace). Para ello se dice que se debe lanzar la excepción hacia los métodos previos de la pila de llamadas. Para entender este concepto debemos primero comprender el funcionamiento de la pila de llamadas de un método. La pila de llamadas de un método es la cadena de métodos que el programa ejecuta para obtener el método actual que está siendo ejecutado.
Si nuestro programa inicia con el método “main()”, y dentro de este método se invoca al método “a()”, y éste invoca al método “b()”, y éste último invoca al método “c()”, la pila de llamadas es la siguiente: c() b() a() main()
En la pila de llamadas de métodos, el último método invocado es el que se encuentra en lo más alto del listado, mientras que el primer método invocado es el que encuentra en lo más bajo del listado. En este caso, “c()” es el método que se estaba ejecutando actualmente, y a medida que descendemos hacia abajo en el listado observamos los métodos previamente invocados. En algunos casos se representa la pila de llamadas con el orden invertido al expuesto en el ejemplo. Supongamos ahora que existe la posibilidad de que durante la ejecución del método “c()” ocurra una excepción. Una de las opciones que tenemos es la de capturar dicha excepción mediante los bloques de código “try” y “catch” dentro del mismo método “c()”. Otra opción que tenemos es la de lanzar la excepción hacia el método que previamente fue invocado, en este caso el método “b()” y que sea éste quien tenga la responsabilidad de manipular la excepción. Pero para ello, debemos indicarle a la Máquina Virtual Java que existe la posibilidad de que el método “c()” pueda arrojar una excepción. Esto lo indicamos en la declaración del método utilizando la palabra clave “throws”. A continuación, analizaremos un código de ejemplo para que podamos comprender mejor estos conceptos:
Programación Orientada a Objetos – De las Heras, Sebastián | 48
import java.io.FileWriter; import java.io.IOException; public class EscribirArchivo { private static FileWriter file; public static void main(String args[]) { leerArchivo(); escribirArchivo(); } public static void leerArchivo() { try { file = new FileWriter("test.txt"); } catch (IOException ex) { System.out.println("Una excepción ha ocurrido: " + ex.toString()); } } public static void escribirArchivo() { try { file.write("Programación Orientada a Objetos"); file.close(); } catch (IOException ex) { System.out.println("Una excepción ha ocurrido: " + ex.toString()); } } }
El código anterior escribe la cadena de caracteres “Programación Orientada a Objetos” en un archivo de texto plano denominado “test.txt”. Si dicho archivo no existe, entonces el archivo es creado. Cuando se ejecuta el método “leerArchivo()”, la pila de llamadas es la siguiente: leerArchivo() main() Esto indica que primero se ejecutó el método “main()” y luego dentro de este método se invoca al método “leerArchivo()”. Una vez que finaliza el método “leerArchivo()”, el control vuelve al método “main()” y este último invoca ahora al método “escribirArchivo()”. Cuando se ejecuta el método “escribirArchivo()” la pila de llamadas es la siguiente: escribirArchivo() main() Esto indica que el primer método en ser ejecutado fue el método “main()” y en algún momento este último invoca al método “escribirArchivo()”.
Programación Orientada a Objetos – De las Heras, Sebastián | 49
Si observamos los métodos “leerArchivo()” y “escribirArchivo()”, ambos métodos contienen bloques de código “try” y “catch” debido a que las sentencias utilizadas pueden arrojar excepciones de tipo “IOException”. Este tipo de excepción representa errores relacionados con operaciones de entrada y salida de datos, como puede ser por ejemplo algún error en el intento de creación del archivo “test.txt” o algún problema mientras se intenta guardar una cadena de caracteres en el archivo. En este caso, hemos definido bloques de código “try” y “catch” que se encargan de manipular las excepciones dentro del método donde ha sucedido la excepción. A continuación modificaremos el código para que veamos de qué forma se puede lanzar la excepción que ha ocurrido a un método previo en la pila de llamadas: import java.io.FileWriter; import java.io.IOException; public class EscribirArchivo { private static FileWriter file; public static void main(String args[]) { try { leerArchivo(); escribirArchivo(); } catch (IOException ex) { System.out.println("Una excepción ha ocurrido: " + ex.toString()); } } public static void leerArchivo() throws IOException { file = new FileWriter("test.txt"); } public static void escribirArchivo() throws IOException { file.write("Programación Orientada a Objetos");//línea 22 file.close(); } }
Como bien dijimos con anterioridad, para lanzar una excepción a un método previo de la pila de llamadas se debe indicar la posibilidad de que el método actual puede arrojar una excepción. Si observamos los métodos “leerArchivo()” y “escribirArchivo()”, ambos métodos tienen en su declaración la siguiente sentencia: throws IOException Esto quiere decir que el conjunto de sentencias de este método tiene la posibilidad de arrojar una excepción de tipo “IOException”. Entonces si por ejemplo, ocurre una excepción en la línea de código “22” mientras se intenta escribir la cadena de caracteres “Programación Orientada a Objetos” en el archivo, la pila de llamadas de la excepción será la siguiente:
Programación Orientada a Objetos – De las Heras, Sebastián | 50
escribirArchivo() main() En este caso, como la línea de código “22” no forma parte de un bloque de código “try” y “catch” entonces se dice que la excepción es lanzada hacia el método previo de la pila de llamadas, en este caso el método “main()”, y este último es ahora responsable de manipular la excepción, que efectivamente lo hace mediante un bloque de código “try” y “catch”. La misma lógica se aplica para el método “leerArchivo()”. Si una excepción de tipo “IOException” ocurre durante la ejecución de este método, entonces dicha excepción será lanzada hacia el método “main()”. Es importante tener en cuenta el uso de la sentencia “throws” en la declaración de un método para indicar que el mismo puede lanzar excepciones. De esta forma, si un método “a()” invoca a otro método “b()” cuya declaración incluye la sentencia “throws”, entonces el método “a()” conoce que puede recibir excepciones desde el método “b()” y deberá estar preparado para manipularlas. Para indicar que un método puede lanzar más de una excepción, se indican los distintos tipos de excepciones que se pueden lanzar separados por coma: public static void escribirArchivo() throws FileNotFoundException, InterruptedIOException, RemoteException { La declaración de este método indica que puede arrojar excepciones de tipo FileNotFoundException, InterruptedIOException y RemoteException. Antes de que una excepción pueda ser capturada en algún punto, debe existir algún código que inicie y lance dicha excepción. Este lanzamiento de la excepción puede estar indicado en nuestro código o en el código de alguna librería que utilicemos o en las clases que componen los paquetes de Java. Sin importar quién lanza específicamente la excepción, lo importante es saber que para que se inicie el flujo de una excepción, la misma debe ser lanzada o arrojada utilizando la sentencia “throw” (no confundir con la sentencia “throws”). En los ejemplos anteriores, las excepciones eran creadas y lanzadas por clases de Java. Por ejemplo, cuando evaluábamos la siguiente expresión: Integer.parseInt(br.readLine()); Si la sentencia anterior recibe como parámetro una cadena de caracteres que no representa un número entero, entonces la implementación del método “parseInt()” crea y lanza una excepción de tipo “NumberFormatException”, es decir, la implementación del método “parseInt()” contiene líneas de código que evalúan si el parámetro que recibe se corresponde al de un número entero, y en caso contrario crea un objeto de tipo “NumberFormatException” que representa la excepción producida y la misma es arrojada utilizando la sentencia “throw”. A continuación mostraremos un ejemplo, en donde verifiquemos una condición, y en el caso que dicha condición no se cumpla entonces lanzaremos una excepción. Supongamos que a nuestra clase Division que
Programación Orientada a Objetos – De las Heras, Sebastián | 51
hemos definido en un ejemplo anterior que se encarga de realizar la división entre dos números enteros deseamos incluir como restricción que únicamente se puedan dividir números enteros positivos. En otras palabras, si al momento de ingresar el dividendo y el divisor, se ingresan números negativos entonces se debe informar que no se podrá realizar la división porque el número ingresado es un número entero negativo. El código modificado para cumplir con esta restricción sería el siguiente: import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class Division { public static void main(String args[]) throws IOException { boolean reprocesarCiclo = true; do { try { System.out.println("División entre dos números enteros"); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.println("Ingrese un dividendo entero"); int dividendo = Integer.parseInt(br.readLine()); if (dividendo < 0) { Exception e = new Exception("Dividendo ingresado negativo!"); throw e; } System.out.println("Ingrese un divisor entero"); int divisor = Integer.parseInt(br.readLine()); if (divisor < 0) { Exception e = new Exception("Divisor ingresado negativo!"); throw e; } int resultado = dividir(dividendo, divisor); System.out.println("El resultado de la división es: " + resultado); reprocesarCiclo = false; } catch (ArithmeticException e) { System.out.println("No se puede dividir por cero!"); } catch (NumberFormatException e) { System.out.println("El valor ingresado no es un número entero!"); } catch (Exception e) { System.out.println("Una excepción ha ocurrido! " + e); } } while (reprocesarCiclo); System.out.println("Fin de la aplicación"); } public static int dividir(int dividendo, int divisor) {
Programación Orientada a Objetos – De las Heras, Sebastián | 52
int cociente = dividendo/divisor; return cociente; } }
En el código anterior, en las líneas “17” y “24” hemos agregado la condición de que el dividendo y el divisor ingresados no pueden ser números enteros negativos. Si se cumple la condición de que los valores ingresados son números enteros negativos entonces se crea una instancia de la clase Exception y luego se lanza la excepción utilizando la palabra clave throw. Exception e = new Exception("Dividendo ingresado negativo!"); throw e; Si se lanza la excepción, entonces el bloque de código “catch” de la línea “38” captura dicha excepción y la procesa. En el constructor de la clase Exception hemos pasado como parámetro una cadena de caracteres con información sobre el error producido. Si ejecutamos el programa e introducimos un número entero negativo, entonces en la consola se nos informa que una excepción ha ocurrido y se nos muestra el mensaje de error que hemos definido en el código: División entre dos números enteros Ingrese un dividendo entero 8 Ingrese un divisor entero -4 Una excepción ha ocurrido! java.lang.Exception: Divisor ingresado negativo! La plataforma de Java dispone de muchas clases para representar excepciones. Todas estas clases son descendientes de la clase “java.lang.Throwable”. Cuando se lanza una excepción utilizando la sentencia “throw”, el único requerimiento de esta sentencia es que el objeto que se ha de lanzar debe ser una instancia de la clase “Throwable”. En el ejemplo anterior hemos creado una instancia de la clase Exception (subclase de Throwable) y dicho objeto ha sido lanzado utilizando la sentencia “throw”. No hay que confundir la utilización de las palabras claves “throws” y “throw”. La palabra clave “throws” es utilizada en la declaración de un método para indicar las excepciones que pueden ser arrojadas por dicho método; mientras que la palabra clave “throw” es utilizada para lanzar en forma explícita una excepción.
Programación Orientada a Objetos – De las Heras, Sebastián | 53
4.8.4- Jerarquía de excepciones Todas las clases que representan excepciones en la plataforma Java son subclases de la clase “java.lang.Exception”. A su vez, esta última deriva de la clase “java.lang.Throwable” y ésta de la clase “java.lang.Object”.
Object
Throwable
Error (unchecked)
Exception
RuntimeException (unchecked)
IOException (checked)
Excepciones del programador
(checked)
En la figura anterior se representa la jerarquía de excepciones de la plataforma Java con sus clases más representativas. La clase Throwable tiene dos subclases: Error: las clases que derivan de Error representan situaciones anómalas que no son causadas por errores de programación y pueden estar asociadas a errores graves de hardware o del sistema en los cuales el programador nada puede hacer al respecto más que ser notificado sobre el error. Un ejemplo de este tipo de error se genera cuando la Máquina Virtual Java no cuenta con la memoria suficiente para almacenar algún objeto en memoria y en consecuencia se produce un error de tipo OutOfMemoryError (subclase de Error). En general, cuando sucede alguno de estos tipos de errores (instancias de la clase Error), la aplicación no estará en condiciones de manipular el error y recuperarse de la situación, motivo por el cual muchas veces no tiene sentido intentar manipular estos tipos de errores. Técnicamente, las instancias de la clase Error no se las considera excepciones porque no derivan de la clase Exception.
Programación Orientada a Objetos – De las Heras, Sebastián | 54
Exception: una instancia de la clase Exception representa una situación anómala excepcional que puede ocurrir y puede ser manipulada de forma tal que la aplicación se puede recuperar de dicho estado anormal. La mayoría de los programas lanzan y capturan objetos que derivan de la clase Exception. Una excepción indica que un problema ha ocurrido pero no es tan grave como un error del sistema. La plataforma Java define muchas clases descendientes de la clase Exception. Cada una de estas clases representa algún tipo de tipo de excepción en particular que puede requerir por parte del programador la obligación de definir algún código que manipule dicha excepción ya que de lo contrario la aplicación no compilaría. De acuerdo a la obligación o no de definir código que manipule una excepción, las mismas se pueden clasificar en verificadas en compilación (o chequeadas) y no verificadas en compilación (o no chequeadas). Para las excepciones que son verificadas en compilación es obligatorio definir un código que haga un tratamiento de la posible excepción que se puede producir aunque la misma no llegue nunca a ejecutarse, ya que de lo contrario el código no compilará. Todas las clases que extienden de Exception salvo la clase RutimeException son verificadas o chequeadas (checked). Como ejemplo, podemos citar las excepciones que son instancias de la clase IOException. Esta clase deriva de Exception y representa a las excepciones relacionadas con operaciones de entrada y salida de datos desde diversos tipos de dispositivos. Si en algún momento utilizamos un método que puede arrojar este tipo de excepción, entonces al ser una excepción verificada, tenemos la obligación de definir algún código que haga un tratamiento de la excepción, de lo contrario obtendremos un error de compilación. Por ejemplo, los métodos “write()” y “close()” de la clase “FileWriter” pueden arrojar excepciones de tipo IOException, por lo tanto si no indicamos ningún tratamiento ante la posibilidad de recibir una excepción cuando utilizamos estos métodos, obtendremos un error de compilación: public static void escribirArchivo() { //file es una instancia de FileWriter file.write("Programación Orientada a Objetos"); file.close(); } En el código anterior, hacemos uso de los métodos “write()” y “close()”, pero no indicamos ningún tratamiento para la posible excepción de tipo IOException que se podría recibir desde estos métodos, por lo tanto el error de compilación que obtenemos es el siguiente: Exception in thread "main" java.lang.RuntimeException: Uncompilable source code - unreported exception java.io.IOException; must be caught or declared to be thrown at EscribirArchivo.escribirArchivo(EscribirArchivo.java:22) at EscribirArchivo.main(EscribirArchivo.java:11) Java Result: 1
Programación Orientada a Objetos – De las Heras, Sebastián | 55
El error de compilación informa que se debe indicar algún código que permita darle un tratamiento a la posible excepción de tipo IOException que se puede arrojar. Para definir un tratamiento a la posible excepción, se puede definir código que capture la excepción mediante bloques “try” y “catch” o se puede seguir lanzando la excepción a un método superior (sentencia “throws” en la declaración del método). Como ejemplo, vamos a dejar que la excepción sea manipulada por algún método previo y para ello vamos a utilizar la sentencia “throws” en la declaración del método. public static void escribirArchivo() throws IOException {
//file es una instancia de FileWriter file.write("Programación Orientada a Objetos"); file.close(); }
Debido a que hemos definido un tratamiento para la excepción verificada o chequeada de tipo IOException, entonces ahora el método “escribirArchivo()” compila en forma satisfactoria. Por el otro lado, para las excepciones no verificadas o no chequeadas (unchecked) el compilador no obliga a establecer algún código que defina un tratamiento a las posibles excepciones que se pueden ejecutar, pero el programador lo puede hacer si así lo deseare. Las clases que derivan de RuntimeException y Error representan excepciones no verificadas. En el siguiente ejemplo, hacemos uso del método “parseInt()” de la clase Integer: Integer.parseInt("a"); Este método puede lanzar una excepción de tipo NumberFormatException y se trata de una clase que extiende de RuntimeException, por lo tanto es una excepción no verificada. Debido a esto, no es necesario definir un código que trate a la posible excepción que se puede llegar a lanzar, pero podemos hacerlo si deseamos ejecutar alguna acción en particular ante la ocurrencia de dicha excepción: try { Integer.parseInt("a"); } catch (NumberFormatException e) { System.out.println("El valor ingresado no es un número entero!"); } Algunas de las clases más conocidas que extienden de Exception son: -
ClassNotFoundException IllegalAccessException InterruptedException IOException NamingException SQLException TimeoutException
Programación Orientada a Objetos – De las Heras, Sebastián | 56
Algunas de las clases más conocidas que extienden de RuntimeException son: -
ArithmeticException ClassCastException ConcurrentModificationException IllegalArgumentException IndexOutOfBoundsException MissingResourceException NegativeArraySizeException NoSuchElementException NullPointerException
Una vez que tenemos un objeto que representa una excepción, hay dos formas de obtener información acerca de la misma. Una opción es por medio del tipo de excepción, es decir, por medio de la clase de la cual el objeto es instancia. Por otro lado, la clase Throwable provee algunos métodos que pueden ser útiles para obtener información acerca de la excepción ocurrida. Como estos métodos se encuentran definidos en la clase Throwable, entonces los mismos son heredados por el resto de las clases del árbol de excepciones. Uno de los métodos útiles es el método “printStackTrace()”, el cual imprime en consola la pila de llamadas de métodos al punto en el que se ejecutó la excepción. El método “getMessage()” retorna un objeto de tipo String con información sobre el error que dio origen a la excepción.
4.8.5- Excepciones definidas por el programador Cuando una excepción ocurre, un objeto de la clase Exception o de alguna de sus subclases es instanciada y lanzada. Cuando estamos escribiendo código y deseamos lanzar una excepción, podemos crear una instancia de una clase ya existente como pueden ser aquellas que pertenecen a la plataforma Java, o podemos definir nuestra propia clase que represente alguna excepción. Para ello, lo único que debemos realizar es definir una nueva clase y que la misma extienda de la clase Exception. Para ejemplificar este concepto, supongamos que en la aplicación anterior en la cual se intenta realizar la división entre dos números enteros no negativos definimos la siguiente clase que representa una excepción: public class NumeroNegativoException extends Exception { private String mensaje = "Se produjo un error debido a que el número ingresado es un número negativo"; public NumeroNegativoException() { } public NumeroNegativoException(String mensaje) { this.mensaje = mensaje;
Programación Orientada a Objetos – De las Heras, Sebastián | 57
} public String getMensaje() { return mensaje; } }
Esta excepción puede ser utilizada para representar un error cuando se ingresa un número con valor negativo. De esta forma, en lugar de lanzar una excepción de tipo Exception como lo hacíamos anteriormente cuando se ingresa un valor negativo, podemos modificar el código de la aplicación para crear una excepción de tipo NumeroNegativoException y lanzarla. int dividendo = Integer.parseInt(br.readLine()); if (dividendo < 0) { NumeroNegativoException e = new NumeroNegativoException("Dividendo ingresado negativo!"); throw e; } System.out.println("Ingrese un divisor entero"); int divisor = Integer.parseInt(br.readLine()); if (divisor < 0) { NumeroNegativoException e = new NumeroNegativoException("Divisor ingresado negativo!"); throw e; } De esta forma, podemos crear una clase que represente alguna excepción con la estructura que a nosotros nos parezca más conveniente.
Programación Orientada a Objetos – De las Heras, Sebastián | 58
4.9- Aplicaciones en Java Aplicación: Carga de salarios La siguiente aplicación tiene por objetivo realizar la carga por teclado de una determinada cantidad de salarios para luego mostrar los valores ingresados y el promedio de los mismos. En la aplicación se hace uso de arreglos y manejo de excepciones.
// Clase CargaSalarios package salarios; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class CargaSalarios { private int[] salarios; BufferedReader br; private static int LIMITE_CANTIDAD_SALARIOS = 10; public CargaSalarios() { br = new BufferedReader(new InputStreamReader(System.in)); } public void cargarCantidadDeSalarios() throws IOException { boolean pedirCargaDeDatos = true; while (pedirCargaDeDatos) { System.out.println("\nIngrese la cantidad de salarios con la que desea operar (entre 1 y 10): "); try { int cantidadDeSalarios = Integer.parseInt(br.readLine()); if (cantidadDeSalarios < 0 || cantidadDeSalarios > LIMITE_CANTIDAD_SALARIOS) { ValorIngresadoException ex = new ValorIngresadoException("La cantidad de salarios ingresada no es correcta. Debe ser entre 1 y 10"); throw ex; } salarios = new int[cantidadDeSalarios]; pedirCargaDeDatos = false; } catch (NumberFormatException ex) { System.out.println("El valor ingresado no es un número entero! Intente ingresar un número entero."); } catch (ValorIngresadoException ex) { System.out.println("ValorIngresadoException: " + ex.getMensaje()); } } }
Programación Orientada a Objetos – De las Heras, Sebastián | 59
public void cargarSalarios() throws IOException { for (int i=0 ; i
Programación Orientada a Objetos – De las Heras, Sebastián | 60
// Clase ValorIngresadoException package salarios; public class ValorIngresadoException extends Exception { private String mensaje = "El valor ingresado no es un valor esperado!"; public ValorIngresadoException() { } public ValorIngresadoException(String mensaje) { this.mensaje = mensaje; } public String getMensaje() { return mensaje; } public void setMensaje(String mensaje) { this.mensaje = mensaje; } }
Programación Orientada a Objetos – De las Heras, Sebastián | 61
Bibliografía Lectura 5 Sierra Kathy, Bates Bert, (2008), “SCJP Sun Certified Programmer for Java 6 Study Guide (Exam 310-065)”, EE.UU, Editorial McGraw-Hill. Bruce Eckel, (2006), “Thinking in Java” (4a ed.), EE.UU, Editorial Prentice Hall.
Frittelli Valerio, (2007), “Manual Programador Java” (2a ed.), Argentina. The Java Tutorials (Oracle), (2011), “Learning the Java Language”, Recuperado el 24/01/2013 de http://download.oracle.com/javase/tutorial/java/TOC.html
Programación Orientada a Objetos – De las Heras, Sebastián | 62