Prog Estructuras de almacenamiento

De MediaWiki
Ir a la navegación Ir a la búsqueda

Introducción

  • En lo que llevamos de curso ya hemos visto varias estructuras de almacenamiento: clases y arrays.
  • Para almacenar información no siempre llega guardar datos en tipo de datos simples, como un entero, una cadena, o un float.
Muchas veces es necesario guardar información de varios tipos o guardar un conjunto de datos del mismo tipo.
  • Vimos que podemos guardar información de diferentes tipos de datos formando 'un todo' en una clase. Por ejemplo, si quiero guardar la información de un empleado, con su nombre, edad, años de antigüedad...
Cuando definimos una clase indicamos con sus atributos (tipos de datos simples o compuestos) que información va a guardar.
Otras veces, la información que queremos guardar necesita una estructura de datos que permite guardar múltiples valores del mismo tipo, por ejemplo, un conjunto de notas de un alumno.
Esto ya vimos que lo podíamos guardar utilizando arrays.


  • Las estructuras de datos en base a diferentes criterios las podemos clasificar de varias formas.
  • En función de los diferentes tipos de datos que puedan guardar:
  • Que puedan guardar tipos de datos del mismo tipo. Ejemplo de este tipo de estructuras son las cadenas de caracteres (guardan múltiples caracteres), los arrays (ya vistos), las listas y los conjuntos.
  • Que puedan guardar tipos de datos diferentes. Por ejemplo las clases.
  • En función de si se puede modificar o no el número de elementos que conforman el array (su tamaño):
  • Que no se puedan modificar su tamaño una vez creada (estructuras estáticas). Por ejemplo, los arrays.
  • Que se pueda modificar su tamaño en tiempo de ejecución de forma dinámica (aumentar y disminuir de tamaño). Se conocen como estructuras dinámicas. Por ejemplo las listas, árboles, conjuntos y algunas clases que manejan caracteres.
  • En función de si los datos guardados en la estructura pueden estar ordenados:
  • Que no puedan estar ordenados. Por ejemplo los arrays. Debe ser el programador el que modifique los datos guardados para ordenarlos.
  • Que se puedan ordenar en base a diferentes criterios.



Cadena de caracteres

Existe otro método, el método compareTo. A diferencia del anterior, compara las dos cadenas lexicográficamente.
Disponemos de dos variantes en las que se ignora el hecho de que las letras estén en mayúsculas o en minúsculas:
  • Métodos usados habitualmente:

Strings dinámicos

  • Cada vez que usamos una cadena estamos creando un objeto de una clase String, cogiendo memoria. Esa memoria se reserva 'consecutivamente' y no puede variarse su tamaño, por lo que si queremos concatenar otra cadena, necesitamos utilizar otra zona de memoria donde quepa la cadena al completo.
Además las cadenas son 'inmutables' en el sentido que no podemos 'acortalas' o 'ampliarlas', ya que estaríamos creando una nueva cadena.
  • Por estos problemas, Java dispone de dos clases que permiten crear cadenas dinámicas, a las que vamos a poder ampliar o reducir su contenido.
  • StringBuilder.
  • StringBuffer: Esta es igual a la anterior pero está optimizada para ser usada por aplicaciones multihilo (multi-threads)
  • Dentro de la clase StringBuilder disponemos de los métodos:




Expresiones regulares

  • Para hacer uso de expresiones regulares necesitamos:
  • Verificar que la expresión regular es correcta. Para ello debemos hacer uso de la clase Pattern.
  • Verificar que la cadena cumple con la expresión regular. Para ello debemos hacer uso del método matcher de la misma clase, el cual devolverá un objeto de la clase Matcher que nos servirá para verificar si cumple la expresión regular llamando a su método matches().


  • Para compilar la expresión regular debemos hacer uso del método compile(String exp) de la clase Pattern.
En caso de que la expresión se incorrecta lanzará una excepción PatternSyntaxException.
Esta excepción es del tipo RunTimeException polo que no obliga a capturarla como ya vimos anteriormente en esta wiki.
Ejemplo:
1 Pattern patron = Pattern.compile("[abc]");
  • Una vez compilado ya podemos aplicarla sobre una cadena haciendo uso del método matcher'
Ejemplo:
1         Pattern patron = Pattern.compile("[abc]");
2         Matcher coincidencia = patron.matcher("a");
3         
4         if (coincidencia.matches()){
5             System.out.println("Patrón encontrado en la cadena");
6         }
7         else{
8             System.out.println("Patrón no encontrado en la cadena");
9         }


  • Métodos útiles de la clase Matcher:
  • matches(): En este caso la cadena no puede tener caracteres adicionales a los de la expresión regular.
  • lookingAt(): Busca el patrón al principio de la cadena y la cadena puede tener caracteres adicionales diferentes a los del patrón.
  • find(): Busca el patrón dentro de la cadena. Se utiliza en el caso de que haya el mismo patrón repetido varias veces en la cadena. Cada vez que se llame al métood find() buscará el siguiente patrón repetido. Por lo tanto la cadena puede contener caracteres adicionales a los del patrón a buscar.
Relacionado con find:
  • start(): Indica la posición inicial del patrón encontrado con find() en la cadena.
  • end(): Indica la posición final del patrón encontrado con find() en la cadena.
  • reset(): Hace que el find() vuelva a comenzar al principio de la cadena.


  • Indicar que en el método compile, en caso de necesitar indicar un carácter que pueda entrar en conflicto por su significado, debemos 'escaparlo' con '\'.
Así, si queremos buscar una expresión que contenga la barra (\) deberemos de poner: Pattern.compile("[abc]\\\\");
Si queremos buscar por \w (equivalente a [a-zA-Z_0-9]) deberemos poner: Pattern.compile("[abc]\\w");
  • En el método matcher, si queremos buscar la '\' deberemos poner: patron.matcher("\\\\casa");


  • Es posible formar grupos de combinaciones utilizando los paréntesis como por ejemplo: ([0-9]{2}@){2}
En este caso estamos buscando una cadena formada por dos números seguido del símbolo arroba repetido dos veces: 12@34@
Como vemos podemos aplicar al conjunto modificadores de repetición como los vistos anteriormente.
  • Otra utilidad de los conjuntos es que podemos 'acceder' a cada uno de ellos directamente.
Por ejemplo: ([a-z])([1-5]{3})([A-Z])
Tenemos tres conjuntos, el formado por la letra minúscula, el de los números y el de la letra mayúscula.
Después de hacer el matcher sobre esta expresión regular, por ejemplo: Matcher m = p.matcher("a123A" b444B");
Podemos acceder, después de realizar un find() (en este caso, ya que se repite varias veces en la cadena, deberíamos tener un while) podremos acceder a los valores de cada uno de los conjuntos de la forma: m.group(1), m.group(2) y m.group(3).
En el caso de que el grupo no exista (por tenerlo como optativo en la expresión regular) tendrá el valor de null.


  • Si queremos indicar en la expresión regular que queremos que aparezca una palabra u otra (o más de dos, pero que sólo pueda aparecer una a la vez) deberemos separarlas con | con paréntesis (tiene el mismo significado que en programación, or): String patron = (CADENA|ARBOL|CASA);


Ejercicios cadenas

Básicos

  • Indica cuantas instancias de clase String se crean en cada una de las instrucciones:
  • String cad = "Hola" + "Mundo";
  • String cad = new String("Hola Mundo");
  • String cad = new String("Hola Mundo") + ". Bienvenido" + new String(" a casa");
Lo mismo pasaría si usamos el método concat:
  • String cad = "Hola".concat("Mundo");
  • String cad = 10+5 + " no es igual " + 12+3;
¿ Cual es el resultado ?
  • Crea un objeto de cada una de las clases envoltorio de los tipos de datos numéricos primitivos (Integer, Float, Byte, Decimal) y imprímelos (sin necesidad de convertirlos) con el siguiente formato, haciendo uso:
  • Del método String.format
  • Del método printf:
Byte: ZZ
SHORT: GG
Integer: XX
Long: DDDDD
Float: YY.YY (dos dígitos para los decimales)
Double: DD.DDD (tres dígitos para los decimales)
Haz lo mismo pero concatenando (sin establecer formato) haciendo uso del símbolo más (+)


  • Explica como puede funcionar la siguiente instrucción:
byte num = 55;
String cad = "Juan tiene" + num;


  • Para convertir cadenas a números hacemos uso de los métodos parseXXXX de las clases envoltorio o bien llamando al método valueOf que devuelve una instancia de la clase envoltorio.
Pide gráficamente (JOptionPane.showInputDialog) que un usuario introduzca un número entero. Se debe informar al usuario (JOptionPane.showMessageDialog) si el número introducido no es correcto. En caso de que lo sea se mostrará el valor.
Deberá seguir solicitando introducir un número hasta que introduzca un número correcto entre 1 y 100.


  • Implementa un ejemplo de cada uno de los métodos utilizados habitualmente con la clase String.


  • Indica la salida de las siguientes órdenes:
1         String cad = "Casa".concat("Angel").replace("a", "o");
2         String cad = "Juan".length() + "Pedro".substring("ejemplo".indexOf("e"),2);
3         boolean salida = "Pase y diga una frase".replace("ase","to").endsWith("ta");
4         int comparar = "Amigo".compareToIgnoreCase("amiga");  (indicar si el número 'comparar' es positivo o negativo o cero.
5         int pos = "Abracadabra".replace("ra","le").lastIndexOf("bl");



Strings dinámicos

  • Haz uso de la clase StringBuilder para a partir de una cadena obtener otra utilizando los métodos disponibles para borrar, reemplazar, insertar y añadir (primero hazlo con varias órdenes y después con una sola. Debes de hacer uso de los tres métodos):
  • Tenemos: "La casa de la pradera" => "El coso mío de praderaaaa"


  • Utiliza la clase StringBuilder (y sólo ella) para hacer que el último carácter de la cadena pase a la primera posición posición.
Haz una modificación y crea un método al que se le pase como parámetro el número de posiciones a rotar y una cadena y devuelva la cadena rotada el número de veces indicado en el parámetro.


Expresiones regulares

  • Busca la expresión regular (y compruébala en este enlace) que cumpla lo siguiente:
  • Una entrada de 5 a 10 números entre 3 y 7 seguidos por la letra a o A.
  • Una entrada con vocales hasta un máximo de 10 seguido opcionalmente de un número.
  • Una entrada con el siguiente patrón: XX00000ZZ0 siendo XX letras entra la a y la z, ZZ vocales, 00000 números entre 0 y 5 y 0 números entre 0 y 9 y que pueden repetirse indefinidamente
  • Una entrada de 5 a 10 letras seguidos por la letra a o b de forma opcional (pueden aparecer o no)


  • Una entrada que debe comenzar y acabar con un número y pueda llevar cualquier carácter entre los dos números.
  • Dada la siguiente expresión regular: ^[0-9]{2,}[^a-g]?[0-9]*.{2}$ indica cuales de las siguientes cadenas cumplirían dicha expresión utilizando el método find y el método lookingAt
  • 12q1qq
  • 2q111w
  • 11q11casa
  • p11axxs543323
Compruébalo en regexr.com.
  • Una expresión que valide un correo electrónico, partiendo que el dominio tendrá el formato: galicia.xx o galicia.xxx



  • Crea una expresión regular que responda a este patrón:
  • Almohadilla seguido de 5 números, seguido de almohadilla, repitiendo esta misma combinación de dos a cuatro veces.
  • Un código postal está formado por dos dígitos para la provincia seguido del número postal formado por tres números. Crea un programa que utilizando expresiones regulares analice una cadena de texto en la que van a venir múltiples códigos postales separados por espacio (puede venir un código postal solo). Tendrá que analizar completamente dicha cadena y mostrar el código de provincia y número de cada uno de ellos.


  • Pide gráficamente al usuario una entrada para que introduzca su NIF o NIF extranjero (lleva una letra al comienzo y al final). Deberás informar si el dato introducido es correcto.
El formato que quieres que se cumpla es el de una letra inicial opcional, un número de entre cinco y ocho dígitos y una letra final. La letra se podrá introducir en mayúsculas o minúsculas, pero se guardará (en una variable) todo con letras mayúsculas.
Debes asegurarte que el usuario no introduce más caracteres que los indicados.


  • Crea un JFrame con un textarea y un botón buscar.
Al pulsar el botón deberás buscar en el contenido del textarea cuantas veces aparece el patrón: un carácter cualquiera, seguido de 2 números con valores entre uno y seis cada uno y seis letras (mayúsculas o minúsculas).
Informarás al usuario de cuantas veces ha aparecido el patrón.


  • Dado el siguiente contenido de un archivo de texto:
**  CLIENTES   **
* CLIENTE *
Nombre: Juan
Dirección: C/ AAAA
Tfno: 999999999
*** PEDIDOS ***
{Codigo - Cantidad}
{C1 - 10}
{E2 - 5}
* FIN PEDIDOS *
** FIN CLIENTE **
* CLIENTE *
Nombre: Pedro
Dirección: C/ BBBB
Tfno: 111111111
*** PEDIDOS ***
{Codigo - Cantidad}
{W22221 - 20}
{E22112 - 15}
* FIN PEDIDOS *
** FIN CLIENTE **
** FIN CLIENTES   **
El número de asteriscos en cada sección puede variar.
El número de espacios en blanco en cada sección puede variar.
En un futuro pueden aparecer nuevas secciones, por lo que no usaremos patrones buscando cadenas específicas.
Todas las secciones deben de tener la palabra FIN XXXXXX.
Siempre van a venir algún cliente y algún pedido.
El teléfono siempre será de 9 dígitos.
El código de pedido siempre será una letra mayúscula seguida de dígitos.
La cantidad siempre será mayor que cero.
La línea {Codigo - Cantidad} no hace falta procesarla (no hace falta que crees una expresión regular para ella).
Una vez tengas los patrones comprueba que te funcionan en la página de regexr.com.
Copia el archivo de muestra y compruébalo.
Lee el siguiente archivo línea a línea comprobando que cumple alguna de las expresiones regulares.
Deberás crear expresiones regulares.
  • Una que englobe a los principio / fin de todas las secciones.
  • Otra para cada una de las líneas de información del cliente.
  • Otra para cada línea de pedido.
Deberás mostrar por pantalla cada una de las secciones con los datos de los clientes y los pedidos.
Recuerda que si defines un grupo 'optativo' en una expresión regular, en caso de que no vengan datos para ese grupo tendrá el valor de null.

Solución ejercicios cadena

Básicos

  • Indica cuantas instancias de clase String se crean en cada una de las instrucciones:
  • String cad = "Hola" + "Mundo"; => 3.
  • String cad = new String("Hola Mundo"); => 2.
  • String cad = new String("Hola Mundo") + " Bienvenido" + new String(" a casa"); => 7.
Lo mismo pasaría si usamos el método concat:
  • String cad = "Hola".concat("Mundo"); => 3.
  • String cad = 10+5 + " no es igual a " + 12+3; => 5
Resultado: 15 no es igual a 123


  • Crea un objeto de cada una de las clases envoltorio de los tipos de datos numéricos primitivos (Integer, Float, Byte, Decimal) y imprímelos (sin necesidad de convertirlos) con el siguiente formato, haciendo uso del método printf:

Byte: ZZ SHORT: GG Integer: XX Long: DDDDD Float: YY.YY (dos dígitos para los decimales) Double: DD.DDD (tres dígitos para los decimales)

1         Byte b = 2;
2         Short s = 2131;
3         Integer i = 34;
4         Long l = 324324l;
5         Float f = 12.45f;
6         Double d = 44.24d;
7         
8         System.out.printf("\nByte:%d\nShort:%d\nInteger:%d\nLong:%d\nFloat:%.2f\nDouble:%.3f",b,s,i,l,f,d);
En este caso estamos haciendo uso de objetos de clases y cuando se imprimen se llama al método toString de la clase.
Haz lo mismo pero concatenando (sin establecer formato).
Al igual que en el caso anterior está llamando al método toString.


  • Explica como puede funcionar la siguiente instrucción:
byte num = 55;
String cad = "Juan tiene" + num;
En este caso, Java convierte el tipo de dato primitivo (byte) a la clase envoltorio Byte y después llama al método toString de la misma.


  • Indica la salida de las siguientes órdenes:
1         String cad = "Casa".concat("Angel").replace("a", "o");                                 => CosoAngel
2         String cad = "Juan".length() + "Pedro".substring("ejemplo".indexOf("e"),2);            => 4Pe
3         boolean salida = "Pase y diga una frase".replace("ase","to").endsWith("ta");           => false
4         int comparar = "Amigo".compareToIgnoreCase("amiga");                                   => Positivo 
5         int pos = "Abracadabra".replace("ra","le").lastIndexOf("bl");                          => 8



Strings dinámicos

  • Haz uso de la clase StringBuilder para a partir de una cadena obtener otra utilizando los métodos disponibles para borrar, reemplazar, insertar y añadir (primero hazlo con varias órdenes y después con una sola. Debes de hacer uso de los tres métodos):
  • Tenemos: "La casa de la pradera" => "El coso mío de praderaaaa"
1         StringBuilder cadena = new StringBuilder("La casa de la pradera");
2         cadena.replace(cadena.indexOf("La"), 2, "El");
3         cadena.replace(cadena.indexOf("casa"), 7, "coso");
4         cadena.insert(8,"mío ");
5         cadena.delete(14, 17);
6         cadena.append("aaa");


  • Utiliza la clase StringBuilder (y sólo ella) para hacer que el último carácter de la cadena pase a la primera posición posición.
Haz una modificación y crea un método al que se le pase como parámetro el número de posiciones a rotar y una cadena y devuelva la cadena rotada el número de veces indicado en el parámetro.
1        StringBuilder sb = new StringBuilder("Cadena de ejemplo");
2        sb.insert(0, sb.charAt(sb.length()-1));
3        sb.deleteCharAt(sb.length()-1);
4        System.out.println(sb);
Método rotarCadena:
 1     public static String rotarCadena(String cadena, int numRotar) {
 2         
 3         StringBuilder sb = new StringBuilder(cadena);
 4         
 5         for (int cont=0;cont <numRotar;cont++){
 6             sb.insert(0, sb.charAt(sb.length()-1));
 7             sb.deleteCharAt(sb.length()-1);
 8         }
 9         
10         return sb.toString();
11     }



Expresiones regulares

  • Busca la expresión regular (y compruébala en este enlace) que cumpla lo siguiente:
  • Una entrada de 5 a 10 números entre 3 y 7 seguidos por la letra a o A. => [3-7]{5,10}[aA]
  • Una entrada con vocales hasta un máximo de 10 seguido opcionalmente de un número. => [a-zA-Z]{1,10}[0-9]
  • Una entrada con el siguiente patrón: XX00000ZZ0 siendo XX letras entra la a y la z, ZZ vocales, 00000 números entre 0 y 5 y 0 números entre 0 y 9 y que pueden repetirse indefinidamente => [a-z]{2}[0-5]{5}[aeiou]{2}[0-9]+
  • Una entrada de 5 a 10 letras seguidos por la letra a o b de forma opcional (pueden aparecer o no) => [a-z]{5-10}[ab]?


  • Una entrada que cumpla: Debe comenzar y acabar con un número y puede llevar cualquier carácter entre los dos números => ^[0-9].+[0-9]$
  • Una expresión que valide un correo electrónico, partiendo que el dominio tendrá el formato: galicia.xx o galicia.xxx => ^\w+@galicia\.[a-zA-Z]{2,3}$
Nota: Fijarse que en el caso de utilizar el método match no hace falta poner ^ y $ en la expresión.
Debemos escapar el punto ya que sino lo interpretará como 'cualquier carácter'.


  • Crea una expresión regular que responda a este patrón:
  • Almohadilla seguido de 5 números, seguido de almohadilla, repitiendo esta misma combinación de dos a cuatro veces => ^(#[0-9]{5}#){2,4}$
Nota: Fijarse que en el caso de utilizar el método match no hace falta poner ^ y $ en la expresión.
  • Un código postal está formado por dos dígitos para la provincia seguido del número postal formado por tres números. Crea un programa que utilizando expresiones regulares analice una cadena de texto en la que van a venir múltiples códigos postales separados por espacio (puede venir un código postal solo). Tendrá que analizar completamente dicha cadena y mostrar el código de provincia y número de cada uno de ellos.
1         Pattern patron = Pattern.compile("([0-9]{2})([0-9]{3})");
2         Matcher coincidencia = patron.matcher("12111 aaaa 13444");
3         
4         while (coincidencia.find()){
5             System.out.println("Provincia: " + coincidencia.group(1));
6             System.out.println("Número: " + coincidencia.group(2));
7             
8         }


  • Dado el siguiente contenido de un archivo de texto:
**  CLIENTES   **
* CLIENTE *
Nombre: Juan
Dirección: C/ AAAA
Tfno: 999999999
*** PEDIDOS ***
{Codigo - Cantidad}
{C1 - 10}
{E2 - 5}
* FIN PEDIDOS *
** FIN CLIENTE **
* CLIENTE *
Nombre: Pedro
Dirección: C/ BBBB
Tfno: 111111111
*** PEDIDOS ***
{Codigo - Cantidad}
{W22221 - 20}
{E22112 - 15}
* FIN PEDIDOS *
** FIN CLIENTE **
** FIN CLIENTES   **
El número de asteriscos en cada sección puede variar.
El número de espacios en blanco en cada sección puede variar.
En un futuro pueden aparecer nuevas secciones, por lo que no usaremos patrones buscando cadenas específicas.
Todas las secciones deben de tener la palabra FIN XXXXXX.
Siempre van a venir algún cliente y algún pedido.
El teléfono siempre será de 9 dígitos.
El código de pedido siempre será una letra mayúscula seguida de dígitos.
La cantidad siempre será mayor que cero.
La línea {Codigo - Cantidad} no hace falta procesarla (no hace falta que crees una expresión regular para ella).
Una vez tengas los patrones comprueba que te funcionan en la página de regexr.com.
Copia el archivo de muestra y compruébalo.
Lee el siguiente archivo línea a línea comprobando que cumple alguna de las expresiones regulares.
Deberás crear expresiones regulares.
  • Una que englobe a los principio / fin de todas las secciones.
  • Otra para cada una de las líneas de información del cliente.
  • Otra para cada línea de pedido.
Deberás mostrar por pantalla cada una de las secciones con los datos de los clientes y los pedidos.
Recuerda que si defines un grupo 'optativo' en una expresión regular, en caso de que no vengan datos para ese grupo tendrá el valor de null.


Método procesarArchivo:

 1     public static void procesarArchivo(String fichero) throws FileNotFoundException{
 2         String linea = "";
 3         File fich = new File(fichero);
 4         if(!fich.exists()) {
 5             throw new FileNotFoundException("Fichero no encontrado");
 6         }
 7         
 8         try(FileReader fir = new FileReader(fichero);
 9             BufferedReader bf = new BufferedReader(fir);
10            )
11         {
12           while((linea = bf.readLine())!=null){
13               procesarLinea(linea);
14           }
15         } catch (IOException ex) {
16             Logger.getLogger(ExpresionesRegulares.class.getName()).log(Level.SEVERE, null, ex);
17         }
18         
19     }


Método procesarLinea:

 1     public static void procesarLinea(String linea){
 2         String cadenaSecciones="^\\*+\\s+(FIN\\s+){0,1}([A-Z]+)\\s+\\*+$";
 3         String cadenaDatos = "^([a-zA-Záéíóú]+):\\s(.+)$";
 4         String cadenaPedido = "^\\{([a-zA-Z0-9]+)\\s+-\\s+([1-9][0-9]*)\\}$";
 5         
 6         Pattern patronSecciones = Pattern.compile(cadenaSecciones);
 7         Pattern patronDatos = Pattern.compile(cadenaDatos);
 8         Pattern patronPedido = Pattern.compile(cadenaPedido);
 9         
10         Matcher coincideSeccion = patronSecciones.matcher(linea);
11         Matcher coincideDatos = patronDatos.matcher(linea);
12         Matcher coincidePedido = patronPedido.matcher(linea);
13 
14         if (coincideSeccion.matches()){
15             if (coincideSeccion.group(1)!=null){    // SECCION DE FIN
16                 System.out.printf("Encontrado el fin de la sección %s\n",coincideSeccion.group(2));
17             }
18             else{
19                 System.out.printf("Encontrado el inicio de la sección %s\n",coincideSeccion.group(2));
20             }
21         }
22         else if (coincideDatos.matches()){
23             System.out.printf("Dato del cliente: %s con valor %s\n",coincideDatos.group(1),coincideDatos.group(2));
24         }
25         else if (coincidePedido.matches()){
26             System.out.printf("\tCódigo del producto:%s\n\tCantidad pedida:%s\n",coincidePedido.group(1),coincidePedido.group(2));
27         }
28     }



Arrays unidimensionales y multidimensionales


Introducción


  • Los arrays son estructuras de datos estáticas y por tanto una vez creados no pueden modificarse en su tamaño (dimensión).
La dimensión del array es el número de elementos que lo integran y viene indicado por un número que ponemos dentro de los corchetes en su instanciación y siempre tiene que ser entero positivo.
Permiten guardar una colección de datos (tipos primitivos) u objetos todos del mismo tipo, es decir, no podemos guardar en un array datos de tipo entero y float, por ejemplo.


Creación

  • Para utilizar un array necesitamos realizar dos pasos:
  • Declaración.
  • Instanciación.
  • En el caso de los unidimensionales:
  • int[] datos;
  • datos = new int[20];
En un solo paso: int[] datos = new int[20];


  • En el caso de los multidimensionales (sería lo mismo para dos que tres, cuatro o más dimensiones, debemos de aumentar el número de corchetes):
  • int [][] datos;
  • datos = new int[5][10];
En un solo paso: int[][] datos = new int[5][10];



Acceso

Unidimensional

  • Para acceder a un elemento de un array debemos indicarlo por su posición entre los corchetes, teniendo en cuenta que las posiciones en el array van desde la posición cero hasta su tamaño menos uno.
  • int[] datos = new int[5]; // Definimos un array de 5 elementos
datos[0] = 0; // Empienza en la posición cero
datos[1] = 1;
datos[2] = 2;
datos[3] = 3;
datos[4] = 4; // Llega hasta la posición 5-1 o lo que es lo mismo: datos.length()-1


  • Para acceder a todos los elementos del array haciendo uso de una sentencia repetitiva debemos hacer uso de la propiedad length
1 int[] datos = new int[5];
2 
3 for (int cont=0; cont < datos.length; cont++){
4   datos[cont] = cont;
5 }
Nota: Para obtener el tamaño de un array también podemos hacer uso del método getLength() de la clase Array.


  • Existe otra forma de acceder para leer los valores guardados en un array. Es mediante un tipo de bucle denominado for each.
La forma de utilizarlo es la siguiente:
1 for (int numero : datos){
2    System.out.println(numero);
3 }
Se define una variable del mismo tipo que el array (en este caso enteros).
Se ponen dos puntos entre esta variable y el nombre del array.
Todo lo anterior dentro de los paréntesis de la instrucción for.
Lo que va a suceder ahora es que dentro del for, en cada iteracción, la variable numero tendrá el valor de cada uno de los elementos del array hasta que llegue al final.
Como podemos ver sólo podemos acceder a los valores guardados en el array de forma secuencial y 'hacia adelante'.


Multidimensional

  • En este caso definimos un array de varias dimensiones.
Cada elemento del array podrá está formado por otros arrays y así sucesivamente (depende del número de dimensiones) hasta llegar a la posición del array que guarda un valor del mismo tipo definido en el array.


  • Definimos un array (en este caso bidimensional) de la forma: int[][]datos = new int[3][5];
Tendremos un array de 15 elementos (3 x 5)
La posición datos[0] 'guarda' un array de 5 posiciones, igual que datos[1] y datos[2].
Nota: Si queremos añadir más dimensiones debemos de poner mas corchetes.


  • Para cambiar u obtener el valor de un elemento del array debemos de acceder de la forma: datos[x][y]
Siendo x un valor entre 0 y 2 e y un valor entre 0 y 4.
  • Para acceder a todos los elementos del array haciendo uso de una sentencia repetitiva debemos hacer uso de la propiedad length teniendo en cuenta que ahora tenemos dos dimensiones.
  • datos.length => 3 (dimensión primero)
  • datos[0].length => 5 (dimensión segunda) // Daría lo mismo poner datos[1].length o datos[2].length en este caso.
 1         int[][] datos = new int[3][5];
 2 
 3         for (int cont1=0; cont1 < datos.length; cont1++){
 4           for (int cont2=0; cont2 < datos[cont1].length; cont2++){  
 5 
 6              datos[cont1][cont2] = cont1*datos[cont1].length+cont2;   // Guarda los valores 0,1,2,3,.....13,14
 7           }
 8         }
 9         
10         for (int cont1=0; cont1 < datos.length; cont1++){
11           System.out.println("");  // Nueva fila
12           for (int cont2=0; cont2 < datos[cont1].length; cont2++){  
13               System.out.printf("%d",datos[cont1][cont2]);
14           }
15         }


  • Existe otra forma de acceder para leer los valores guardados en un array. Es mediante un tipo de bucle denominado for each.
La forma de utilizarlo es la siguiente:
1         for (int[] cont1 : datos){
2           System.out.println("");  // Nueva fila
3           for (int cont2 : cont1){  
4               System.out.printf("%d",cont2);
5           }
6         }
Se define una variable del mismo tipo que el array.
Se ponen dos puntos entre esta variable y el nombre del array.
Todo lo anterior dentro de los paréntesis de la instrucción for.
En el caso de los arrays multidimensionales, cada uno de los elementos del array datos (datos[0], datos[1], datos[2]) es un array y por tanto el for tiene que estar definido de la siguiente forma:
for (int[] cont1 : datos) {
cont1 representa la segunda dimensión.
Por tanto cont1 será un array de enteros.
Aplicando el mismo concepto que en los arrays unidimensionales, ahora tenemos que recorrer un array, por lo tanto, dentro del segundo for, debemos de definir una variable entera, ya que cada uno de los elementos del array es un entero.
for (int cont2 : cont1){
Ahora cont2 tiene en cada iteracción el valor de cada posición del array de la segunda dimensión.


Como podemos ver sólo podemos acceder a los valores guardados en el array de forma secuencial y 'hacia adelante'.



Iniciación

Unidimensional

  • Cuando definimos un array podemos inicializarlo al mismo tiempo.
La forma de hacerlo es poniendo entre corchetes los datos del array en vez de la instanciación.
El tamaño del array será el número de elementos indicados.
  • Veamos un ejemplo:
int[] datos = {0,1,2,3};
En este caso tenemos un array que sería equivalente a poner:
int[] datos = new int[4];
datos[0]=0; datos[1]=1;datos[2]=2;datos[3]=3;



Multidimensionales

  • El concepto es el mismo que en el caso anterior, pero teniendo en cuenta que cada grupo de corchetes forma un array:
  • Veamos un ejemplo:
int[][] datos = {{0,1},{2,3},{4,5},{6,7}};
Sería equivalente a poner:
int[][] datos = new datos[4][2];
datos[0][0] = 0;datos[0][1] = 1;datos[1][0] = 2;datos[1][1] = 3;datos[2][0] = 4;datos[2][1] = 5;datos[3][0] = 6;datos[3][1] = 7;


  • Otro ejemplo con tres dimensiones:
int[][][] datos = {{{0,1,2},{3,4,5}},{{6,7,8},{9,10,11}}};
Para determinar el número de dimensiones, primero contamos los grupos de llaves desde la más externa a la más interna (sin contar la llave primera):
int[][][] datos = {{XXXXXXXXXX},{XXXXXXXXXXXXX}};
Como vemos hay dos grupos, por lo tanto tenemos un array de dos dimensiones:
int[2][][] datos = {{XXXXXXXXXX},{XXXXXXXXXXXXX}};
Ahora contamos dentro de uno de los grupos los grupos de llaves:
{{0,1,2},{3,4,5}} => {AAAAA,BBBBB}
Por lo tanto tenemos dos dimensiones:
int[2][2][] datos = {{AAAAA,BBBBB},{AAAAA,BBBBB}};
Dentro de cada grupo contamos el número de llaves o elementos si ya hemos llegado al final:
{0,1,2} => 3 elementos
int[2][2][3] datos = {{{0,1,2},{3,4,5}},{{6,7,8},{9,10,11}}};
Lógicamente para recorrer este array con bucles necesitamos tres for..each o tres bucles for.
Al usar for..each tenéis que tener en cuenta que el primer bucle, cada elemento del array es un array bidimensional (le quitáis una de las dimensiones):
1         int[][][] datos2 = {{{0,1,2},{3,4,5}},{{6,7,8},{9,10,11}}};
2         for (int[][] elem1 : datos2){
3             for (int[] elem2 : elem1){
4                 for (int elem3 : elem2){
5                     System.out.println(elem3);
6                 }
7             }
8         }



Paso de parámetros y de retorno

  • Al igual que con los tipos de datos primitivos, podemos tener métodos que reciban como parámetro un array y también que devuelvan un tipo de datos array.
  • Un ejemplo en el que se retorna un array:
 1     public static int[] crearArray(int tam){
 2         int[] array = new int[tam];
 3         for (int cont=0; cont < array.length; cont++){
 4             array[cont] = 1;
 5         }
 6         return array;
 7     }
 8     
 9     public static void main(String[] args) {
10         // TODO code application logic here
11 
12         int[] datos = crearArray(4);
13         
14         for (int dato : datos){
15             System.out.println(dato);
16         }
17     }
Notas:
  • El método es de clase (static) para poder llamarlo desde el main. Se podría implementar en cualquier método de instancia.
  • El mismo concepto puede ser aplicado a arrays multidimensionales.
  • Línea 12: Fijarse que definimos un array pero no instanciamos (hacemos el new). Eso ya lo hace el método.


  • Ahora veamos un ejemplo de paso de parámetros de tipo array:


 1     public static void inicializarArray(int[] datos){
 2         
 3         for (int cont=0; cont < datos.length; cont++){
 4             datos[cont] = 1;
 5         }
 6     }
 7     
 8     public static void main(String[] args) {
 9         
10         int[] datos = {1,2,3,4,5};
11         inicializarArray(datos);
12         
13         for (int dato : datos){
14             System.out.println(dato);
15         }
16      }
IMPORTANTE: Fijarse como el array que hemos creado en main (variable local) es modificado en el método y dicha modificación queda reflejada en el main cuando vuelve de la llamada.
Esto es así ya que los arrays al igual que los objetos se pasan por referencia no por valor como los tipos primitivos de datos. Esto quiere decir que el parámetro va a recibir la dirección de memoria donde se encuentra la estructura de datos y por eso cualquier modificación supondrá la modificación en el método desde donde se realiza la llamada.
Esto ya fue comentando anteriormente en esta Wiki.



Guardando objetos de clases

  • Como comentamos inicialmente un array es una estructura que puede guardar información de tipos primitivos de datos o de objetos de una clase.
  • Cuando definimos un array para guardar objetos de una clase, al escribir esta orden:
ClaseA[] datos = new ClaseA[10];
Estamos reservando espacio de memoria para el array pero no para cada uno de los elementos del array que en este caso sería objetos de la clase ClaseA.
Es decir, datos[0]....datos[9] tienen el valor null.


  • Si quisiéramos inicializar un array de objetos tendríamos que poner:
Clase de prueba:
 1 public class ClaseA {
 2     private int valor;
 3     
 4     ClaseA(int valor){
 5         this.valor = valor;
 6     }
 7     public void setValor(int valor){
 8         this.valor = valor;
 9     }
10 
11     @Override
12     public String toString(){
13         return "Valor:" + valor;
14     }
15 }
Inicializando el array de objetos:
1         ClaseA[] datos = new ClaseA[10];
2         
3         for (int cont=0; cont < datos.length; cont++){
4             datos[cont] = new ClaseA(cont);
5         }
6         
7         for (ClaseA dato : datos){
8             System.out.println(dato);
9         }


  • En los ejercicios realizados hasta ahora (interface gráfica), no inicializábamos el array.
Simplemente lo definíamos de la forma:
ClaseA[] datos = new ClaseA[10];
Y hacíamos uso de una variable contador que nos indicaba en qué posición del array debíamos añadir un objeto ya instanciado.


Importante: seguridad en el paso de objetos como parámetros

  • Cuando hacemos uso de objetos debemos de tener cuidado y tener en mente que lo que se pasa a los métodos es una referencia del objeto.
  • Imaginemos el caso siguiente (utilizando al clase ClaseA del punto anterior):
Dispongo de una clase para guardar objetos de la clase ClaseA. Dicha clase tiene un método añadirObjeto para añadir un objeto al array.
Cuando alguien llama a dicho método, no debe de poder acceder a los objetos guardados.
  • Lo lógico sería poner algo así:
 1 public class ArraysMultidimensionales {
 2 
 3     private static ClaseA[] datos = new ClaseA[100];
 4     private static int posicion = 0;
 5     
 6     public static boolean addObjetoClaseA(ClaseA dato){
 7         if (posicion > 100) return false;
 8         
 9         datos[posicion] = dato;
10         posicion++;
11         return true;
12     }
13 
14     public static void imprimirObjeto(int pos){
15         if (posicion > 100) return;
16         
17         System.out.println(datos[pos]);
18     }
19 
20 }


  • Ahora hagamos uso de esta clase:
 1     public static void main(String[] args) {
 2         // TODO code application logic here
 3 
 4         ClaseA elem = new ClaseA(10);
 5         ArraysMultidimensionales.addObjetoClaseA(elem);
 6         ArraysMultidimensionales.imprimirObjeto(0);
 7         
 8         elem.setValor(30);
 9         ArraysMultidimensionales.imprimirObjeto(0);
10     }
Salida:
Prog arrays 1.jpg
Como vemos el valor dentro del array se ha modificado y eso es así debido a que el objeto que le enviamos al método addObjetoClaseA se pasa por referencia y por tanto su dirección de memoria es la misma que la que tenemos en el método main.


Imaginar esta situación si fuera una cuenta bancaria...
Para evitarlo, lo que tenemos que hacer es crear una copia del objeto que estamos recibiendo como parámetro.
  • Para clonar un objeto o bien copiamos manualmente todos los atributos (incluidos las instancias de objetos que pueda tener y estas también tendrían que ser instanciadas-copiadas) o podemos implementar la interface cloneable, pero teniendo en cuenta que si el objeto tiene entre sus atributos referencias a otros objetos, tanto el original como la copia apuntarán a las mismas direcciones de memoria (más información en este enlace).
Clase ClaseA con interface cloneable:
 1 public class ClaseA implements Cloneable{
 2     private int valor;
 3     
 4     ClaseA(int valor){
 5         this.valor = valor;
 6     }
 7     
 8     public void setValor(int valor){
 9         this.valor = valor;
10     }
11     
12     @Override
13     public String toString(){
14         return "Valor:" + valor;
15     }
16     
17     @Override
18     public Object clone()throws CloneNotSupportedException{  
19         return super.clone();  
20     }  
21 }


Clase ArraysMultidimensionales con el método addObjetoClaseA modificado:

 1     public static boolean addObjetoClaseA(ClaseA dato){
 2         if (posicion > 100) return false;
 3         
 4         try {
 5             datos[posicion] = (ClaseA)dato.clone();
 6         } catch (CloneNotSupportedException ex) {
 7             return false;
 8         }
 9         posicion++;
10         return true;
11     }
Ahora el resultado sí es correcto:
Prog arrays 2.jpg



Ejercicios propuestos

  • Crea un array bidimensional para guardar información sobre el precio que tendrá cada una de las butacas de un cine con 5 filas y 10 columnas.
Asigna un valor inicial a las butacas de la primera fila de 30 euros, la de la segunda 40 y así sucesivamente.
Muestra los valores del array indicando la fila y la columna con su precio de dos formas diferentes (for y for...each).


  • Queremos guardar por cada alumno sus notas. El alumno es examinado en tres trimestres y al final tiene una nota media.
Crea una clase Alumno donde guardar esta información (tendrá de atributo nombre).
Tendrán un valor inicial de 1 en sus tres notas y nota media (aplicarlo en la definición del array).
Sobreescribe el método toString para mostrar los datos del alumno (nombre) y sus notas y nota media en una única línea.
Crea un alumno y muestra sus datos.


  • Crea un programa gráfico en el que se pida unas coordenadas x,y,z y un valor que indica el color en ese punto (el color está determinado por un valor entre 0 y 2 elevado a 64).
Las coordenadas x,y,z indican la posición dentro del array y la altura el valor para ese punto.
Las coordenadas nunca podrán ser mayores que x=5,y=5, z=5.
Una vez se pulse el botón FIN, se mostrará en un textarea el contenido del array, con el formato: (x,y,z) = XXXX (una línea por cada punto)
Muestra el contenido del array de dos formas diferentes (for y for...each).


  • Crea un método de clase que cree un array bidimensional de enteros de dos dimensiones, pasando su tamaño con dos parámetros (impedir que el tamaño sea negativo o mayor que 10), lo inicialice (con todos sus valores a -1) y lo devuelva.
Llama a dicho método para crear un array modifica los valores de la segunda fila del array a +2 (haz uso de un bucle).


  • Crea un método al que se le pase un objeto de la clase Alumno y que lo añada a un array de objetos de dicha clase, pero haciendo uso de una copia del mismo.



Clases y métodos genéricos

Métodos

  • El uso de genéricos nos permite definir clases y métodos que van a trabajar con datos pero que en la definición no indicamos el tipo de dato.
Posteriormente, cuando se hace uso de la clase o método se indica el tipo de dato a utilizar, el cual será 'adoptado' por el método o clase en tiempo de ejecución.


  • Veamos un ejemplo de un método que utiliza un tipo de dato genérico:
 1     public static <D> void imprimirGenerico(D dato){
 2         System.out.println(dato);
 3     }
 4 
 5     public static void main(String[] args) {
 6         // TODO code application logic here
 7         Integer i = new Integer(10);
 8         imprimirGenerico(i);
 9         
10         Float f = new Float(20.33f);
11         imprimirGenerico(f);
12     }
En este ejemplo, vamos a poder llamar al método con cualquier tipo de datos que tenga implementado el método toString.
  • Nota: Antes de Java 7, en la invocación de un método genérico era necesario indicar el tipo de la forma: objeto.<Integer>imprimirGenerico(i); o Clase.<Integer>imprimirGenerico(i); en el caso de que el método sea de clase.
  • Nota: Normalmente los tipos genéricos se van a aplicar sobre colecciones de datos sobre los que vamos a poder realizar operaciones.


  • Veamos otro ejemplo:
 1     public static <D> boolean addElemento(D[] datos,D dato){
 2         boolean resultado = false;
 3         
 4         if (dato !=null) {
 5             for (int cont=0; cont<datos.length;cont++){
 6                 if (datos[cont]==null){
 7                     datos[cont]=dato;
 8                     resultado = true;
 9                     break;
10                 }
11             }
12         }
13         return resultado;
14     }
15 
16     public static void main(String[] args) {
17         Integer[] enteros = {1,2,null,null};
18         addElemento(enteros, 3);
19         addElemento(enteros, 4);
20         if (!addElemento(enteros, 5)){
21             System.out.println("No se puede añadir más...");
22         }
23         for (Integer entero : enteros){
24             System.out.println(entero);
25         }
26 
27     }


En este ejemplo podemos ver como definimos un array de un tipo de datos genérico al que pasamos como parámetro y también un parámetro del mismo tipo genérico que el definido en el array. Dentro del método, añadimos dentro de una posición libre (buscamos una que tenga el valor null) el objeto enviado.
De esta forma podemos utilizar cualquier clase de objetos que siempre va a funcionar.


  • Veamos otro ejemplo:
 1     public static <T,U> boolean getMayor(T[] t,U[] u){
 2         return ((t.length > u.length));
 3     }
 4     
 5     public static void main(String[] arg){
 6         
 7         Integer[] enteros = {1,2,3,4};
 8         String[] cadena = {"Uno","Dos"};
 9         boolean resultado = Genericos.<Integer,String>getMayor(enteros, cadena);
10         
11         if (resultado){
12             System.out.println("El array de enteros es mayor");
13         }
14         else{
15             System.out.println("El array de cadenas es mayor");
16         }
17         
18     }


  • Como podemos observar el método getMayor compara dos arrays pero sin especificar el tipo de cada uno de ellos.
Esta forma de definir el método tiene la ventaja que podemos llamarlo con cualquier array de objetos de cualquier tipo.
Si no estuviera definido así tendríamos que tener un método getMayor para cada tipo de objeto o bien hacer uso de la clase Object (todo deriva de Object) de la forma:
1     public static boolean getMayor(Object[] t,Object[] u){
2         return ((t.length > u.length));
3     }
Pero en este caso no estaríamos empleando una forma genérica, ya que lo que hace el método es 'convertir' realizar un 'cast' al tipo de dato Object.


  • Debemos de tener en cuenta cuando hablamos de genéricos lo siguente:
  • Java prohíbe la creación de nuevos arrays basados en genéricos (con tipo de dato uno genérico).
Si quieremos crear uno podemos hacerlo de la forma:
T[] copia = (T[]) Array.newInstace(datos[0].getClass,dimension);
Debemos asegurarnos que el array al menos tiene un elemento.
  • Siempre trabajamos sobre colecciones de objetos u objetos, por lo tanto no podremos utilizar tipos primitivos de datos cuando el método espera recibir un array de objetos de una clase genérica. Si podemos enviar tipos primitivos cuando lo que espera recibar el método es un objeto de una clase genérica, y esto es ási debido a que Java 'envuelve' el tipo primitivo con la clase que le corresponde.


  • Veamos otro ejemplo:


 1     public static <T> T[] clonarArray(T[] datos,int dimension){
 2         if (datos.length==0) return null;
 3         
 4         T[] copia = (T[])Array.newInstance(datos[0].getClass(), datos.length+dimension);
 5         int cont = 0;
 6         for (T dato : datos){
 7             copia[cont] = dato;
 8             cont++;
 9         }
10         return copia;
11     }
12     
13     public static void main(String[] arg){
14         
15         Integer[] datos = {1,2,3};
16         Integer[] copia = clonarArray(datos,2);
17         
18         System.out.println(copia.length);   // DEVUELVE 5
19         for (Integer dato : copia){
20             System.out.println(dato);
21         }
22         
23     }


  • Veamos otro ejemplo:
 1     public static <Y extends Number> void inicializarArray(Y[] datos,Y valorInicial){
 2         for (int cont=0; cont < datos.length;cont++){
 3             datos[cont] = valorInicial;
 4         }
 5     }
 6 
 7     public static void main(String[] args) {
 8         // TODO code application logic here
 9         Integer[] datos = {1,2,3,4};
10         inicializarArray(datos,2);
11         
12         for (Integer dato : datos){
13             System.out.println(dato);
14         }
15 
16         Float[] datos2 = {1.1f,2.2f,3.3f,4.4f};
17         inicializarArray(datos2,4f);
18         for (Float dato : datos2){
19             System.out.println(dato);
20         }
21     }
En este caso, utilizamos la palabra extends en el tipo genérico para indicar que clases podemos enviar. En este caso todas las numéricas serán aceptadas. Si intentamos hacer uso de un array de String nos daría un error en tiempo de compilación.



Ejercicios propuestos Métodos Genéricos

  • Haz un método genérico de nombre copiarArrays al que se le pase dos arrays de objetos (del mismo tipo los dos) y que copie el contenido de un array en otro array. Si no puede, los datos que no quepan serán descartados.


  • Haz un método genérico de nombre inicializarArray al que se le pase un array de objetos de un tipo genérico y un objeto de ese tipo y se guarde una copia de dicho objeto en cada una de las posiciones del array (utiliza el método clone para enviar una copia del objeto al método inicializarArray).


  • Haz un método genérico al que se le pasen dos objetos de tipos de datos diferentes y que devuelva true en caso de que el primero sea mayor que el segundo y false en caso contrario. Haz uso del método instanceof para determinar el tipo de objeto. Impleméntalo para objetos de tipo String e Integer. En el caso de un String se intentará convertir a número y el método lanzará la excepción NumberFormatException en el caso de que no se pueda convertir a número. En el caso de enviar un dato de un tipo diferente, se lanzará una excepción ClassCastException
Pista: El el caso de los números, saber el mayor es comparar sus valores.




Solución ejercicios propuestos Métodos Genéricos

  • Haz un método de nombre copiarArrays al que se le pase dos arrays de objetos (del mismo tipo los dos) y que copie el contenido de un array en otro array. Si no puede, los datos que no quepan serán descartados.
 1     public static <T> void copiarArrays(T[] original,T[]copia){
 2         int cont=0;
 3         for(T dato : original){
 4             copia[cont] = dato;
 5             cont++;
 6             if (cont >= copia.length) break;
 7         }
 8     }
 9     public static void main(String[] arg){
10         Integer[] ori = {1,2,3,4,5,6};
11         Integer[] copia = new Integer[3];
12         copiarArrays(ori, copia);
13         
14         for(Integer dato : copia){
15             System.out.println(dato);
16         }
17     }


  • Haz un método genérico al que se le pasen dos objetos de tipos de datos diferentes y que devuelva true en caso de que el primero sea mayor que el segundo y false en caso contrario. Haz uso del método instanceof para determinar el tipo de objeto. Impleméntalo para objetos de tipo String e Integer. En el caso de un String se intentará convertir a número y el método lanzará la excepción NumberFormatException en el caso de que no se pueda convertir a número. En el caso de enviar un dato de un tipo diferente, se lanzará una excepción ClassCastException
Pista: El el caso de los números, saber el mayor es comparar sus valores.


 1     public static <T,U>boolean compararObjetos(T t,U u) throws NumberFormatException,ClassCastException{
 2         int tamT=0,tamU=0;
 3         
 4         if ((t instanceof String) || (t instanceof Integer)){
 5             tamT = Integer.parseInt(t.toString());
 6         }
 7         else {
 8             throw new ClassCastException("Tipo invalido");
 9         } 
10             
11         if ((u instanceof String) || (u instanceof Integer)){
12             tamU = Integer.parseInt(u.toString());
13         }
14         else {
15             throw new ClassCastException("Tipo invalido");
16         } 
17         
18         return (tamT > tamU);
19     }
20 
21     public static void main(String[] arg){
22         Integer i1 = 3;
23         String s1 ="22";
24         if (compararObjetos(i1, s1)){
25             System.out.println("El primero es mayor");
26         }
27         else{
28             System.out.println("El segundo es mayor");
29         }
30 
31     }



Clases genéricas

  • Las clases genéricas son aquellas en las que definimos atributos de un tipo genérico.
En la clase se indican los tipos genéricos que se van a utilizar y después se definen atributos con dichos tipos.
La forma de hacerlo es parecida a la de los métodos, pero en la definición de la clase: public class MiClase<T> { T atributo; }
  • Al igual que en los métodos vamos a poder utilizar varios tipos de datos, separados por comas.


  • Veamos un ejemplo:
 1 public class OperacionesGenericos<T> {
 2     
 3     private T medio;
 4     
 5     // Guarda el elemento del medio del array
 6     public void guardarMedio(T[] datos){
 7         
 8         if (datos==null) return;
 9         
10         int tam = datos.length;
11         medio = datos[tam/2];
12     }
13     
14     public T getMedio(){
15         return medio;
16     }
17     
18 }
Esta clase define un atributo de un tipo genérico y después define un método para obtener el elemento del medio de un array (de cualquier tipo) y guardarlo en el atributo.
 1 public class Genericos {
 2 
 3     public static void main(String[] args) {
 4         // TODO code application logic here
 5         
 6         OperacionesGenericos og = new OperacionesGenericos();
 7         
 8         Integer[] datInt = {1,2,3,4,5};
 9         String[] datString = {"uno","dos","tres"};
10         
11         og.guardarMedio(datInt);
12         System.out.println(og.getMedio());
13         
14         og.guardarMedio(datString);
15         System.out.println(og.getMedio());
16         
17     }
18 }
Como podemos observar, estamos utilizando dos arrays de dos tipos diferentes (Integer, String) para llamar al método 'guardarMedio'.
Fijarse que en la instanciación de la clase no estamos indicando el tipo de datos que vamos a utilizar.
Esto es posible a partir de la versión 7 de Java.
  • Podríamos definir con que tipo de datos vamos a trabajar (de que tipo será T en la clase) de la forma:
 1 public class Genericos {
 2 
 3     public static void main(String[] args) {
 4         // TODO code application logic here
 5         
 6         OperacionesGenericos<Integer> og = new <Integer>OperacionesGenericos();
 7         
 8         ......
 9     }
10 
11 }
Ahora la línea: og.guardarMedio(datString); provocará un error.


  • Veamos ahora otro ejemplo:
 1 public class OperacionesGenericos<T> {
 2     
 3     private T medio;
 4     private T temp;
 5     
 6     /**
 7      * Intercambia los dato del array en las posiciones indicadas
 8      * @param datos array con los datos de tipo genérico
 9      * @param pos1 primera posición del array. Empieza en 0
10      * @param pos2 segunda posición del array. Empieza en 0
11      */
12     public void intercambiarDato(T[]datos,int pos1,int pos2){
13         
14         // Primero comprobamos si es nulo y después la longitud. Es importante el orden
15         if (datos==null || pos1 < 0 || pos2 < 0 || pos1 > datos.length || pos2 > datos.length) return;
16            
17         temp = datos[pos1];
18         datos[pos1] = datos[pos2];
19         datos[pos2] = temp;
20         
21     }
22     
23     
24     // Guarda el elemento del medio del array
25     public void guardarMedio(T[] datos){
26         
27         if (datos==null) return;
28         
29         int tam = datos.length;
30         medio = datos[tam/2];
31     }
32     
33     public T getMedio(){
34         return medio;
35     }
36     
37 }
En este caso definimos un método que intercambia los valores del array entre dos posiciones dadas.
Podríamos usar una variable local para guardar el valor a intercambiar, pero así vemos la posibilidad de emplear atributos de clase.
 1     public static void main(String[] args) {
 2         // TODO code application logic here
 3         
 4         OperacionesGenericos og = new OperacionesGenericos();
 5         
 6         Integer[] datInt = {1,2,3,4,5};
 7         String[] datString = {"uno","dos","tres"};
 8 
 9         og.intercambiarDato(datInt, 0, 3);
10         System.out.printf("%s - %s%n",datInt[0],datInt[3]);
11 
12         ........
13 
14     }


  • Veamos otro ejemplo:
 1     /**
 2      * Invierte el array
 3      * @param datos array con los datos a ser invertido
 4      */
 5     public void invertir(T[]datos){
 6         T temp = null;
 7         int tam = datos.length;
 8         int mitad = tam/2;
 9         
10         for (int cont = 0; cont <= mitad;cont++){
11             temp = datos[cont];
12             datos[cont] = datos[tam-cont-1];
13             datos[tam-cont-1] = temp;
14         }
15     }
En este caso definimos un método 'invertir' que modificará los datos del array pasado como parámetro para darle la vuelta.
1         Integer[] datInt = {1,2,3,4,5};
2 
3         og.invertir(datInt);
4         for(Integer dato: datInt) {
5             System.out.println(dato);
6         }


  • Al igual que en los método, podemos definir varios tipos de datos en la clase.
Veamos otro ejemplo:
 1 class OperacionesGenericos<T,U extends Number> {
 2     
 3     private T medio;
 4     private T temp;
 5     private U menor;
 6     
 7     public void guardarMenor(U[]datos){
 8         U menor=null;
 9         
10         for (U dato: datos){
11              if (menor==null || menor.doubleValue() > dato.doubleValue()){
12                  menor = dato;
13              }
14         }
15         this.menor = menor;
16     }
17     
18     public U getMenor(){
19         return menor;
20     }
21 
22     .............
En este caso estamos definiendo un tipo de dato numérico, que empleamos en un método 'guardarMenor' el cual va a guardar el valor más pequeño de los datos enviados en el array,
Nota: Debéis de tener en mente que siempre estáis trabajando con clases y no con los valores que enviáis en el array. Por tanto, cada elemento del array es una clase (en este caso deriva de la clase Number) por lo que podremos acceder a los métodos de dicha clase (doubleValue()) para obtener el valor numérico de lo que enviemos.
1         OperacionesGenericos og = new OperacionesGenericos();
2         
3         Integer[] datInt = {3,4,5,2};
4 
5         og.guardarMenor(datInt);
6         Number menor = og.getMenor();
7         System.out.println("MENOR:" + menor.intValue());


  • Podremos utilizar los tipos de datos genéricos como parámetros de un procedimiento de la forma: public void guardarMenor(U[]datos) como vimos en el ejemplo anterior.




Ejercicios propuestos Clases Genéricas

  • Modifica el método invertir para que en vez de invertir el array pasado como parámetro, devuelva un nuevo array con los datos invertidos del enviado como parámetro. Para ello haz uso de un atributo de tipo genérico definido a nivel de clase.
 1     public void invertir(T[]datos){
 2         T temp = null;
 3         int tam = datos.length;
 4         int mitad = tam/2;
 5         
 6         for (int cont = 0; cont <= mitad;cont++){
 7             temp = datos[cont];
 8             datos[cont] = datos[tam-cont-1];
 9             datos[tam-cont-1] = temp;
10         }
11     }


  • Crea un método de nombre 'guardarPrimerUltimo' que guarde en atributos de clase el primer y el último elemento de un array enviado como parámetro, verificando que el array trae datos.


  • Crea un método de nombre 'guardarPares' que guarde en un array local los elementos del array (que se le pasa como parámetro) que se encuentren guardados en posiciones pares.
Crea un método getPares que devuelva el array de los pares.
Modifica el valor de un elemento del array guardado localmente. ¿ Qué sucede con el array original enviado como parámetro previamente ?
Como harías para que fueran 'independientes'.




Solución Ejercicios propuestos Clases Genéricas

  • Crea un método de nombre 'guardarPares' que guarde en un array local los elementos del array (que se le pasa como parámetro) que se encuentren guardados en posiciones pares.
Crea un método getPares que devuelva el array de los pares.
Modifica el valor de un elemento del array guardado localmente. ¿ Qué sucede con el array original enviado como parámetro previamente ?
Como harías para que fueran 'independientes'.
Clase ClaseGenerica:
 1 package colecciones;
 2 
 3 import java.lang.reflect.Array;
 4 
 5 /**
 6  *
 7  * @author clase
 8  */
 9 public class ClaseGenerica<T> {
10     private T[] pares;
11     public void guardarPares(T[] array){
12         int cont = 0;
13         pares = (T[])Array.newInstance(array[0].getClass(), array.length/2);    
14         
15         for(int c=1; c<array.length;c+=2){
16             pares[cont] = array[c];
17             cont++;
18         }
19     }
20     public T[] getPares(){
21         return pares;
22     }
23    
24 }
Clase Principal:
 1     public static void main(String[] arg){
 2 
 3         Integer[] datos={1,2,3,4,5,6,7,8};
 4         ClaseGenerica cg = new ClaseGenerica();  // No indicamos el tipo de objeto. Si quisiéramos => ClaseGenerica<Integer> cg = new ClaseGenerica();
 5         cg.guardarPares(datos);
 6         Integer[] pares = (Integer[])cg.getPares();
 7         for (Integer dato : pares){
 8             System.out.println(dato);
 9         }
10         
11         
12     }
Línea 6: Como no indicamos el tipo de dato genérico cuando instanciamos la clase genérica (línea 4) el método getPares() devuelve un array de Objects por lo que hay que hacer un cast al tipo de dato que usamos (Integer).
Resultado:
Prog colecciones 11.jpg


  • Debemos de recordar que cuando trabajamos con objetos de clases, trabajamos con referencias, por lo tanto, cuando se crear el array de pares, se crea la estructura del array, pero en el caso de los objetos, cuando asignamos uno de los objetos del array de datos al array de pares, no estamos instanciando, sólo estamos pasando una referencia, por lo que cualquier modificación en cualquier objeto tanto del array de datos como el de pares, se reflejará en los dos.
Veamos un ejemplo:
Clase ClaseA:
1 public class ClaseA {
2     public int valor;
3     public ClaseA(int val){
4         valor = val;
5     }
6     
7 }
Clase Principal:
 1     public static void main(String[] arg){
 2 
 3         ClaseA[] datosObjeto=new ClaseA[6];
 4         datosObjeto[0] = new ClaseA(1);
 5         datosObjeto[1] = new ClaseA(2);
 6         datosObjeto[2] = new ClaseA(3);
 7         datosObjeto[3] = new ClaseA(4);
 8         datosObjeto[4] = new ClaseA(5);
 9         datosObjeto[5] = new ClaseA(6);
10         
11         
12         cg.guardarPares(datosObjeto);
13         ClaseA[] paresObjeto = (ClaseA[])cg.getPares();
14         for (ClaseA dato : paresObjeto){
15             System.out.println(dato.valor);
16         }
17         
18         System.out.println("Modifamos uno de los datos pares en el array de datos....");
19         datosObjeto[1].valor = 55;
20         for (ClaseA dato : paresObjeto){
21             System.out.println(dato.valor);
22         }
23     }
Línea 19: Como vemos, al modificar un objeto, este queda modificado en el array de pares, ya que los dos están 'apuntando' a la misma dirección de memoria.
Resultado:
Prog colecciones 12.jpg



Colecciones

  • Una colección es un conjunto de elementos almacenados de forma conjunta en una misma estructura.
  • Cada colección (existen diferentes tipos) incorpora diferentes formas de guardar dichos elementos y permitirá realizar diferentes operaciones sobre ellos (por ejemplo, tipos de ordenación).
  • Cada colección va a definir una o varias interfaces y van a poder guardar cualquier tipo de objeto (de ahí que hagan uso de métodos y clases genéricas como lo vistos en el punto anterior).
Las colecciones también van a incorporar algoritmos que nos van a permitir realizar operaciones de búsqueda y ordenación.
  • Nota Importante: Recordar que si los elementos que guardamos en las colecciones son objetos de clases (objetos mutables), estos se pasan por referencia, por lo que cualquier modificación en el objeto original también supondrá una modificación en el objeto referenciado de la colección. Ya vimos esto anteriormente aplicado a arrays (sucede lo mismo en las colecciones).
Frente a estos objetos tenemos los objetos inmutables (como String, Integer, Byte,...). Al añadirlos a una colección se crea una copia.


Podéis ver en el enlace todos los métodos de los que vamos a poder hacer uso en una colección. Fijarse que hace uso de un tipo de datos genérico (<E>) por lo que dentro de una colección vamos a poder guardar cualquier objeto de cualquier clase.
Algunos métodos que incorpora dicha interface:
  • clear(): Borra todos los elementos de la colección.
  • add(E): Añade un elemento a la colección.
  • contains(java.lang.Object): Comprueba si el elemento está en la colección.
  • isEmpty(): Devuelve true/false en función si la colección esta vacía o no.
  • remove(java.lang.Object): Elimina un elemento de la colección.
  • size(): Devuelve el número de elementos de la colección.
  • retainAll(java.util.Collection): Elimina todos los elementos de la colección menos los que están en la colección que enviamos como parámetro.
  • removeAll(java.util.Collection): Elimina todos los elementos de la colección que estén en la colección enviada como parámetro.
  • toArray(): Devuelve en forma de array los elemntos de la colección. Como no sabe de que tipo van a ser devuelve Object.
  • toArray(T[]): Devuelve en forma de array los elemntos de la colección pero convertiéndolos al tipo indicado por el parámetro.
Prog colecciones 1.jpg



Conjuntos

  • Un conjunto es una colección que tiene como principal característica que los elementos de la colección no pueden estar repetidos.
  • La interfaz que utilizan los conjuntos es java.util.Set. Esta interface deriva de la interface Collection (vista antes) pero no añade ningún método nuevo a los vistos.
  • Recordar que las interfaces no pueden ser instanciadas, por lo que necesitamos una clase que 'implemente' la interface.
Podemos crearla nosotros, pero Java ya incorpora clases genéricas que la implementan y sobre las cuales podemos crear instancias.


  • Nota: Es posible definir el número de elementos del conjunto cuando instanciamos alguna de las clases anteriores. Si lo hacemos estaremos aumentando el rendimiento ya que al añadir un elemento al conjunto, el espacio en memoria ya estará reservado.


Prog colecciones 2.jpg



HashSet

  • Utiliza tablas hash para referenciar los datos guardados.
Prog colecciones 4 Tabla hash1.png


  • Ventajas:
  • Al hacer uso de tablas hash los accesos a los elementos de la colección son muy rápidos.
  • Desventajas:
  • No se pueden ordenar los datos y aparecen de forma desordenada.
  • Ocupan bastante memoria.



  • Como todas las clases genéricas va a poder almacenar objetos de cualquier clase.
Por ejemplo:
1 HashSet colec1 = new HashSet();
2 colec1.add(2);
3 colec1.add("casa");
Pero esto no suele ser lo habitual, ya que al recuperar los datos tendríamos que preguntar por el tipo de dato y además en una colección se supone que guardamos datos con algún tipo de relación y por lo tanto todos deberían ser del mismo tipo.
  • Para indicar el tipo de dato que va a guardar la colección, lo indicamos en la instanciación: HashSet<Integer> conjunto=new HashSet<>();
  • Recordar que podemos hacer uso de todos los métodos de la interface Collection.


  • Veamos unos ejemplos:
 1     public static void main(String[] arg){
 2 
 3         HashSet<Integer> colec1 = new HashSet<>();
 4         colec1.add(2);
 5         colec1.add(1);
 6         colec1.add(4);
 7         colec1.add(6);
 8 
 9         if (colec1.contains(2)){
10             System.out.println("El número 2 está en el conjunto");
11         }
12         System.out.printf("El tamaño del conjunto es:%d%n",colec1.size());
13         
14         HashSet<Integer> colec2 = new <>HashSet();
15         colec2.add(7);
16         colec2.add(9);
17         
18         colec1.addAll(colec2);
19         
20         if (colec1.contains(7)){
21             System.out.println("El número 7 está en el conjunto");
22         }
23         
24         colec1.removeAll(colec2);
25         
26         if (!colec1.contains(7)){
27             System.out.println("El número 7 YA NO está en el conjunto");
28         }
29         
30         System.out.println("Datos del array");
31         for (Integer dato : colec1){
32             System.out.println(dato);
33         }
34  
35         colec1.clear();
36         
37         System.out.printf("El tamaño del conjunto es:%d%n",colec1.size());
38 
39         // Si queremos obtener los datos de la colección en forma de array
40         Integer[] datosColec = colec1.toArray(new Integer[0]);   // Vale poner cualquier índice. Sólo usa el parámetro para saber a que tipo de dato tiene que realizar la conversión.
41         
42     }
La ejecución de este programa produce como resultado:
Prog hashset 2.JPG
Fijarse como los datos no aparecen en el orden en que fueron añadidos al array.




LinkedHashSet

  • Toda información de la clase LinkedHashSet este enlace.
  • Usa tablas hash pero utiliza listas enlazadas como estructura de datos para guardar los elementos de la colección.
Prog colecciones 3 ListaEnlazada.gif


  • Ventajas:
  • Hace uso de tablas hash (acceso rápido)
  • Usa listas enlazadas, por lo que conserva el orden de inserción de los elementos. La búsqueda de un elemento concreto puede ser más rápida.
  • Desventajas:
  • Necesita más memoria (por el uso de las listas) que las colecciones HashSet.
  • Es un poco más lenta que las HashSet cuando queremos acceder a todos los elementos.


  • Veamos un ejemplo:
 1     public static void main(String[] arg){
 2         LinkedHashSet<Integer> colec1 = new LinkedHashSet();
 3         colec1.add(2);
 4         colec1.add(1);
 5         colec1.add(4);
 6         colec1.add(6);
 7 
 8         if (colec1.contains(2)){
 9             System.out.println("El número 2 está en el conjunto");
10         }
11         System.out.printf("El tamaño del conjunto es:%d%n",colec1.size());
12         
13         HashSet<Integer> colec2 = new <Integer>HashSet();
14         colec2.add(7);
15         colec2.add(9);
16         
17         colec1.addAll(colec2);
18         
19         if (colec1.contains(7)){
20             System.out.println("El número 7 está en el conjunto");
21         }
22         
23         colec1.removeAll(colec2);
24         
25         if (!colec1.contains(7)){
26             System.out.println("El número 7 YA NO está en el conjunto");
27         }
28         
29         System.out.println("Datos del array");
30         for (Integer dato: colec1){
31             System.out.println(dato);
32         }
33  
34         colec1.clear();
35         
36         System.out.printf("El tamaño del conjunto es:%d%n",colec1.size());
37         
38     }
Como vemos el código es el mismo que en el caso anterior, cambiando el tipo de colección.
  • La salida:
Prog colecciones 6.jpg
Fijarse como los datos del array aparecen en el mismo orden en que fueron añadidos.



TreeSet

  • Utiliza como estructura de almacenamiento árboles binarios.
Prog colecciones 5 Tabla hash1.png


  • Ventajas:
  • Los datos se ordenan en base al valor de cada uno de ellos en el momento en que son añadidos.
  • Desventajas:
  • Son más lentas que las HashSet y LinkedHashSet.


  • A diferencia de las dos anteriores, implementa la interface SortedSet, por lo que podremos llamar a los métodos first() y last() para obtener el primer y último elemento de la colección ordenada.
También incorpora la interface NavigableSet lo que nos va a permitir hacer uso de métodos para manejar el conjunto de elementos de la colección, como obtener el siguiente elemento a un valor dado, borrar el primer o último elemento de la colección, obtener una parte de los elementos de la colección,...


  • Veamos un ejemplo:
 1         TreeSet<Integer> colec1 = new TreeSet<>();
 2         colec1.add(2);
 3         colec1.add(1);
 4         colec1.add(4);
 5         colec1.add(6);
 6 
 7         if (colec1.contains(2)){
 8             System.out.println("El número 2 está en el conjunto");
 9         }
10         System.out.printf("El tamaño del conjunto es:%d%n",colec1.size());
11         
12         HashSet<Integer> colec2 = new HashSet<>();
13         colec2.add(7);
14         colec2.add(9);
15         
16         colec1.addAll(colec2);
17         
18         if (colec1.contains(7)){
19             System.out.println("El número 7 está en el conjunto");
20         }
21         
22         colec1.removeAll(colec2);
23         
24         if (!colec1.contains(7)){
25             System.out.println("El número 7 YA NO está en el conjunto");
26         }
27         
28         System.out.println("Datos del array");
29         for (Integer dato: colec1){
30             System.out.println(dato);
31         }
32  
33         // MÉTODOS DE LA INTERFACE SortedSet
34         System.out.println("El primer elemento de la lista:" + colec1.first());
35         System.out.println("El último elemento de la lista:" + colec1.last());
36 
37         // MÉTODOS DE LA INTERFACE NaveigableSet
38         System.out.println("El entero que haya en a lista que sea menor o igual que  2:" + colec1.floor(3));
39         System.out.println("El entero que haya en a lista que sea mayor o igual que 5:" + colec1.ceiling(5));
40         NavigableSet<Integer> inverso = colec1.descendingSet();
41         System.out.println("Datos invertidos:");
42         for (Integer dato: inverso){
43             System.out.println(dato);
44         }
45 
46         colec1.clear();
47         
48         System.out.printf("El tamaño del conjunto es:%d%n",colec1.size());
Como vemos el código es el mismo que en el caso anterior, cambiando el tipo de colección y he añadido alguno de los métodos de las interfaces NavigableSet y SortedSet.
  • La salida:
Prog colecciones 7B.jpg
Fijarse como los datos del array aparecen ordenados según su valor.


  • En el caso de utilizar cadenas como elementos de la colección, la ordenación se hará alfabéticamente:
Prog colecciones 8.jpg



Rendimiento

  • Ya vimos las diferencias principales entre unos tipos de colecciones y otros, por lo que la elección de uno u otra va a depender de las necesidades que tengamos.
  • Cuando disponemos de diferentes tipos de colecciones que se adaptan tendremos que escoger la que sea más rápida en el acceso a los elementos.
El acceso va a depender de si queremos acceder directamente a un elemento (operaciones de búsqueda) o si queremos recorrerlos todos de forma secuencial (pudiendo necesitar que estén ordenados o no).
El rendimiento también se verá afectado si tenemos que realizar operaciones de inserción y/o borrado de los elementos de la colección de una forma habitual.
  • Para determinar cual es el tipo de colección que más nos interesa podemos realizar pruebas de 'carga' en las que reproducimos las operaciones que vamos a realizar sobre las colecciones y comparamos los tiempos entre los diferentes tipos de colecciones.
  • Podéis consultar en este enlace una idea de como realizar una simulación.
En el mismo se prueban tres tipos de listas (ArrayList,un HashSet y un TreeSet) en las que se va a realizar una búsqueda de un elemento concreto de la colección.
Por lo tanto, nos dará una idea de cual es la mejor colección para realizar búsquedas concretas (el dato que se va a buscar es el mismo que utiliza la colección para ordenar, en el caso de que los guarde ordenados, como el TreeSet)


  • Basado en el código del enlace anterior vamos a realizar una comparación de rendimiento entre las tres colecciones vistas hasta ahora.
Recordar que estamos comparando velocidades de acceso, pero no tenemos en cuenta la cantidad de memoria necesaria para crear las estructuras, factor que puede ser necesario tener en cuenta.
En este primer ejemplo, estamos creando una colección de un millón de elementos.
  1     public enum TIPO_COLECCION{LinkedHashSet,HashSet,TreeSet,TODOS};
  2     
  3     public static void getRendimiento(TIPO_COLECCION tipo){
  4         long tiempo1=0,tiempo2=0;
  5     
  6         if (tipo == TIPO_COLECCION.HashSet || tipo==TIPO_COLECCION.TODOS){
  7             HashSet<String> hashset1 = new HashSet<>();
  8 
  9             System.out.println("**************************HASHSET************************");
 10             tiempo1 = System.nanoTime();
 11             for (int i = 0; i < 1000000; i++) {
 12 
 13                 hashset1.add("item" + i);
 14 
 15             }
 16             tiempo2 = System.nanoTime();
 17             System.out.printf("TIEMPO en mseg CREAR 1000000 en un HashSet:%d %n", TimeUnit.NANOSECONDS.toMillis(tiempo2 - tiempo1));
 18 
 19             tiempo1 = System.nanoTime();
 20             hashset1.contains("item899999");
 21             tiempo2 = System.nanoTime();
 22             System.out.printf("Tiempo en microseg acceder al elemento item899999:%d %n", TimeUnit.NANOSECONDS.toMicros(tiempo2 - tiempo1));
 23             tiempo1 = System.nanoTime();
 24             hashset1.contains("item4999");
 25             tiempo2 = System.nanoTime();
 26             System.out.printf("Tiempo en microseg acceder al elemento item4999:%d %n", TimeUnit.NANOSECONDS.toMicros(tiempo2 - tiempo1));
 27 
 28             tiempo1 = System.nanoTime();
 29             for (String dato: hashset1){
 30 
 31             }
 32             tiempo2 = System.nanoTime();
 33             System.out.printf("Tiempo en mseg en acceder a todos los elementos:%d %n", TimeUnit.NANOSECONDS.toMillis(tiempo2 - tiempo1));
 34 
 35 
 36             
 37         }
 38 
 39         if (tipo == TIPO_COLECCION.LinkedHashSet || tipo==TIPO_COLECCION.TODOS){
 40             LinkedHashSet<String> linkedhashset1 = new LinkedHashSet<>();
 41             
 42             System.out.println("**************************LINKEDHASHSET************************");
 43             tiempo1 = System.nanoTime();
 44             for (int i = 0; i < 1000000; i++) {
 45 
 46                 linkedhashset1.add("item" + i);
 47 
 48             }
 49             tiempo2 = System.nanoTime();
 50             System.out.printf("TIEMPO en mseg CREAR 1000000 en un LinkedHashSet:%d %n", TimeUnit.NANOSECONDS.toMillis(tiempo2 - tiempo1));
 51 
 52 
 53             tiempo1 = System.nanoTime();
 54             linkedhashset1.contains("item899999");
 55             tiempo2 = System.nanoTime();
 56             System.out.printf("Tiempo en microseg acceder al elemento item899999:%d %n", TimeUnit.NANOSECONDS.toMicros(tiempo2 - tiempo1));
 57             tiempo1 = System.nanoTime();
 58             linkedhashset1.contains("item4999");
 59             tiempo2 = System.nanoTime();
 60             System.out.printf("Tiempo en microseg acceder al elemento item4999:%d %n", TimeUnit.NANOSECONDS.toMicros(tiempo2 - tiempo1));
 61 
 62             tiempo1 = System.nanoTime();
 63             for (String dato: linkedhashset1){
 64 
 65             }
 66             tiempo2 = System.nanoTime();
 67             System.out.printf("Tiempo en mseg en acceder a todos los elementos:%d %n", TimeUnit.NANOSECONDS.toMillis(tiempo2 - tiempo1));
 68         }
 69         
 70         if (tipo == TIPO_COLECCION.TreeSet || tipo==TIPO_COLECCION.TODOS){
 71             TreeSet<String> treeset1 = new TreeSet<String>();
 72 
 73             System.out.println("**************************TREESET************************");
 74             tiempo1 = System.nanoTime();
 75             for (int i = 0; i < 1000000; i++) {
 76 
 77                 treeset1.add("item" + i);
 78 
 79             }
 80             tiempo2 = System.nanoTime();
 81             System.out.printf("TIEMPO en mseg CREAR 1000000 en un TreeSet:%d %n", TimeUnit.NANOSECONDS.toMillis(tiempo2 - tiempo1));
 82 
 83 
 84             tiempo1 = System.nanoTime();
 85             treeset1.contains("item899999");
 86             tiempo2 = System.nanoTime();
 87             System.out.printf("Tiempo en microseg acceder al elemento item899999:%d %n", TimeUnit.NANOSECONDS.toMicros(tiempo2 - tiempo1));
 88             tiempo1 = System.nanoTime();
 89             treeset1.contains("item4999");
 90             tiempo2 = System.nanoTime();
 91             System.out.printf("Tiempo en microseg acceder al elemento item4999:%d %n", TimeUnit.NANOSECONDS.toMicros(tiempo2 - tiempo1));
 92 
 93             tiempo1 = System.nanoTime();
 94             for (String dato: treeset1){
 95 
 96             }
 97             tiempo2 = System.nanoTime();
 98             System.out.printf("Tiempo en mseg en acceder a todos los elementos:%d %n", TimeUnit.NANOSECONDS.toMillis(tiempo2 - tiempo1));
 99             
100         }
101 
102 
103     }
1     public static void main(String[] arg) {
2 
3         getRendimiento(TIPO_COLECCION.TODOS);
4 
5     }
  • El resultado de dicha ejecución es el siguiente:
Prog colecciones 9.jpg
Como vemos el TreeSet es la estructura que más tarda en crear los elementos y la que más tarda en recorrerlos. La principal ventaja (como comentamos) es que todos los datos los recuperamos de forma ordenada.
Entre la HashSet y LinkedHashSet, el HashSet tarda menos en crear la estructura, menos en recorrer todos los elementos y cuando buscamos un elemento concreto en un caso tarda algo menos y en el otro algo más.
Esto es así por la forma en como se estarán guardando los elementos en el HashSet y en el LinkedHashSet. Sabemos seguro que en el LinkedHashSet se van guardando con el mismo orden en como se añaden (haciendo uso de listas enlazadas), mientras que en el HashSet no lo sabemos.


Ejercicios propuestos Conjuntos

  • Pide al usuario de forma gráfica (con una caja de diálogo) 5 datos numéricos.
Guarda los datos en una colección que te permita obtener de la forma más inmediata la nota más baja y la más alta.
Muestra dichas notas.


  • Crea dos colecciones de cadenas y añade los datos de una a los de la otra (recuerda que en este tipo de colección no se permiten duplicados).
  • Crea dos colecciones (A y B) de números y elimina todos los datos de colección A que se encuentren en la colección B.
  • Crea dos colecciones (A y B) de números y obtén los datos comunes a las dos colecciones.




Listas

  • Es otro tipo de colección que tiene las siguentes diferencias con respecto a los conjuntos:
  • Pueden almacenar duplicados.
  • Podemos acceder a un elemento en concreto de la lista indicando la posición.
  • Podemos buscar elementos en la lista y obtener su posición. En los conjuntos los métodos que buscaban dentro del conjunto devolvían true/false.
  • Podemos obtener una parte de los elementos de la lista.


Alguno de los métodos que podemos utilizar en todas las clases que implementen esta interface:
  • add(E): Añade un elemento a la lista.
  • add(int,E): Añade un elemento en la posición indicada. La primera posición empieza en cero.
  • addAll(java.util.Collection): Añade una colección a la lista.
  • get(int): Devuelve el elemento de la lista que se encuentra en la posición indicada. La primera posición empieza en cero.
  • remove(int): Elimina el elemento de la lista indicado por la posición. La primera posición empieza en cero.
  • set(int,20E): Reemplaza el elemento de la lista indicado por la posición por el elemento enviado. La primera posición empieza en cero.
  • subList(int,int):Devuelve una lista con los elementos que se encuentren entre las dos posiciones enviadas. La primera posición empieza en cero.
  • indexOf(java.lang.Object): Devuelve la posición en la lista del elemento enviado. Si hay varios, devuelve la posición del primero. La primera posición empieza en cero.
  • lastIndexOf(java.lang.Object): Devuelve la posición en la lista del elemento enviado. Si hay varios, devuelve la posición del último. La primera posición empieza en cero.


  • Alguna de las clases que implementan esta interface son:


Prog colecciones 10.jpg



LinkedList

  • Utiliza como estructura de almacenamiento las listas doblemente enlazadas.
  • A no ser que hagamos operaciones de borrado sobre el primer o último elemento de la lista, el rendimiento será superior en el ArrayList.


  • Esta clase implementa:


ArrayList

  • Un ArrayList es un array el cual es 'redimensionado', aumentando su tamaño cuando añadimos nuevos elementos o disminuyéndolo cuando borramos elementos.
Esta redimensión la realiza la clase internamente.
  • Esta clase es más rápida a la hora de acceder a los elementos de la colección que la clase LinkedList, ya que por medio del índice accedemos 'directamente' al elemento del array. En el caso de las listas doblemente enlazadas(estructura utilizada por la clase LinkedList) es necesario recorrer todos los 'nodos' intermedios hasta llegar al elemento.
Sin embargo con operaciones que impliquen modificar su tamaño el rendimiento es peor. Lo lógico es definir el tamaño de la colección en la instanciación.


Rendimiento

  • Al igual que en el caso de los conjuntos recordar que se puede indicar en el constructor el tamaño inicial de la colección con lo que el tiempo para añadir nuevos elementos a la colección mejorará notablemente.
  • Tampoco se tiene en cuenta la cantidad de memoria reservada, aspecto que puede ser necesario tener en cuenta.
  • Veamos un código parecido al utilizado para medir el rendimiento de los conjuntos, añadiendo operaciones de añadir y borrar elementos de la colección.
  1     public enum TIPO_COLECCION{LinkedList,ArrayList,TODOS};
  2    
  3     public static void getRendimiento(TIPO_COLECCION tipo){
  4         long tiempo1=0,tiempo2=0;
  5    
  6         if (tipo == TIPO_COLECCION.LinkedList || tipo==TIPO_COLECCION.TODOS){
  7             LinkedList<String> linkedlist1 = new LinkedList<>();
  8  
  9             System.out.println("**************************LinkedList************************");
 10             tiempo1 = System.nanoTime();
 11             for (int i = 0; i < 1000000; i++) {
 12  
 13                 linkedlist1.add("item" + i);
 14  
 15             }
 16             tiempo2 = System.nanoTime();
 17             System.out.printf("TIEMPO en mseg CREAR 1000000 en un LinkedList:%d %n", TimeUnit.NANOSECONDS.toMillis(tiempo2 - tiempo1));
 18  
 19             tiempo1 = System.nanoTime();
 20             linkedlist1.contains("item899999");
 21             tiempo2 = System.nanoTime();
 22             System.out.printf("Tiempo en microseg encontrar el elemento item899999:%d %n", TimeUnit.NANOSECONDS.toMicros(tiempo2 - tiempo1));
 23             tiempo1 = System.nanoTime();
 24             linkedlist1.contains("item4999");
 25             tiempo2 = System.nanoTime();
 26             System.out.printf("Tiempo en microseg encontrar el elemento item4999:%d %n", TimeUnit.NANOSECONDS.toMicros(tiempo2 - tiempo1));
 27  
 28             tiempo1 = System.nanoTime();
 29             linkedlist1.get(900000);
 30             tiempo2 = System.nanoTime();
 31             System.out.printf("Tiempo en microseg acceder al elemento 900000:%d %n", TimeUnit.NANOSECONDS.toMicros(tiempo2 - tiempo1));
 32 
 33             tiempo1 = System.nanoTime();
 34             for (String dato: linkedlist1){
 35  
 36             }
 37             tiempo2 = System.nanoTime();
 38             System.out.printf("Tiempo en mseg en acceder a todos los elementos:%d %n", TimeUnit.NANOSECONDS.toMillis(tiempo2 - tiempo1));
 39  
 40             tiempo1 = System.nanoTime();
 41             for (int c=100000;c<110000;c++){
 42                 linkedlist1.add("Add" + c);
 43             }
 44             tiempo2 = System.nanoTime();
 45             System.out.printf("Tiempo en microseg en insertar 10000 registros:%d %n", TimeUnit.NANOSECONDS.toMicros(tiempo2 - tiempo1));
 46  
 47             tiempo1 = System.nanoTime();
 48             for (int c=100000;c<101000;c++){
 49                 linkedlist1.remove("Add" + c);
 50             }
 51             tiempo2 = System.nanoTime();
 52             System.out.printf("Tiempo en milesegundos en borrar 1000 registros por su valor:%d %n", TimeUnit.NANOSECONDS.toMillis(tiempo2 - tiempo1));
 53 
 54             tiempo1 = System.nanoTime();
 55             for (int c=101000;c<102000;c++){
 56                 linkedlist1.remove("Add" + c);
 57             }
 58             tiempo2 = System.nanoTime();
 59             System.out.printf("Tiempo en milesegundos en borrar 1000 registros por su indice:%d %n", TimeUnit.NANOSECONDS.toMillis(tiempo2 - tiempo1));
 60 
 61             tiempo1 = System.nanoTime();
 62             linkedlist1.remove(0);
 63             linkedlist1.remove(linkedlist1.size()-1);
 64             tiempo2 = System.nanoTime();
 65             System.out.printf("Tiempo en microseg en borrar el primer elemento y el último de la lista:%d %n", TimeUnit.NANOSECONDS.toMicros(tiempo2 - tiempo1));
 66 
 67         }
 68  
 69         if (tipo == TIPO_COLECCION.ArrayList || tipo==TIPO_COLECCION.TODOS){
 70             ArrayList<String> arraylist = new ArrayList<>();
 71            
 72             System.out.println("**************************ArrayList************************");
 73             tiempo1 = System.nanoTime();
 74             for (int i = 0; i < 1000000; i++) {
 75  
 76                 arraylist.add("item" + i);
 77  
 78             }
 79             tiempo2 = System.nanoTime();
 80             System.out.printf("TIEMPO en mseg CREAR 1000000 en un ArrayList:%d %n", TimeUnit.NANOSECONDS.toMillis(tiempo2 - tiempo1));
 81  
 82  
 83             tiempo1 = System.nanoTime();
 84             arraylist.contains("item899999");
 85             tiempo2 = System.nanoTime();
 86             System.out.printf("Tiempo en microseg en encontrar al elemento item899999:%d %n", TimeUnit.NANOSECONDS.toMicros(tiempo2 - tiempo1));
 87             tiempo1 = System.nanoTime();
 88             arraylist.contains("item4999");
 89             tiempo2 = System.nanoTime();
 90             System.out.printf("Tiempo en microseg en encontrar al elemento item4999:%d %n", TimeUnit.NANOSECONDS.toMicros(tiempo2 - tiempo1));
 91  
 92             tiempo1 = System.nanoTime();
 93             arraylist.get(900000);
 94             tiempo2 = System.nanoTime();
 95             System.out.printf("Tiempo en microseg acceder al elemento 900000:%d %n", TimeUnit.NANOSECONDS.toMicros(tiempo2 - tiempo1));
 96 
 97             
 98             tiempo1 = System.nanoTime();
 99             for (String dato: arraylist){
100  
101             }
102             tiempo2 = System.nanoTime();
103             System.out.printf("Tiempo en mseg en acceder a todos los elementos:%d %n", TimeUnit.NANOSECONDS.toMillis(tiempo2 - tiempo1));
104 
105             tiempo1 = System.nanoTime();
106             for (int c=100000;c<110000;c++){
107                 arraylist.add("Add" + c);
108             }
109             tiempo2 = System.nanoTime();
110             System.out.printf("Tiempo en microseg en insertar 10000 registros:%d %n", TimeUnit.NANOSECONDS.toMicros(tiempo2 - tiempo1));
111             
112             tiempo1 = System.nanoTime();
113             for (int c=100000;c<101000;c++){
114                 arraylist.remove("Add" + c);
115             }
116             tiempo2 = System.nanoTime();
117             System.out.printf("Tiempo en milesegundos en borrar 1000 registros por su valor:%d %n", TimeUnit.NANOSECONDS.toMillis(tiempo2 - tiempo1));
118 
119             tiempo1 = System.nanoTime();
120             for (int c=101000;c<102000;c++){
121                 arraylist.remove("Add" + c);
122             }
123             tiempo2 = System.nanoTime();
124             System.out.printf("Tiempo en milesegundos en borrar 1000 registros por su indice:%d %n", TimeUnit.NANOSECONDS.toMillis(tiempo2 - tiempo1));
125 
126             tiempo1 = System.nanoTime();
127             arraylist.remove(0);
128             arraylist.remove(arraylist.size()-1);
129             tiempo2 = System.nanoTime();
130             System.out.printf("Tiempo en microseg en borrar el primer elemento y el último de la lista:%d %n", TimeUnit.NANOSECONDS.toMicros(tiempo2 - tiempo1));
131 
132         }
133        
134  
135  
136     }    
137     public static void main(String[] arg){
138         
139         getRendimiento(TIPO_COLECCION.TODOS);
140         
141     }


Resultado:

Prog colecciones 13B.jpg
Como podemos comprobar:
LinkedList
  • Es más rápida cuando se eliminan elememtos que quedan al principio o al final de la colección.
  • En todos los demás aspectos es más rápida la ArrayList.
Nota: Aún no vimos como recorrer las colecciones con los iterator, que sería otro factor a tener en cuenta.


Ejercicios de Listas


  • Crea un programa que haga que el usuario introduzca nombres y notas de alumnos hasta que introduzca la cadena SALIR en el nombre. Esos datos deben guardarse en una lista en forma de objetos de una clase creada por ti previamente y con el método toString implementado mostrando los datos del alumno (puedes hacerlo gráficamente)
Muestra la información del array.
Haz que el usuario introduzca una posición (entre 1 y el número de elementos de la colección) y que se muestre la información guardada en dicha posición.
Haz que el usuario introduzca dos posiciones (entre 1 y el número de elementos de la colección) y muestra los alumnos que hay entre esas dos posiciones.
Haz que el usuario introduzca una posición (entre 1 y el número de elementos de la colección) y elimina el elemento de la colección en esa posición. Muestra los elementos que quedan.


  • Modifica el ejercicio realizado durante la explicación de la Interace Gráfica y utiliza un ArrayList en vez de un Array. Actualiza todo el código necesario aprovechando los métodos que proporcionan los ArrayList.


  • Se quiere implementar una solución informática que modelice el tráfico de coches en una autopista. Disponemos de cuatro cabinas.
Crea un programa que haga que se 'creen' coches aleatoriamente (hazlo en función del valor de una constante de clase) y que lleguen de forma aleatoria a cada una de las cuatro cabinas.
Muestra los coches que salen y entran de cada cabina.



Solución Ejercicios de Listas

 1     public static void addElementoFinal(LinkedList lista, Integer elem){
 2         lista.add(lista.size(),elem);
 3     }
 4     
 5     public static void main(String[] arg){
 6 
 7         LinkedList<Integer> lista = new LinkedList();
 8         lista.add(1);
 9         lista.add(3);
10         addElementoFinal(lista, 5);
11         
12         for(Integer dato: lista) {
13             System.out.println(dato);
14         }
15     }



Conjunto de pares clave/valor

  • En Java disponemos de clases que nos permiten implementar otro tipo de estructuras de datos diferentes a las vistas hasta ahora.
  • Estas estructuras se basan en guardar información en base a una clave la cual está asociada a un valor.
  • Un ejemplo lo podemos tener en el índice de un libro.
En dicho índice disponemos de una relación de capítulos y la página en la que se encuentra dicho capítulo.
En este caso, podemos tener como clave el capítulo y el valor asociado sería la página en la que se encuentra.
  • Otro ejemplo sería un diccionario, en el que las claves serían cada una de las palabras del diccionario y los valores asociados a dichas claves serían cada una de las definiciones.
  • Este tipo de estructuras se denominan array asociativos y un tipo de ellos son los Mapas o Diccionarios.


Disponemos de los métodos:


  • En Java existen varias clases que implementen esta interface. Algunas de ellas:


  • Las diferencias entre las mismas son las que ya vimos en el punto de los conjuntos.





Ordenar / Comparar objetos

  • Como indicamos anteriormente, todas las colecciones van a poder guardar objetos de clases diferentes.
  • Cuando una colección ordena los datos (por ejemplo, la TreeSet, al añadirlos o llamando al método sort de la clase Collection) es necesario que dichos objetos se puedan ordenar.
  • Si son numéricos, se ordenan por su valor.
  • Si son cadena, los ordena alfabéticamente.
  • Si son objetos de una clase diferente a las dos anteriores, podemos implementar la ordenación de dos formas diferentes:
Que la clase a la que pertenecen los objetos que queramos ordenadr mplemente la interface java.util.comparable y después llamar al método sort de la clase Collections



Interface Comparator


Interface Comparable


-- Ángel D. Fernández González -- (2017).