• No se han encontrado resultados

Arreglos String Polimorfismo y Excepciones en Java

N/A
N/A
Protected

Academic year: 2021

Share "Arreglos String Polimorfismo y Excepciones en Java"

Copied!
62
0
0

Texto completo

(1)

Módulo 4

Arreglos, String,

Polimorfismo y

Excepciones en Java

Programación Orientada a Objetos

De las Heras, Sebastián

(2)

Programación Orientada a Objetos – De las Heras, Sebastián | 2

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 0 1 2 3 4 5 Elementos 24 12 14 28 9 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.

(3)

Programación Orientada a Objetos – De las Heras, Sebastián | 3 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:

Columnas 0 1 2 3 Filas 0 43 21 7 25 1 3 67 34 67 2 6 2 56 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:

(4)

Programación Orientada a Objetos – De las Heras, Sebastián | 4 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.

(5)

Programación Orientada a Objetos – De las Heras, Sebastián | 5 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.

(6)

Programación Orientada a Objetos – De las Heras, Sebastián | 6 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.

(7)

Programación Orientada a Objetos – De las Heras, Sebastián | 7 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.

(8)

Programación Orientada a Objetos – De las Heras, Sebastián | 8 Juan Persona Lucas Persona Florencia Persona

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

(9)

Programación Orientada a Objetos – De las Heras, Sebastián | 9 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<s.length; i++) { s[i] = new String(“”);

}

En cada iteración, el valor de i es aumentado en una unidad comenzando desde cero, entonces se utiliza esta variable para hacer referencia a las posiciones de los elementos del arreglo. Cuando la variable i vale 100, no se cumple la condición “i < s.length” y por consiguiente el ciclo for deja de ser ejecutado. Utilizar un ciclo for para inicializar un arreglo es una de las formas más utilizadas pero también se pueden utilizar otras sentencias iterativas, como por ejemplo la sentencia while:

String[] s = new String[100]; int i=0;

while (i<100) {

s[i] = new String(""); i++;

}

Si deseamos utilizar sentencias iterativas para inicializar un arreglo de más de una dimensión entonces se debe iterar sobre todos los elementos para cada una de las dimensiones que componen el arreglo. Veamos el siguiente ejemplo:

(10)

Programación Orientada a Objetos – De las Heras, Sebastián | 10

2. for (int a=0 ; a<arregloBytes.length ; a++) { 3. for (int b=0 ; b<arregloBytes[a].length ; b++) { 4. for (int c=0 ; c<arregloBytes[a][b].length ; c++) { 5. arregloBytes[a][b][c] = 2;

6. } 7. } 8. }

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:

(11)

Programación Orientada a Objetos – De las Heras, Sebastián | 11 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<listadoUsuarios.length; i++) { int idGenerado = (int)(Math.random() * 1000); listadoUsuarios[i] = new Usuario(idGenerado); }

for (int i=0; i<listadoUsuarios.length; i++) { System.out.println(listadoUsuarios[i]); } } } // Clase Usuario package programacion;

public class Usuario { private int id;

public Usuario(int idUsuario) { id = idUsuario;

}

public int getId() { return id;

}

public void setId(int id) { this.id = id;

}

public String toString() {

String s = "Usuario - id: " + id; return s;

} }

(12)

Programación Orientada a Objetos – De las Heras, Sebastián | 12

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:

(13)

Programación Orientada a Objetos – De las Heras, Sebastián | 13 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

(14)

Programación Orientada a Objetos – De las Heras, Sebastián | 14 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.

(15)

Programación Orientada a Objetos – De las Heras, Sebastián | 15 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

(16)

Programación Orientada a Objetos – De las Heras, Sebastián | 16 /* 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”);

(17)

Programación Orientada a Objetos – De las Heras, Sebastián | 17 * 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.

(18)

Programación Orientada a Objetos – De las Heras, Sebastián | 18 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<numeros.size(); i++) { System.out.println(numeros.elementAt(i)); }

Otra forma de acceder a los elementos de un vector es mediante la interface Enumeration. Para ello, se utilizan las funciones hasMoreElements() y nextElement() de esta interface. Si invocamos al método elements(), este último retorna un objeto que implementa la interface Enumeration y en consecuencia, tiene implementadas las funciones hasMoreElements() y nextElement(). Estos métodos permiten recorrer secuencialmente los elementos de un vector. El método hasMoreElements() retorna un valor boolean “true” o “false” que indica si el vector tiene más elementos para recorrer o no. Cuando se llega al último elemento del vector, este método devuelve un valor boolean “false”, indicando que no hay más elementos para recorrer. El método nextElement() retorna una referencia al próximo elemento del vector. Para entender mejor estos conceptos, a continuación se presenta un código que recorre secuencialmente un vector utilizando estos métodos:

Vector numeros = new Vector(5); numeros.add(“uno”);

numeros.add(“dos”); numeros.add(“tres”); numeros.add(“cuatro”);

Enumeration elementos = numeros.elements(); while (elementos.hasMoreElements()) {

System.out.println(elementos.nextElement()); }

(19)

Programación Orientada a Objetos – De las Heras, Sebastián | 19 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 y desventajas de arreglos y

Vectores

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

(20)

Programación Orientada a Objetos – De las Heras, Sebastián | 20 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.

(21)

Programación Orientada a Objetos – De las Heras, Sebastián | 21

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:

(22)

Programación Orientada a Objetos – De las Heras, Sebastián | 22 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); // la salida en // consola es “java” System.out.println(“s6 = ” + s6) ;// la salida en // consola es “JAVA”

Las siguientes figuras describen las sentencias ejecutadas del código anterior: String s5 = “java”; “java” s5 Variable de tipo String Objetos en memoria

(23)

Programación Orientada a Objetos – De las Heras, Sebastián | 23 String s6 = s5.toUpperCase(); “java” “JAVA” s5 s6 Objetos en memoria toUpperCase() Variable de tipo String 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.

(24)

Programación Orientada a Objetos – De las Heras, Sebastián | 24 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")); // true System.out.println(JAVA.equals("java")); // false System.out.println(JAVA.equals("jAvA")); // false System.out.println(JAVA.equals(c)); // 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")); // true System.out.println(JAVA.equalsIgnoreCase("java")); // true System.out.println(JAVA.equalsIgnoreCase("jAvA")); // true System.out.println(JAVA.equalsIgnoreCase(c)); // false

(25)

Programación Orientada a Objetos – De las Heras, Sebastián | 25 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";

(26)

Programación Orientada a Objetos – De las Heras, Sebastián | 26 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")); // true System.out.println(poo.startsWith("Programación No

Orientada")); // 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";

(27)

Programación Orientada a Objetos – De las Heras, Sebastián | 27 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 Java "; System.out.println(lenguajeJava.trim());

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.

(28)

Programación Orientada a Objetos – De las Heras, Sebastián | 28

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; }

(29)

Programación Orientada a Objetos – De las Heras, Sebastián | 29 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.

(30)

Programación Orientada a Objetos – De las Heras, Sebastián | 30

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<figuras.length ; i++) { Figura figura = figuras[i];

System.out.println("El cálculo del área para la figura " + figura.getNombre() + " es igual a " + figura.calcularArea()); } } }

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

(31)

Programación Orientada a Objetos – De las Heras, Sebastián | 31 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.

(32)

Programación Orientada a Objetos – De las Heras, Sebastián | 32

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 a = 10; int b = 6; int c = 2; int d = 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:

(33)

Programación Orientada a Objetos – De las Heras, Sebastián | 33 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 a1; Auto a2; Auto a3; Auto 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. a1 = new Auto("Ford Fiesta", "LAR123"); 2. a2 = new Auto("Volkswagen Gol", "HIJ583"); 3. a3 = new Auto("Ford Fiesta", "KUS392"); 4. a4 = 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.

Referencias

Documento similar

&#34;No porque las dos, que vinieron de Valencia, no merecieran ese favor, pues eran entrambas de tan grande espíritu […] La razón porque no vió Coronas para ellas, sería

Cedulario se inicia a mediados del siglo XVIL, por sus propias cédulas puede advertirse que no estaba totalmente conquistada la Nueva Gali- cia, ya que a fines del siglo xvn y en

Habiendo organizado un movimiento revolucionario en Valencia a principios de 1929 y persistido en las reuniones conspirativo-constitucionalistas desde entonces —cierto que a aquellas

The part I assessment is coordinated involving all MSCs and led by the RMS who prepares a draft assessment report, sends the request for information (RFI) with considerations,

Ciaurriz quien, durante su primer arlo de estancia en Loyola 40 , catalogó sus fondos siguiendo la división previa a la que nos hemos referido; y si esta labor fue de

Por PEDRO A. EUROPEIZACIÓN DEL DERECHO PRIVADO. Re- laciones entre el Derecho privado y el ordenamiento comunitario. Ca- racterización del Derecho privado comunitario. A) Mecanismos

En el capítulo de desventajas o posibles inconvenientes que ofrece la forma del Organismo autónomo figura la rigidez de su régimen jurídico, absorbentemente de Derecho público por

(29) Cfr. MUÑOZ MACHADO: Derecho público de las Comunidades Autóno- mas, cit., vol. Es necesario advertir que en la doctrina clásica este tipo de competencias suele reconducirse