Fundamentos de Programación - us
Transcript of Fundamentos de Programación - us
FUNDAMENTOS DE PROGRAMACIÓN
Versión: 0.0.7
Tema 8. Iterables: Implementación Autor: Miguel Toro Bonilla
Revisión: Jorge García Gutiérrez, Antonia M. Reina
Tiempo estimado: 4 horas
1. Introducción ................................................................................................................................................ 1
2. Iterables e iteradores .................................................................................................................................. 1
3. Iterables Virtuales (ejemplos) .................................................................................................................... 4
4. Iterables basados en iterables (ejemplos) ................................................................................................. 7
5. Lectura de ficheros: Scanner e Iterables2.fromFile ................................................................................. 12
6. Ejercicios .................................................................................................................................................... 14
1. Introducción
En temas anteriores, vimos que es muy importante la reutilización para conseguir un software de calidad.
Esta reutilización es, también, muy importante en el diseño de las soluciones a los problemas, ya que
muchas de ellas siguen un mismo patrón.
Los patrones de software son soluciones probadas para determinados tipos de problemas que se
presentan a menudo en distintos contextos, pero con características similares. Estos patrones de software
tienen muchas ventajas:
Están probados, porque han sido usados en múltiples problemas anteriormente.
Son reutilizables, porque los problemas que resuelven aparecen repetidos múltiples veces en las
aplicaciones.
Son expresivos, porque establecen unos conceptos y, en muchos casos, una notación, que permite
plantear la solución a un problema concreto como un caso particular de una solución general.
La factoría de un tipo, vista ya en temas anteriores, es un patrón de diseño. El patrón que presentamos en
este tema y, cuyo uso está muy extendido, es el patrón Iterator. Además, hablaremos del tipo Iterable que
define objetos que actúan como factorías de objetos Iterator.
2. Iterables e iteradores
Una tarea habitual en programación es realizar recorridos sobre todos los elementos de un agregado de
objetos y hacer diversos tratamientos con ellos (por ejemplo, los vistos en el tema sobre tratamientos
secuenciales). Para poder reutilizar el código de los diversos tratamientos es necesario que todos los
agregados de datos sean vistos desde el exterior de forma similar. Es decir, que todos los agregados de datos
ofrezcan el mismo contrato a sus posibles clientes. La solución a este problema es que cada agregado de
2 Fundamentos de Programación
datos implemente el contrato Iterable. En seguida veremos los detalles de este contrato, pero antes,
veamos las ventajas que ofrece.
En primer lugar, si un agregado de datos ag implementa el contrato Iterable<T> entonces el agregado puede
ser recorrido mediante un for extendido.
…
for (T e : ag) {
mostrar(e);
}
…
El código anterior es el mismo para cualquier agregado que implemente Iterable<T> y, como hemos visto en
temas anteriores, la sentencia for recorre cada elemento del agregado y termina cuando los haya recorrido
todos. En cualquier caso, para que un agregado de datos pueda recorrerse mediante un for extendido debe
ofrecer la interfaz Iterable (con la excepción de los arrays, como ya comentamos anteriormente).
El tipo Iterable
El tipo Iterable<T> es una factoría de objetos de tipo Iterator<T>. El tipo Iterable<T> viene definido por la
interfaz:
public interface Iterable<T> { Iterator<T> iterator(); }
El método iterator() devuelve un iterador que describe cómo se recorre el agregado que implementa este
tipo Iterable.
El tipo Iterator
Cada agregado de datos puede tener diversas formas de recorrer los objetos que lo componen. Un objeto
que implementa el tipo Iterator<T> tiene como responsabilidad gestionar un recorrido concreto de un
agregado. Es decir, debe proporcionar cada uno de los elementos del recorrido de forma consecutiva y en el
orden especificado por el mismo.
Por cada recorrido de interés, el agregado debe implementar un iterador (un objeto que implementa el tipo
Iterator<T>). El tipo Iterator<T> viene definido en la interfaz:
public interface Iterator<T> { boolean hasNext(); T next(); void remove(); }
3 8. Iterables: Implementación
Como hemos indicado, los objetos que implementan este tipo (iteradores) tienen como responsabilidad
proporcionar un primer elemento del recorrido que tienen asociado y, posteriormente, los siguientes
elementos del mismo. Además, en cada momento deben indicarnos si quedan más objetos por recorrer o
no. El significado de los métodos de la interfaz está íntimamente relacionado con estas necesidades:
boolean hasNext(): Devuelve true si aún quedan elementos por recorrer y false en caso contrario.
T next(): La primera vez que se le llama devuelve el primer elemento. En cada llamada posterior
devuelve el siguiente elemento del recorrido. Este método tiene como precondición que hasNext()
devuelva true. Si no se cumple la precondición, entonces elevará la excepción NoSuchElementException.
void remove(): Elimina del agregado el último elemento devuelto por el método next.
En general, definiremos iteradores que implementen recorridos puros que no modifiquen el agregado. Los
objetos de este tipo son objetos del tipo Iterator<T> que no tienen disponible la operación remove. Si se
invoca esta operación, se elevará la excepción UnsupportedOperationException.
Funcionamiento del tipo Iterable
Para comprender el funcionamiento del tipo Iterable<T> veamos la forma de traducir una sentencia for
extendido a otra con for clásico o while (ejemplo 1). Supongamos que el objeto ag implementa Iterable<T>.
Entonces los tres segmentos de código (con for extendido, for clásico y while) son equivalentes. De hecho, un
for extendido es siempre desplegado a su versión clásica en una etapa previa al compilado del código.
Teniendo esto en cuenta podemos ver que la sentencia de control for extendido obtiene un iterador del
agregado en primer lugar, que al implementar Iterable<T> es una factoría de los mismos. Usando el iterador,
el bucle for entra en una secuencia de iteraciones mientras que el método hasNext devuelva true. En cada
iteración obtiene el elemento siguiente llamando al método next del iterador.
Ejemplo 1:
…
for (T e : ag) {
mostrar(e);
}
…
…
for (Iterator<T> it = ag.iterator();
it.hasNext(); ) {
T e = it.next();
mostrar(e);
}
…
…
Iterator<T> it = ag.iterator();
while (it.hasNext()) {
T e = it.next();
mostrar(e);
}
…
Como vemos en la sentencia con el for extendido, el iterador se usa internamente, pero no se muestra
públicamente. Es por esto que si un agregado quiere participar en un for extendido, debe implementar el
tipo Iterable<T>. Como último comentario, se debe tener en cuenta que siempre se crea un iterador nuevo
al inicio de cada sentencia for extendido.
Pasos para implementar un objeto Iterable
Para implementar un tipo de objeto que sea Iterable<T>, debemos dar los siguientes pasos:
1. Definir una clase que implemente la interfaz Iterable<T>, siendo T el tipo de los elementos que se
van a recorrer.
4 Fundamentos de Programación
2. Para cada tipo de recorrido que queramos permitir, crearemos una clase interna, la cual debe
implementar Iterator<T>. Igual que dentro de una clase podemos declarar un atributo o un método,
también podemos incluir otra clase u otra interfaz. A las clases incluidas dentro de otras se las
denomina clases internas y pueden tener visibilidad pública o privada.
3. Cada posible iterador debe tener un estado para mantener la posición actual del recorrido. Ese
estado se concretará en atributos de la clase interna correspondiente. El constructor de la misma
debe inicializar el estado del iterador.
4. Los métodos hasNext() y next() de dicha clase interna deben implementarse de acuerdo al tipo de
recorrido que se esté llevando a cabo. El método hasNext(), como hemos visto anteriormente, es un
método observador que nos indica si hay, o no, más elementos en el agregado. El método next()
tiene como precondición que hasNext() sea verdadero. Si se cumple que existe elemento disponible,
devuelve dicho elemento del agregado y prepara el estado del iterador para poder devolver el
siguiente.
5. El método remove() contendrá el código adecuado para eliminar del agregado el último elemento
devuelto por el método next(). Si no queremos permitir el uso de este método el cuerpo del mismo
elevará la excepción UnsupportedOperationException.
6. Finalmente, definiremos el método iterator() en la clase contenedora. Dicho método devolverá un
nuevo objeto de la clase interna oportuna. Si hay varios recorridos posibles diseñaremos varias
clases internas. En todo caso, el método iterator() se diseñará como una factoría para devolver un
objeto creado desde alguna de las clases internas.
3. Iterables Virtuales (ejemplos)
En este apartado nos centraremos en definir como ejemplos algunos iterables a los que llamaremos
agregados virtuales que permitan recorrer secuencias virtuales de elementos. Es decir, agregados que no
están en la memoria del ordenador y que devuelven los valores generados en cada iteración.
SecuenciaAritmetica
Iterable que permite recorrer los números de un intervalo con un incremento determinado.
Constructor:
public SecuenciaAritmetica(Double vi, Double vf, Double inc)
Ejemplo de uso:
Iterable<Double> sec = new SecuenciaAritmetica(1.,30.,5.);
for (Double d : sec) {
mostrar(d);
}
Resultado del código de ejemplo:
5 8. Iterables: Implementación
1.0
6.0
11.0
16.0
21.0
26.0
La implementación sigue una estructura que repetiremos en los siguientes iterables:
o Definimos una variable privada en la clase interna. Esta variable mantiene el estado del iterador.
La variable guarda el siguiente valor a devolver por el método next o un valor que está fuera del
rango permitido. En algunos casos es necesario definir varias variables privadas para mantener
el estado el iterador.
o El método hasNext simplemente comprueba si el valor de la variable está dentro del rango.
o El método next guarda el valor de la variable, calcula el siguiente actualizando la misma, y
devuelve el valor antiguo.
public class SecuenciaAritmetica implements Iterable<Double> { private Double primero,ultimo,incremento; public SecuenciaAritmetica(Double a, Double b, Double c) { if( b < a || c <= 0 ) { throw new IllegalArgumentException( “SecuenciaAritmetica.SecuenciaAritmetica::Rango no válido”); } primero = a; ultimo = b; incremento= c; } public SecuenciaAritmetica(Double a, Double b) { this(a,b,1.); } public Iterator<Double> iterator() { return new IteradorSecuenciaAritmetica(); } private class IteradorSecuenciaAritmetica implements Iterator<Double> { private Double actual; public IteradorSecuenciaAritmetica() { actual = primero; } public Double next() { if(!hasNext()) throw new NoSuchElementException(); Double r = actual; actual = actual + incremento; return r; }
6 Fundamentos de Programación
public boolean hasNext() { return actual < ultimo; } public void remove() { throw new UnsupportedOperationException(); } } }
Secuencia de Pares
Es un iterable para generar pares de números enteros donde la primera componente del par estará formada
por los enteros desde ma1 (incluido) hasta ma2 (no incluido) y la segunda componente formada por los
enteros desde mb1 (incluido) hasta mb2 (no incluido).
Constructor:
public SecuenciaDePares(Integer ma1, Integer ma2, Integer mb1, Integer mb2)
Ejemplo de uso:
Iterable<Par<Integer>> it = new SecuenciaDePares(1, 3, 2, 5); for (Par<Integer> p : it) { mostrar(p); }
Resultado del código de ejemplo:
(1,2)
(1,3)
(1,4)
(2,2)
(2,3)
(2,4)
El tipo Par representa un par de elementos y su diseño ya se ha visto con anterioridad. La
implementación del iterable sigue el esquema visto en el anterior ejercicio.
public class SecuenciaDePares implements Iterable<Par<Integer>> { private Integer a1; private Integer a2; private Integer b1; private Integer b2; public SecuenciaDePares(Integer a1, Integer a2, Integer b1, Integer b2) { if (a1>a2 || b1>b2) throw new IllegalArgumentException( “SecuenciaDePares.SecuenciaDePares::Rango no válido”); this.a1 = a1; this.a2 = a2;
7 8. Iterables: Implementación
this.b1 = b1; this.b2 = b2; } public Iterator<Par<Integer>> iterator() { return new IteradorDePares(); } private class IteradorDePares implements Iterator<Par<Integer>> { private Integer actualA; private Integer actualB; public IteradorDePares() { actualA = a1; actualB = b1; } public boolean hasNext() { return actualA < a2; } public Par<Integer> next() { if (!hasNext()) throw new NoSuchElementException(); Par<Integer> res = new ParImpl<Integer>(actualA, actualB); if (actualB < b2 - 1) { actualB++; } else { actualA++; actualB = b1; } return res; } public void remove() { throw new UnsupportedOperationException(); } } }
4. Iterables basados en iterables (ejemplos)
Para finalizar esta parte del temario, proporcionamos otros ejemplos de iterables que se basan en
información contenida en memoria, disco,... En este caso, los objetos iterables suelen usarse para
proporcionar maneras adicionales de acceder a la información.
Iterable de array
Se trata de hacer iterable un objeto de tipo array. Este tipo de objetos no es directamente iterable en Java,
aunque sí se puede recorrer mediante un for extendido. Para este ejemplo, vamos a permitir dos tipos de
recorridos: desde la posición 0 hasta la n-1 y la opuesta. Para completar el tipo, definiremos una interfaz
ArrayIterable que extiende Iterable y que tiene un solo método: setOpcion. Por lo demás, el tipo sigue el
patrón anteriormente visto.
8 Fundamentos de Programación
Constructor:
public ArrayIterableImpl (T[] a) {…}
Ejemplo de uso:
Integer [] a = {3,4,-7,23};
ArrayIterable<Integer> v = new ArrayIterableImpl<Integer>(a);
for (Integer p : v) {
mostrar(p);
}
v.setOpcion(Recorrido.HACIA_ATRAS);
for (Integer p : v) {
mostrar(p);
}
Resultado del código de ejemplo:
3
4
-7
23
23
-7
4
3
Implementación:
La implementación para este tipo de ejercicios se basa en el control mediante una variable índice de la
posición del elemento a devolver en cada iteración. El control del tipo de recorrido se realiza mediante el
método setOpcion que actualiza un atributo privado que define la forma de recorrer el objeto. Para cada
tipo de recorrido tendremos una clase interna y el método iterator se diseñará como una factoría que
selecciona el iterador correcto en cada caso.
public interface ArrayIterable<T> extends Iterable<T> { T[] getArray(); void setOpcion(Recorrido r) Recorrido getOpcion(); } public class ArrayIterableImpl<T> implements Iterable<T> { public enum Recorrido {
HACIA_ADELANTE, HACIA_ATRAS }
9 8. Iterables: Implementación
private T[] array; private Recorrido opcion = Recorrido.HACIA_ADELANTE; public ArrayIterableImpl(T[] array) { if(array == null){ throw new IllegalArgumentException(); } this.array = array; } public Iterator<T> iterator() { Iterator<T> it = null; switch (opcion) { case HACIA_ADELANTE: it = new IteratorAdelante(); break; case HACIA_ATRAS: it = new IteratorAtras(); break; } return it; }
public T[] getArray() { return array; } public Recorrido getOpcion() { return opcion; } public void setOpcion(Recorrido opcion) { this.opcion = opcion; } private class IteratorAdelante implements Iterator<T> { private int actual; public IteratorAdelante() { actual = 0; }
public T next() { if (!hasNext()) throw new NoSuchElementException(); int r = actual; actual = actual + 1; return array[r]; } public boolean hasNext() { return actual < array.length; } public void remove() { throw new UnsupportedOperationException(); } }
10 Fundamentos de Programación
private class IteratorAtras implements Iterator<T> { private int actual; public IteratorAtras() { actual = array.length-1; } public T next() { if (!hasNext()) throw new NoSuchElementException(); int r = actual; actual = actual - 1; return array[r]; } public boolean hasNext() { return actual >= 0; } public void remove() { throw new UnsupportedOperationException(); } } }
Iterable Flujo Entrada
Esta clase nos permitirá leer datos desde un fichero de texto o desde el teclado convirtiéndolo en un
Iterable<String>.
Constructor:
public FlujoEntrada(String nombreFich)
Ejemplo de uso:
Iterable<String> it = new FlujoEntrada(“Puntos.txt”); for(String s: it) { mostrar(s); }
Resultado del código de ejemplo:
Suponiendo que el fichero de nombre Puntos.txt es de la forma:
(2.0,3.1)
(2.2,3.1)
(3.2,3.0)
(4.1,3.2)
(2.0,3.2)
(2.1,3.2)
11 8. Iterables: Implementación
Entonces cada uno de los objetos en it son las líneas de ese fichero y en el ejemplo anterior lo que haríamos
sería mostrar las mismas líneas por consola.
Implementación:
La implementación usa clases del paquete java.io y java.util. Estas clases son File y Scanner, cuyos detalles
pueden verse en la documentación de la API de Java y que brevemente se verán en la siguiente sección. En
cualquier caso, es importante tener en cuenta que Scanner implementa Iterator<String> y devuelve cadenas
(entre otras posibilidades) leídas desde la entrada estándar o desde ficheros (los objetos tipo File
representan los ficheros en Java). Por lo tanto, podemos reutilizar dicha clase para resolver nuestro iterador
y, de esta forma, completar nuestro objeto iterable FlujoEntrada.
Además, el constructor de Scanner eleva excepciones que debemos gestionar. En nuestro caso, hemos
decidido convertir esas excepciones en IllegalStateException para indicar que el fichero al que se intenta
acceder no es válido en el momento en el que se intenta iterar. Tenga en cuenta que usar
FileNotFoundException directamente, implicaría forzosamente cambiar el prototipo del método iterator
(cosa que no podemos hacer puesto que es un método de Iterable) ya que se trata de una excepción
comprobable (no hereda de RuntimeException).
public class FlujoEntrada implements Iterable<String> { private String nf; private String delimiter; public FlujoEntrada(String nombreFich) { nf = nombreFich; delimiter = null; } public FlujoEntrada(String nombreFich, String delim) { nf = nombreFich; delimiter = delim; } public Iterator<String> iterator() { Iterator<String> itor = null; try { if (delimiter!=null) { itor = new Scanner(new File(nf)).useDelimiter(delimiter); } else { itor = new IteradorFicheroLineas(); } } catch (FileNotFoundException e) { throw new IllegalStateException(); } return itor; }
12 Fundamentos de Programación
private class IteradorFicheroLineas implements Iterator<String> { private Scanner sc; public IteradorFicheroLineas() { try { sc = new Scanner(new File(nf)); } catch (FileNotFoundException e) { throw new IllegalStateException(); } } public void remove() { throw new UnsupportedOperationException(); } public boolean hasNext() { return sc.hasNextLine(); } public String next() { return sc.nextLine(); } } }
5. Lectura de ficheros: Scanner e Iterables2.fromFile
La clase Scanner
Como se ha visto antes, Java proporciona en el paquete java.util una clase que se denomina Scanner y que
permite leer datos desde ficheros de texto o incluso desde teclado. Los objetos de tipo Scanner, mediante la
invocación de distintos métodos, permiten leer datos de cualquier tipo (String, enteros, reales, etc.) con
diversas posibilidades de separadores.
Para construir un objeto de tipo Scanner se invocará al constructor de la clase pasándole como argumento
un objeto de tipo File (que se encuentra en el paquete java.io). Los objetos de tipo File relacionan un fichero
con su nombre y ruta en el sistema de archivos del ordenador. Un objeto de tipo File se crea mediante un
constructor al que se le pasa como argumento una cadena de caracteres con el nombre y la ruta del fichero
que se quiere leer o escribir:
File f = new File("palabras.txt");
La anterior línea indica que el fichero palabras.txt se encuentra en el directorio raíz de la carpeta que
contenga nuestro proyecto Java. Si quisiéramos el fichero que esté en una carpeta concreta:
File f = new File("c:\Usuarios\pedro\clases\poo\tema6\palabras.txt");
Una vez creado el fichero f se puede invocar al constructor de la clase Scanner:
13 8. Iterables: Implementación
Scanner sc = new Scanner(f);
Sin embargo, lo usual es hacerlo todo en la misma sentencia ya que el objeto de tipo File normalmente no se
va a reutilizar:
Scanner sc = new Scanner(new File("palabras.txt"));
La clase Scanner proporciona un conjunto de métodos para leer el contenido del fichero de texto que se ha
conectado mediante la invocación del constructor. En la siguiente tabla se exponen algunos de los más
interesantes; como siempre, se puede ver la relación completa en la documentación de Java1.
Boolean hasNext()
Returns true if this scanner has another token in its input.
String next()
Finds and returns the next complete token from this scanner.
Scanner useDelimiter(String pattern)
Sets this scanner's delimiting pattern to a pattern constructed from the specified String.
El método useDelimiter sobre un objeto Scanner lleva como argumento un objeto de tipo Pattern2, que es
una representación como cadena de una expresión regular3. En esta asignatura sólo lo vamos a usar para
indicar separadores distintos del espacio en blanco, que es el separador por defecto de la clase Scanner (por
ejemplo para leer línea a línea podemos utilizar la expresión regular “\\n+$”). Es importante recordar que
Scanner no elimina los blancos por lo que puede ser interesante usar el método trim() de String para
eliminar blancos, tabuladores,… para cada uno de los elementos que se devuelvan. En cualquier caso, el
resultado final de la llamada a useDelimiter() es un nuevo objeto Scanner distinto del que ha invocado el
método con delimitadores personalizados.
Encapsulación de la lectura: La clase Iterables2 y el método fromFile
Los trozos de código de la sección anterior no están situados en ningún método en concreto. Como ya
sabemos de temas anteriores, la programación orientada a objetos debe encapsular las líneas de código de
una determinada acción en un método, bien sea un método de una clase, bien sea un método de una clase
de utilidad. En el caso de la lectura de ficheros, aprovecharemos el tipo FlujoEntrada para encapsular la
definición del objeto Scanner y, de esta forma, a partir de un fichero, podemos obtener un Iterable<String>
que permita recorrer las líneas de dicho fichero.
La clase en la que iremos definiendo los métodos de utilidades para objetos iterables, se denominará
Iterables2 (veremos más adelante el porqué del uso del número en su nombre). Y para empezar,
definiremos dentro de ella el método fromFile de la siguiente manera:
1 http://docs.oracle.com/javase/7/docs/api/java/util/Scanner.html
2 http://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html
3 http://es.wikipedia.org/wiki/Expresi%C3%B3n_regular
14 Fundamentos de Programación
public class Iterables2 { public static Iterable<String> fromFile(String file){ return new FlujoEntrada(file); } }
Para utilizar la clase de utilidad, podríamos definir una clase TestLectura como sigue: public static void main(String[] args) { Iterable<String> itbl = Iterables2.fromFile("Puntos.txt"); for (String s : itbl) { mostrar(s); } }
Donde la salida nos daría algo como:
(2.0,3.1)
(2.2,3.1)
(3.2,3.0)
(4.1,3.2)
(2.0,3.2)
(2.1,3.2)
6. Ejercicios
1. Cree una clase denominada SecuenciaGeométrica que implemente Iterable<Double>. Los objetos
SecuenciaGeométrica se recorrerán mediante un iterador, teniendo en cuenta que cada valor devuelto
es un número entre dos valores dados que se recogerán como parámetros en el constructor.
o Constructor:
public SecuenciaGeometrica (Double inicial, Double fin, Double razon)
o Ejemplo de uso:
Iterable<Double> it = new SecuenciaGeometrica(1.0, 1000.0, 5.0); for(Double d: it) {
mostrar(d); }
o Resultado del código de ejemplo:
1.0
5.0
25.0
125.0
625.0
15 8. Iterables: Implementación
2. En este ejercicio abordaremos la creación de un tipo SecuenciaFibonacci. Este tipo debe devolver la
sucesión de Fibonacci: 1, 1, 2, 3, 5,…, hasta un valor concreto que se pasa como parámetro al
constructor. Recuerde que cada valor se calcula sumando los valores de los dos anteriores (v0=1, v1=1,
vn=vn-1 + vn-2, tal que n>1).
o Constructor:
public SecuenciaFibonacci (Long fin)
o Ejemplo de uso:
Iterable<Long> it = new SecuenciaFibonacci(5L); for(Long d: it) {
mostrar(d); }
o Resultado del código de ejemplo:
1
1
2
3
3. Se desea definir un tipo análogo a ArrayBidireccional para listas que se denomine ListaBidireccional.
Implemente la clase ListaBidireccionaImpl teniendo en cuenta que la interfaz ListaBidireccional extiende
de List y que sólo tiene un método nuevo para la propiedad recorrido (análoga a la vista en
ArrayBidireccional) que es consultable y modificable.
o Constructor:
public ListaBidireccionalImpl (Collection<T> l)
o Ejemplo de uso:
List<Integer> laux = new ArrayList<Integer>(); laux.add(2); laux.add(3); ListaBidireccional<Integer> l= new ListaBidireccionalImpl<Integer>(laux); l.setRecorrido(Recorrido.HACIA_ATRAS); for(Integer d: l) {
mostrar(d); }
o Resultado del código de ejemplo:
3
2
16 Fundamentos de Programación
4. Se desea construir un tipo que permita recorrer los caracteres de una cadena de izquierda a derecha y
viceversa. Para ello, se pide implementar un nuevo tipo denominado CadenaIterableBidireccional que
extiende a Iterable y que tiene una propiedad consultable de tipo String, denominada cadena, y una
propiedad tipoRecorrido de tipo Recorrido, consultable y modificable. Tenga en cuenta que el criterio de
igualdad será el de la cadena.
o Constructor:
public CadenaBidireccionalImpl (String s)
o Ejemplo de uso:
CadenaBidireccional l= new CadenaBidireccionalImpl (“HOLA”); l.setRecorrido(Recorrido.HACIA_ATRAS); for(Character d: l) {
mostrar(d); }
o Resultado del código de ejemplo:
A
L
O
H
NOTA: Reutilice ArrayIterable.