Ana ha recibido un pequeño encargo de parte de su tutora, María. Se trata de que realice un pequeño programita, muy sencillo pero fundamental.
-Hola Ana -dice María-, hoy tengo una tarea especial para ti.
-¿Sí? -responde Ana-. Estoy deseando, últimamente no hay nada que se me resista, llevo dos semanas en racha.
-Bueno, quizás esto se te resista un poco más. Es fácil, pero tiene cierta complicación. Un cliente para el que hicimos una aplicación, nos ha preguntado si podemos ayudarle. El cliente tiene una aplicación para gestionar sus clientes. Ahora está pensando en que se le añada el correo electrónico de cada cliente, para en un futuro poderle enviar publicidad y otras cosas. Además, también quiere que la aplicación busque cadenas de caracteres, por ejemplo, para cuando le interese ver los clientes cuyo apellido contenga "Garc" o la cadena que sea.
-¿Qué? -dice Ana con cierta perplejidad.
-Me alegra que te guste -dice María esbozando una sonrisa pícara-, sé que te atraen los retos.
-Pero, ¿eso cómo se hace? ¿Cómo compruebo yo si un correo electrónico es válido? -pregunta Ana.
-Bueno, tranquila, que es más fácil de lo que parece, lo del correo electrónico lo puedes hacer con expresiones regulares. Y respecto a lo de ver las cadenas de caracteres que contienen o empiezan por tal o cual otra cadena, recuerda que Java tiene un montón de métodos y operadores para trabajar con cadenas de caracteres.
-Bueno -dice María justo después de resoplar tres veces seguidas-, parece que no será tan difícil después de todo.
Cuando el volumen de datos a manejar por una aplicación es elevado, no basta con utilizar variables. Manejar los datos de un único cliente en una aplicación puede ser relativamente sencillo, pues un cliente está compuesto por una serie de datos y eso simplemente se traduce en varias variables. Pero, ¿qué ocurre cuando en una aplicación tenemos que gestionar varios clientes a la vez?
Lo mismo ocurre en otros casos. Para poder realizar ciertas aplicaciones se necesita poder manejar datos que van más allá de meros datos simples (números y letras). A veces, los datos que tiene que manejar la aplicación son datos compuestos, es decir, datos que están compuestos a su vez de varios datos más simples. Por ejemplo, una persona está compuesto por varios datos, los datos podrían ser el nombre del cliente, la dirección donde vive, la fecha de nacimiento, etc.
Los datos compuestos son un tipo de estructura de datos, y en realidad ya los has manejado. Las clases son un ejemplo de estructuras de datos que permiten almacenar datos compuestos. Y el objeto en sí, una instancia de la clase, sería el dato compuesto. Pero, a veces, los datos tienen estructuras aún más complejas, y son necesarias soluciones adicionales.
Más adelante veremos esas soluciones adicionales. En este tema nos centraremos en las cadenas de caracteres y en los vectores o arrays.
1.- Introducción a las estructuras de almacenamiento.
¿Cómo almacenarías en memoria un listado de números del que tienes que extraer el valor máximo? Hasta ahora solo podríamos hacerlo mediante un conjunto de variables de tipo numérico. Pero, ¿y si el listado de números no tiene un tamaño conocido en tiempo de compilación, sino que ese tamaño puede ser diferente para cada ejecución del programa? Entonces la cosa se complica.
Un listado de varios elementos de un tipo simple (por ejemplo, números enteros) consiste en un tipo de dato de tipo compuesto o estructurado. De hecho, una clase podría considerarse como la evolución de un tipo de dato compuesto donde los elementos que contiene no tienen por qué ser todos del mismo tipo, sino que pueden tener tipos diferentes (serían los atributos de la clase) y además integra métodos que permiten manipular esos atributos. En lenguajes no orientados a objetos, a ese tipo de estructuras se les suele llamar registros. Pero todo eso lo veremos en detalle en la próxima unidad. Ahora nos toca trabajar con una primera aproximación a los tipos compuestos más sencillos: las cadenas de caracteres y los arrays.
Las estructuras de almacenamiento, en general, se pueden clasificar de varias formas. Por ejemplo, atendiendo a si pueden almacenar datos de diferente tipo, o si solo pueden almacenar datos de un solo tipo, se pueden distinguir:
Estructuras con capacidad de almacenar varios datos del mismo tipo: varios números, varios caracteres, etc. Ejemplos de estas estructuras son los arrays, las cadenas de caracteres, las listas y los conjuntos.
Estructuras con capacidad de almacenar varios datos de distinto tipo: números, fechas, cadenas de caracteres, etc., todo junto dentro de una misma estructura. Ejemplos de este tipo de estructuras son las clases.
Otra forma de clasificar las estructuras de almacenamiento va en función de si pueden o no cambiar de tamaño de forma dinámica:
Estructuras cuyo tamaño se establece en el momento de la creación o definición y su tamaño no puede variar después. Ejemplos de estas estructuras son los arrays (de una o varias dimensiones), donde una vez que se reserva memoria para ellas, ya no podemos aumentar o disminuir su tamaño.
Estructuras cuyo tamaño es variable (conocidas como estructuras dinámicas). Su tamaño crece o decrece según las necesidades de forma dinámica. Es el caso de las listas, árboles, conjuntos y, como veremos también, el caso de algunos tipos de cadenas de caracteres.
Desde el punto de vista de dónde se almacenan los datos, también podemos clasificarlas como:
Estructuras internas, si se almacenan en memoria principal (memoria RAM). Eso significa que cuando el proceso (programa en ejecución) finalice, esa información se perderá.
Estructuras externas, si se almacenan en memoria secundaria (discos magnéticos o de estado sólido, dispositivos de memoria flash, cintas, etc.), permitiendo que la información que allí se registre persista más allá de la ejecución del programa.
Por último, atendiendo a la forma en la que los datos se ordenan dentro de la estructura, podemos diferenciar varios tipos de estructuras:
Estructuras que no se ordenan de por sí, y debe ser el programador el encargado de ordenar los datos si fuera necesario. Un ejemplo de estas estructuras son los arrays o las listas.
Estructuras ordenadas. Se trata de estructuras que al incorporar un dato nuevo a todos los datos existentes, este se almacena en una posición concreta que irá en función del orden. El orden establecido en la estructura puede variar dependiendo de las necesidades del programa: alfabético, orden numérico de mayor a menor, momento de inserción, etc.
Todavía no conoces mucho de las estructuras, y probablemente todo te suena raro y extraño. No te preocupes, poco a poco irás descubriéndolas. Verás que son sencillas de utilizar y muy cómodas.
Estructura de datos que almacena uno o varios datos de diferente tipo (números enteros, cadenas de texto, números flotantes, ...) de forma conjunta, porque dichos datos están íntimamente relacionados entre sí. Por ejemplo, una dirección está compuesta de varios datos: calle (cadena de texto), número (número entero), código postal (cadena de texto), etc.
Ana está asustada, le acaban de asignar una nueva tarea y está pensando qué métodos le ayudarían a buscar cadenas de caracteres que empiecen por unos caracteres determinados. Por ejemplo, la lista de más abajo presenta apellidos de personas que empiezan por "Garc":
Por tanto, Ana podrá obtener las personas que necesite la aplicación del cliente, según los caracteres que le interese buscar al usuario de la aplicación:
Como puedes observar, también podría interesar buscar solo aquellas personas en cuyos apellidos aparezcan los caracteres "Pi", para apellidos como Pineda o Piquillo.
Probablemente, una de las herramientas que más utilizarás cuando estés trabajando con cualquier lenguaje de programación son las cadenas de caracteres. Las cadenas de caracteres son estructuras de almacenamiento que permiten guardar una secuencia de caracteres de casi cualquier longitud. Y la pregunta ahora es, ¿qué es un carácter?
En Java, y en todo lenguaje de programación, y por ende, en todo sistema informático, los caracteres se codifican mediante secuencias de bits que representan a los símbolos usados en la comunicación escrita humana. Estos símbolos pueden ser letras, números, símbolos matemáticos e incluso ideogramas y pictogramas.
Imagen, ilustración o dibujo que en algunos idiomas puede tener un significado y que pueden ser leídos, como por ejemplo los ideogramas del egipcio antiguo, del chino o del japonés.
Signo, generalmente a modo de dibujo, que representa una idea, un símbolo común o una figura real. Un ejemplo de pictograma son los emoticonos, con ellos se expresan emociones.
La codificación es la representación interna de cada carácter dentro de un ordenador. Cada símbolo usado en la comunicación humana tiene que traducirse en una serie de unos y ceros que puedan almacenarse en el ordenador, dado que un símbolo, como tal, no puede almacenarse. Después será el software del ordenador el encargado de transformar el código binario en una representación gráfica reconocible por el ser humano. Las codificaciones más extendidas son ASCII, ISO 885915, y la más usada hoy día, UTF-8, recogida dentro las codificaciones del estándar Unicode (UTF8, UTF-16 y UTF-32).
La forma más habitual de ver escrita una cadena de caracteres es como un literal de cadena. Consiste simplemente en una secuencia de caracteres entre comillas dobles, por ejemplo: "Ejemplo de cadena de caracteres".
En Java, los literales de cadena son en realidad instancias de la clase String, lo cual quiere decir que, por el mero hecho de escribir un literal en el código de nuestro programa, se creará una instancia de dicha clase. Es como si hiciéramos un new sin tener que escribirlo explícitamente.
Esto da mucha flexibilidad, puesto que permite crear cadenas de muchas formas diferentes, pero obviamente consume mucha memoria. La forma más habitual es crear una cadena partiendo de un literal:
String cad = "Ejemplo de cadena";
En este caso, el literal de cadena situado a la derecha del igual es en realidad una instancia de la clase String. Al realizar esta asignación hacemos que la variable cad se convierta en una referencia al objeto ya creado. Es el único caso (junto con los tipos "envoltorio" en los que puede pasar algo parecido) que nos ofrece Java de poder "crear" objetos sin tener que usar el operador new. Eso es posible gracias a que los objetos de tipo String tienen la opción de poderse escribir como "literales" (valores), del mismo tipo que sucede con los ocho tipos primitivos (byte, short, int, long, float, double, char, boolean). En este caso, para que el compilador de Java sepa que se trata de un valor literal de tipo String, se utilizan las comillas dobles, del mismo modo que se utilizan las simples para los literales de tipo char, el punto decimal para los de tipo double o el sufijo L para los de tipo long. Esta manera "especial" que tiene el lenguaje Java de tratar a los objetos de la clase String es debido a que se trata de un tipo tan utilizado que en su momento los diseñadores del lenguaje permitieron que se pudiera trabajar con él casi como si se tratara de un tipo primitivo del lenguaje, aunque no lo sea (se trata de objetos a los que se accede a través de una variable de tipo referencia, como sucede con cualquier objeto, tal y como hemos visto en la unidad anterior). En otros lenguajes directamente se ha optado porque sea parte los tipos disponibles en lenguaje y no de un elemento que se incorpora a partir de una biblioteca o librería.
Otra forma de crear una cadena es usando el operador new y un constructor, que sería la forma "estándar" de crear cualquier objeto. Para el ejemplo anterior sería algo como:
String cad = new String ("Ejemplo de cadena");
De hecho, esto es lo que en realidad Java "entiende" cuando hacemos la asignación sin hacer el new. Pero recuerda, esta "omisión" de la llamada al constructor con el operador new solo podemos hacerlo con los objetos String, donde sí existe un modo de poder escribir un valor constante (literal) de ese tipo en el código del programa. Para el resto de objetos que inicialices en tus programas (Rectangle, Color, LocalDate, StringBuilder, etc.) tendrás que utilizar llamadas al constructor o bien métodos estáticos de tipo "fábrica", tal y como hemos visto en la unidad anterior.
Cuando se originan las cadenas de esta manera, se realiza una copia en memoria de la cadena pasada por parámetro. La nueva instancia de la clase String hará referencia, por tanto, a la copia de la cadena, y no al original. Es decir, que en el ejemplo anterior, Java estará internamente creando dos objetos instancia de la clase String:
uno por el hecho de haber escrito entre comillas un texto (un literal de tipo String), que hace que automáticamente se reserve memoria para un objeto de tipo String;
otro por el hecho de llamar al constructor de String mediante el operador new, donde se recibirá como parámetro el texto entre comillas y, por tanto, el nuevo objeto String contendrá el mismo valor (el texto entre comillas) que el primer objeto.
Dado que los objetos de tipo String son inmutables (no pueden modificarse), Java trata a todos los literales de cadena con el mismo contenido como un solo objeto String que tiene muchas referencias. Es decir, que aunque tengamos varias variables de tipo String, si todas ellas hacen referencia al mismo valor (mismo texto), Java almacenará una única vez ese valor de cadena en una única zona de memoria y todas las variables apuntarán a esa misma zona de memoria. De ese modo el texto "repetido" se almacena una única vez, pues no puede cambiar.
Eso no significa que una variable de tipo String no pueda modificar el valor al que apunta, pues bastaría con hacer una nueva asignación a esa variable. Lo que significa es que el valor al que se apunta no puede ser modificado por tratarse de un objeto inmutable. Si, por ejemplo, tres variables String contienen (o, mejor dicho, "apuntan") a un mismo valor y a una de ellas se le asigna un nuevo valor de cadena diferente, esa variable dejará de apuntar al lugar común de las otras dos y apuntará a otra zona de memoria donde se encontrará el nuevo valor de texto. Pero las otras dos variables continuarán apuntando al mismo sitio (el valor antiguo).
Aquí tienes algunos artículos interesantes sobre este tema por si quieres profundizar en ello:
¿Qué operaciones puedes hacer con una cadena? Muchas más de las que te imaginas. Empezaremos con la operación más sencilla: la concatenación. La concatenación es la unión de dos cadenas, para formar una sola. En Java es muy fácil, pues solamente tienes que utilizar el operador de concatenación (signo de suma):
Incluso podríamos haberlo hecho sin utilizar una variable para apuntar al resultado:
System.out.println("¡Bien" + "venido!");
En los ejemplos anteriores se está creando una nueva cadena, resultado de unir dos cadenas: una cadena con el texto "¡Bien", y otra cadena con el texto "venido!".
En estos ejemplos se puede observar una vez más cómo la clase String es tratada de una manera "especial" en Java. El lenguaje ha permitido "sobrecargar" el operador +, que es un operador aritmético de tipos primitivos numéricos, para que pueda ser utilizado también por un tipo externo al lenguaje como es la clase String. Nuevamente, esto se ha hecho para simplificar y facilitar a los programadores la operación de concatenación, que es una de las más usadas en la mayoría de los programas.
Además de disponer de este operador de concatenación, la clase String proporciona también un método para poder concatenar dos cadenas: el método concat. A cualquier objeto de tipo String se le puede aplicar el método concat, que necesitará como parámetro un segundo objeto de tipo String. El método devolverá una tercera cadena, que también será instancia de la clase String y que consistirá en la cadena resultado de unir o concatenar esas dos cadenas.
En este ejemplo participan tres instancias de la clase String. Una instancia literal que contiene el texto "¡Bien" (un literal de tipo String al que no apuntará ninguna variable) otra instancia que contiene el texto "venido!" (otro literal) y otra que contiene el texto "¡Bienvenido!". La tercera cadena se crea nueva al realizar la operación de concatenación, sin que las otras dos hayan desaparecido o se hayan modificado (recuerda que los String son inmutables). Ahora bien, como esas dos cadenas no están siendo referenciadas por ninguna variable de tipo String, se borrarán de la memoria cuando el recolector de basura detecte que ya no se usan.
Fíjate, además, que se puede invocar directamente un método de la clase String, posponiendo el método al literal de cadena. Esto es una señal de que al escribir un literal de cadena, se produce una instancia del objeto inmutable String.
Pero no solo podemos concatenar una cadena a otra cadena. Gracias al método toString(), que incorpora toda clase Java, podemos concatenar cadenas con literales numéricos e instancias de otros objetos sin problemas.
Es una pieza clave de Java. Se trata de un mecanismo para ir eliminando de memoria aquellas instancias de clases u objetos que ya no están siendo utilizados.
Los objetos inmutables son aquellos que no se pueden modificar una vez creados. Es el caso de las clases String, LocalDate y LocalTime de Java, así como las clases envoltorio Integer, Float, Double, etc.
El método toString() es un método disponible en todas las clases de Java. Su objetivo es simple: permitir la conversión de una instancia de clase en cadena, de forma que se pueda obtener una representación textual del contenido del objeto. Pero esa conversión no siempre es tan sencilla. Hay clases fácilmente convertibles a texto, como por ejemplo los objetos de la clase Integer, que contienen simplemente un número entero, o la clase LocalDate, que contiene un mes, un día y un año. Sin embargo, hay otras en las que el objeto no tiene una conversión trivial a texto, y el método toString() tendrá que ser implementado según algún criterio para que genere algún tipo de representación apropiada del contenido del objeto.
Dado que cualquier valor literal de tipo numérico en Java (int, double, long, etc.) dispone de una proyección a un objeto instancia de una clase de tipo envoltorio (Integer, Double, Long, etc.), si intentamos concatenar una cadena con un literal numérico (o incluso una variable), sucederá lo siguiente:
el compilador automáticamente "envolverá" ese valor numérico en un objeto de su clase envoltorio correspondiente;
se le aplicará el método toString.
Por ejemplo:
// La variable numero, de tipo int, contiene el entero 1223.
// Al tratarse de un tipo primitivo y no referencia, sí "contiene" el número y no "apunta" a una zona de memoria que contiene el número
int numero = 123;
// La variable cadena contendrá el texto "Número: 1223"
// llevándose a cabo una conversión a texto del contenido la variable número
String cadena = "Número: " + numero; // automáticamente se "envuelve" numero en un objeto Integer y se le aplica el método toString
// Se mostrará por pantalla el texto "Número: 1223"
System.out.println(cadena);
De esta manera hemos evitado el uso explícito de tipos "envoltorio" y la llamada al método toString. En conclusión, cuando Java observa que vamos a concatenar una cadena con un número, se encarga automáticamente de llevar a cabo esas dos operaciones ("envolver" el valor en un objeto y aplicarle el método toString). Es decir, nos evita tener que escribir lo siguiente:
Respecto al orden de evaluación de varios operadores '+' en secuencia, debes tener en cuenta que la evaluación de operadores del mismo tipo (en este caso el operador '+') se lleva siempre a cabo de izquierda a derecha. Esto significa que si el primer operador de tipo '+' que aparece en una expresión es aritmético (entre dos valores numéricos), se llevará a cabo una suma de números, mientras que si alguno de ellos es de tipo cadena, entonces sí se realizará una concatenación de cadenas. Fíjate en el siguiente ejemplo:
System.out.println (2 + 5 + " es siete")
¿Qué piensas que se mostrará por pantalla? En este caso, sucederá lo siguiente:
el primer operador que se ejecuta, en orden de izquierda a derecha, es el primer '+', que es un operador aritmético (suma), dando lugar al entero 7;
a continuación, se intentará llevar a cabo el segundo '+', donde al ser uno de sus operandos de tipo String (la cadena " es siete"), obligará a Java a que lleve a cabo una conversión implícita o automática del entero 7 a la cadena "7" para que la operación sea posible. Eso hará que el segundo operador '+' sea "operador de concatenación de cadenas" (y no suma, como en el primer caso), generando la cadena "7 es siete";
por último, esa cadena será mostrada por pantalla.
Vamos a continuar revisando las operaciones básicas que se pueden realizar con cadenas. Algunas de ellas ya las vimos en la primera unidad, al presentar las cadenas de caracteres como un posible tipo más. En todos los ejemplos que presentaremos a continuación, la variable cad contiene la cadena "¡Bienvenido!", como se muestra en las imágenes.
<strong>int length(</strong>). Devuelve un número entero que contiene la longitud de una cadena, resultado de contar el número de caracteres que contiene. Recuerda que un espacio en blanco es también un carácter.
char charAt(int pos). Devuelve el carácter ubicado en la posición pasada por parámetro. El carácter obtenido de dicha posición será almacenado en un tipo de dato char. Las posiciones se empiezan a contar desde el 0 (y no desde el 1), y van desde 0 hasta longitud - 1. Por ejemplo, el código siguiente mostraría por pantalla el carácter "v":
char t = cad.charAt(5);
System.out.println(t);
String substring(int beginIndex, int endIndex). Este método permite extraer una subcadena de otra de mayor tamaño. Devolverá una nueva cadena compuesta por todos los caracteres existentes entre la posición beginIndex y la posición <span title="endIndex o índice final menos uno.">endIndex - 1</span>. Por ejemplo, si pusiéramos cad.substring(0,5) en nuestro programa, sobre la variable cad anterior, dicho método devolvería la subcadena "¡Bien" tal y como se muestra en la imagen.
String substring (int beginIndex). Cuando al método substring solo le proporcionamos un parámetro, extraerá una cadena que comenzará en el carácter con posición beginIndex e irá hasta el final de la cadena. En el siguiente ejemplo se mostraría por pantalla la cadena "ienvenido!":
Si se intenta acceder a un carácter que se encuentra fuera de los límites de un objeto String (es decir, un índice menor que 0 o un índice mayor o igual a la longitud del objeto String), se produce una excepción StringIndexOutOfBoundsException.
Vamos ahora a poner en práctica lo que hemos aprendido sobre los métodos <strong>length</strong> y <strong>charAt</strong> con algunos ejercicios sencillos.
Escribe un programa que solicite por teclado un nombre cuya longitud debe ser como mínimo de una letra y como máximo de diez. Si el nombre introducido no cumple esos criterios, debe mostrarse un mensaje de error y volver a solicitarse hasta que en efecto se cumpla esa regla.
Aquí tienes un ejemplo de lo que podría aparecer en pantalla al probarlo:
Introduzca el nombre (máximo diez caracteres y mínimo uno): Stanislavski
El nombre debe tener como como mínimo un carácter y como máximo diez.
Introduzca el nombre (máximo diez caracteres y mínimo uno):
El nombre debe tener como como mínimo un carácter y como máximo diez.
Introduzca el nombre (máximo diez caracteres y mínimo uno): Hermenegildo
El nombre debe tener como como mínimo un carácter y como máximo diez.
Introduzca el nombre (máximo diez caracteres y mínimo uno): Diosdado
El nombre introducido es: Diosdado
Añadir al programa anterior la condición de que la primera letra del nombre debe ser una mayúscula. Es decir, que debe ser un carácter que se encuentre entre la 'A' y la 'Z'. No tendremos en cuenta la 'Ñ' como letra para simplificar el ejercicio.
Aquí tienes un nuevo ejemplo de lo que podría aparecer en pantalla al probarlo:
Introduzca el nombre (máximo diez caracteres y mínimo uno): juan
El nombre debe tener como como mínimo un carácter y como máximo diez, y además debe comenzar por letra mayúscula.
Introduzca el nombre (máximo diez caracteres y mínimo uno):
El nombre debe tener como como mínimo un carácter y como máximo diez, y además debe comenzar por letra mayúscula.
Introduzca el nombre (máximo diez caracteres y mínimo uno): Hermenegildo
El nombre debe tener como como mínimo un carácter y como máximo diez, y además debe comenzar por letra mayúscula.
Introduzca el nombre (máximo diez caracteres y mínimo uno): Juan
El nombre introducido es: Juan
Con lo que llevamos hasta ahora, hemos logrado comprobar que el nombre introducido tiene un tamaño dentro del rango que deseamos y que además comienza por mayúscula. Pero, ¿y qué sucede con el resto de los caracteres introducidos? Podrían ser cualquier cosa: números, caracteres especiales, etc. Lo que ahora nos interesa es llegar un poco más allá y garantizar que el resto de caracteres sean letras minúsculas.
Te proponemos entonces ampliar el programa anterior para garantizar que el resto de caracteres del nombre introducido son letras minúsculas (caracteres entre 'a' y 'z').
Aquí tienes otro ejemplo de lo que podría aparecer en pantalla al probarlo:
Introduzca el nombre (máximo diez caracteres y mínimo uno): juan
El nombre debe tener como como mínimo un carácter y como máximo diez, debe comenzar por letra mayúscula y el resto deben ser minúsculas.
Introduzca el nombre (máximo diez caracteres y mínimo uno): JUAN
El nombre debe tener como como mínimo un carácter y como máximo diez, debe comenzar por letra mayúscula y el resto deben ser minúsculas.
Introduzca el nombre (máximo diez caracteres y mínimo uno): Juan12
El nombre debe tener como como mínimo un carácter y como máximo diez, debe comenzar por letra mayúscula y el resto deben ser minúsculas.
Introduzca el nombre (máximo diez caracteres y mínimo uno): Juan
El nombre introducido es: Juan
Vamos ahora a seguir practicando con los métodos <strong>length</strong> y <strong>charAt</strong>, así como con <strong>substring</strong>.
Escribe un programa que solicite por teclado un nombre compuesto por dos palabras separadas por un espacio. La cadena que se debe leer debe contener un único espacio, ninguno más, y no puede ser ni el primer ni el último carácter. El resto de caracteres deben ser letras minúsculas.
Aquí tienes un ejemplo de lo que podría aparecer en pantalla al probarlo:
Introduzca un nombre formado por dos palabras en minúscula separadas por un espacio:
juan
Introduzca un nombre formado por dos palabras en minúscula separadas por un espacio:
juan jose
Introduzca un nombre formado por dos palabras en minúscula separadas por un espacio:
juan jose
Introduzca un nombre formado por dos palabras en minúscula separadas por un espacio:
juan jose
Nombre introducido: juan jose
Amplía el programa anterior para que, además de realizar las comprobaciones, muestre por pantalla cuál es el primer nombre y cuál el segundo.
Un ejemplo de salida podría ser:
Introduzca un nombre formado por dos palabras en minúscula separadas por un espacio:
juan jose
Nombre introducido: juan jose
La primera palabra es "juan" y la última "jose"
2.1.2.- Operaciones básicas con cadenas de caracteres (II).
Continuamos estudiando algunas de las operaciones básicas que se pueden llevar a cabo con esta estructura de datos. ¿Cómo puedo comprobar si dos cadenas son iguales (tienen el mismo contenido)? ¿Se pueden realizar comparaciones sin tener en cuenta las mayúsculas y las minúsculas? ¿Existe la posibilidad de pasar todo el contenido de un texto a mayúsculas o a minúsculas? En la siguiente tabla puedes observar algunas operaciones adicionales a las que llevamos vistas hasta el momento. En todos los ejemplos expuestos, las variables cad1 y cad2 son cadenas ya existentes.
Algunos métodos básicos de la clase String.
Método
Descripción
cad1.compareTo(cad2)
Permite comparar dos cadenas entre sí lexicográficamente. Retornará 0 si son iguales, un número menor que cero si la cadena (cad1) es anterior en orden alfabético a la que se pasa por argumento (cad2), y un número mayor que cero si la cadena es posterior en orden alfabético.
cad1.equals(cad2)
Cuando se compara si dos cadenas son iguales, no se debe usar el operador de comparación "==", sino el método equals(). Retornará true si son iguales, y false si no lo son.
El método compareToIgnoreCase() funciona igual que el método compareTo(), pero ignora las mayúsculas y las minúsculas a la hora de hacer la comparación. Las mayúsculas van antes en orden alfabético que las minúsculas, por lo que hay que tenerlo en cuenta. El método equalsIgnoresCase() es igual que el método equals() pero sin tener en cuenta las minúsculas.
cad1.toLowerCase()
Genera una copia de la cadena con todos los caracteres a minúscula.
cad1.toUpperCase()
Genera una copia de la cadena con todos los caracteres a mayúsculas.
Para comparar dos cadenas en Java (objetos de tipo String) se deben utilizar métodos de comparación de objetos (equals) o bien métodos específicos que proporcione la clase String (equalsIgnoreCase, CompareTo, compareToIgnoreCase), pero nunca el operador ==.
Como ya vimos en la unidad dedicada a los objetos, si utilizamos el operador== para comparar variables de tipo referencia (referencias a objetos) estaremos cometiendo una incorrección. ¿Por qué? Recuerda que == compara las referencias para determinar si se refieren al mismo objeto (misma zona de memoria), no a si dos objetos tienen el mismo contenido.
Si se comparan dos objetos idénticos con ==, pero se trata distintos objetos en memoria, es decir, diferentes referencias, el resultado será false. Si vas a comparar objetos para determinar si tienen el mismo contenido, se debe utilizar el método equals o similares (compareTo).
Ahora bien, para el caso de objetos de tipo String es probable que en muchas ocasiones el operador == también funcione, pues, como ya hemos comentado anteriormente, Java almacena una única vez valores idénticos de literales de cadena.
¿Significa esto que podemos usar el operador == para comparar objetos de tipo String? La respuesta es no, pues aunque muchas veces pueda funcionar, nunca tendremos la garantía de que sea así y, además, no es la manera correcta de comparar objetos, aunque en algún caso específico pudiera funcionar.
Puedes profundizar sobre este tema echándole un vistazo a alguna de las siguientes referencias:
Un palíndromo es una palabra o expresión que se lee igual de izquierda a derecha que de derecha a izquierda, como por ejemplo "Ana", "salas", "reconocer" o "No deseo ese don".
Escribe un programa que lea desde teclado una cadena que solo pueda contener letras (mayúsculas o minúsculas, sin contar con la eñe). Mientras que la cadena introducida contenga algo que no sean letras, habrá que volver a solicitarla. Una vez la cadena cumpla las reglas, habrá que comprobar si se trata de un palíndromo o no.
A la hora de llevar a cabo la comprobación, no deberían importar mayúsculas y minúsculas.
Una primera forma de intentar resolver este problema podría ser ir comparando letra a letra, ¿cómo lo harías?
Otra posible forma de resolver el problema podría ser mediante la generación de un segundo texto, que sea la inversa del primero, y a continuación compararlos. Si los dos son iguales, se trata de un palíndromo. Si no lo son, entonces no es un palíndromo. ¿Cómo lo llevarías a cabo?
2.1.3.- Operaciones avanzadas con cadenas de caracteres (I).
Además de las operaciones vistas hasta ahora, ¿qué más posibilidades de trabajo ofrece Java sobre las cadenas? Vamos a continuar viendo algunos métodos algo más sofisticados como la búsqueda de una cadena dentro de otra, la eliminación de espacios en blanco o la sustitución. Nuevamente, en todos los ejemplos expuestos, las variables cad1, cad2 y cad3 son cadenas ya existentes, y la variable num es un número entero mayor o igual a cero.
Más métodos de la clase String.
Método
Descripción
cad1.trim()
Genera una copia de la cadena eliminando los espacios en blanco anteriores y posteriores de la cadena. Si solo quieres eliminar los espacios anteriores o solo los posteriores, dispones también de los métodos stripLeading y stripTrailing.
cad1.indexOf(cad2)<br /> cad1.indexOf(cad2,num)
Si la cadena o carácter pasado por argumento está contenida en la cadena invocante, retorna su posición, en caso contrario retornará -1. Opcionalmente se le puede indicar la posición a partir de la cual buscar, lo cual es útil para buscar varias apariciones de una cadena dentro de otra.
cad1.contains(cad2)
Retornará true si la cadena pasada por argumento está contenida dentro de la cadena. En caso contrario retornará false.
cad1.startsWith(cad2)
Retornará true si la cadena comienza por la cadena pasada como argumento. En caso contrario retornará false.
cad1.endsWith(cad2)
Retornará true si la cadena acaba por la cadena pasada como argumento. En caso contrario retornará false.
cad1.replace(cad2,cad3)
Generará una copia de la cadena cad1, en la que se sustituirán todas las apariciones de cad2 por cad3. El reemplazo se hará de izquierda a derecha, por ejemplo: reemplazar "zzz" por "xx" en la cadena "zzzzz" generará "xxzz" y no "zzxx".
Queremos saber si en un texto aparecen las letras 'm', 'a', 'l' de manera consecutiva, ya sea porque se trate de la palabra "mal" o bien porque forme parte de otra palabra como "maldad", "ramal", "formalizar", etc. ¿cómo lo harías? ¿Qué alternativas se te ocurren?
Imagina ahora que no solo queremos saber si la cadena "mal" aparece en un texto determinado, sino en qué posición del texto aparece. ¿Cómo podríamos resolverlo?
Supongamos ahora que lo que nos interesa saber es si la cadena "mal" aparece más de una vez en el texto, ¿qué se te ocurre para resolverlo?
Avanzando un paso más, imagina que lo que nos interesa es averiguar cuántas veces aparece la cadena "mal" en todo el texto, ¿cómo lo harías?
Siguiendo con el ejercicio anterior, imagina que nos interesa saber si una cadena comienza con el texto "mal", sin tener en cuenta los posibles espacios iniciales ni las mayúsculas/minúsculas.
Si quisiéramos ampliar el ejercicio anterior, comprobando si el texto comienza o termina por la cadena "mal", nuevamente sin tener en cuenta mayúsculas/minúsculas ni los espacios de los extremos (iniciales y/o finales), ¿cómo crees que podríamos resolverlo?
¿Y si lo que queremos es comprobar si el texto tanto empieza como termina con la cadena "mal"?
Imagina ahora que lo que nos interesa es sustituir todas las apariciones de la cadena "mal" por la cadena "---", ¿cómo lo harías?
¿Y si quisiéramos no tener en cuenta mayúsculas/minúsculas como hemos hecho en otras ocasiones?
En este caso, la situación se complica bastante, pues el método replace es "estricto" y la clase replace no dispone de una versión del estilo replaceIgnoreCase. Tendríamos nosotros que escribir el código que lo hiciera y tendría cierta complejidad.
Para casos así y muchos otros en los cuales las búsquedas y/o las sustituciones no son tan inmediatas, haremos uso más adelante de una potente herramienta conocida con el nombre de expresiones regulares. Por ahora, basta con que nos vayamos quedando con el nombre.
2.1.4.- Operaciones avanzadas con cadenas de caracteres (II).
Podríamos continuar revisando uno por uno todos los métodos disponibles para la clase String, pero acabaría convirtiéndose en una actividad tediosa y, probablemente, aburrida. En general, la idea es conocer los métodos más básicos e importantes de una clase y a continuación empezar a utilizarla para comprender bien su funcionamiento. Si más adelante intuimos que necesitamos algo más, siempre podemos consultar la documentación javadoc de la API de Java para la clase String e investigar si hay algún método que puede ayudarnos a resolver nuestro problema:
Por el momento nos vamos a limitar a enumerar y describir brevemente algunos métodos más cuya potencia descubrirás más adelente pero que aún no podrás utilizar porque hacen uso de algunos conceptos y herramientas que todavía no conocemos. Según vayamos avanzando, podremos hacer uso de ellos en nuestros programas:
Algunos métodos avanzados de la clase String.
Método
Descripción
boolean matches (String regex)
Indica si la expresión regular regex, que se pasa como parámetro, "encaja" (o hace "match") con la cadena. Dado que aún no sabemos lo que es una expresión regular, todavía no vamos a utilizar este método. Lo dejaremos para los siguientes apartados.
String[] split (String regex)
Divide una cadena en fragmentos teniendo en cuenta que cada fragmento estaría separado en la cadena original mediante una cadena de caracteres representada por la expresión regular regex, que se pasa como parámetro. El método devuelve un array de regex donde cada elemento del array será cada uno de los fragmentos encontrados. Nuevamente, como tampoco sabemos lo que es un array (ni una expresión regular), tendremos que dejarlo para más adelante.
Similar al método replace que ya has visto, pero permitiendo que la sustitución no sea simplemente para las subcadenas que sean exactamente iguales al parámetro pasado (regex), sino que se nos permite especificar toda una familia de cadenas que cumplan unas determinadas características (expresión regular).
2.2.- Conversiones entre cadenas y tipos numéricos (I).
Una operación muy habitual al trabajar con textos es la conversión de número a cadena y viceversa. Imagínate que un texto aparece un número que representa un precio, ¿cómo compruebas que esa edad es mayor que una determinada cantidad? Para poder realizar esa comprobación tienes que pasar la cadena a número. Por otro lado, a veces tenemos valores numéricos que nos interesa que formen parte de un texto. En ese otro caso lo que nos interesa es transformar ese número en una cadena.
Supongamos que disponemos de una cadena que contiene el texto "35". Se trata de un texto que contiene caracteres numéricos, pero no es un número. No podemos efectuar operaciones aritméticas con él. Por ejemplo, si intentamos sumarlo a un 35, ya hemos visto en apartados anteriores que Java automáticamente transformará el int a un String para llevar a cabo una concatenación:
String elem1 = 35;
int elem2 = 5;
System.out.printf ("La suma es: %s\n", elem1+elem2); // Mostrará por pantalla 355 y no 40
Por tanto, la conversión de un número a texto es automática si el número forma parte de una concatenación con otras cadenas, pues Java llevará a cabo la conversión de manera implícita y automática:
System.out.printf ("La suma es: %s\n", Integer.toString(elem1) + elem2); // Esto no es necesario pues se hace de manera automática
En general, siempre que deseemos transformar un valor numérico a un texto, podemos usar alguno de las clases "wrapper" o "envoltorio" (Byte, Short, Integer, Long, Float, Double) que nos proporciona Java para "envolver" cada uno de los tipos primitivos numéricos (byte, short, int, long, float, double). A partir de ahí, basta con utilizar el método estático toString(). Por ejemplo:
Como puedes observar, la variable valorInt es de tipo int y la variable textoInt es de tipo String. Y aunque al mostrarlas por pantalla podría parecer que contienen el mismo valor, al observar el código se ve claramente que no es así:
en un caso se trata de la representación mediante 32 bits de un número entero mediante ceros y unos (variable o literal de tipo int);
en el otro caso se trata de la representación, mediante dos caracteres del código Unicode, de un número de dos cifras (objeto o literal de tipo String).
En ambos casos se trata de una representación de un contenido mediante ceros y unos (como cualquier dato contenido en la memoria de un ordenador), pero esos ceros y unos serán diferentes dado que estarán representando algo diferente, aunque aparentemente ante nuestros ojos puedan parecer lo mismo al mostrarse por pantalla (pues en ambos casos tienen la misma representación "textual" por pantalla).
El mismo experimento podríamos realizarlo con cualquier otro valor numérico (literal o variable) tanto de tipo entero como real. Aquí tienes un par de ejemplos más con valores de tipo long y de tipo double:
long valorLong = 25_000_000_000L; // Veinticinco mil millones
String textoLong = Long.toString(valorLong);
System.out.printf ("Long: %d - Cadena: \"%s\"\n", valorLong, textoLong);
double valorDouble = 6.023e23; // Equivalente a 6.023 multiplicado por 10 elevado a 23
String textoDouble = Double.toString(valorDouble);
System.out.printf ("Double: %f - Cadena: \"%s\"\n", valorDouble, textoDouble);
Las clases envoltorio son clases que encapsulan tipos de datos básicos, tales como tales como int, short, double, etc. Sirven básicamente para poder usar los tipos básicos como si fueran objetos. Las clases envoltorio son generalmente inmutables.
Los números generalmente se almacenan en memoria como números binarios, es decir, secuencias de unos y ceros con los que se puede operar (sumar, restar, etc.). No debes confundir los tipos de datos que contienen números (int, short, long, float y double) con las secuencias de caracteres que representan un número. No es lo mismo 123 que "123", el primero es un número y el segundo es una cadena formada por tres caracteres: '1', '2' y '3'.
Las clases envoltorio de los tipos numéricos, además de proporcionar el método toString, que genera una cadena con los caracteres numéricos para representar el número de manera "textual", también ofrecen métodos de conversión de número a texto en los que la representación se realiza en otra base numérica y no necesariamente la decimal. Se trata de los métodos: toHexString, toBinaryString y toOctalString, que llevan a cabo una transformación a cadena utilizando las bases hexadecimal, binaria y octal respectivamente. Aquí tienes un ejemplo de uso:
int valorInt=255;
String textoIntDec= Integer.toString(valorInt);
String textoIntHex= Integer.toHexString(valorInt);
String textoIntBin= Integer.toBinaryString(valorInt);
String textoIntOct= Integer.toOctalString(valorInt);
System.out.printf("Entero: %d (la pantalla lo representa en base decimal, aunque internamente en el ordenador esté en binario)\n", valorInt);
System.out.printf("Cadena (dec): \"%s\" (representación textual del número en base decimal)\n", textoIntDec);
System.out.printf("Cadena (hex): \"%s\" (representación textual del número en base hexadecimal)\n", textoIntHex);
System.out.printf("Cadena (bin): \"%s\" (representación textual del número en base binaria)\n", textoIntBin);
System.out.printf("Cadena (oct): \"%s\" (representación textual del número en base octal)\n", textoIntOct);
Si lo pruebas, deberías obtener una salida similar a la siguiente:
Entero: 255 (la pantalla lo representa en base decimal, aunque internamente en el ordenador esté en binario)
Cadena (dec): "255" (representación textual del número en base decimal)
Cadena (hex): "ff" (representación textual del número en base hexadecimal)
Cadena (bin): "11111111" (representación textual del número en base binaria)
Cadena (oct): "377" (representación textual del número en base octal)
Además, el método toString está sobrecargado mediante una segunda versión con dos parámetros que permite especificar cualquier tipo de base, de manera que el ejemplo anterior también se podría haber resuelto de la siguiente manera:
String textoIntDec= Integer.toString(valorInt,10);// En este caso no es necesario indicar el 10
String textoIntHex= Integer.toString(valorInt,16);
String textoIntBin= Integer.toString(valorInt,2);
String textoIntOct= Integer.toString(valorInt,8);
Obviamente, además de poder indicar las bases típicas (10, 2, 16, 8), este método permite representar textualmente un valor numérico en cualquier base arbitraria que elijamos.
Respecto a los envoltorios para valores reales (Double y Float), tan solo se dispone de los métodos toString (con tan solo un parámetro) y toHexString, de manera que solo se podrán obtener representaciones textuales de números reales usando las bases decimal y hexadecimal.
Por último, otra forma de obtener una representación textual de un valor numérico es mediante el uso del "formateado de cadenas". La clase String de Java dispone de un método que permite "formatear" valores de manera similar a como vimos que se podía hacer con System.out.printf. Se trata del método format, que permite crear una cadena proyectando los argumentos en un formato específico de salida.
Ese formato de salida, también denominado "cadena de formato", es el primer argumento del método format. A partir de ahí, se podrán añadir tantos parámetros como valores queremos que se integren en la cadena que deseamos generar. La forma de integrarse esos valores se llevará a cabo mediante los especificadores de formato, que estarán formados, como mínimo, por el símbolo tanto por ciento (%) y una letra (d, f, x, etc.). Esos especificadores indican cómo se deben formatear o proyectar los argumentos que hay después de la cadena de formato en el método format.
Como ya se ha indicado, la forma de usar los especificadores de formato es idéntica a la que ya has visto para System.out.printf, de manera que tampoco es necesario insistir en ello.
Operación consistente en especificar cómo queremos que se transforme un dato, generalmente numérico, en cadena. Dicha conversión permite especificar de forma precisa cosas como el número de decimales que queremos que tenga el resultado.
Consiste en una subcadena dentro de una cadena que especifica cómo se debe formatear un dato concreto que se pasa como argumento al método format. El especificador de formato está formado por un carácter de escape "%" seguido de varios símbolos, destinados a describir cómo será el formato de salida.
Intenta reproducir el efecto obtenido mediante los métodos toString, toHexString y toOctalString. pero usando en esta ocasión el método format con el especificador de formato adecuado. Dado que no existe especificador de formato para la base binaria, no se te pide que generes un equivalente a toBinaryString.
Intenta obtener ahora representaciones textuales del número real 3,141592 con los siguientes formatos:
de la manera más fiel posible al número original;
quedándote solo con dos decimales;
con una anchura de seis caracteres y tres decimales;
con una anchura de seis caracteres, tres decimales y rellenando con ceros a la izquierda;
con cuatro decimales y añadiendo el texto "cm" al final.
Dado que el formato ya lo vamos a generar mediante String.format, utiliza para mostrar por pantalla los resultados System.out.println en lugar de System.out.printf.
Respecto a las clases "envoltorio" de los tipos numéricos, además de los métodos estáticos "herramienta" que hemos visto, hay algunos otros que en el futuro podrían resultarte de utilidad. Puedes consultar toda esa información en la documentación javadoc de cada una de esas clases: Integer, Double, Long, etc.
2.2.1.- Conversiones entre cadenas y tipos numéricos (II).
Ya hemos visto cómo obtener una representación textual de un valor numérico mediante diferentes técnicas (de manera automática cuando es posible, mediante métodos estáticos de las clases "envoltorio" o mediante el método estático format de la clase String). Ahora es el momento de plantearnos la operación inversa: obtener valores numéricos (byte, short, int, long, float o double) a partir de textos (objetos String) que supuestamente contengan números. Decimos "supuestamente" porque a priori no estaremos seguros de si lo que contiene un texto es realmente un número correctamente formateado o no. Por eso normalmente habrá que realizar un análisis previo del texto para comprobar que realmente contiene un número (entero corto o largo, número real con decimales, etc.).
Del mismo modo que todas las clases "envoltorio" de tipos numéricos (Byte, Short, Integer, Long, Float, Double) disponían de uno o varios métodos estáticos (por ejemplo toString) que permitían obtener la representación textual de valores numéricos, también disponen de uno o varios métodos que permiten justamente lo contrario: la obtención de un valor numérico a partir una cadena.
Veamos un primer ejemplo:
String textoInt = "250";
Integer objetoInteger = Integer.valueOf(textoInt); // Crea un objeto de tipo Integer a partir de un String
int valInt1 = objetoInteger.intValue(); // Obtiene un valor int a partir de un objeto Integer
int valInt2 = objetoInteger; // Obtiene un valor int a partir de un objeto Integer (automático)
System.out.printf ("textoInt1 = \"%s\" \nobjetoInteger = %d \nvalInt1 = %d \nvalInt2 = %d\n",
textoInt, objetoInteger, valInt1, valInt2);
En este ejemplo podemos observar cómo hemos conseguido, a partir de la cadena "250", el valor numérico entero 250:
en la primera línea creamos un objeto String con el texto "250";
en la segunda línea creamos un objeto de tipo Integer mediante el método estático "fábrica" valueOf de la clase Integer;
en la tercera línea obtenemos el valor int que "envuelve" el objeto Integer mediante el método intValue;
en la cuarta línea conseguimos lo mismo que en la tercera sin necesidad de invocar al método intValue, pues Java lleva a cabo la operación "unboxing" ("desenvolver" el valor int del "envoltorio" objeto Integer) automáticamente.
Resumiendo, podríamos haberlo hecho todo en una única línea de la siguiente manera:
int valInt = Integer.valueOf("250");
Hasta aquí todo ha funcionado perfectamente. El problema está cuando lo que hay en la cadena de caracteres no puede ser convertido a un valor numérico. Por ejemplo, imagina que el valor de textoInt es "25x". ¿Qué crees que sucederá al ejecutar el método Integer.valueOf(textoInt)? Te invitamos a que lo pruebes. Si lo haces, lo más probable es que se lance la excepción NumberFormatException. Y si tu código no está preparado para reaccionar ante esa posible excepción, lo hará la máquina virtual de Java, abortando la ejecución de tu programa de manera brusca e inesperada (algo que debemos siempre evitar).
Cuando hacíamos una conversión de número a texto no había ningún problema, pues cualquier número puede ser transformado a un texto (un texto "admite" cualquier cosa), pero en el sentido contrario debemos procurar ser más precavidos, pues no todo texto es susceptible de ser interpretado como un valor numérico. Por esta razón, cada vez que intentemos obtener un número a partir de un texto, se tratará de una operación "sensible" y susceptible de fallos y, por tanto, deberá estar encerrada dentro de un bloque try, preparándonos para capturar una posible excepción de tipo NumberFormatException.
En consecuencia, los ejemplos anteriores deberían estar siempre "protegidos" dentro de un try con un catch ante una posible NumberFormatException:
String textoInt1 = "25e";
try {
Integer objetoInteger = Integer.valueOf(textoInt1); // Crea un objeto de tipo Integer a partir de un String
System.out.printf ("Número entero obtenido correctamente: %d\n", objetoInteger); // No es necesario hacer objetoInteger.intValue()
} catch (NumberFormatException ex) {
System.out.printf ("El texto no cumple el formato de un número entero.\n");
}
Solo podríamos "permitirnos" no encerrar este tipo de conversiones en un bloque try-catch si estamos plenamente seguros de que el texto es perfectamente "transformable" en un número, algo que habitualmente no podremos garantizar. Por eso lo habitual será "asegurar" este tipo de conversiones en bloques try-catch. En el fondo es algo similar a lo que hacemos cuando "aseguramos" las lecturas de teclado de valores numéricos en los objetos de la clase Scanner cuando hacemos nextInt, nextDouble, nextLong, etc. pues cuando introducimos un supuesto valor numérico por teclado no existe la garantía de que el usuario lo introduzca correctamente. De hecho, es exactamente la misma situación: se intenta transformar un texto introducido por teclado (lo que teclea el usuario, que deben ser caracteres numéricos) en un valor numérico.
Hasta el momento hemos trabajado con la posibilidad de realizar conversiones de texto a número para el caso de enteros de tipo int, pero puede hacerse también para el resto de enteros (byte, short, long) y para los reales (float y double). Por ejemplo, para el caso de un número real, podríamos hacer algo así:
String textoDouble = "250.12";
try {
Double objetoDouble = Double.valueOf(textoDouble);
System.out.printf ("El texto cumple el formato de un número real. El valor es: %6.2f\n", objetoDouble);
} catch (NumberFormatException ex) {
System.out.printf ("El texto no cumple el formato de un número real.\n");
}
En este caso sabemos que va a funcionar seguro, pues el texto "250.12" cumple las reglas de formato de un número real. Sin embargo, si el valor de esa variable no fuera conocida a priori, debemos disponer de un mecanismo de seguridad que garantice que nuestro programa no va a abortar abruptamente. Por esto necesitamos el bloque try-catch. Prueba a leer de teclado distintos posibles valores en lugar de escribir un literal en el código y podrás observar el comportamiento de este ejemplo:
Scanner teclado = new Scanner (System.in);
System.out.print ("Introduzca un número real: ");
String textoDouble = teclado.nextLine();
try {
Double objetoDouble = Double.valueOf(textoDouble);
System.out.printf ("El texto cumple el formato de un número real. El valor es: %6.2f\n", objetoDouble);
} catch (NumberFormatException ex) {
System.out.printf ("El texto no cumple el formato de un número real.\n");
}
Del mismo modo que hemos trabajado con la posibilidad de transformar valores numéricos a su representación textual en diferentes bases numéricas (decimal, binaria, hexadecimal, etc.), también podemos hacer algo similar en el proceso de conversión de textos a números. Para ello podemos usar la versión sobrecargada del método valueOf, que admite un segundo parámetro en el que se indica la base de conversión. Por ejemplo:
Scanner teclado = new Scanner (System.in);
System.out.print ("Introduzca un número entero en base binaria: ");
String textoIntBin = teclado.nextLine();
try {
Integer objetoInt = Integer.valueOf(textoIntBin, 2); // Interpretamos el texto como un número entero en base binaria
System.out.printf ("El texto cumple el formato de un número entero binario. El valor es: %d\n", objetoInt);
} catch (NumberFormatException ex) {
System.out.printf ("El texto no cumple el formato de un número entero binario.\n");
}
Si probamos el programa e introducimos un número binario correcto:
Introduzca un número entero en base binaria: 1111
El texto cumple el formato de un número entero binario. El valor es: 15
Sin embargo, si introducimos algo que no coincide con el patrón de un número en base binaria, obtendremos un error:
Introduzca un número entero en base binaria: 15
El texto no cumple el formato de un número entero binario.
¿Por qué? Porque aunque el texto "15" es convertible en un número entero en formato decimal, no es convertible en un número entero en formato binario (donde solamente se admiten los caracteres '0' y '1') y en consecuencia la invocación Integer.valueOf(textoIntBin,2) hará saltar la excepción NumberFormatException.
Por último, indicarte que además de los métodos valueOf, la clase Integer también dispone de métodos del tipo parseInt, que llevan a cabo un análisis (o "parseo") de un texto para intentar convertirlo en int. Si el análisis no tiene éxito, también se lanzará una excepción de tipo NumberFormatException. Su funcionamiento es muy similar al de valueOf.
Como has podido observar, al igual que sucede en la conversión de números a texto, dispones de diversas alternativas para la conversión de texto a valor numérico (valueOf, parseInt, parseDouble, etc.). Te invitamos una vez más a revisar la documentación javadoc de las distintas clases "envoltorio" para que puedas explorar todas sus posibilidades: Integer, Double, Long, etc.
Nuestra recomendación es que escojas una forma adecuada de hacer las cosas que para ti resulte sencilla de entender. A partir de ahí, utiliza siempre la misma para que te habitúes a ello.
Escribe un programa que solicite por teclado un texto que represente un valor numérico natural (no admitimos negativos) en base hexadecimal. Si la cadena no cumple el formato, debería volver a pedirse hasta que se introduzca algo correcto.
Una vez se disponga de un valor entero hexadecimal correcto deberá mostrarse por pantalla:
ese mismo valor expresado en las bases binaria, octal y decimal;
el número de bits a uno que tiene ese valor;
el valor del bit 1 más a la derecha;
el valor del bit 1 más a la izquierda;
Un ejemplo de ejecución del programa podría ser algo así:
CONVERSIONES ENTRE TEXTOS Y NÚMEROS
-----------------------------------
Introduzca número entero en base hexadecimal: 12.1
Introduzca número entero en base hexadecimal: hola
Introduzca número entero en base hexadecimal: ffx
Introduzca número entero en base hexadecimal: f70
RESULTADO
---------
Número original: F70
En binario: 111101110000
En decimal: 3952
En octal: 7560
Número de bits a 1: 7
Valor del bit a 1 más alto: 2048
Valor del bit a 1 más bajo: 16
La clase StringTokenizer es una herramienta que nos proporciona la API de Java para dividir una cadena en subcadenas (también conocidas como "tokens" o "elementos") en base a un separador o delimitador (otra cadena). Este proceso es conocido también como "tokenización" o "división". Esta clase se encuentra en el paquete java.util.
Los métodos más usuales de esta clase son:
constructores, que crean un objeto StringTokenizer con la cadena que se pasa como parámetro. Si se añade un segundo parámetro, será la cadena delimitadora. Si no se indica un segundo parámetro, se considera que la cadena delimitadora es el espacio en blanco (así como el avance de línea y el tabulador). A partir de este objeto podremos llevar a cabo el proceso de "tokenización" u obtención de cada uno de los elementos o tokens de la cadena en función del separador que se haya especificado;
método nextToken, que devuelve el siguiente elemento o token de la cadena. La primera vez que se llama a este método se devolverá el primer token, la segunda vez el segundo token, y así sucesivamente. Si no quedaran tokens por devolver, se lanzaría la excepción NoSuchElementException.
método hasMoreTokens, que indica si quedan o no más tokens por devolver. Este método te puede servir para evitar que se te lance la excepción anterior;
método countTokens, que devuelve la cantidad de tokens que tiene un objeto de tipo StringTokenizer.
La mejor manera de comprender su funcionamiento es mediante un ejemplo. Imagina una cadena formada por el nombre y los dos apellidos de una persona separados por espacios en blanco. La clase StringTokenizer nos ayuda a separar esa cadena en tres subcadenas usando como delimitador un espacio en blanco. Una forma sencilla de extraer el nombre y los apellidos podría ser la siguiente (no olvides el import java.lang.StringTokenizer al comienzo de tu programa):
import java.lang.StringTokenizer;
. . .
. . .
String nombreCompleto ="Luis Torres Ugarte";
StringTokenizer tokens =new StringTokenizer(nombreCompleto); // Si no hay segundo parámetro se asume el espacio como separador
String nombre = tokens.nextToken();
String apellido1 = tokens.nextToken();
String apellido2 = tokens.nextToken();
System.out.printf ("Nombre completo: %s\n", nombreCompleto);
System.out.printf ("Nombre: %s \nApellido1: %s \nApellido2: %s\n",
nombre, apellido1, apellido2);
La salida de ese programa debería ser algo similar a lo siguiente:
Nombre completo: Luis Torres Ugarte
Nombre: Luis
Apellido1: Torres
Apellido2: Ugarte
Ahora bien, este ejemplo es extremadamente simple, pues conocíamos a priori cuántos tokens o elementos íbamos a encontrar en la cadena. Eso no será lo habitual, sino que el número de elementos será desconocido y habrá que ir recorriéndolos hasta que se acaben. Para ello podremos utilizar el método hasMoreTokens como condición de continuidad dentro de un bucle donde iremos extrayendo uno a uno cada elemento mediante el método nextToken.
Por ejemplo, imagina un programa en el que se piden por teclado una serie de palabras separadas por comas y posteriormente deseamos mostrar por pantalla cada una de esas palabras. Podríamos hacer algo así:
System.out.println ("Introduzca una lista de palabras separadas por comas: ");
String linea = teclado.nextLine();
StringTokenizer lista = new StringTokenizer (linea, ","); // El separador de elementos será la cadena ","
System.out.println ("\nLas palabras son:");
do {
String palabra = lista.nextToken().trim(); // Obtenemos la siguiente palabra y quitamos los posibles espacios de los extremos
System.out.printf ("%s\n", palabra);
} while (lista.hasMoreTokens());
Un ejemplo de funcionamiento podría ser el siguiente:
Introduzca una lista de palabras separadas por coma:
Argentina, Polonia, Portugal, Chile
Las palabras son:
Argentina
Polonia
Portugal
Chile
También podrías haberlo resuelto mediante un bucle for, teniendo en cuenta que puedes obtener la cantidad de elementos mediante el método countTokens:
System.out.println ("Introduzca una lista de palabras separadas por comas: ");
String linea = teclado.nextLine();
StringTokenizer lista = new StringTokenizer (linea, ","); // El separador de elementos será la cadena ","
System.out.println ("\nLas palabras son:");
for (int i=0 ; i<lista.countTokens() ; i++) {
String palabra = lista.nextToken().trim(); // Obtenemos la siguiente palabra y quitamos espacios de los extremos
System.out.printf ("%s\n", palabra);
}
Si quieres revisar bien el funcionamiento de cada uno de los métodos de la clase StringTokenizer, siempre puedes echar un vistazo a la documentación de la API de Java:
Sería interesante que volvieras a esta sección una vez que veamos el método split de la clase String, que permite hacer algo muy similar a lo que permite hacer esta clase (proceso de "tokenización" o "separación"), salvo que de una manera más potente, dado que:
permite especificar un separador más genérico y flexible mediante una expresión regular;
devuelve de manera inmediata todos los tokens en un array de String sin necesidad de tener que implementar un bucle.
El problema es que aún no sabemos lo que es una expresión regular ni un array, así que tendremos que esperar un poco para poder usar ese método. Mientras tanto, puedes utilizar el mecanismo proporcionado por la clase StringTokenizer que acabamos de estudiar. Cuando hayas visto también el método split, podrás decidir, para cada problema concreto, qué herramienta te interesa más.
2.4.- Otros tipos de cadenas de caracteres en Java.
Probablemente, el principal problema de los objetos String en Java es su alto consumo de memoria. Por tanto, cuando realizamos un programa que realiza muchísimas operaciones con cadenas, es necesario optimizar el uso de memoria.
¿Cuál crees que es la razón de ese alto consumo de memoria? Recuerda que en Java, los objetos de la clase String son objetos inmutables, lo cual significa, entre otras cosas, que cada vez que creamos un String, o un literal de String, se crea un nuevo objeto que nuevamente no es modificable.
Para evitar estos problemas de rendimiento, o bien para simplificar en algunos casos, Java proporciona la clase StringBuilder, cuyos objetos sí son mutables, permitiendo una mayor optimización de la memoria. También existe la clase StringBuffer, pero consume mayores recursos al estar pensada para programas multi-hilo, por lo que en nuestro caso nos centraremos en la primera.
Pero, ¿en qué se diferencia StringBuilder de la clase String? Pues básicamente en que la clase StringBuilder permite modificar la cadena que contiene, mientras que la clase String no. Como ya se ha indicado en anteriores ocasiones, siempre que se realice una operación sobre un objeto String, se genera una nueva instancia de la clase String.
Veamos un pequeño ejemplo de uso de esta clase. En el ejemplo que vas a ver, se parte de una cadena con errores que modificaremos para ir haciéndola legible. Lo primero que tenemos que hacer es crear la instancia de esta clase. Se puede inicializar de muchas formas, por ejemplo, partiendo de un literal de cadena:
Y ahora, usando los métodos append (insertar al final), insert (insertar una cadena o carácter en una posición específica), delete (eliminar los caracteres que hay entre dos posiciones) y replace (reemplazar los caracteres que hay entre dos posiciones por otros diferentes), rectificaremos la cadena anterior y la haremos correcta:
strb.delete(6,8); Eliminamos las 'uu' que sobran en la cadena. La primera 'u' que sobra está en la posición 6 (no olvides contar el espacio), y la última 'u' a eliminar está en la posición 7. Para eliminar dichos caracteres de forma correcta hay que pasar como primer argumento la posición 6 (posición inicial) y como segundo argumento la posición 8 (posición contigua al último carácter a eliminar), dado que la posición final no indica el último carácter a eliminar, sino el carácter justo posterior al último que hay que eliminar (igual que ocurría con el método substring()).
strb.append ("!"); Añadimos al final de la cadena el símbolo de cierre de exclamación.
strb.insert (0,"¡"); Insertamos en la posición 0, el símbolo de apertura de exclamación.
strb.replace (3,5,"la"); Reemplazamos los caracteres 'al' situados entre la posición inicial 3 y la posición final 4, por la cadena 'la'. En este método ocurre igual que en los métodos delete() y substring(), en vez de indicar como posición final la posición 4, se debe indicar justo la posición contigua, es decir 5.
StringBuilder proporciona muchos métodos similares a los de la clase String (charAt, indexOf, lenght, substring, replace, etc.), aunque no todos, pues se trata de clases diferentes.
Es lo contrario a un objeto inmutable. Son objetos que, una vez creados, se puede modificar su contenido sin problema.
Se trata de aplicaciones que ejecutan varias líneas de ejecución simultáneamente y que necesitan acceder a datos compartidos. Cuando varias líneas de ejecución, cada una a su ritmo, necesitan acceder a datos compartidos, hay que tener mucho cuidado para que los datos que manejan no sean incoherentes.
Java puede realizar ciertas optimizaciones en las que se involucran objetos String (como hacer referencia a un objeto String desde múltiples variables), ya que sabe que estos objetos no cambiarán. Si los datos no van a cambiar, debemos usar objetos String (y no StringBuilder).
En los programas que llevan a cabo modificaciones de cadenas con frecuencia (concatenaciones, extracciones, etc.) a menudo es más eficiente implementar las modificaciones con la clase StringBuilder.
Por otro lado, el proceso de incrementar en forma dinámica la capacidad de un objeto StringBuilder puede requerir una cantidad importante de tiempo. La ejecución de un gran número de estas operaciones puede degradar el rendimiento de una aplicación. Si un objeto StringBuilder va a aumentar su tamaño en forma considerable, tal vez varias veces, puede que sea buena idea establecer una alta capacidad desde un principio (cuando se instancie el objeto en el constructor).
Ana continúa avanzando con la manipulación de cadenas, pero se ha encontrado con algunas dificultades a la hora de buscar determinados patrones dentro de un texto. Parece sencillo cuando el patrón es algo fijo, pero si lo que busca dentro del texto puede ser algo variable (por ejemplo números o ciertas combinaciones de letras y números), la cosa ya no es tan evidente. Por ejemplo, si tuviera que comprobar si una determinada entrada es o no un DNI correcto (cumple la estructura de números y letras apropiada) no parece tan inmediato de resolver con los métodos que de la clase String que ha visto hasta ahora.
Uno de sus compañeros le ha hablado de la posibilidad de utilizar expresiones regulares para representar esos patrones y de esa manera poder realizar las búsquedas o las comprobaciones de una manera muchísimo más sencilla y eficaz.
¿Tienen algo en común todos los números de DNI? ¿Podrías hacer un programa que verificara si un DNI (o un NIE) es correcto? Seguro que sí. Si te fijas, los números de DNI y los de NIE tienen una estructura fija. Por ejemplo, X1234567Z (en el caso del NIE) y 1234567Z (en el caso del DNI). Ambos siguen un patrón que podría describirse como: una letra inicial opcional (solo presente en los NIE), seguida de una secuencia numérica y finalizando con otra letra. Parece fácil, ¿no?
En otras ocasiones podría interesarnos detectar si una determinada cadena contiene únicamente ceros y unos (sigue el patrón de un número binario) o bien si se trata de una dirección de correo electrónico correctamente construida, un número de teléfono, una matrícula de un vehículo, etc.
Pues precisamente esa es la función de las expresiones regulares: poder expresar reglas que definen patrones para posteriormente comprobar si una cadena concreta sigue o no ese patrón preestablecido.
Las expresiones regulares son un mecanismo para describir esos patrones, y se construyen de una forma relativamente sencilla. Se trata de una herramienta que no solamente se usa en programación, sino también en administración de sistemas, editores de texto, sistemas documentales y de búsqueda de información, bases de datos, etc.
Dependiendo del entorno o el lenguaje de programación, existen muchas bibliotecas o librerías diferentes para trabajar con expresiones regulares. Casi todas siguen una sintaxis más o menos similar, con ligeras variaciones. Dicha sintaxis nos permite indicar el patrón con sencillez mediante una cadena de texto (un objeto de tipo String en el caso de Java).
Modelo con el que encaja la información que estamos analizando o que simplemente se ha utilizado para construir dicha información. Un patrón, en el caso de la informática, está constituido por una serie de reglas fijas que deben cumplirse o ser seguidas para formar la información. Las direcciones de correo electrónico, por ejemplo, todas tienen la misma forma, y eso es porque todas siguen el mismo patrón para ser construidas.
Antes de empezar a trabajar con ningún lenguaje de programación concreto, vamos a presentar las expresiones regulares de manera general, para intentar comprender su sintaxis y funcionamiento, independientemente del contexto en el que las utilicemos en el futuro.
Se trata de un conjunto de reglas con las cuales podemos expresar un patrón. Normalmente, ese patrón estará compuesto por símbolos (letras, números, espacios, caracteres especiales). La mayoría de esos símbolos tendrá un significado "literal", aunque también habrá determinados símbolos que tengan un significado especial (se les suele llamar "metacaracteres") y para poder usar su valor "literal" tendrán que ser "escapados".
Veamos cuáles son las reglas generales para construir una expresión regular:
Podemos indicar que una cadena debe contener una secuencia de símbolos fija, simplemente colocando dichos símbolos en el patrón, excepto para algunos símbolos especiales que necesitarán un carácter de escape, como veremos más adelante. Por ejemplo, el patrón "aaa"admitirá únicamente cadenas que estén formadas tres aes. Cualquier otro texto no "encajará" con ese patrón.
Dado que la regla anterior es muy estricta y sería equivalente a una simple comparación de cadenas, las expresiones regulares flexibilizan esto al permitir indicar un conjunto de posibles símbolos (y no solo un símbolo) mediante el uso de los corchetes. Por ejemplo, el patrón [xyz] indica que en la posición donde aparezcan esos corchetes podrá aparecer uno de los símbolos que están encerrados entre los corchetes (el conjunto). Basándonos en ese ejemplo, la expresión regular "aaa[xyz]" admitirá como válidas las cadenas "aaax", la cadena "aaay" y la cadena "aaaz". Son las únicas tres cadenas que "encajan" (o hacen "matching") con el patrón representado por esa expresión regular. Los corchetes representan una posición de la cadena que puede tomar uno de varios valores posibles.
Veamos otro ejemplo: ¿cómo construirías un patrón que represente cualquier número impar negativo de una sola cifra?
Lo primero que tendríamos que hacer sería indicar el signo menos (carácter guion '-') y a continuación cualquier dígito que sea impar (el uno, el tres, el cinco, el siete o el nueve). Para ello podríamos especificar un conjunto con esos caracteres: [13579]. Por tanto, la expresión regular podría quedar como:
-[13579]
Esa expresión regular representa a un patrón que encajará o se "acoplará" con las cadenas "-1", "-3", "-5", "-7" y "-9". Es decir, que escribiendo la expresión "-[13579]", estamos representando a todas esas posibles cadenas.
A partir de ahora tendrás que ir construyendo tus propias expresiones regulares que representen ciertos conjuntos de cadenas de caracteres.
Para comprobar si tu expresión funciona correctamente (las cadenas que te interesan hacen "matching" y las que no te interesan no lo hacen), existen una gran cantidad de herramientas online que puedes utilizar. Te dejamos aquí la referencia a algunas de ellas para cuando te hagan falta:
Seguro que te resultan de gran utilidad a la hora de empezar a elaborar y comprobar tus expresiones regulares, sobre todo al principio. ¡Te vendrán muy bien para los ejercicios!
Imagina que deseamos elaborar un patrón que represente todas las cadenas posibles de tres letras formadas exclusivamente por vocales, donde la primera letra tiene que estar obligatoriamente en mayúscula y el resto en minúscula.
¿Qué expresión regular construirías para implementar ese patrón?
Como acabamos de ver, una de las principales ventajas que nos ofrece la posibilidad de expresar un patrón mediante una expresión regular es la posibilidad de indicar un posible conjunto de caracteres para una o varias posiciones en una cadena.
Ahora bien, una de las dificultades que puede que te hayas encontrado es que si quieres escribir un conjunto que represente todas las letras del alfabeto, las tendrías que escribir una a una. Por ejemplo:
para referirte a las letras mayúsculas, tendrías que escribir "[abcdefghijklmnopqrstuvwxyz]";
para referirte a las letras mayúsculas, tendrías que escribir "[ABCDEFGHIKLMNOPQRSTUVWXYZ]";
para referirte a las letras mayúsculas y también a las minúsculas, tendrías que escribir el conjunto siguiente:
y así sucesivamente, si quieres ir incluyendo más caracteres en el conjunto.
Para intentar simplificar estas expresiones tan largas dentro de los conjuntos, disponemos de los rangos de caracteres.
Mediante el empleo del guion dentro de los corchetes podemos indicar que el patrón admite cualquier carácter entre el símbolo inicial y el final. Es posible indicar varios posibles rangos dentro de un conjunto e incluso añadir algunos caracteres más. Aquí tienes varios ejemplos de especificación de rangos dentro de conjuntos:
"[a-z]" indica que el conjunto representa a todas las letras minúsculas de alfabeto latino (sin incluir la eñe, que no está entre los caracteres estándar). Sería equivalente a escribir "[abcdefghijklmnopqrstuvwxyz]";
"[A-Z]" hace referencia a todas las letras mayúsculas. Equivalente a "[ABCDEFGHIKLMNOPQRSTUVWXYZ]";
"[a-zA-Z]" se refiere al conjunto de todas las letras minúsculas y mayúsculas. En este caso hemos unido dos rangos y sería equivalente a "[abcdefghijklmnopqrstuvwxyzABCDEFGHIKLMNOPQRSTUVWXYZ]";
"[A-Fu-z]" hace referencia al conjunto de todas las letras mayúsculas entre 'A' y 'F' y las minúsculas entre 'u y 'z'. Equivalente a "[ABCDEFuvwxyz]";
"[0-9]" indica el conjunto de los diez posibles caracteres numéricos del sistema decimal. De este modo podemos indicar que se permite la presencia de un dígito numérico entre 0 y 9, cualquiera de ellos, pero solo uno. Sería equivalente a haber escrito "[0123456789]";
"[a-zA-Z0-9_]" hace referencia a cualquier letra mayúscula o minúscula, o dígito, o el carácter '_' (underscoreo "guion bajo" o "subrayado"). En este caso se han incluido tres rangos y un carácter. Sería equivalente a haber escrito "[abcdefghijklmnopqrstuvwxyzABCDEFGHIKLMNOPQRSTUVWXYZ0123456789_]".
Dadas las siguientes expresiones regulares con rangos:
Expresiones regulares
Número
Expresión Regular
Número
Expresión Regular
1
"[0-9A-F]"
5
"[0-1]<code>[01]"
2
"[0-9A-Fa-f]"
6
"[a-cx-zv][1]"
3
"[0-9]X[0-9]"
7
"2[u-w]1"
4
"[0-9][0-9]"
8
"[NRJQKA][1-3]"
relaciónalas con los tipos de textos o cadenas con las que harían "matching" o "acoplamiento":
Fíjate que los conjuntos [0-1] y [01] representan los mismos elementos (los caracteres '<code>0' y '<code>1').
Observa que incluir un único elemento entre corchetes no tiene mucho sentido, pues es lo mismo que no poner corchetes. Por tanto, en lugar de tener "[a-cx-z][1]", se podría debería haber puesto simplemente "[a-cx-z]1". Hay que procurar simplificar lo máximo posible las expresiones regulares para facilitar la legibilidad del código.
Otra opción que nos permite la definición de conjuntos mediante corchetes, es la del conjunto complementario u opuesto. Es decir, podemos especificar un conjunto diciendo precisamente que queremos admitir cualquier símbolo, excepto los que indiquemos explícitamente. Eso se consigue gracias a la utilización del circunflejo '^' dentro de los corchetes. El símbolo '^', cuando se coloca justo detrás del corchete de apertura, significa "negación". La expresión regular admitirá cualquier símbolo diferente a los puestos entre corchetes.
Por ejemplo, en el caso "[^abc]" estaríamos especificando, cualquier símbolo diferente de 'a', 'b' o 'c'. Es decir, justo lo contrario a lo que se indicaría sin el circunflejo. En el caso de la expresión "[^0-9]" haríamos referencia a cualquier símbolo, excepto los dígitos numéricos (letras mayúsculas y minúsculas, espacios, paréntesis, símbolos aritméticos, etc.). Especificar ese conjunto de manera explícita implicaría escribir cientos de caracteres. Sin embargo, al poder utilizar la negación se puede expresar de una manera muy sencilla.
Imagina que deseamos elaborar un patrón que represente todas las cadenas posibles de cinco letras, donde la primera letra tiene que ser obligatoriamente en mayúscula y el resto obligatoriamente en minúscula.
¿Qué expresión regular construirías para implementar ese patrón?
Con las reglas que hemos visto hasta el momento, podemos indicar el conjunto de símbolos que admite el patrón y el orden que deben tener. Si una cadena no contiene los símbolos especificados en el patrón, en el mismo orden, entonces la cadena no encajará con el patrón. El problema que habrás visto al resolver algunos ejercicios, es que en más de una ocasión habrás tenido que repetir una y otra vez el mismo carácter o conjunto de caracteres para poder construir el patrón que necesitas. Eso puede llegar a ser tedioso y dificultar muchísimo la legibilidad de la expresión regular, además de resultar muy poco flexible si se pretende definir un patrón que represente un conjunto de cadenas que no tuvieran todas el mismo tamaño.
Para resolver esto vamos a ver cómo indicar repeticiones. Se llevan a cabo mediante los operadores de cuantificación (o cuantificadores). Son cuatro operadores: interrogación (símbolo "?"), estrella (símbolo "*"), más (símbolo "+") y llaves (símbolos "{" y "}"):
Interrogante (?). Por ejemplo, "a?". Usaremos el interrogante para indicar que un símbolo puede aparecer una vez o ninguna. De esta forma, la letra "a" podrá aparecer una vez o simplemente no aparecer.
Asterisco (*). Por ejemplo, "a*". Emplearemos el asterisco para indicar que un símbolo puede aparecer una vez o muchas veces, pero también ninguna (cero o más veces). Cadenas válidas que cumplen esta expresión regular podrían ser "aa", "aaa" o "aaaaaaaa".
Más (+). Por ejemplo, "a+". Con el símbolo de suma podemos indicar que otro símbolo debe aparecer al menos una vez (una o más veces), pudiendo repetirse cuantas veces quiera.
Llaves ({n,m}). Por ejemplo, "a{1,4}". Mediante las llaves, podemos indicar el número mínimo y máximo de veces que un símbolo podrá repetirse. El primer número del ejemplo es el número 1, y quiere decir que la letra "a" debe aparecer al menos una vez. El segundo número, 4, indica que como máximo puede repetirse cuatro veces.
Las llaves permiten además utilizar sólo uno de sus parámetros:
Indicación de un número mínimo de repeticiones. Por ejemplo, "a{2,}". Es posible indicar solo el número mínimo de veces que un carácter debe aparecer (sin determinar el máximo), haciendo uso de las llaves, indicando el primer número y poniendo la coma (no la olvides).
Indicación un número exacto de repeticiones. Por ejemplo, "a{5}". A diferencia de la forma anterior, si únicamente escribimos un número entre llaves, sin poner la coma detrás, significará que el símbolo debe aparecer un número exacto de veces. En este caso, la "a" debe aparecer exactamente cinco veces.
Mediante el uso de las llaves podríamos sustituir al resto de los cuantificadores (asterisco, interrogación y más), pero suelen utilizarse mucho porque simplifican bastante la expresión:
"a?" es equivalente a "a{0,1}";
"a*" es equivalente a "a{0,}";
"a+" es equivalente a "a{1,}";
Dado que un corchete representa a un símbolo entre varios posibles, los cuantificadores pueden utilizarse para indicar posibles repeticiones no solamente de un carácter concreto literal, sino de un conjunto de posibles valores expresado mediante el uso de los corchetes. Por ejemplo:
"[a-z]{1,4}[0-9]" es un patrón que hace referencia a una secuencia de entre una y cuatro letras minúsculas (entre 'a' y 'z') y a continuación un único dígito numérico (entre '0' y '9'). Por tanto, las siguientes cadenas podrían encajar con ese patrón: "a0", "ab7", "xyz1", "aaxx5", "wow9", etc. Sin embargo, las siguientes cadenas no encajarían: "0", "aaxxx", "a1x5","A0", "Wow9", "x22", "xyz".
"[0-9A-F]+" es un patrón que hace referencia a una secuencia de dígitos y/o letras entre la 'A' y la 'F' que pueden ir mezclados. La secuencia tendrá como mínimo un símbolo y no se especifica límite máximo. Por tanto, las siguientes cadenas podrían encajar con ese patrón: "A0", "AB7", "1", "000", "0FF", "A0FF", "00011101010111", "BBB", etc. Sin embargo, las siguientes cadenas no encajarían: "a0", "AX7", "j","0ff", "A0fF", "91G", "bbb".
Fíjate que la cuantificación se aplica siempre al elemento inmediatamente anterior al operador de cuantificación. Si deseamos que se aplique a más de un elemento, habrá que agruparlos mediante el uso de los paréntesis. Eso lo veremos en el siguiente apartado.
Vamos ahora a intentar resolver de nuevo algunos de los ejercicios de apartados anteriores utilizando los cuantificadores.
1.- Expresa, mediante una expresión regular, un patrón que represente todas las cadenas posibles de tres letras formadas exclusivamente por vocales, donde la primera letra tiene que ser obligatoriamente en mayúscula.
2.- Elabora un patrón que represente todas las cadenas posibles de cinco letras, donde la primera letra tiene que ser obligatoriamente en mayúscula y el resto obligatoriamente en minúscula.
Con lo que ya sabemos hacer podemos empezar a intentar resolver algunos ejercicios un poco más complejos:
1.- Construye una expresión regular que represente a un número de DNI, teniendo en cuenta que debe tener ocho dígitos y una letra mayúscula al final, y que no se utilizan las letras I, Ñ, O, U.
2.- Intenta ahora crear una expresión regular similar para representar a un NIE moderno (de siete dígitos). Ten en cuenta que el NIE se compone de una letra inicial (X, Y o bien Z), siete dígitos y un carácter alfabético del mismo tipo que el DNI.
3.- Elabora una expresión regular que represente a una matrícula española moderna (formato a partir del año 2000), formada por cuatro dígitos y tres letras. Las letras permitidas son B, C, D, F, G, H, J, K, L, M, N, P, R, S, T, V, W, X, Y, Z.
4.- Construye una expresión regular que represente a cualquier número entero entre 100 y 99999. El número no puede tener ceros a la izquierda.
En ocasiones, tendremos necesidad de agrupar una secuencia de símbolos para que se le pueda aplicar un cuantificador a toda esa agrupación. Por ejemplo, si tenemos la siguiente expresión regular:
xy+
es un patrón que indica que debe aparecer un carácter 'x' y a continuación uno más caracteres 'y'. Es decir, que se admitirían cadenas del tipo "xy", "xyy", "xyyy", etc.
Sin embargo, si lo que queremos es poder reconocer o admitir cadenas del tipo "xy", "xyxy", "xyxyxy", etc. Es decir, repeticiones del par "xy", entonces tendríamos que cuantificar ambos elementos. Pero no podemos cuantificarlos individualmente haciendo algo como:
x+y+
Porque en tal caso, estaríamos indicando repeticiones del carácter 'x' y luego repeticiones del carácter 'y', pero no repeticiones del par "xy". Por ejemplo, se admitirían cadenas del tipo "xxxxy", "xxyyyyy", "xy", etc. Pero una vez que aparezca la primera 'y', ya no aparecerán más 'x'.
Por tanto, lo que tenemos que hacer es agrupar la secuencia de símbolos que queremos que se repita. De ese modo el cuantificador afectará a toda esa secuencia. Para ello se utilizan los paréntesis:
(xy)+
De este modo convertimos a la secuencia "xy" en una unidad indivisible a la cual se le aplicará el cuantificador '+'.
Si en lugar de utilizar caracteres "fijos" (como 'x' o 'y'), se utilizan conjuntos mediante los corchetes, nuestras posibilidades a la hora de flexibilizar nuestras reglas se multiplican. Por ejemplo, si queremos detectar cadenas que sigan la siguiente secuencia:
"una cifra cero o uno seguida de una letra minúscula, todo eso entre una y tres veces"
podríamos construir la siguiente expresión regular:
([01][a-z]){1,3}
De esa manera, este patrón detectaría cadenas del tipo "0s", "1b1a0x", "0c1z", "1a1a", "1a<code>1a1a", "0b<code>0b0b", "0b1<code>b0j",etc.
1.- Plantea una expresión regular que represente a cualquier número real, con cuatro decimales como máximo, entre 1 y 99. Es decir, todos los números reales entre 1,0000 y 99,9999. Ten en cuenta que en la parte entera no deben permitirse ceros a la izquierda y en la parte decimal no deben permitirse ceros a la derecha (ni siquiera se admiten número del estilo a 2,0). El símbolo de decimal lo representaremos mediante el carácter coma (',') o mediante el carácter punto ('.').
2.- Construye una expresión regular que sirva para comprobar si una cadena de caracteres contiene únicamente palabras formadas por vocales. La primera letra de cada palabra siempre tendrá que estar en mayúsculas y las palabras pueden estar separadas por una coma (y solo una) o bien uno o varios espacios, o bien por una coma y espacios (pero siempre una coma como máximo entre dos palabras).
3.1.4.- Alternativas: operador barra ('|') o disyuntor.
Otro operador importante en las expresiones regulares es la barra (símbolo '|') o disyuntor, utilizado para indicar varias opciones o alternativas. Por ejemplo, la expresión regular "o|u" buscará cualquier "o" o "u" dentro del texto y la expresión regular "este|oeste|norte|sur" puede servir para encontrar el nombre de cualquiera de los puntos cardinales (en minúscula).
Cuando se usa una barra ('|'), la primera opción o alternativa comienza al principio de la expresión regular y finaliza cuando encuentra una barra (símbolo '|'). A partir de ahí comienza la segunda alternativa, que finalizará cuando haya otra barra o bien cuando termine la expresión, y así sucesivamente. Por ejemplo, si queremos dar la alternativa entre las palabras "Antes" y "antes", podríamos escribir "Antes|antes", aunque en tal caso podríamos interesarnos escribir una sola vez la parte común ("ntes"). Ahora bien, para hacerlo correctamente no podemos escribir "<strong><strong><code>A|antes", porque entonces las alternativas serían "<strong><strong><code>A" y "antes", sino que habrá que aislar entre paréntesis la parte que contenga la barra para que la opcionalidad únicamente afecte a ese fragmento de la expresión: "(A|a)". Eso hará que la expresión regular quede finalmente como "(A|a)ntes". Esto significa que podemos encerrar la opcionalidad entre paréntesis para que no afecte a todo el patrón, sino solamente a una parte de él.
En algunos casos (normalmente cuando las alternativas representan un único carácter) se puede obtener el mismo resultado mediante el uso de la barra o bien de los corchetes, pues en ambos casos se está expresando una opcionalidad. Por ejemplo:
el patrón "(A|a)ntes" es equivalente a "[Aa]ntes";
el patrón "[xyz]=[0-2]" es equivalente a "(x|y|z)=(0|1|2)".
1.- Con lo visto hasta ahora ya es posible construir una expresión regular capaz de verificar si una cadena contiene un DNI o un NIE, ¿cómo lo harías?
2.- Escribe una expresión que represente todas las fechas posibles entre el 1 de enero de 1900 y el 31 de diciembre de 2099 en el formato aaaa-mm-dd. El rango permitido para el campo de los meses (mm) es de 01 a 12. El rango permitido para el campo de los días (dd) es de 01 a 31. Se permiten incoherencias entre días y meses (por ejemplo, se permiten el 30 de febrero o el 31 de abril, aunque no existan).
3.- Plantea una expresión regular que sirva para comprobar si un texto cumple el formato de una hora en formato hh:mm:ss en sistema horario de 24 horas. En cada campo siempre tiene que haber dos dígitos. Si es necesario se completa con un cero a la izquierda.
4.- Diseña una expresión regular que reconozca cualquier número romano entre 1 (I) y el 3999 (MMMCMXCIX<code>).
Como ya has podido observar, dentro del contexto de una expresión regular algunos caracteres no tienen su significado "literal", sino que cumplen otra función. Entre ellos se encuentran las llaves (usadas para cuantificar elementos), los paréntesis (utilizados para agrupar elementos básicos y así formar elementos mayores), los corchetes (para indicar conjuntos de posibles valores), la barra (operador disyuntor), el asterisco (cuantificador), el guion dentro de unos corchetes (para indicar rangos), la interrogación (cuantificador), etc. A estos símbolos, que pierden su significado "literal" para cumplir una función más avanzada, se les suele llamar metacaracteres.
Como ya habíamos hecho con las cadenas en Java, para poder indicar el significado "literal" de esos caracteres es necesario escaparlos mediante el carácter barra invertida '\'. Es decir que si en una expresión queremos indicar que puede aparecer un asterisco, dado que carácter * ya tiene un significado especial (metacarácter), debemos escribir \* para dejar claro que nos estamos refiriendo al carácter "asterisco" y no al "cuantificador asterisco". Dependiendo del contexto dentro de la expresión regular, es posible que a veces sea necesario llevar a cabo el escape y en otras no. Por ejemplo, el asterisco tiene normalmente significado como cuantificador, pero si lo incluimos dentro de unos corchetes (para indicar un conjunto de opciones), entonces no hay que escaparlo porque dentro de los corchetes ya no existe el concepto de cuantificación (no tiene sentido).
Por ejemplo:
la expresión regular x* representa una cadena que consiste en un carácter 'x' cero o más veces;
sin embargo, la expresión regular x\* representa la cadena "x*" (el carácter 'x' seguido del carácter '*').
Ahora bien, dado que normalmente vamos a escribir las expresiones regulares entre comillas dobles, que es como Java las trata (utiliza objetos String para procesarlas), habrá que tener en cuenta que en las cadenas de Java la barra invertida ya sirve para escapar. Eso significa que si escribes "x\*", tendrás un problema de interpretación en Java, dado que se estaría intentando escapar el asterisco dentro de la propia cadena y confundiéndose lo que quieres indicar.
Digamos que aquí tendríamos dos "niveles" de escape. Uno inmediato (el de los String de Java) y otro superior (el de la sintaxis de las expresiones regulares). Por tanto, cuando encerremos una expresión regular entre comillas (que será lo habitual, al menos al trabajar en un lenguaje de programación convencional), tendremos que "escapar" la barra invertida dentro de la cadena para poder indicar que queremos literalmente una barra. De ese modo no se perderá en ese "primer nivel" de escape y conservaremos la barra invertida para el intérprete de expresiones regulares.
La conclusión de todo esto es que cada vez que vayamos a utilizar una barra invertida (\) en una expresión regular entre comillas tendremos que escribir dos barras invertidas ("\\").
Por tanto, en el ejemplo anterior, la expresión regular entre comillas debería haber quedado así: "x\\*". Esa "doble barra invertida" significará para las cadenas en Java que deseamos escribir realmente una barra invertida dentro de la cadena (hemos "escapado" la barra invertida). Así habremos "conservado" la barra y podrá usarse a continuación, en el intérprete de expresiones regulares de Java, para escapar el asterisco e indicar que deseamos hacer "match" con un asterisco y no llevar a cabo una cuantificación del elemento anterior.
Veamos un ejemplo. Imagina que quieres elaborar una expresión regular que represente cadenas que empiecen con la barra invertida (\), a continuación un espacio, un asterisco (*), un símbolo de suma (+) y otro espacio. Tras esto una palabra que empiece en mayúscula y con el resto de letras en minúscula ([A-Z][a-z]*). Finalmente, la cadena debe finalizar obligatoriamente con otro espacio, otro símbolo de suma, otro asterisco, otro espacio y otra barra invertida.
Para implementar ese patrón tendríamos que incluir el asterisco escapado (\*), la barra invertida escapada (\\) y el símbolo de suma (\+):
\\ \*\+ [A-Z][a-z]* \+\* \\
Esa expresión regular admitirá cadenas como, por ejemplo, "\ *+ Hola +* \". Así podrías utilizarla, por ejemplo, en la herramienta onlineRegexr.com.
Ahora bien, dado que nosotros normalmente encerraremos esa expresión regular entre comillas dobles (para poder usarla como una cadena en Java), cada vez que aparezca una barra invertida, habrá que añadirle otra para que la cadena de Java entienda cada dos barras invertidas como una barra invertida "literal". Es decir, que cada barra invertida (\) habrá que sustituirla por un par de barras invertidas (\\):
Por último, vamos a ver algunos metacaracteres y construcciones adicionales que pueden ayudarnos a enriquecer nuestras expresiones:
Circunflejo '^' dentro de unos corchetes: "[^abc]". Aunque ya lo hemos visto, no viene mal recordar que el símbolo '^', cuando se pone justo detrás del corchete de apertura, significa "negación". La expresión regular admitirá cualquier símbolo diferente a los puestos entre corchetes. En este caso, cualquier símbolo diferente de "a", "b" o "c". Es decir, es justo lo contrario a los corchetes. Cuidado, porque fuera de los corchetes tiene otro significado.
Comienzo y fin de línea, circunflejo y dólar:"^...$". Cuando el símbolo '^' aparece al comienzo de la expresión regular, permite indicar comienzo de línea o de entrada. El símbolo "$" permite indicar fin de línea o fin de entrada. Usándolos podemos verificar que una línea completa (de principio a fin) encaje con la expresión regular. Puede resultar muy útil cuando se trabaja en modo multilínea. Recuerda que si deseas utilizar esos caracteres de manera "literal", tendrás que escaparlos.
Carácter comodín '.'. El punto simboliza cualquier carácter (pero solo uno, si necesitas indicar más de uno deberás cuantificarlo).
Además de lo anterior, también te proponemos algunas construcciones habitualmente admitidas en las expresiones regulares que permiten simplificar su escritura:
"\\d". Un dígito numérico. Equivale a "[0-9]".
"\\D". Cualquier cosa excepto un dígito numérico. Equivale a "[^0-9]".
"\\s". Un espacio en blanco (incluye espacios, tabulaciones, saltos de línea y otras formas de espacio).
"\\S". Cualquier cosa excepto un espacio en blanco.
"\\w". Cualquier carácter que podrías encontrar en un identificador (letras mayúsculas, minúsculas, dígitos y carácter '_') . Equivale a "[a-zA-Z_0-9]".
Estas construcciones abreviadas puedes incluirlas dentro de unos corchetes. Podrías escribir, por ejemplo "<strong><span style="font-size: medium;"><code>[\\dABC]" y sería equivalente a "<strong><span style="font-size: medium;"><code>[0-9ABC]".
Recuerda una vez más que si no van entre comillas (por ejemplo, si los vas a usar en la herramienta onlineRegexr.com) habría que escribir nada más que una barra invertida ('\'). Por ejemplo \d.
Además de estas construcciones, tienes muchas otras que puedes consultar en la documentación según vayas necesitando.
Las cadenas que contienen en su interior saltos de línea, a veces no se procesan apropiadamente con las expresiones regulares, dado que éstas, se limitan a verificar generalmente solo la primera línea. El modo multilínea permite buscar líneas completas, que encajan con un patrón dentro de una cadena que contiene varias líneas.
Construye una expresión regular que represente una dirección de correo electrónico con el siguiente formato:
nombre@dominio.dom
donde:
el nombre puede estar compuesto por letras (mayúsculas o minúsculas), dígitos numéricos, guion ('-'), guion bajo/subrayado/underscore ('_') o punto ('.'). Ha de empezar por una letra. Debe tener como mínimo tres caracteres y como máximo quince;
el dominio puede estar compuesto por letras (mayúsculas o minúsculas), dígitos numéricos o el guion ('-'). No puede comenzar por un número ni por el guion y no puede haber dos guiones seguidos. Debe estar formado como mínimo por un carácter y como máximo por sesenta;
el dominio de nivel superior (TLD), dom, debe tener como mínimo dos caracteres y como máximo seis. Debe estar formado únicamente por letras (mayúsculas o minúsculas).
3.2.- Expresiones regulares en Java (I): clases Pattern y Matcher.
Una vez que ya hemos aprendido a construir expresiones regulares de cierta complejidad, la siguiente pregunta queºes: ¿y cómo uso las expresiones regulares en un programa?
Para poder trabajar con expresiones regulares, Java ofrece las clases Pattern y Matcher contenidas en el paquete java.util.regex:
la clase Pattern o "patrón" se utiliza para procesar la expresión regular y "compilarla", lo cual significa verificar que es correcta, y dejarla lista para su utilización en un objeto de tipo Pattern. Para ello disponemos del método estático Pattern.compile;
la clase Matcher o "acoplador" sirve para comprobar si una cadena cualquiera sigue o no un patrón indicado en un objeto de tipo Pattern. A eso se le suele llamar hacer "matching", o "acoplar", o "encajar" con el patrón. Para ello disponemos del método matcher en la clase Pattern, que nos devolverá un objeto de tipo Matcher. Sobre ese objeto podremos aplicar el método matches de la clase Matcher para saber si se produce un "matching" o no.
Veámoslo con un ejemplo:
Pattern patron=Pattern.compile("[01]+"); // Creamos el patrón: objeto de tipo Pattern
Matcher texto=patron.matcher("00001010"); // Creamos un caso de posible acoplamiento, encaje o matching: objeto de tipo Matcher
if (texto.matches()) // Se produce "matching" o "acoplamiento"
System.out.println("La cadena encaja con el patrón.");
else // No se produce "matching"
System.out.println("La cadena no encaja con el patrón.");
En el ejemplo, el método estático compile de la clase Pattern permite crear un objeto de tipo patrón (Pattern). Dicho método compila la expresión regular pasada por parámetro en forma de String y genera una instancia de Pattern (patron en el ejemplo). El patrón patron podrá ser usado múltiples veces para revisar si una cadena coincide o no con el patrón, dicha comprobación se hace invocando el método matcher, el cual combina el patrón con la cadena de entrada y genera una instancia de la clase Matcher (texto en el ejemplo). La clase Matcher contiene el resultado de la comprobación y ofrece varios métodos para analizar el modo en el que la cadena puede encajar o acoplarse (o hacer "matching") con un patrón. En este primer ejemplo hemos utilizado el método matches(), que devolverá true si toda la cadena (de principio a fin) encaja con el patrón o false en caso contrario.
Ahora ya podemos empezar a escribir pequeños programas en Java que prueben todos los ejemplos de expresiones regulares que hemos estado viendo en los apartados anteriores.
Ahora puedes plantearte implementar en Java algunos de los ejercicios y ejemplos planteados en apartados anteriores. Intentemos algunos de ellos:
1.- Implementa un programa en Java que solicite por teclado una hora en el formato hh:mm:ss en 24 horas y compruebe si esa hora es válida.
2.- Implementa un programa en Java que solicite por teclado un día de la semana en minúscula y sin tildes, y compruebe si ese día es válido.
3.- Implementa un programa en Java que solicite por teclado un número romano entre 0 y 3999 (considerando el cero como la cadena vacía) y compruebe si número romano es válido.
Si en algún caso es indiferente que un texto esté en mayúsculas o minúsculas, para intentar simplificar la expresión regular (evitar tener que incluir los rangos A-Z y a-z), tenemos varias opciones:
transformar la cadena que se va a comprobar a todo mayúsculas o minúsculas, según se haya planteado la expresión regular;
utilizar una versión sobrecargada del método fábrica Pattern.compile, que admite un segundo parámetro con algunos flags o indicadores interesantes. Para este caso nos vendría bien el flag CASE_INSENSITIVE.
Algunos de los flags que admite el método "fábrica" Pattern.compile son:
CASE_INSENSITIVE, que, como hemos dicho, hace que la expresión regular no diferencie entre mayúsculas y minúsculas sin tener que indicarlo explícitamente;
LITERAL, que anula el significado de cualquier metacarácter que pudiera haber en la expresión regular, otorgándole su valor literal. Es decir, que los asteriscos significarán asteriscos y no cuantificación, los corchetes no indicarán conjuntos, etc.
Puedes ver el resto flags o indicadores disponibles para Pattern.compile en la documentación de la API de Java.
Además de comprobar si un determinado texto encaja exactamente con un patrón, las clases Pattern y Matcher ofrecen muchas más posibilidades. Ahora bien, si simplemente queremos hacer ese tipo de comprobación (si un texto "encaja" con un determinado patrón), podemos evitar la creación de este tipo de objetos y utilizar directamente el método matches que proporciona la clase String. Únicamente hay que pasarle como parámetro una cadena con la expresión regular que representa el patrón que deseamos verificar.
Veamos el mismo ejemplo del apartado anterior utilizando esta técnica:
String patron = "[01]+"; // Almacenamos el patron en una cadena
String texto = "00001010"; // Almacenamos el texto a comprobar en otra cadena
if (texto.matches()) // Se produce "matching" o "acoplamiento"
System.out.println("La cadena encaja con el patrón.");
else // No se produce "matching"
System.out.println("La cadena no encaja con el patrón.");
Queda bastante más sencillo, ¿verdad?
En realidad, ni siquiera es necesario almacenar el patrón en una cadena. Podría haberse incluido directamente la cadena con el patrón en la llamada al método:
Si lo único que tienes que hacer es verificar si una cadena cumple un patrón, basta con el propio método matches de la clase String. No es necesario recurrir a objetos Pattern y Matcher.
Intenta ahora repetir los ejercicios del apartado anterior sin hacer uso de objetos Pattern y Matcher.
1.- Implementa un programa en Java que solicite por teclado una hora en el formato hh:mm:ss en 24 horas y compruebe si esa hora es válida.
2.- Implementa un programa en Java que solicite por teclado un día de la semana en minúscula y sin tildes, y compruebe si ese día es válido.
3.- Implementa un programa en Java que solicite por teclado un número romano entre 0 y 3999 (considerando el cero como la cadena vacía) y compruebe si número romano es válido.
3.3.- Expresiones regulares en Java (II): búsquedas.
El método matches de la clase Matcher nos permite comprobar si una cadena encaja exactamente o no con un determinado patrón. Pero esta clase nos ofrece un conjunto de métodos con bastantes más posibilidades a la hora analizar la forma en la que la cadena ha encajado con un patrón. Veamos algunos de ellos:
matches(), que devolverá true si toda la cadena (de principio a fin) encaja con el patrón o false en caso contrario;
lookingAt(), que devolverá true si el patrón se ha encontrado al principio de la cadena. A diferencia del método matches(), la cadena podrá contener al final caracteres adicionales a los indicados por el patrón, sin que ello suponga un problema;
find(), que devolverá true si el patrón existe en algún lugar la cadena (no necesariamente toda la cadena debe coincidir con el patrón) y false en caso contrario, pudiendo tener más de una coincidencia. Para obtener la posición exacta donde se ha producido la coincidencia con el patrón podemos usar los métodos start() y end(), para saber la posición inicial y final donde se ha encontrado. Una segunda invocación del método find() irá a la segunda coincidencia (si existe), y así sucesivamente. Podemos reiniciar el método find(), para que vuelva a comenzar por la primera coincidencia, invocando el método reset().
Como puedes observar ya no se trata simplemente de poder comprobar si un texto cumple exactamente un determinado patrón o requisito, sino que también podríamos comprobar si lo cumplen una o varias partes del texto. Veamos un ejemplo:
Comprobar si en un determinado texto aparecen números enteros, cuántos y en qué posición.
Lo primero que tendríamos que hacer sería diseñar la expresión regular para representar números enteros. Una forma sencilla de hacerlo podría ser "[0-9]+". Si queremos, podríamos complicarlo un poco más si queremos evitar que puedan tener ceros a la izquierda. Eso ya dependería del problema concreto que tuviéramos que resolver.
Una vez que tenemos clara la expresión regular, habría que aplicarla al texto que deseamos analizar y a partir de ahí empezar a usar el método find en un bucle hasta que nos devuelva false:
// Suponemos que la variable "texto" contiene el texto que se desea analizar
Pattern patron = Pattern.compile("[0-9]+"); // Patrón de búsqueda
Matcher acoplamiento = patron.matcher(texto); // Texto donde buscar o "acoplar"
boolean patronEcontrado; // Si el patron ha sido encontrado
int numVeces= 0; // Contador para saber cuántas veces aparece el patrón
do {
patronEncontrado= acoplamiento.find(); // Buscamos el patrón en el texto
if ( patronEncontrado ) { // Si lo encontramos, lo contabilizamos, ubicamos y extraemos
numVeces++;
inicio= acoplamiento.start();
fin= acoplamiento.end();
System.out.printf ("%2d.- Patrón numérico encontrado. Ubicado entre las posiciones %d y %d: %s\n",
numVeces, inicio, fin, texto.substring(inicio, fin));
}
} while (patronEncontrado);
if (numVeces == 0) {
System.out.println ("Patrón numérico entero no encontrado en el texto.");
} else {
System.out.printf ("Encontrado %d veces.", numVeces);
}
En este ejemplo, si el texto de entrada hubiera sido "Este número 220 es mayor que el 200, pero menor que el 300.", el resultado debería haber sido algo como:
Este número 220 es mayor que el 200, pero menor que el 300.
1.- Patrón numérico encontrado. Ubicado entre las posiciones 12 y 15: 220
2.- Patrón numérico encontrado. Ubicado entre las posiciones 32 y 35: 200
3.- Patrón numérico encontrado. Ubicado entre las posiciones 55 y 58: 300
Encontrado 3 veces.
Escribe un programa en Java que reciba un texto por teclado y contabilice cuántas palabras hay que empiecen por mayúscula, en qué posición se encuentra ubicada cada palabra y de qué palabra se trata.
Un ejemplo de ejecución podría ser el siguiente:
BÚSQUEDA DE PATRÓN EN TEXTO
---------------------------
Introduzca texto donde buscar:
El padre de Laura se llama Antonio y su hermana Gracia. Juan es el mayor. El menor es Rodrigo. A las dos horas llegaron.
1.- Palabra en mayúscula encontrada. Ubicada entre las posiciones 0 y 2: El
2.- Palabra en mayúscula encontrada. Ubicada entre las posiciones 12 y 17: Laura
3.- Palabra en mayúscula encontrada. Ubicada entre las posiciones 27 y 34: Antonio
4.- Palabra en mayúscula encontrada. Ubicada entre las posiciones 48 y 54: Gracia
5.- Palabra en mayúscula encontrada. Ubicada entre las posiciones 56 y 60: Juan
6.- Palabra en mayúscula encontrada. Ubicada entre las posiciones 74 y 76: El
7.- Palabra en mayúscula encontrada. Ubicada entre las posiciones 86 y 93: Rodrigo
8.- Palabra en mayúscula encontrada. Ubicada entre las posiciones 95 y 96: A
Encontradas 8 palabras.
3.4.- Expresiones regulares en Java (III): agrupaciones.
Como ya hemos visto, los paréntesis tienen un significado especial en las expresiones regulares (son metacaracteres): permiten establecer agrupaciones de un conjunto de símbolos. Por ejemplo, en la expresión "(#[01]){2,3}", el subpatrón "#[01]" representa cadenas como "#0" o "#1". Pero al encerrarlo entre paréntesis y añadir un cuantificador, lo que estamos indicando es que la misma secuencia se tiene que repetir entre dos y tres veces, con lo que las cadenas que admitiría serían del estilo a: "#0#1" o "#0#1#0".
Pero los paréntesis tienen una función adicional, y es la de poder extraer grupos. Un grupo comienza cuando se abre un paréntesis y termina cuando se cierra el paréntesis. El establecimiento de grupos permite acceder de forma cómoda a las diferentes partes de una cadena cuando esta coincide con una expresión regular. En Java podemos extraer los distintos grupos de un texto que encaja con una expresión regular mediante el método group de la clase Matcher. Vamos a verlo con un ejemplo:
Imagina que debemos reconocer si una fecha cumple el formato dd/mm/aaaa, donde el año solo puede estar entre 2000 y 2099. Ya hemos construido expresiones regulares muy similares para expresar ese patrón. Por ejemplo, podríamos hacer: "(3[01]|[12][0-9]|0[1-9])/(1[0-2]|0[1-9])/20[0-9]{2}".
Supongamos ahora que deseamos obtener por separado el día, el mes y el año. Podríamos entonces aislar cada uno de esos campos entre paréntesis para poder extraer esos grupos:
Para el caso del día y del mes, ya habíamos tenido que aislar los grupos para un correcto funcionamiento del disyuntor ('|'), así que solamente hemos necesitado encerrar entre paréntesis la parte del año.
Para extraer cada uno de los grupos, basta con utilizar el método group sobre el objeto Matcher. Si se la pasa 1 como parámetro, se obtendrá una subcadena con el primer grupo, si se le pasa 2, el segundo, y así sucesivamente.
Primero creamos los objetos patrón (Pattern) y acomplamiento (Matcher):
// Suponemos que la variable "texto" contiene el texto que se desea analizar
Pattern patron = Pattern.compile("[0-9]+"); // Patrón de búsqueda
Matcher acoplamiento = patron.matcher(texto); // Objeto que contiene el texto donde buscar o "acoplar"
A partir de ahí no tendríamos más que ir obteniendo cada uno de los grupos que proporciona el acoplamiento (objeto Matcher):
if (acoplamiento.matches()) { // Solo debes extraer grupos si se produce acoplamiento
dia = acoplamiento.group(1);
mes = acoplamiento.group(2);
ano = acoplamiento.group(3);
}
Usando los grupos, podemos obtener por separado el texto contenido en cada uno de los grupos. En el ejemplo anterior, en el patrón hay tres grupos: uno para el día (grupo 1), otro el mes (grupo 2), y otro para el año (grupo 3). Solo debes extraer grupos si se ha producido acoplamiento. Si no es así, la llamada al método group generará una excepción.
Aquí tienes otro ejemplo en el que combinamos los agrupamientos con el uso del método find:
En este caso también hay tres grupos en el patrón: uno para la letra inicial (grupo 1), otro para el número del DNI o NIE (grupo 2), y otro para la letra final o letra NIF (grupo 3). Como además hemos empleado find en lugar de matches, podremos extraer los grupos para cada fragmento de texto en el cual se ha producido un acoplamiento.
Ten en cuenta que el primer grupo es el 1, y no el 0. Si intentas hacer group(0) obtendrás una cadena con toda la ocurrencia o coincidencia del patrón en la cadena, es decir, obtendrás la secuencia entera de símbolos que coincide con el patrón.
Con lo estudiado hasta ahora, ya puedes utilizar las expresiones regulares y sacarles partido casi que al máximo. Pero las expresiones regulares son un campo muy amplio, por lo que es muy aconsejable que amplíes tus conocimientos con el siguiente enlace:
Un almacén de ropa identifica sus productos siguiendo un código formado por números y letras, el cual permite reconocer el tipo de cliente al que van dirigidos, un identificador y su origen de fabricación. Su estructura es la siguiente:
una letra que indica el tipo de cliente ('H' - Hombre, 'M' - Mujer o 'B' - Bebé);
un identificador de entre tres y siete dígitos numéricos;
una letra que indica el origen de fabricación del producto: 'E' (Europa) o 'A' (Asia);
un código de control (dos dígitos).
Las letras solo pueden ser mayúsculas. Si hay alguna letra minúscula, el código se considera inválido.
Escribe un programa en Java que permita detectar si una cadena cumple la estructura de un código textil. Y, en caso afirmativo, extraer en variables de tipo String el tipo de cliente, el identificador, el origen de fabricación y el código de control.
En ejercicios anteriores ya hemos resuelto cómo analizar si un número romano es válido o no mediante una expresión regular y lo hemos implementado en Java. Ahora podríamos empezar a plantearnos cómo convertir ese número romano en un número en formato decimal.
1.- El primer paso para resolver ese problema consistiría en intentar "extraer" cada uno de los elementos del número romano en los siguientes grupos:
unidades de millar (o millares);
centenas;
decenas;
unidades.
¿Cómo podríamos obtener cada una de esas subcadenas? Un ejemplo de salida de un programa que realizara ese trabajo podría ser algo así:
Introduzca número romano (entre 0 y 3999):
(entendemos el 0 como la cadena vacía)
MDCCLXXVIII
RESULTADO
---------
Millares: M
Centenas: DCC
Decenas: LXX
Unidades: VIII
2.- El siguiente paso sería comenzar a estudiar cómo transformar o decodificar cada uno de esos grupos en números que posteriormente podamos sumar. Comencemos con el grupo más sencillo: los millares. ¿Cómo podríamos hacerlo?
3.- El resto de casos ya no son tan inmediatos. ¿Cómo decodificarías las centenas?
4.- Teniendo en cuenta cómo has resuelto el apartado anterior, ¿cómo lo harías para las decenas y para las unidades?
5.- Uniendo todo lo anterior, escribe el programa completo que solicite un número romano y, una vez comprobada su validez, muestre por pantalla su valor en formato decimal.
Un ejemplo de salida del programa podría ser:
PROGRAMA PARA RESOLVER NÚMEROS ROMANOS
--------------------------------------
Introduzca número romano (entre 0 y 3999):
(entendemos el 0 como la cadena vacía)
MDCCXCIX
RESULTADO
---------
El número introducido es: 1799
6.- Generalizando lo que acabamos de hacer y utilizando el método find, ¿se te ocurre cómo podrías obtener todos los números romanos insertos en un texto? El programa, según fuera encontrando números romanos, podría ir incluyéndolos en una cadena de caracteres separados por comas y, a la vez, ir realizando las decodificaciones a números decimales e ir incluyéndolos en una segunda cadena, también separados por comas. El programa, una vez finalizada la búsqueda, podría mostrar las dos listas (dos cadenas de caracteres).
Este podría ser un ejemplo de funcionamiento del programa:
PROGRAMA PARA EXPLORAR TEXTOS CON NÚMEROS ROMANOS
-------------------------------------------------
Introduzca texto que puede contener números romanos (entre I y MMMCMXCIX):
I.- El rey Alfonso X el sabio vivió en el siglo XIII, mientras que el rey Alfonso XII vivió en el siglo XIX.
RESULTADO
---------
La lista de números romanos encontrados es:
I, X, XIII, XII, XIX
La lista de números decodificados es:
1, 10, 13, 12, 19
Aquí tienes otro ejemplo más:
PROGRAMA PARA EXPLORAR TEXTOS CON NÚMEROS ROMANOS
-------------------------------------------------
Introduzca texto que puede contener números romanos (entre I y MMMCMXCIX):
II.- Esta película es del año MCMXCVIII, es decir, del siglo XX. Estamos ahora en el sigo XXI.
RESULTADO
---------
La lista de números romanos encontrados es:
II, MCMXCVIII, XX, XXI
La lista de números decodificados es:
2, 1998, 20, 21
7.- Por último, utilizando los métodos de sustitución de la clase String (replace, replaceFirst, replaceAll, etc.). ¿Se te ocurre cómo podríamos generar una cadena de salida que fuera el texto original con los números romanos transformados a números en formato decimal?
Los ejemplos anteriores de funcionamiento podrían entonces quedar como:
PROGRAMA PARA EXPLORAR TEXTOS CON NÚMEROS ROMANOS
-------------------------------------------------
Introduzca texto que puede contener números romanos (entre I y MMMCMXCIX):
I.- El rey Alfonso X el sabio es del siglo XIII, mientras que el rey Alfonso XII es del siglo XIX.
RESULTADO
---------
La lista de números romanos encontrados es:
I, X, XIII, XII, XIX
La lista de números decodificados es:
1, 10, 13, 12, 19
El texto final sustituido sería:
1.- El rey Alfonso 10 el sabio es del siglo 13, mientras que el rey Alfonso 12 es del siglo 19.
PROGRAMA PARA EXPLORAR TEXTOS CON NÚMEROS ROMANOS
-------------------------------------------------
Introduzca texto que puede contener números romanos (entre I y MMMCMXCIX):
II.- Esta peli es de MCMXCVIII, es decir, del siglo XX. Estamos ahora en el sigo XXI.
RESULTADO
---------
La lista de números romanos encontrados es:
II, MCMXCVIII, XX, XXI
La lista de números decodificados es:
2, 1998, 20, 21
El texto final sustituido sería:
2.- Esta peli es de 1998, es decir, del siglo 20. Estamos ahora en el sigo 21.
Ana se ha puesto a pensar en que cuando hay que trabajar con muchos elementos: números, o cadenas de caracteres, etc., lo más adecuado es utilizar algún tipo de variable que nos permita usar el mismo nombre, donde almacenar varios elementos. Es decir, si tenemos 100 elementos sobre los que hacer determinada operación, una solución sería utilizar cien variables, y entonces en el programa ir usando cada una de esas cien variables, digamos var1, var2,..., y así hasta var100. Pero esto es muy incómodo para el programador, puesto que entre otras cosas tendrá que usar cien nombres distintos para referirse a las variables. Además, ¿y si luego en vez de 100 elementos, necesita que sean 200? Pues el programador tendrá que declarar otras cien variables más en el programa. Por tanto, hay que buscar otra solución más adecuada, y esa solución son los arrays, como vamos a ver.
¿Dónde almacenarías tú una lista de nombres de personas? Una de las soluciones es usar arrays, puede que sea justo lo que necesita Ana, pero puede que no. La mayoría de los lenguajes de programación permiten el uso de arrays. Veamos cómo son en Java.
Los arrays permiten almacenar una colección de objetos o datos del mismo tipo. Son muy útiles y su utilización es muy simple:
Declaración del array. Consiste en decir "esto es un array" y sigue la siguiente estructura: "tipo[] nombre;". El tipo será un tipo de variable o una clase ya existente, de la cual se quieran almacenar varias unidades.
Creación o reserva de memoria del array. Consiste en indicar el tamaño que tendrá el array, es decir, el número de elementos que contendrá, y se pone de la siguiente forma: "nombre = new tipo[tamaño]", donde tamaño es un número entero positivo que indicará el tamaño del array. En Java, una vez creado el array, no podrá cambiar de tamaño.
Veamos un ejemplo de su uso:
int[] unArray ; // Declaración del array.
unArray = new int[10] ; //Creación del array reservando para el un espacio en memoria.
int[] otroArray = new int[10]; // Declaración y creación en un mismo lugar.
La única diferencia entre la declaración de una variable de tipo "escalar" (un único elemento de tipo int, double, String, LocalDate, etc.) y una de tipo "vectorial" o array (una agrupación de varios elementos del mismo tipo) es el uso de los corchetes ([]). Si no lleva corchetes es única o escalar. Si lleva corchetes es un array o "vectorial".
Dado que para reservar espacio para el array debes usar el operador new, podrás imaginar que lo que contiene una variable de tipo array no es más que una referencia a una zona de memoria donde realmente se encuentran la secuencia de valores que componen el array. Es algo similar a lo que sucede con las variables de un tipo objeto o clase, que lo que contienen es una referencia a una zona de memoria donde realmente se encuentran los atributos del objeto. Por tanto, las variables de tipo array son también variables de tipo referencia. Es decir, que la variable no contiene un valor (o valores) del tipo del array, sino una referencia a una zona de la memoria donde se encuentran todos los componentes del array.
Una vez creado un array (reservado su espacio en memoria mediante el operador new), ya podemos almacenar valores en cada una de las posiciones del array. Para ello se usarán los corchetes, indicando en su interior la posición sobre la que queremos leer o escribir, teniendo en cuenta que la primera posición es la cero y la última el tamaño del array menos uno. En el ejemplo anterior, la primera posición sería la 0 y la última sería la 9.
Un índice debe ser un valor int, o un valor de un tipo que pueda promoverse a int, es decir, los tipos byte, short o char, pero no long. De lo contrario, se producirá un error de compilación (error semántico por "pérdida de precisión" al promover de long a int).
Al contrario que en otros lenguajes de programación, en la declaración de un array no se debe indicar el número de elementos. Si se especifica el número de elementos en los corchetes de la declaración (por ejemplo int[12] c), se produce también un error de compilación (en este caso de sintaxis).
Debes ser cuidadoso con las declaraciones múltiples de variables de tipo array, pues pueden llevar a confusión. Por ejemplo, en la declaración:
int[] a, b, c;
Si a, b y c deben ser variables de tipo array de enteros, entonces esa declaración es correcta. Al colocar corchetes directamente después del tipo, indicamos que todos los identificadores en la declaración son variables tipo array. Ahora bien, si solamente a debe ser una variable tipo array, mientras que b y c deben ser variables int individuales (escalares), entonces la declaración no sería apropiada. En tal caso, la declaración
int a[], b, c;
o bien esta otra:
int[] a;
int b, c;
lograrían el resultado deseado.
Buena práctica de programación
Por cuestión de legibilidad, suele ser apropiado indicar solo una variable de tipo array en cada declaración. Y si se añade un comentario que describa a la variable que se está declarando, mucho mejor.
Ya sabes declarar y crear de arrays, pero, ¿cómo y cuándo se usan?
Las operaciones habituales que se suelen realizar sobre ellos son:
modificación del elemento ubicado en una posición del array;
acceso al elemento ubicado en una posición del array.
La modificación de un elemento en una posición del array se realiza mediante una simple asignación. Simplemente, se especifica entre corchetes la posición a modificar después del nombre del array (operador "corchete"). Veámoslo con un simple ejemplo:
int[] numeros = new int[3] ; // Array de 3 números (posiciones del 0 al 2).
numeros[0] = 99 ; // Primera posición del array.
numeros[1] = 120 ; // Segunda posición del array.
numeros[2] = 33; // Tercera y última posición del array.
El acceso a un valor ya existente dentro de una posición del array se consigue de forma similar, simplemente poniendo el nombre del array y la posición a la cual se quiere acceder entre corchetes:
int suma = numeros[0] + numeros[1] + numeros[2];
Para nuestra comodidad, los arrays, como referencias que son en Java, disponen de una propiedad pública muy útil: la propiedad length, que nos permite conocer el tamaño de cualquier array.
System.out.println("Longitud del array: " + numeros.length);
Dependiendo del lenguaje de programación, a las variables constantes también se les conoce como constantes con nombre. Este tipo de variables mejoran la legibilidad del código respecto al uso de valores literales. Por ejemplo, una constante con un nombre como LONGITUD_VECTOR indica sin duda su propósito, mientras que un valor literal (por ejemplo, 10) podría tener distintos significados, dependiendo de su contexto.
De esta manera si se hace:
final int LONGITUD_VECTOR = 10;
. . .
. . .
String[] listaNombres = new String[LONGITUD_VECTOR];
quedará más claro y más fácil de gestionar que se hubiera hecho directamente:
String[] listaNombres = new String[10];
Error común de programación
Tratar de asignar un valor a una variable constante después de inicializarla produce un error de compilación.
Intentar usar una constante antes de inicializarla también da lugar a un error de compilación.
Observación para prevenir errores
Al acceder a un elemento de un array, hay que asegurarse de que el índice del array siempre sea mayor o igual a 0 y menor que su longitud. Esto ayudará a evitar excepciones del tipo ArraylndexOutOfBoundsException.
Rellenar un array para su primera utilización, es una tarea muy habitual que en muchas ocasiones puede ser simplificada. Vamos a explorar dos sistemas que nos van a permitir inicializar un array de forma cómoda y rápida.
En primer lugar, se puede inicializar un array simplemente accediendo a cada posición del mismo (directamente una a una o mediante un bucle) y asignando un valor a cada una. Veamos un ejemplo:
int totalNumeros = 10 ;
int[] consecutivos = new int[totalNumeros];
for (int i=0; i < totalNumeros;i++)
consecutivos[i] = i ;
En el ejemplo anterior se crea un array con una serie de números consecutivos, empezando por el cero, ¿sencillo no? Este uso suele ahorrar bastantes líneas de código en tareas repetitivas. Otra manera de inicializar los arrays, cuando el número de elementos es fijo y conocido a priori, es indicando entre llaves el listado de valores que tiene el array. En el siguiente ejemplo puedes ver la inicialización de un array de tres números, y la inicialización de un array con siete cadenas de texto:
Pero cuidado, esta inicialización solo se puede usar en ciertos casos. La inicialización anterior funciona cuando se trata de un tipo de dato del cual Java permita expresar literales en el propio código. Es decir, para los literales de los tipos primitivos (int, short, float, double, boolean, char, etc.) o de un String (cuyos literales se expresan mediante un texto entre comillas dobles). Para otro tipo de objetos habrá que realizar una instanciación (mediante invocaciones bien a constructores bien a métodos que devuelvan la referencia a un objeto).
Cuando se trata de un array de referencias a objetos, la inicialización del mismo requiere, como acabamos de comentar, invocaciones a métodos que devuelvan referencias a instancias.
Un array de referencias a objetos recién creado y sin inicializar contendrá valores de tipo null. Debemos tener en cuenta que crear el array (reservar memoria para él mediante el operador new) no significa que se hayan creado las instancias de los objetos a los que va a apuntar cada celda. Hay que instanciar, para cada posición del array, el objeto del tipo correspondiente con el operador new o, al menos, asignarle la referencia a un objeto que ya exista.
Veamos un ejemplo con la clase StringBuilder. En el siguiente ejemplo solo aparecerá null por pantalla:
StringBuilder[] oStrBuilds = new StringBuilder[10];
for (int i = 0; i < oStrBuilds.length; i++)
System.out.println("Valor" + i + "=" + oStrBuilds[i]); // Imprimirá null para los 10 valores.
Para solucionar este problema podemos optar por lo siguiente, crear para cada posición del array una instancia del objeto:
StringBuilder[] oStrBuilds = new StringBuilder[10];
for (int i=0; i < oStrBuilds.length;i++)
oStrBuilds[i] = new StringBuilder("cadena " + i);
Si hubiéramos querido inicializar un array de referencias a objetos StringBuilder usando las llaves, habríamos tenido que realizar las instanciaciones con new dentro de la propia declaración. Es decir, hacer algo del estilo a lo siguiente:
StringBuilder[] oStrBuilds = {new StringBuilder ("cadena 0"), new StringBuilder ("cadena 1"), new StringBuilder ("cadena 2"), . . . };
Si se tratara, por ejemplo, de un array de referencias a objetos de tipo LocalDate, podríamos haber hecho lo siguiente (recuerda que la clase LocalDate no dispone de constructores, sino de métodos "fábrica" para instanciarse):
Una variable que almacena un objeto en realidad es un apuntador a la zona de memoria donde se almacena dicho objeto. Cuando dicha variable no apunta a ningún objeto, porque el objeto aún no se ha creado con el operador new, entonces la variable apunta a null para indicar que no hace referencia todavía a ningún objeto.
Para acceder a una propiedad o a un método cuando los elementos del array son objetos, puedes usar la notación de punto detrás de los corchetes, por ejemplo: diasSemana[0].length.
Fíjate bien en el array diasSemana anterior y piensa en lo que se mostraría por pantalla con el siguiente código:
Ya sabemos lo que son los arrays, y cómo inicializarlos. Supongamos un array, por ejemplo, de enteros (valores de tipo int) ¿Se te ocurre cómo haríamos para ordenarlo?
1.- La idea puede ser ir recorriendo el array, y para cada elemento del mismo, hacemos una segunda pasada, un segundo recorrido del array desde el siguiente elemento más uno en adelante y comparo. Así, si el elemento del array que estoy recorriendo en primer lugar es mayor que el de la segunda pasada, entonces intercambio esos elementos.
Intenta resolverlo antes de mirar la solución.
2.- Ahora supón que tenemos un array de Integer (en realidad referencias a objetos de tipo Integer), ordenado, pero que puede contener huecos (null) donde insertar un número. Supongamos que los nulos, los huecos están al final. Queremos poder insertar un número al array, y que siga estando ordenado ascendentemente. ¿Cómo lo harías? Una idea puede ser: primero comprobar si hay hueco, y si no lo hay aviso y termino. Si lo hay, empiezo a recorrer el array y si es nulo, lo inserto sin más, si no, comparo si el elemento que quiero insertar es menor que el de la posición actual del array. Cuando detecte un número mayor que el que yo quiero insertar, tendré que desplazar los elementos del array desde esa posición en adelante para abrir hueco. ¿Eres capaz de hacerlo? ¡Adelante! Inténtalo antes de ver la solución más abajo.
3.- Por último, veamos cómo borrar un elemento de un array manteniendo el array ordenado y empaquetado (sin huecos null en medio, esto quiere decir que todos los espacios vacíos que tenga el vector van a estar siempre juntos en las últimas posiciones del vector). ¿Cómo lo harías? La idea es desplazar todos los elementos de las posiciones siguientes al elemento que queremos borrar una posición hacia abajo, para mantener el vector empaquetado, sin abrir huecos en medio. Una vez hecho esto, debemos asegurarnos de igualar a null la última posición del vector, para que no aparezca duplicada, y para que el vector tenga un hueco más de los que tenía. Como siempre, antes de mirar la solución, intenta hacerlo.
Es posible que en nuestros programas necesitemos mostrar por pantalla el contenido completo de todos los elementos contenidos en un array. Para ello bastaría con realizar un bucle que fuera desde el primer índice hasta el último y se encargara de ir mostrando el contenido de cada elemento correspondiente a esa posición o índice:
// Mostrar el contenido del array escribiendo cada elemento en una línea diferente
for (int i = 0; i < arrayElementos.length; i++) {
System.out.printf("El contenido de la posición: %d es %d\n", i, arrayElementos[i]);
}
Este ejemplo mostraría cada elemento en una línea diferente indicando su posición. Si quisiéramos, por ejemplo, mostrar todos los elementos en una misma línea, separándolos por un espacio en blanco podríamos hacer:
// Mostrar el contenido del array escribiendo cada elemento seperado por un espacio
System.out.printf("El contenido del array es: ");
for (int i = 0; i < arrayElementos.length; i++) {
System.out.printf("%d ", i, arrayElementos[i]);
}
Y así en general para cualquier manera que se te ocurra a la hora de presentar esa información.
También puedes, en lugar de escribir toda esa información en pantalla, generar una única cadena con todos los datos para más adelante mostrarla en pantalla con una sóla sentencia o bien para que la utilice otra parte de tu programa (o incluso otro programa). En tal caso, en lugar de ir mostrando cada fragmento de información por pantalla irías concatenando todos esos fragmentos en una única cadena. Los ejemplos anteriores podrían quedar entonces así:
// Volcar el contenido del array en un objeto String
String contenidoArray= "";
for (int i = 0; i < arrayElementos.length; i++) {
contenidoArray += String.format ("El contenido de la posición: %d es %d\n", i, arrayElementos[i]);
}
// Mostrar el contenido del array
System.out.printf("%s\n", contenidoArray);
// Volcar el contenido del array en un objeto String
String contenidoArray= "";
for (int i = 0; i < arrayElementos.length; i++) {
contenidoArray += arrayElementos[i] + " ";
}
// Mostrar el contenido del array
System.out.printf("El contenido del array es: %s", contenidoArray);
Basándose en esa filosofía de encapsular los resultados, Java proporciona algunas herramientas para generar un String con una representación textual del contenido de un array. Se trata del método estático toString de la clase Arrays, que recibe como parámetro un arrray y devuelve un objeto String con una representación textual. El formato de la cadena que devuelve es del estilo:
En tal caso, el código para mostrar el contenido de un array se vería sensiblemente simplificado:
System.out.printf("El contenido del array es: %s\n", Arrays.toString(arrayElementos));
La clase Arrays se encuentra en el paquete java.util, así que recuerda que deberás hacer un import java.util.Arrays para poder utilizarla en tus programas. Puedes consultar toda la información sobre el método estático Arrays.toString en la documentación de la API de Java:
Escribe un programa en Java que solicite un número n y rellene un array de enteros de tamaño n con los n primeros números pares empezando desde 0 y terminando con 2(n-1). Una vez relleno el array, se debe mostrar su contenido por pantalla escribiendo cada elemento en una línea diferente.
Un posible resultado de la ejecución del programa podría ser:
ARRAY DE NÚMEROS PARES
----------------------
Introduzca la cantidad de elementos: 12
RESULTADO: LISTA DE PARES EN EL ARRAY
-------------------------------------
Elemento 0: 0
Elemento 1: 2
Elemento 2: 4
Elemento 3: 6
Elemento 4: 8
Elemento 5: 10
Elemento 6: 12
Elemento 7: 14
Elemento 8: 16
Elemento 9: 18
Elemento 10: 20
Elemento 11: 22
Prueba ahora a mostrar todos los elementos en una única línea separándolos por un espacio en blanco. En tal caso, el resultado debería ser algo como:
0246810121416182022
¿Y si quisiéramos Arrays.toString para mostrar el contenido? ¿Cómo lo harías?
2.- Dados los dos arrays anteriores, escribir ahora un programa en Java que cree otro tercer array del mismo tamaño y con el siguiente contenido:
en la primera posición se almacenará la suma de todos los elementos del primer array y en la última posición se almacenará la suma de todos los elementos del segundo array;
en el resto de posiciones, se almacenará el contenido de mayor valor de los arrays en esa posición;
al final del programa, se debe mostrar la suma de todos los elementos contenidos en este nuevo array.
La clase Arrays de la API de Java contiene varios métodos interesantes para manipular arrays, además de los que ya hemos visto para generar una "representación textual (método Arrays)". Entre ellos puedes encontrar utilidades para buscar un valor, ordenar, comparar dos arrays, copiar un array (o parte de él) en otro, etc. Estas son algunas de ellas:
toString, que ya lo conocemos.
fill, para rellenar todo el array (o parte de él) con un determinado valor;
equals, para comparar el contenido de dos arrays;
copyOf, copyRange, para generar una copia del array (o de parte de él).
binarySearch, para llevar a cabo una búsqueda en el array. El array debe estar ordenado para poder realizar la búsqueda con éxito.
sort, para ordenar el array.
Es interesante que le eches un vistazo a estas dos referencias para que observes el potencial que puede ofrecerte. Seguro que más adelante te pueden resultar de gran utilidad:
Cuando estuvimos estudiando las posibilidades de los objetos String en Java, se mencionó el método split y lo dejamos pendiente para más adelante porque aún necesitábamos conocer un par de conceptos previos: las expresiones regulares y los arrays. Ese momento ya ha llegado.
El método split de la clase String permite dividir o trocear una cadena a partir de un separador. Ya vimos una forma de llevar a cabo este proceso mediante el uso de la clase StringTokenizer. Con este método ese proceso se puede simplificar y potenciar porque:
el separador, en lugar de ser una cadena de caracteres fija, es una expresión regular, de manera que podrían especificarse a la vez varios tipos o familias de separadores;
se devuelve directamente el resultado de la división en fragmentos en un array de referencias a objetos String, de manera que con una única ejecución de este método ya tenemos todos los fragmentos o trozos separados en un único contenedor (el array resultado). No hay que ir recorriendo con un bucle cada token como se hacía con la clase StringTokenizer.
Veamos un ejemplo en el que vamos a resolver el mismo problema que se resolvió en apartados anteriores utilizando StringTokenizer: un programa en el que se piden por teclado una serie de palabras separadas por comas y posteriormente deseamos mostrar por pantalla cada una de esas palabras.
System.out.println("Introduzca una lista de palabras separadas por comas: ");
String linea = teclado.nextLine();
String[] lista = linea.split(" *, *");// El separador de elementos será la cadena "," con posibles espacios antes y después
System.out.printf("\nLas palabras son: %s\n", Arrays.toString(lista));
Si en lugar de mostrar el array directamente, preferimos ir recorriendo elemento a elemento mostrándolo en una línea diferente, también podríamos haber hecho:
System.out.println ("Las palabras son:");
for (int i = 0; i < lista.length; i++) {
System.out.printf ("%s\n", lista[i]);
}
Escribe un programa en Java que reciba un texto donde podría haber distintos elementos separados por uno o varios asteriscos ('*'), símbolos de suma ('+') o guiones ('-'), que pueden ir combinados. Es decir, que la cadena "Juan++Antonio-*-Miguel" contendría los siguientes elementos: "Juan", "Antonio" y "Miguel". A partir de ahí se obtendrá cada elemento dentro de un array de String.<br />
Ana reflexiona sobre la gran utilidad que ofrecen los arrays. Ya no solamente cuando interesa almacenar listas sencillas de elementos, sino que es posible manejar arrays bidimensionales, tridimensionales, etc., permitiendo así tener matrices. Así, declarando un array bidimensional, podríamos tener un elemento en una posición a la que se accede indicando dos coordenadas. La manera más fácil de comprenderlo es mediante un ejemplo, así que Ana le pregunta a su tutora, María, que le pone un ejemplo referente a cómo almacenar los distintos puntos de una imagen digital.
¿Qué estructura de datos utilizarías para almacenar los píxeles de una imagen digital?
Normalmente, las imágenes son rectangulares, así que una de las estructuras más adecuadas es la matriz. En esa estructura cada valor podría ser el color de cada píxel. Pero, ¿qué es una matriz en el ámbito de la programación? Pues es un array con dos dimensiones o, lo que es lo mismo, un array cuyos elementos son arrays (en realidad "referencias" a arrays).
Los arrays multidimensionales están en todos los lenguajes de programación actuales, y obviamente también en Java. La forma de declarar y reservar memoria para un array de dos dimensiones en Java es la siguiente:
int[][] a2d = new int[4][5];
El código anterior creará un array de dos dimensiones. En realidad se trata de un array que contendrá 4 arrays de 5 números cada uno. Veámoslo con un ejemplo gráfico:
Al igual que con los arrays de una sola dimensión, los arrays multidimensionales deben declararse y crearse. Podremos construir arrays multidimensionales de todas las dimensiones que queramos y de cualquier tipo. En ellos todos los elementos del array serán del mismo tipo, como en el caso de los arrays de una sola dimensión.
La declaración comenzará especificando el tipo o la clase de los elementos que forman el array, después pondremos tantos corchetes como dimensiones tenga el array y, por último, el nombre del array. Por ejemplo:
int [][][] arrayde3dim ;
La reserva de memoria o creación se consigue igualmente usando el operador new, seguido del tipo y los corchetes, en los cuales se especifica el tamaño (cantidad de elementos) de cada dimensión:
arrayde3dim = new int[2][3][4] ;
Todo esto, como ya has visto en un ejemplo anterior, se puede escribir en una única sentencia:
int[][][] arrayde3dim = new int[2][3][4] ;
Punto que compone una imagen digital.
Estructura matemática compuesta de datos numéricos dispuestos en filas y columnas, similar a una tabla.
Para calcular la cantidad de elementos "finales" de tipo int que contendrá todo el array podemos llevar a cabo los siguientes cálculos:
el primer corchete indica que se trata de un array de "referencias" a otras estructuras. Esas estructuras son de tipo int[][], es decir, se trata de un array de referencias a int[][]. Esa es la primera "dimensión" o "array principal", de tamaño diez. Tiene un tamaño de diez elementos de tipo int[][];
cada uno de los elementos de esa primera dimensión apunta a un array de tamaño once cuyos elementos son referencias a referencias a int (arrays bidimensionales);
los elementos de la segunda dimensión (de tamaño once) son referencias a int, es decir, elementos que apuntan a arrays de tamaño doce (arrays unidimensionales);
los elementos de la tercera dimensión (de tamaño doce) ya sí serán de tipo int.
Por tanto, se trata de un array de diez elementos, cada uno de los cuales apunta a un nuevo array de once elementos (10x11 =110 arrays), cada uno de los cuales a punta a doce elementos de tipo int (110x12 =1320 elementos de tipo int).
En conclusión, el array es de tres dimensiones y apunta a o "contiene" un total de 1320 valores de tipoint.
¿Y en qué se diferencia el uso de un array multidimensional con respecto a uno de una única dimensión?
Pues en muy poco, la verdad. Continuamos con el ejemplo del apartado anterior:
int[][] a2d = new int[4][5];
Para acceder a cada uno de los elementos del array anterior, habrá que indicar su posición en las dos dimensiones, teniendo en cuenta que los índices de cada una de las dimensiones empieza a numerarse en 0 y que la última posición es el tamaño de la dimensión en cuestión menos 1.
Puedes asignar un valor a una posición concreta dentro del array, indicando la posición en cada una de las dimensiones entre corchetes:
a2d[0][0] = 3 ;
Y como es de imaginar, puedes usar un valor almacenado en una posición del array multidimensional simplemente poniendo el nombre del array y los índices del elemento al que deseas acceder entre corchetes, para cada una de las dimensiones del array. Por ejemplo:
Los arrays multidimensionales pueden ser recorridos con bucles de forma similar a como se realizaba para arrays de una dimensión, pero teniendo presente que debemos recorrer, para cada posición del array principal, su array interior. Veamos un ejemplo:
int suma = 0;
for (int i1 = 0; i1 < a2d.length; i1++)
for (int i2 = 0; i2 < a2d[i1].length; i2++)
suma += a2d[i1][i2];
Del código anterior, fíjate especialmente en el uso del atributo length (que nos permite obtener el tamaño de un array). Aplicado directamente sobre el array nos permite saber el tamaño de la primera dimensión (a2d.length). Como los arrays multidimensionales son arrays que tienen como elementos arrays (excepto el último nivel que ya será del tipo concreto almacenado), para saber el tamaño de una dimensión superior tenemos que poner el o los índices entre corchetes seguidos de length (a2d[i1].length).
Para saber el tamaño de una segunda dimensión hay que hacerlo así y puede resultar un poco engorroso, pero gracias a esto podemos tener arrays multidimensionales irregulares.
Una matriz es un ejemplo de array multidimensional regular. ¿Por qué?
Pues porque es un array que contiene arrays de números todos del mismo tamaño. Cuando esto no es así, es decir, cuando los arrays de la segunda dimensión son de diferente tamaño entre sí, se puede decir que es un array multidimensional irregular. En Java se puede crear un array irregular de forma relativamente fácil, veamos un ejemplo para dos dimensiones.
Declaramos y creamos el array pero sin especificar la segunda dimensión. Lo que estamos haciendo en realidad es crear simplemente un array que contendrá arrays, sin decir cómo son de grandes los arrays de la siguiente dimensión:
int[][] irregular=new int[4][];
Después creamos cada uno de los arrays unidimensionales (del tamaño que queramos) y lo asignamos a la posición correspondiente del array anterior:
irregular[0]=new int[4]; // El primer elemento de la primera dimensión apunta un array de tamaño 3
irregular[1]=new int[5]; // El segundo elemento de la primera dimensión apunta un array de tamaño 5
irregular[2]=new int[3]; // El tecer elemento de la primera dimensión apunta un array de tamaño 3
irregular[3]=new int[5]; // El cuarto elemento de la primera dimensión apunta un array de tamaño 4
Cuando uses arrays irregulares, por seguridad, es conveniente que verifiques siempre que el array no sea null en segundas dimensiones, y que la longitud sea la esperada antes de acceder a los datos:
if (irregular[1]!=null && irregular[1].length>10) {...}
Escribe un programa en Java que reserve memoria para un array de dos dimensiones de elementos de tipo int. La primera dimensión será de tamaño cinco, y el tamaño de los elementos de esa segunda dimensión sean a su vez arrays de tamaño uno, tamaño dos, y así hasta el tamaño cinco (para el último elemento). Los elementos finales de tipo int se irán rellenando sucesivamente con los números 1, 2, 3, 4, 5, 6, 7, etc. Esto significa que el tamaño de la segunda dimensión no es regular. Se trata de un array bidimensional "irregular".
Finalmente, se mostrarán por pantalla cada uno de los elementos del array. Cada subarray ocupará una única línea. El resultado final por pantalla debería tener un aspecto similar a este:
1
2 3
4 5 6
7 8 9 10
11 12 13 14 15
5.2.- Inicialización de arrays multidimensionales.
¿En qué se diferencia la inicialización de arrays unidimensionales y de arrays multidimensionales? En muy poco, la inicialización se puede hacer de las mismas formas.
Podemos inicializar un array multidimensional recorriéndolo con dos bucles y asignando un valor a cada posición de los arrays internos. Veamos un ejemplo:
int n=3;
int m=4;
int[][] a2d=new int[n][m];
for (int i=0;i<n;i++)
for (int j=0;j<m;j++)
a2d[i][j]=n*m;
También se puede inicializar un array multidimensional usando las llaves, poniendo después de la declaración del array un símbolo de igual, y encerrado entre llaves los diferentes valores del array separados por comas, con la salvedad de que hay que poner unas llaves nuevas cada vez que haya que poner los datos de una nueva dimensión, lo mejor es verlo con un ejemplo:
El primer array anterior sería un array de 4 por 3 y el siguiente sería un array de 2x2x2. Como puedes observar esta notación a partir de 3 dimensiones ya es muy liosa y normalmente no se usa. Utilizando esta notación también podemos inicializar rápidamente arrays irregulares, simplemente poniendo separados por comas los elementos que tiene cada dimensión:
Es posible que en algunos libros y en Internet se refieran a los arrays usando otra terminología. A los arrays unidimensionales es común llamarlos también arreglos o vectores, a los arrays de dos dimensiones es común llamarlos directamente matrices, y a los arrays de más de dos dimensiones es posible que los veas escritos como matrices multidimensionales. Sea como sea, lo más normal en la jerga informática es llamarlos arrays, término que procede del inglés.
Dado el siguiente array: int[][][] i3d={{{7,1},{0,2}}, {{8,1,3}},{{6,3,4},{0,1,5}}};
¿Cuál es el contenido de las siguientes posiciones? (si consideras que alguna posición no existe, sebes indicarlo con X mayúscula)
Si no tenías claro cómo resolverlo, una forma sencilla de asegurarte del valor de cada posición podría ser ejecutar un pequeño programa que fuera recorriendo todos los elementos posibles:
Recuerda que siempre que utilices la propiedad length en cada array o subarray estarás evitando salirte del rango y, por tanto, la excepción ArrayIndexOutOfBoundsException.
El resultado de ese fragmento de código debería ser algo del estilo a:
Las matrices tienen asociadas un amplio abanico de operaciones (transposición, suma, producto, etc.). En la siguiente página tienes ejemplos de cómo realizar algunas de estas operaciones, usando por supuesto arrays:
Repite el ejercicio del apartado anterior en el que había que escribir un programa en Java que reservara memoria para un array de dos dimensiones de elementos de tipo int. La primera dimensión será de tamaño cinco, y el tamaño de los elementos de esa segunda dimensión sean a su vez arrays de tamaño uno, tamaño dos, y así hasta el tamaño cinco (para el último elemento). Los elementos finales de tipo int se irán rellenando sucesivamente con los números 1, 2, 3, 4, 5, 6, 7, etc. En este caso, inicializa directamente el array al declarar la variable, sin utilizar ningún bucle.
5.3.- Mostrar el contenido de un array multidimensional.
Para mostrar por pantalla un array de más de una dimensión o simplemente para realizar una representación textual que queramos almacenar en una cadena, tendremos que construir una estructura de bucles anidados que vayan recorriendo cada dimensión, uno dentro de otro. Necesitaremos por tanto un bucle por cada dimensión.
Por ejemplo, para recorrer un array de dos dimensiones (bidimensional) regular (todas las filas tienen las mismas columnas), podríamos hacer algo así:
int numFilas= tabla.length;// Tamaño de la primera dimensión (número de filas)int numColumnas= tabla[0].length;// Tamaño de la segunda dimensión: número columnas en primera fila (igual para todas)for(int fila=0; fila<numFilas; fila++){for(int columna=0; columna<numColumnas; columna++){// Mostramos en elemento colocado en la posición [fila][columna]// o bien vamos concatenando fragmentos en una cadena de resultado final}}
Ahora bien, si los elementos de la segunda dimensión (columnas de cada fila) no tienen todos el mismo tamaño (cada fila puede tener un número diferente de columnas) entonces tendremos que recorrer en cada caso un número de columnas diferente y deberemos consultar ese tamaño para cada fila (nombreArray[fila].length):
int numFilas= tabla.length;// Tamaño de la primera dimensión ()for(int fila=0; fila<numFilas; fila++){int numColumnas= tabla[fila].length;// Tamaño de la segunda dimensión (potencialmente diferente para cada fila)for(int columna=0; columna<numColumnas; columna++){// Mostramos en elemento colocado en la posición [fila][columna]// o bien vamos concatenando fragmentos en una cadena de resultado final}}
Otra opción es no utilizar una variable para guardar los tamaños de cada dimensión y consultarlos con el atributo length cada vez que sea necesario, que suele ser lo más habitual:
¿Y qué ocurre si decidimos usar la herramienta Arrays.toString? En este caso la representación textual del array que devuelve este método puede resultarnos algo confusa pues devolverá una cadena con un aspecto similar al siguiente:
[[I@28d93b30, [I@1b6d3586, . . . , [I@1540e19d]
¿Qué significado tiene esto? Ten en cuenta que cada elemento de este array es, a su vez, una referencia a un nuevo array (la siguiente dimensión). Sin embargo, para cada elemento no se nos muestra su contenido (otro array) sino el hash de esa referencia (una marca de identificación de la máquina virtual respecto a su almacenamiento). Si quisiéramos que esa referencia se "expandiera" y se mostrara también una representación textual de los elementos de cada subarray (segunda dimensión) podríamos usar otro método estático proporcionado por la clase Arrays llamado deepToString. Este método sí "profundizará" en todas las subdimensiones o subarrays y generará una representación textual de todas las subdimensiones, sean de los niveles que sean. Por ejemplo, para un array de dos dimensiones podría obtenerse un resultado de este estilo:
Podemos observar que se abre un nuevo corchete para cada "subdimensión" o "subarray" que exista. Es decir, que para llegar a un elemento final (escalar) habrá que pasar por tantos corchetes como dimensiones existan. Para un array de tres dimensiones habría por tanto que pasar a través de tres corchetes para poder llegar a los elementos finales almacenados. Por ejemplo:
Aquí se puede observar que en la posición [0][0][0] hay almacenado un 1, en la [0][0][1] un 2, en la [0][1]0] un 3, en la [1][0][0] un 4, en la [1][1][0] un 5, en la [1][2][0] un 6, y así sucesivamente, tal y como se ha visto en el apartado anterior al explicar el funcionamiento de los arrays multidimensionales.
Ahora, podemos ver un ejemplo donde se rellena primero y se muestra después el contenido de un array de dos dimensiones. En cada celda de este array se almacenará el valor de la suma de las posiciones de cada dimensión. Es decir, en la posición [0][0] se almacenará un 0, en la [0][1] un 1, en la [0][2] un 2, en la [1][0] un 1, en la [1][1] un 2, en la [0][3] un 3, y así sucesivamente.
final int FILAS = 5 ;
final int COLUMNAS = 3 ;
// Declaramos el array y reservamos espacio para sus dos dimensiones
int[][] arrayBidimensional = new int[FILAS][COLUMNAS];
// Rellenamos el array
for (int i=0; i< arrayBidimensional.length; i++)
for (int j=0;j< arrayBidimensional[i].length;j++)
arrayBidimensional[i][j] = i + j ;
// Escribimos el contenido de cada celda del array usando dos bucles anidados
for (int i=0; i< arrayBidimensional.length; i++) {
for (int j=0;j< arrayBidimensional[i].length;j++) {
System.out.print ("[" + i + "][" + j + "] = " + arrayBidimensional[i][j] + " ") ;
}
System.out.println() ;
}
El resultado final de este fragmento de código debería ser algo similar a lo siguiente:
solicite al usuario el número de filas para un array de números enteros;
solicite al usuario, una por una, el número de columnas que se desea que tenga cada fila (cada fila no tiene por qué tener el mismo número de columnas);
según se vaya conociendo el rango de cada dimensión se vaya reservando espacio para esa dimensión o subdmensión;
una vez creada la estructura completa, rellenarla con números enteros comenzando por 1 e incrementando de uno en uno;
una vez relleno el array completo, recorrerlo para mostrar por pantalla todo su contenido escribiendo el contenido de cada fila en una línea diferente;
mostrar también el contenido del array usando los métodos estáticos de la clase Arrays:
Arrays.toString;
Arrays.deepToString.
Aquí tienes un posible ejemplo de ejecución:
Introduzca el número de filas en el array: 5
Introduzca el número de columnas en la fila 0: 2
Introduzca el número de columnas en la fila 1: 3
Introduzca el número de columnas en la fila 2: 6
Introduzca el número de columnas en la fila 3: 1
Introduzca el número de columnas en la fila 4: 4
El contenido del array es:
1 2
3 4 5
6 7 8 9 10 11
12
13 14 15 16
Contenido usando Arrays.toString: [[I@28d93b30, [I@1b6d3586, [I@4554617c, [I@74a14482, [I@1540e19d]
Contenido usando Arrays.deepToString: [[1, 2], [3, 4, 5], [6, 7, 8, 9, 10, 11], [12], [13, 14, 15, 16]]
solicite al usuario el número de filas base para un array de caracteres (entre 3 y 10);
se genere un array bidimensional de caracteres cuyo número de filas sea el doble de ese número de filas "base";
la primera fila contendrá 1 solo elemento (una columna), la segunda 3 elementos (dos más que la anterior), la tercera 5 (dos más que la anterior) y así sucesivamente hasta el número de filas "base", a partir de esa fila, se repetirá otra fila con la misma cantidad de elementos y a continuación se irán teniendo filas con dos elementos menos cada vez, hasta la última fila que contendrá un único elemento;
se rellenarán todas las posiciones de la misma fila con el mismo carácter: la primera fila con la letra 'A', la segunda con la 'B', la tercera con la 'C' y así sucesivamente;
se mostrará el contenido del array usando Arrays.deepToString;
se mostrará el contenido del array en forma de "tabla" usando un doble bucle que recorra primero filas y dentro de cada fila, cada una de las celdas (columnas).
Un ejemplo de ejecución podría ser algo así:
ARRAY 2D EN FORMA DE ROMBO
--------------------------
Introduzca el número de filas "base" (3-10): 3
RESULTADO: ARRAY 2D EN FORMA DE ROMBO
-------------------------------------
Contenido del array usando Arrays.deepToString: [[A], [B, B, B], [C, C, C, C, C], [D, D, D, D, D], [E, E, E], [F]]
Contenido del array mostrado elemento a elemento (doble bucle):
A
B B B
C C C C C
D D D D D
E E E
F
Una vez que hemos conseguido tener adecuadamente relleno el array y mostrar su contenido por pantalla, ¿crees que serías capaz de mostrarlo de una manera más estética dando la apariencia de "rombo" o "diamante"? La salida por pantalla podría tener entonces un aspecto similar al siguiente:
A
B B B
C C C C C
D D D D D
E E E
F
¿Te ves capaz de intentarlo?
5.4.- Aplicaciones de los arrays multidimensionales.
Hasta el momento hemos trabajado con arrays de una y dos dimensiones, junto con algún ejemplo de tres. Pero hemos de tener en cuenta que es posible crear arrays de cuatro, cinco o más dimensiones. Se seguiría el mismo procedimiento.
Los arrays de una dimensión son fáciles de imaginar: una lista de elementos del mismo tipo. Los de dos dimensiones también, aunque aquí ya podemos imaginarlos de dos formas diferentes:
como un array donde cada componente del array es a su vez una referencia a otro array, donde cada uno de esos subarrays no tienen por qué ser del mismo tamaño (un array bidimensional "irregular"). Esa es la definición más precisa y real;
como una "tabla" con filas y columnas. En este caso la "irregularidad" podría indicarse diciendo que cada fila no tiene por qué tener la misma cantidad de celdas o columnas. La primera dimensión del array es donde te puedes "mover" por filas (imaginamos el array en "vertical"), mientras que la segunda dimensión del array es un nuevo subarray para cada fila. En esta segunda coordenada sería donde te podrías "mover" por columnas (imaginamos varios subarrays en "horizontal", uno para cada casilla del array vertical).
Algunos ejemplos de situaciones donde nos podría interesar hacer uso de arrays bidimensionales podrían ser:
estructuras matemáticas matriciales (matrices), con las que se puede operar: sumar matrices, multiplicar matrices, calcular el determinante de una matriz, diagonalizar, etc.;
Almacenamiento y representación de imágenes. Cada celda sería un píxel (un color);
mapas geográficos, topográficos, de juegos etc. donde se utilizan cuadrículas;
juegos donde sea necesario un tablero, cuadrícula, mapa, etc. (crucigramas, sopas de letras, ajedrez, damas, barquitos, buscaminas, etc.);
estructuras jerárquicas de dos niveles. Por ejemplo, un array donde cada elemento de la primera dimensión represente una comunidad autónoma, apuntando a un nuevo array donde cada elemento será un String con el nombre de cada una de las provincias de esa comunidad autónoma. De ese modo, la primera dimensión del array tendrá tamaño 17, mientras que para los arrays de la segunda dimensión, cada uno tendrá un tamaño diferente dependiendo de la cantidad de provincias que haya en cada comunidad. En el caso de Andalucía será ocho, dos para Extremadura, cuatro para Cataluña, una para Murcia, etc.
el ejercicio de las conjugaciones. Un array de tres elementos cada uno de los cuales es otro array con cada conjugación.
El problema puede empezar a complicarse cuando hablamos de arrays tridimensionales, donde estamos hablando ya de tres niveles. Todavía podemos imaginarlos como una figura 3D, pero ya puede costarnos un poco más. Por eso lo mejor es siempre utilizar la primera forma de entender los arrays de varias dimensiones: como arrays cuyos elementos apuntan a arrays cuyos elementos a su vez también apuntan a arrays de algún tipo de elemento. Algunos casos donde nos podría venir bien disponer de arrays tridimensionales podrían ser:
almacenamiento y representación de figuras geométricas en el espacio (tridimensionales), como si estuvieran formadas por pequeños cubos o dados;
registros de información "jerárquicos" de hasta tres niveles. Por ejemplo: guardar un registro temperaturas en un día para cada hora: podría pensarse en un array de una dimensión con 24 celdas de tipo real (una por hora). Si queremos ampliarlo para guardar para todos los días de un mes, podríamos pensar en un array de dos dimensiones de 30x24. Si queremos guardar todo el año (doce meses): un array de 3 dimensiones 12x30x24. Es más, dado que no todos los meses tienen 30 días, podríamos pensar en un array "irregular", si es que vale la pena;
mapas de juegos donde existan varios "planos" o "niveles" de altura. Es decir, como si tuviéramos varios pisos o tableros uno sobre otro;
Es a partir de la cuarta dimensión cuando ya empieza a ser complicado imaginar un array de manera gráfica. Es entonces cuando debemos utilizar la primera forma de imaginar los arrays: como estructuras jerárquicas de distintos niveles, donde el número de niveles será el número de dimensiones del array. Esa manera de representar un array podrá admitir cualquier número de dimensiones sin suponer un problema para nuestra imaginación. Simplemente, se tratará de tener que codificar más o menos bucles para ir recorriendo cada una de las jerarquías (dimensiones) de la estructura (array). En general, se tratará de registros de información "jerárquicos" de hasta n-niveles. Veamos un par de ejemplos de estructuras "n-dimensionales", donde n sería de cuatro en adelante.
guardar un registro de temperaturas:
en un día para cada hora: array de una dimensión con 24 celdas (una por hora) de tipo real;
si queremos guardar para todos los días del mes: array de 2 dimensiones de 30x24;
si queremos almacenar todos los meses del año: array de tres dimensiones: 12x30x24;
si queremos disponer del registro de los últimos 100 años: array de cuatro dimensiones: 100x12x30x24;
si además queremos guardar esa información para 10 ciudades -> array de cinco dimensiones con 10x100x12x30x24 celdas;
y así sucesivamente: países, continentes, etc.;
llevar a cabo un conteo de vehículos que pasan por una calle: por horas (24), por días (30), por meses (12), por calles (xx), por ciudades (yy), por países (zz), etc.
Nosotros, por el momento, no vamos a ir más allá de la tercera dimensión. De hecho, lo habitual será trabajar con arrays de solamente una o de dos dimensiones, pero debes tener en cuenta que en el futuro, durante tu vida profesional, es probable que tengas que enfrentarte con estructuras mucho más complejas. También veremos más adelante, en unidades posteriores, que la mayoría de los lenguajes de programación incorporan herramientas mucho más sofisticadas y más sencillas de utilizar que los arrays para poder trabajar con estructuras complejas: listas, pilas, colas, conjuntos, diccionarios, árboles, grafos, etc.
En español, las formas verbales personales que representan una acción se construyen a partir de la raíz o lexema del infinitivo del verbo (que termina en "ar", "er" o "ir") añadiéndole una desinencia o sufijo que dependerá del tiempo verbal (presente de indicativo, pretérito imperfecto de subjuntivo, etc.) así como de la persona (primera persona del sigular, segunda del plural, etc.).
Por ejemplo, ante el verbo "comer", sabemos que la segunda persona del singular ("tú") del presente de indicativo le correspondería la forma "comes".
Escribe un programa en Java que solicite un verbo regular en castellano (en infinitivo) y lo conjugue en presente de indicativo, indicando cada una de las seis personas (primera, segunda y tercera persona del singular y del plural: yo, tú, él/ella, nosotros/as, vosotros/as y ellos/as).
Para ello, utiliza tres arrays para almacenar las desinencias personales de cada conjugación, que son:
Un ejemplo de salida del programa podría ser algo como:
PROGRAMA PARA CONJUGAR VERBOS REGULARES
---------------------------------------
Introduzca verbo regular: comer
RESULTADO
---------
Conjugación en presente de indicativo del verbo comer:
yo como
tú comes
él/ella come
nosotros/as comemos
vosotros/as coméis
ellos/as comen
Intenta ahora simplificar el programa mediante la creación de un único array de dos dimensiones para almacenar todas las desinencias de las tres conjugaciones.
Sintaxis de las cadenas de formato y uso del método format().
En Java, el método estático format() de la clase String permite formatear los datos que se muestran al usuario o a la usuaria de la aplicación. El método format() tiene los siguientes argumentos:
Cadena de formato. Cadena que especifica cómo será el formato de salida; en ella se mezclará texto normal con especificadores de formato, que indicarán cómo se deben formatear los datos.
Lista de argumentos. Variables que contienen los datos que se formatearán. Tiene que haber tantos argumentos como especificadores de formato haya en la cadena de formato.
Los especificadores de formato comienzan siempre por "%", es lo que se denomina un carácter de escape (carácter que sirve para indicar que lo que hay a continuación no es texto normal, sino algo especial que debe ser interpretado de una determinada manera). El especificador de formato debe llevar como mínimo el símbolo "%" y un carácter que indica la conversión a realizar, por ejemplo "%d".
La conversión se indica con un simple carácter, e indica al método format() cómo debe ser formateado el argumento. Dependiendo del tipo de dato podemos usar unas conversiones u otras. Veamos las conversiones más utilizadas:
Listado de conversiones más utilizadas y ejemplos.
Tipo de conversión
Especificación de formato
Tipos de datos aplicables
Ejemplo
Resultado del ejemplo
Valor lógico o booleano.
"%b" o "%B"
Boolean (cuando se usan otros tipos de datos siempre lo formateará escribiendo true).
Ahora que ya hemos visto alguna de las conversiones existentes (las más importantes), veamos algunos modificadores que se le pueden aplicar a las conversiones, para ajustar como queremos que sea la salida. Los modificadores se sitúan entre el carácter de escape ("%") y la letra que indica el tipo de conversión (d, f, g, etc.).
Podemos especificar, por ejemplo, el número de caracteres que tendrá como mínimo la salida de una conversión. Si el dato mostrado no llega a ese ancho en caracteres, se rellenará con espacios (salvo que se especifique lo contrario):
%[Ancho]Conversión
El hecho de que esté entre corchetes significa que es opcional. Si queremos por ejemplo que la salida genere al menos 5 caracteres (poniendo espacios delante) podríamos ponerlo así:
String.format ("%5d",10);
Se mostrará el "10" pero también se añadirán 3 espacios delante para rellenar. Este tipo de modificador se puede usar con cualquier conversión.
Cuando se trata de conversiones de tipo numéricas con decimales, solo para tipos de datos que admitan decimales, podemos indicar también la precisión, que será el número de decimales mínimos que se mostrarán:
%[Ancho][.Precisión]Conversión
Como puedes ver, tanto el ancho como la precisión van entre corchetes, los corchetes no hay que ponerlos, solo indican que son modificaciones opcionales. Si queremos, por ejemplo, que la salida genere 3 decimales como mínimo, podremos ponerlo así:
String.format ("%.3f",4.2f);
Como el número indicado como parámetro solo tiene un decimal, el resultado se completará con ceros por la derecha, generando una cadena como la siguiente: "4,200".
Una cadena de formato puede contener varios especificadores de formato y varios argumentos. Veamos un ejemplo de una cadena con varios especificadores de formato:
String np = "Lavadora";
int u = 10 ;
float ppu = 302.4f ;
float p = u*ppu ;
String output = String.format("Producto: %s; Unidades: %d; Precio por unidad: %.2f €; Total: %.2f €", np, u, ppu, p);
System.out.println(output);
Cuando el orden de los argumentos es un poco complicado, porque se reutilizan varias veces en la cadena de formato los mismos argumentos, se puede recurrir a los índices de argumento. Se trata de especificar la posición del argumento a utilizar, indicando la posición del argumento (el primer argumento sería el 1 y no el 0) seguido por el símbolo del dólar ("$"). El índice se ubicaría al comienzo del especificador de formato, después del porcentaje, por ejemplo:
int i=10;
int j=20;
String d = String.format("%1$d multiplicado por %2$d (%1$dx%2$d) es %3$d",i,j,i*j) ;
System.out.println(d) ;
El ejemplo anterior mostraría por pantalla la cadena "10 multiplicado por 20 (10x20) es 200". Los índices de argumento se pueden usar con todas las conversiones, y es compatible con otros modificadores de formato (incluida la precisión).
Si quieres profundizar en los especificadores de formato puedes acceder a la siguiente página (en inglés), donde encontrarás información adicional acerca de la sintaxis de los especificadores de formato en Java:
María y Juan han estado trabajando con arrays de diferentes dimensiones y cadenas de caracteres de distintos tipos, así como elaborando patrones mediante expresiones regulares para llevar a cabo búsquedas y comprobaciones. Combinando todas esas herramientas ya se sienten con en el entrenamiento suficiente como para intentar resolver problemas que involucren a una cierta cantidad de información y no solo a unas cuantas variables.
Por supuesto, aún siguen siendo pocas herramientas para trabajar, pero cada vez disponen de un arsenal un poco más amplio para poder enfrentarse a los problemas que sus superiores les van proponiendo. Vamos a ver algunos de ellos.
Un palíndromo consiste en una palabra o frase que se lee igual en un sentido que en otro (por ejemplo, “Ana”, o “Ligar es ser ágil”). Nos han pedido que ideemos una forma sencilla de comprobar si un conjunto de textos son palíndromos o no.
Para ello tendrás que escribir un programa en Java que:
Declare un array de objetos String de entrada. Este array contendrá una serie de textos de ejemplo. Esos textos no contendrán otros caracteres salvo los alfabéticos (mayúsculas y minúsculas, sin acentos ni eñes), el espacio y los signos de puntuación (coma, punto, dos puntos, punto y coma) y nos servirán como entrada para probar nuestro programa. De este modo evitaremos tener que estar tecleando continuamente casos de prueba. Los textos que debe contener el array son:
"Reconocer";
"AMANECER";
"Esto no es un palindromo";
"Dabale arroz a la zorra el abad.";
"A man, a plan, a canal: Panama.";
"A man a plan and a canal, Panama.";
"No deseo ese don...".
Declare un array de boolean para los resultados. Servirá para almacenar en cada posición i si la cadena que hay en la posición i del array anterior es un palíndromo o no.
Reserve en memoria el tamaño apropiado para el array de boolean. No debes indicar el literal 7, sino que debes calcular el tamaño del array de entrada para que si éste cambiara de tamaño no hubiera que modificar nada más.
Recorra cada uno de los textos del array de entrada y los muestre por pantalla cada uno en una línea diferente, numerando cada línea y empezando por 1.
Vuelva a recorrer cada uno de los textos del array de entrada y para cada uno de ellos:
compruebe si se trata o no de un palíndromo, teniendo en cuenta que para este análisis solo se tienen en cuenta las letras y no los espacios o signos de puntuación. Y no olvides que aquí da igual que sean mayúsculas o minúsculas;
si se trata de un palíndromo, almacenaremos en la posición correspondiente del array de resultados el valor true. En caso contrario almacenaremos el valor false.
Muestre por pantalla el contenido de ese array de resultados.
Dado que no hay que introducir datos de entrada y todos los valores son fijos, la ejecución de tu programa siempre deberá proporcionar el mismo resultado. Aquí tienes una muestra de cómo podría quedar:
RECONOCIMIENTO DE PALÍNDROMOS
-----------------------------
Los textos que vamos a analizar son:
-Texto 1: Reconocer
-Texto 2: AMANECER
-Texto 3: Esto no es un palindromo
-Texto 4: Dabale arroz a la zorra el abad.
-Texto 5: A man, a plan, a canal: Panama
-Texto 6: A man a plan and a canal, Panama
-Texto 7: No deseo ese don...
RESULTADOS OBTENIDOS
--------------------
Palíndromos encontrados: [true, false, false, true, true, false, true]
Continuando con la manipulación de cadenas de caracteres, textos y palabras, nos piden ahora que pensemos en cómo podríamos invertir las letras de cada palabra de un texto, generando una lista de palabras invertidas para cada texto.
Para ello tendrás que escribir un programa en Java que:
Declare un array de objetos String de entrada. Este array contendrá una serie de textos de ejemplo. Son los mismos ejemplos que en el ejercicio anterior. Esos textos no contendrán otros caracteres salvo los alfabéticos (mayúsculas y minúsculas, sin acentos ni eñes), el espacio y los signos de puntuación (coma, punto, dos puntos, punto y coma) y nos servirán como entrada para probar nuestro programa. De este modo evitaremos tener que estar tecleando continuamente casos de prueba. Los textos que debe contener el array son:
"Reconocer";
"AMANECER";
"Esto no es un palindromo";
"Dabale arroz a la zorra el abad.";
"A man, a plan, a canal: Panama.";
"A man a plan and a canal, Panama.";
"No deseo ese don...".
Declare un array de String para los resultados. Servirá para almacenar en cada posición i la lista de palabras invertidas (separadas por espacios) correspondiente a cada texto.
Reserve en memoria el tamaño apropiado para el array de String. No debes indicar el literal 7, sino que debes calcular el tamaño del array de entrada para que si este cambiara de tamaño no hubiera que modificar nada más.
Recorra cada uno de los textos del array de entrada y los muestre por pantalla cada uno en una línea diferente, numerando cada línea y empezando por 1.
Vuelva a recorrer cada uno de los textos del array de entrada y para cada uno de ellos:
obtenga cada palabra del texto, teniendo en cuenta que se considera palabra toda acumulación de letras (tanto mayúsculas como minúsculas) hasta que se encuentren espacios o signos de puntuación. Aquí podría venirte muy bien el uso de métodos como trim o split;
se invierta cada una de esas palabras;
se almacenen todas en una misma cadena separándolas por un espacio. Esa cadena resultante se almacenará en el array de textos de resultados en la misma posición en la que se encontrara el texto original en el array de entrada.
Muestre por pantalla el contenido de ese array de resultados, imprimiendo para cada texto primero el original (en una línea) y a continuación el texto con las palabras invertidas (y ya sin signos de puntuación) en la siguiente línea.
Dado que no hay que introducir datos de entrada y todos los valores son fijos, la ejecución de tu programa siempre deberá proporcionar el mismo resultado. Aquí tienes una muestra de cómo podría quedar:
INVERSIÓN DE PALABRAS
---------------------
Los textos que vamos a analizar son:
-Texto 1: Reconocer
-Texto 2: AMANECER
-Texto 3: Esto no es un palindromo
-Texto 4: Dabale arroz a la zorra el abad.
-Texto 5: A man, a plan, a canal: Panama
-Texto 6: A man a plan and a canal, Panama
-Texto 7: No deseo ese don...
RESULTADOS OBTENIDOS
--------------------
Texto 1: "Reconocer"
Palabras invertidas: "reconoceR"
Texto 2: "AMANECER"
Palabras invertidas: "RECENAMA"
Texto 3: "Esto no es un palindromo"
Palabras invertidas: "otsE on se nu omordnilap"
Texto 4: "Dabale arroz a la zorra el abad."
Palabras invertidas: "elabaD zorra a al arroz le daba"
Texto 5: "A man, a plan, a canal: Panama"
Palabras invertidas: "A nam a nalp a lanac amanaP"
Texto 6: "A man a plan and a canal, Panama"
Palabras invertidas: "A nam a nalp dna a lanac amanaP"
Texto 7: "No deseo ese don..."
Palabras invertidas: "oN oesed ese nod"
Estamos analizando la posibilidad de implementar un juego de mesa donde las puntuaciones de cada mano se anotan de la siguiente manera:
en cada mano puede haber entre uno y cuatro turnos;
para cada turno se puede haber ganado o perdido. Si se ha ganado se indica con el símbolo "X" o "x" (letra "equis" mayúscula o minúscula, ambas son válidas). Si no se ha ganado, se anota con el símbolo "O" u "o" (letra "o" mayúscula o minúscula, ambas son válidas);
cada turno ganado supone un punto. Un turno sin ganar supone cero puntos;
al anotarse la puntuación, la información de cada turno debe ir separada del carácter guion ("-");
la puntuación total de una mano se obtiene sumando los puntos de cada turno. Podrá estar por tanto entre cero puntos como mínimo y cuatro como máximo.
Aquí tienes algunos ejemplos cadenas de caracteres que indicarían anotaciones válidas de puntuaciones de distintas manos:
"X", que representaría un único turno en el que se ha ganado. La puntuación sería por tanto de 1;
"O", que indicaría un único turno en el que no se ha ganado. La puntuación sería de 0.
"X-O", con un turno ganado y otro que no. Puntuación de 1;
"X-X-X-X", que indicaría que se han ganado cuatro turnos. Puntuación de 4;
"o-X-x-O", con solo dos turnos ganados de un total de cuatro. Recuerda que debemos ser indiferentes a mayúsculas o minúsculas. Puntuación de 2;
"o-O-o", ningún turno ganado de un total de tres. Puntuación de 0;
Si la anotación de un turno no cumple esas reglas, se considera no válida y tendrá una puntuación de -1.
Se nos ha pedido que ideemos alguna manera de recibir todas las anotaciones de las manos de una partida y que calculemos la puntuación de cada mano.
Para ello tendrás que escribir un programa en Java que:
declare un array de objetos String con el siguiente contenido: {"a", "a-b", "X-A", "O-O-B", "X--X", "O-X-", "-X-X", "O-X-O-X-O", "o", "O-o", "X", "o-x-o", "x", "x-x", "O-x-X", "X-X-X", "x-X-X-x"}. Eso nos servirá como entrada para probar nuestro programa y evitaremos tener que estar tecleando continuamente casos de prueba;
muestre por pantalla el contenido de ese array de entradas (anotaciones de turnos). Para ello lo más sencillo es utilizar la herramienta Arrays.toString;
recorra cada elemento del array (cada anotación) y lo analice para ver si cumple o no con el patrón de anotación válida para un turno:
si cumple con el patrón, se considerará una anotación válida y se calculará su puntuación. La manera más sencilla de hacer esta comprobación es mediante expresiones regulares. Para obtener la puntuación de una anotación tendrás que descomponer cada uno de sus componentes (de tipo X/x o bien O/o) y acumular su valor;
si no cumple con el patrón, se considerará una anotación inválida y su puntuación será de -1;
tanto en un caso como en otro se incluirá la puntuación obtenida en un objeto cadena de tipo StringBuilder que al final del proceso contendrá las puntuaciones de cada anotación separadas por comas;
Una vez explorado el array completo se mostrarán por pantalla los siguientes resultados:
la lista completa de puntuaciones de cada mano separadas por comas. Será el contenido de la cadena StringBuilder que has tenido que ir construyendo durante el proceso;
la cantidad de anotaciones válidas encontradas;
la cantidad de anotaciones no válidas encontradas.
Dado que no hay que introducir datos de entrada y todos los valores son fijos, la ejecución de tu programa siempre deberá proporcionar el mismo resultado. Aquí tienes una muestra de cómo podría quedar:
CÁLCULO DE PUNTUACIONES
-----------------------
Anotaciones obtenidas de cada mano del juego:
[a, a-b, X-A, O-O-B, X--X, O-X-, -X-X, O-X-O-X-O, o, O-o, X, o-x-o, x, x-x, O-x-X, X-X-X, x-X-X-x]
RESULTADO: PUNTUACIONES CALCULADAS
----------------------------------
Puntuaciones: -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 1, 1, 1, 2, 2, 3, 4
Número de anotaciones válidas: 9.
Número de anotaciones inválidas: 8.
Un almacén de ropa identifica sus productos siguiendo un código formado por números y letras, el cual permite reconocer el tipo de cliente al que van dirigidos, un identificador y su origen de fabricación. Su estructura es la siguiente:
una letra que indica el tipo de cliente ('H' - Hombre, 'M' - Mujer o 'B' - Bebé);
un identificador de cinco dígitos numéricos;
una letra que indica el origen de fabricación del producto: 'E' (Europa) o 'A' (Asia);
un código de control (dos dígitos). Se formará sumando todos los dígitos numéricos del identificador. Si la suma resultante es de una sola cifra se antepondrá el 0.
Las letras solo pueden ser mayúsculas. Si hay alguna letra minúscula, el código se considera inválido.
Dado un array con los siguientes códigos de producto:
Desarrolla un programa en Java que identifique cuántos productos del array tienen un patrón válido y, de ellos, cuántos contienen códigos de control válidos y cuántos inválidos. Por último, para los productos válidos, identifica de qué tipo de cliente se trata (hombre, mujer, bebé) y su origen (Europa o Asia).
Puesto que no hay entrada de datos (la entrada es el propio array), la salida debe ser similar a la siguiente:
ANÁLISIS DE CÓDIGOS DE PRODUCTOS TEXTILES
-----------------------------------------
Se analizarán los siguientes códigos de producto:
[H12345E15, M00000E00, A12345E15, H12345E11, B00011A02, B00011A11, h12345E15, B1111A04, B111111A06]
RESULTADO
---------
H12345E15: Prenda de hombre. Fabricación: Europa.
M00000E00: Prenda de mujer. Fabricación: Europa.
A12345E15: Patrón NO válido.
H12345E11: Código inválido.
B00011A02: Prenda de bebé. Fabricación: Asia.
B00011A11: Código inválido.
h12345E15: Patrón NO válido.
B1111A04: Patrón NO válido.
B111111A06: Patrón NO válido.
Número de productos con patrón válido:
5 productos (3 con código de control válido y 2 inválido)
Una empresa de informática identifica sus productos siguiendo un código formado por números y letras el cual permite conocer el tipo de producto, su identificador y su estado. El código de los productos se construye de la siguiente manera:
una letra que indica la disponibilidad del producto: S (en stock) o A (agotado);
Una letra que indica el tipo de producto (T - Tablet, P - Portátil o M - Móvil);
Un identificador de cuatro dígitos numéricos;
un código de control (una letra). Se formará aplicando la operación módulo 10 a la suma de los cuatro dígitos numéricos del identificador, por lo que se obtendrá una letra según el resultado de esta operación: 0 será la A, 1 será la B, etc. Hasta el 9, que será la J.
Las letras pueden ser mayúsculas o minúsculas. En ambos casos se trata de un código válido.
escribe un programa en Java que identifique cuántos productos tienen un patrón válido y, de ellos, cuántos contienen códigos de control válidos y cuántos inválidos. Por último, para los patrones válidos, identifica de qué tipo de producto se trata (móvil, tablet o portátil) y si están "Agotados" o "En stock".
Puesto que no hay entrada de datos, la salida debe ser similar a la siguiente:
ANÁLISIS DE CÓDIGOS DE PRODUCTOS INFORMÁTICOS
---------------------------------------------
Se analizarán los siguientes códigos de producto:
[SP1234A, aM5544I, AO5925N, O26232A, AT5425G, SM4285J, sp1599A, SP12341B, SP111C]
RESULTADO
---------
SP1234A: Portátil (En stock)
aM5544I: Móvil (Agotado)
AO5925N: Patrón NO válido
O26232A: Patrón NO válido
AT5425G: Tablet (Agotado)
SM4285J: Móvil (En stock)
sp1599A: Código inválido
SP12341B: Patrón NO válido
SP111C: Patrón NO válido
Número de productos con patrón válido:
5 productos (4 con código válido y 1 con código inválido)
Estamos modelando una aplicación para simular el juego del bingo y nos ha tocado implementar un comprobador de líneas para los cartones. Los cartones de nuestro bingo tienen una estructura de nueve filas por cuatro columnas con diecinueve números en cada cartón.
Vamos a modelar los cartones usando un array de dos dimensiones de números enteros donde la primera dimensión representará las filas del cartón. Será por tanto un array base de nueve elementos (la primera dimensión). Al tratarse de un array bidimensional, los elementos de la primera dimensión serán a su vez referencias a otros arrays cuyo tamaño máximo será de cuatro, pues estarán representando a cada una de las columnas o elementos que hay en cada fila. Estos arrays sí albergarán en su interior números enteros. No todos los arrays de la segunda dimensión serán de tamaño cuatro, pues no todas las filas del cartón contienen los cuatro elementos, tal y como puedes apreciar en la figura de ejemplo.
Esto significa que no vamos a tener un array "bidimensional regular" con nueve subarrays de cuatro elementos cada uno en forma de rectángulo "perfecto", pues algunos tendrán cuatro elementos, pero otros tendrán menos. No se trata de una tabla de nueve por cuatro con treinta y seis elementos. Por tanto tendrás que declarar el array apropiadamente para rellenarlo con la disposición que aparece en la ilustración de ejemplo.
Por otro lado, la representación de las bolas que han salido del bombo hasta el momento actual la modelaremos mediante un array unidimensional de números enteros. Vamos a suponer que las bolas que han salido hasta el momento son las siguientes: 1, 2, 5, 10, 11, 12, 14, 15, 22, 55, 56, 57, 59, 60, 61, 66, 78, 89, 90.
El programa deberá generar a partir de esa información un array de String que contenga un elemento por cada línea. Si esa línea ya se ha completado, se colocará el texto "línea" en ese elemento. Si aún faltan números por tachar, entonces se colocará el texto "no".
Para implementar nuestro prototipo tendrás que escribir un programa en Java que lleve a cabo las siguientes operaciones:
Declarar un array unidimensional de números enteros que represente las bolas que han salido hasta el momento. Contendrá la lista de números que se nos ha indicado.
Declarar un array bidimensional de números enteros para representar a un cartón específico con las normas que se nos han explicado. Los números que contendrá el array serán los indicados en la estructura irregular de nueve filas y cuatro columnas de la ilustración anterior.
Declarar un array unidimensional de String para almacenar los resultados (si se ha producido línea o no).
Mostrar por pantalla el contenido del cartón. La forma más sencilla es mediante la herramienta Arrays.deepToString.
Mostrar por pantalla la lista de bolas que han salido hasta el momento. La forma más sencilla es mediante la herramienta Arrays.ToString.
Reservar espacio para el array de resultados. Tendrá que ser del mismo tamaño que la cantidad de filas del cartón. Procura no usar un literal.
Recorrer cada fila del cartón (primera dimensión del array bidimensional):
para cada fila, habrá que recorrer todos los números que contenga (segunda dimensión del array) y comprobar si ya han salido o no;
si han salido todos, se marca esa fila como "línea" en el array de resultados;
si no han salido todos, se "marcará" como "no".
Mostrar por pantalla el array de resultados indicando las líneas que se han marcado y cuántas son.
Dado que no hay que introducir datos de entrada y todos los valores son fijos, la ejecución de tu programa siempre deberá proporcionar el mismo resultado. Aquí tienes una muestra de cómo podría quedar:
BUSCADOR DE LÍNEAS EN UN CARTÓN DE BINGO
----------------------------------------
Cartón: [[1, 2, 5, 9], [11, 15], [22, 29], [34], [47, 49], [55, 59, 60], [61], [71, 75], [88, 90]]
Números que ya han salido: [1, 2, 5, 10, 11, 12, 14, 15, 22, 55, 56, 57, 59, 60, 61, 66, 78, 89, 90]
RESULTADO
---------
Resultado de la búsqueda de líneas en el cartón de bingo:
[no, línea, no, no, no, línea, línea, no, no]
Número de líneas obtenidas: 3
Nuestro siguiente encargo está relacionado con el famoso pasatiempo de las "Sopas de letras". Nos han pedido que ideemos un mecanismo para averiguar si las palabras contenidas en una lista se encuentran o no en una determinada sopa. Para simplificar, solo vamos a realizar búsquedas en horizontal y de izquierda a derecha.
Para representar la sopa de letras vamos a usar un array bidimensional de char de tamaño 5x5. Esta vez sí será completamente regular (un rectángulo "perfecto"). El contenido será el de la ilustración adjunta. Las letras en la sopa estarán todas en mayúsculas. Numeraremos las filas y las columnas partiendo del cero, de manera que irán desde 0 hasta 4.
La lista de palabras será representada mediante un array de String. Las palabras de la lista serán "Hola", "sol", "AMOR", "ARA", "Casa", "dos". Como puedes observar a veces se usan mayúsculas y en otras ocasiones minúsculas. Nuestro programa no debería tener problema con eso.
El programa deberá generar a partir de esa información un array de String del mismo tamaño que la lista de palabras. En cada String de ese array se almacenará lo siguiente:
el texto "no", si la palabra no se encuentra horizontalmente en la sopa;
el texto "fila xxx columna yyy", si la palabra sí se encuentra horizontalmente en la sopa, en la fila xxx y columna yyy.
Para implementar nuestro prototipo tendrás que escribir un programa en Java que lleve a cabo las siguientes operaciones:
Declarar un array unidimensional de String que represente la lista de palabras que se desean buscar en la sopa. Contendrá la lista de palabras que se nos ha indicado.
Declarar un array bidimensional de caracteres (tipo char) para representar la sopa de letras. Los caracteres que contendrá el array serán las letras indicadas en la ilustración anterior.
Declarar un array unidimensional de String para almacenar los resultados.
Mostrar por pantalla el contenido de la sopa de letras en forma de tabla. La forma más sencilla es mediante un doble bucle que recorra filas y columnas.
Mostrar por pantalla la lista de palabras que van a ser buscadas en la sopa. La forma más sencilla es mediante la herramienta Arrays.ToString.
Reservar espacio para el array de resultados. Tendrá que ser del mismo tamaño que la cantidad de palabras que van a ser buscadas. Procura no usar un literal.
Explorar la sopa de letras para buscar horizontalmente cada una de las palabras de la lista. Para cada palabra, se almacenará en el array de resultados:
el texto "no", si la palabra no se encuentra horizontalmente en la sopa;
el texto "fila xxx columna yyy", si la palabra sí se encuentra horizontalmente en la sopa, ubicada en la fila xxx, columna yyy.
Mostrar por pantalla el array de resultados indicando las palabras que se han encontrado y cuántas son.
Dado que no hay que introducir datos de entrada y todos los valores son fijos, la ejecución de tu programa siempre deberá proporcionar el mismo resultado. Aquí tienes una muestra de cómo podría quedar:
BUSCADOR DE PALABRAS EN HORIZONTAL
----------------------------------
Sopa de letras:
H J S O L
O E C A U
L Y K D A
A A M O R
V C A S A
Lista de palabras de prueba:
[Hola, sol, AMOR, ARA, Casa, dos]
RESULTADO
---------
Resultado de la búsqueda horizontal:
[no, fila 0 columna 2, fila 3 columna 1, no, fila 4 columna 1, no]
Número de palabras encontradas: 3
Las butacas de un teatro tienen la siguiente disposición en forma de triángulo:
primera fila (la más cercana al escenario): cuatro butacas;
segunda fila: seis butacas;
tercera fila: ocho butacas;
y así sucesivamente hasta la última fila, aumentando dos butacas más en cada fila.
El precio de las butacas tendrá la siguiente estructura:
las más baratas serán las de la última fila, a 10,50 euros, salvo las de los dos extremos, que costarán cincuenta céntimos menos (10,00);
según vayamos avanzando filas, acercándonos al escenario, el precio de la butaca se incrementará en cincuenta céntimos, teniendo en cuenta que la butaca de cada extremo es siempre cincuenta céntimos más barata que el precio general de esa fila.
Debes implementar un programa en Java que:
En su entrada de datos:
solicite por teclado el número de butacas del teatro (entre 2 y 50). Hasta que no se introduzca una entrada válida el programa insistirá hasta que se introduzca correctamente. El programa no puede romperse porque se introduzcan caracteres incompatibles con los números enteros;
reserve espacio en memoria para un array bidimensional de números reales con la estructura de butacas de ese teatro: la primera fila con cuatro elementos, la segunda fila con seis elementos, la tercera con ocho, etc. Como puedes imaginar, no se trata en absoluto de un array bidimensional "regular" en forma de tabla, pues cada fila tiene un número diferente de columnas;
una vez que dispongamos de ese array, que estará vacío, muestre por pantalla su contenido y estructura utilizando la herramienta Arrays.deepToString.
En su procesamiento:
rellene el array de butacas introduciendo en cada casilla el precio correspondiente a cada butaca.
En su salida de resultados:
recorra el array de butacas ya relleno y muestre por pantalla, fila por fila, comenzando por la más cara y cercana al escenario, la estructura y precios de todas las butacas. Los precios de las butacas en cada fila deben aparecer con dos decimales y separados por un espacio.
Aquí tienes un ejemplo de ejecución:
BUTACAS DE UN TEATRO
--------------------
Introduzca el número de filas (2-50): x
Introduzca el número de filas (2-50): 1
Introduzca el número de filas (2-50): -1
Introduzca el número de filas (2-50): 51
Introduzca el número de filas (2-50): 2
Reservado array de 2 filas, cada fila con una cantidad de elementos diferente:
[[0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]]
RESULTADO: ESTRUCTURA DE BUTACAS Y PRECIOS POR FILAS
----------------------------------------------------
Fila 1 ( 4 butacas): 10,50 11,00 11,00 10,50
Fila 2 ( 6 butacas): 10,00 10,50 10,50 10,50 10,50 10,00
Y aquí otro:
BUTACAS DE UN TEATRO
--------------------
Introduzca el número de filas (2-50): 5
Reservado array de 5 filas, cada fila con una cantidad de elementos diferente:
[[0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], ... ]
RESULTADO: ESTRUCTURA DE BUTACAS Y PRECIOS POR FILAS
----------------------------------------------------
Fila 1 ( 4 butacas): 12,00 12,50 12,50 12,00
Fila 2 ( 6 butacas): 11,50 12,00 12,00 12,00 12,00 11,50
Fila 3 ( 8 butacas): 11,00 11,50 11,50 11,50 11,50 11,50 11,50 11,00
Fila 4 (10 butacas): 10,50 11,00 11,00 11,00 11,00 11,00 11,00 11,00 11,00 10,50
Fila 5 (12 butacas): 10,00 10,50 10,50 10,50 10,50 10,50 10,50 10,50 10,50 10,50 10,50 10,00
Añade una línea de código para que se muestre también el contenido del array usando la herramienta Arrays.deepToString.
Intenta ahora que el contenido de la tabla se muestre por pantalla en forma de "triángulo", tal y como sería la disposición física de las butacas en el teatro.
Materiales desarrollados inicialmente por el Ministerio de Educación, Cultura y Deporte y actualizados por el profesorado de la Junta de Andalucía bajo licencia Creative Commons BY-NC-SA.
Antes de cualquier uso leer detenidamente el siguenteAviso legal
Ubicación: Toda la unidad Mejora (tipo 3): Reestructurar la unidad completa incluyendo más secciones para explicar mejor manipulación y el trabajo con cadenas, expresiones regulares y arrays. Añadir algunos apartados para ampliar las explicaciones y sobre todo enriquecerl con ejemplos y ejercicios resueltos según se vaya explicando.
Dado que la unidad toca tres elementos esenciales: cadenas, expresiones regulares y arrays, separaría cada uno de esos elementos en un apartado principal (o dos, según la cantidad de subapartados). Al menos debería haber un apartado(o dos)para cadenas,otro para expresiones regulares(aunque en el fondo se trate también de cadenas, puede considerarse con suficiente entidad), uno para arrays y otro para arrays multidimensionales.
En el caso de las cadenas: se puede mantener el contenido de los primeros subapartados, pero habría que buscar títulos algo más descriptivos (en lugar de operaciones avanzadas (I), (II), etc). En el caso del (III) explicar algo mejor y con más ejemplos la conversión de números a texto y viceversa. Quizá incluso pueda dejarse para más adelante, tras ver más operaciones sobre cadenas.
Incluir un apartado dedicado al formateado de cadenas con String.format donde se haga referencia a formatos % del printf y luego se redirija directamente al anexo que ya existe.
Apartado (IV):convertirlo en un apartado con subapartados con al menos un ejemplo de uso de cada método presentado. Si no, se convierte simplemente en un catálogo de métodos que tiene poca utilidad. Llenarlo de ejemplos y ejercicios resueltos. Cada método debe disponer de al menos uno (y en el peor caso al menos de un ejercicio propuesto). No se puede despachar el contenido más importante de las cadenas con una simple pregunta de autoevaluación. El alumnado necesita ejemplos concretos para poder enfrentarse luego a los ejercicios de las tareas con más “munición”.
Apartado (V):renombrarlo haciendo referencia a otras clases en Java para representar o almacenar cadenas (StringBuilder y otras). No vendría mal algún subapartado para poder incluir algunos ejemplos de uso de algunos métodos. De hecho, quizá este subapartado podría pasar a la categoría de apartado hablando de “Representación de las cadenas de caracteres en Java”. Aquí podría distinguirse entre String y StringBuilder (un apartado para cada una) y un tercer apartado sobre otras clases de cadena (para hacer referencia a StringBuffer y otras).
En el apartado dedicado a String es donde podría hablarse de su inmutabilidad y demás, una vez que se ha aprendido a trabajar con cadenas y así no empezar con esas explicaciones, que al inicio pueden resultar confusas al alumnado y tampoco le aportan demasiado desde el punto de vista práctico. Eso podría explicarse ahora, después de haber estado haciendo muchos ejemplos, pero no al principio, dado que tiene poca utilidad práctica.
Apartado de expresiones regulares con más ejemplos de uso tanto del matches como del find y el group, pero especialmente del match.
Incluir más ejemplos de elaboración de patrones para que el alumnado sepa cómo plantearse el uso de los distintos elementos de las expresiones regulares. Tal y como está ahora, es imposible aprender a construir expresiones regulares solo con lo que hay en los contenidos. Hay que proporcionarle al alumnado ejemplos en los foros, hacer ejercicios con ellos, clases online, etc. Hay que enriquecer esos apartados con ejemplos y ejercicios resueltos.
Hacer referencias a herramientas web de análisis de expresiones regulares, resolver algún ejercicio usándolas.
Plantearse si sería conveniente incluir alguna pequeña introducción a los “asertos” o “aserciones” (“assertions”) (expresiones anticipadas, condicionales, inversas, etc) del estilo de x(?=y), x(?!y), (?=y)x, (?!y)x. Podría al menos dedicarse un nuevo subapartado con algunos ejemplos simples y alguna referencia en un Para saber más.
Explicar el método matches() clase String, dado que en el apartado de cadenas aún no se podía (no se habían visto aún las expresiones regulares). Podría incluirse justo como sub-sub-apartado de las primeras explicaciones de Pattern/Matcher como alternativa a no tener que crear esa pareja de objetos si lo único que queremos hacer es comprobar si se “encaja” (match) con patrón.
Apartado 3. Restructurar subapartados con algo como: declaración,reserva de memoria,inicialización, gestión y manipulación, mostrar contenido de un array. Todo ello con ejemplos y ejercicios resueltos bien integrados entre las explicaciones para poder llevar a cabo el principio de “aprender haciendo”.
Incluir subapartado dedicado al método split() clase String,pues en su momento no se habían explicado ni los arrays ni las expr reg.
El apartado 4 podría plantearse de un modo similar al 3,pero trabajando con arrays multidimensionales. Ejemplos de arrays de 3 y de más de 3 dim.
Anexo ejercicios resueltos con al menos un ejercicio de cadenas,otro de expresiones regulares, otro de arrays unidim, otro de arrays bidim reg y otro de arrays bidim irreg.
Ubicación: En la parte de arriba. Mejora (Mapa conceptual): Arriba pone PROG05, cuando debería de poner PROG04.
Saludos.
Ubicación: Índice Mejora (Orientaciones del alumnado): Se ha actualizado el índice con los nuevos nombres de los apartados.
Versión: 02.03.00
Fecha de actualización: 05/12/21
Autoría: Diosdado Sánchez Hernández
Ubicación: 3.2.- Inicialización Mejora (tipo 1): En este párrafo: En el siguiente ejemplo puedes ver la inicialización de un array de tres números, y la inicialización de un array con tres cadenas de texto
se dice “tres” cuando hay “siete” cadenas realmente.
Cambiar tres por siete
Ubicación: 3.2.- Inicialización Mejora (tipo 2): Reescribir el párrafo que hace referencia a cómo inicializar arrays que contengan referencias a objetos indicando que hay que llevar a cabo una instanciación salvo en el caso de los objetos String, en cuyo caso Java permite escribir literales de esos objetos en el código sin llamar a ningún constructor.
Escribir una forma alternativa a la inicialización del array de StringBuilder y añadir un ejemplo de inicialización de algún otro tipo de array de referencias a objetos, por ejemplo de objetos LocalDate.
Versión: 02.02.00
Fecha de actualización: 26/11/20
Autoría: José Javier Bermúdez Hernández
Ubicación: Todo el contenido Mejora (tipo 1): Cambiados los enlaces que se abrían en la misma ventana para que se abran en ventana nueva, así como las imágenes ampliables.
Ubicación: 4.3.- Mostrar contenido de un array multidimensional Mejora (tipo 2): Añadir un nuevo apartado 4.3 donde se explique paso a paso y detalladamente cómo mostrar por pantalla el contenido de un array de más de una dimensión utilizando un bucle o bien haciendo uso del método estático Arrays.toString y observando el problema que podemos encontrar si el array es de más de una dimensión. Explicar entonces el funcionamiento del método Arrays.deepToString.
Incluir un Para saber más con un enlace la documentación javadoc del método deepToString de la clase Arrays.
Incluir al menos un ejercicio resuelto donde se muestre por pantalla el contenido de un array de más de una dimensión utilizando diversos métodos (recorrido por bucles, método Arrays.toString).
Incluir un ejercicio resuelto donde haya que crear un array de dos dimensiones irregular, es decir, donde cada fila no tenga necesariamente el mismo número de columnas. En la solución, explicar el proceso de reserva de memoria paso a paso, realizando un new inicial para las filas y luego un new específico para las columnas de cada fila (esto incluiría a otra sugerencia que indica hacer este ejercicio dentro del apartado 4.3).
Ubicación: 3.3.- Mostrar contenido de un array Mejora (tipo 2): Crear un nuevo apartado 3.3 donde se explique paso a paso y detalladamente cómo mostrar por pantalla el contenido de un array unidimensional utilizando un bucle o bien haciendo uso del método estático Arrays.toString.
Incluir un Para saber más con un enlace la documentación javadoc del método toString de la clase Arrays.
Incluir al menos un ejercicio resuelto donde se muestre por pantalla el contenido de un array utilizando diversos métodos (recorrido por bucles, método Arrays.toString).
Ubicación: 3.1.- Uso de arrays unidimensionales Mejora (tipo 1): Eliminar el destacado que dice En los métodos y funciones Java las variables se pasan por copia donde se incluye un ejemplo de implementación de un método que modifica los elementos de un array que se le pasa como parámetro, pues en esta unidad aún no se saben implementar métodos en una clase. Eso se verá en la siguiente unidad (unidad 5 sobre implementación de clases). Se recomienda pasar ese bloque de contenido a la unidad 5.
Versión: 02.01.00
Fecha de actualización: 10/12/13
Autoría: José Javier Bermúdez Hernández
Se ha añadido alguna autoevaluación, se ha añadido un par de párrafos explicativos sobre las diferencias entre los distintos tipos de String en Java, y se han modificado los tres ejemplos de ordenación, inserción y borrado en un array ordenado empaquetado.
Versión: 02.00.00
Fecha de actualización: 21/11/13
Autoría: José Javier Bermúdez Hernández
Debido a la reestructuración del temario, esta unidad no es la misma que la original. En esta nueva se ven los arrays, Strings y expresiones regulares.