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 la altura en ese punto (se quiere que sea lo más preciso posible).
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

  • 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:
 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     }


  • 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.


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



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