Introducción
Si bien podemos usar un for
o while
bucle para atravesar una colección de elementos, un Iterator
nos permite hacerlo sin preocuparnos por las posiciones del índice e incluso nos permite no solo revisar una colección, sino también alterarla al mismo tiempo, lo que no siempre es posible con for
bucles si está eliminando elementos en el bucle, por ejemplo.
Combine eso con la capacidad de implementar nuestro Iterador personalizado para iterar a través de objetos mucho más complejos, así como avanzar y retroceder, y las ventajas de saber cómo usarlo se vuelven bastante claras.
Este artículo profundizará bastante en cómo Iterator
y Iterable
Se pueden utilizar interfaces.
Iterador ()
los Iterator
La interfaz se utiliza para iterar sobre los elementos de una colección (List
, Set
o Map
). Se utiliza para recuperar los elementos uno por uno y realizar operaciones sobre cada uno si es necesario.
Estos son los métodos que se utilizan para recorrer colecciones y realizar operaciones:
.hasNext()
: Devolucionestrue
si no hemos llegado al final de una colección, devuelvefalse
de otra manera.next()
: Devuelve el siguiente elemento de una colección..remove()
: Elimina el último elemento devuelto por el iterador de la colección.forEachRemaining()
: Realiza la acción dada para cada elemento restante en una colección, en orden secuencial
En primer lugar, dado que los iteradores están diseñados para usarse con colecciones, hagamos un simple ArrayList
con algunos elementos:
List<String> avengers = new ArrayList<>();
// Now lets add some Avengers to the list
avengers.add("Ant-Man");
avengers.add("Black Widow");
avengers.add("Captain America");
avengers.add("Doctor Strange");
Podemos iterar a través de esta lista usando un ciclo simple:
System.out.println("Simple loop example:n");
for (int i = 0; i < avengers.size(); i++) {
System.out.println(avengers.get(i));
}
Sin embargo, queremos explorar los iteradores:
Te puede interesar:Métodos de objetos de Java: hashCode ()System.out.println("nIterator Example:n");
// First we make an Iterator by calling
// the .iterator() method on the collection
Iterator<String> avengersIterator = avengers.iterator();
// And now we use .hasNext() and .next() to go through it
while (avengersIterator.hasNext()) {
System.out.println(avengersIterator.next());
}
¿Qué sucede si queremos eliminar un elemento de este ArrayList
? Intentemos hacerlo usando el for
lazo:
System.out.println("Simple loop example:n");
for (int i = 0; i < avengers.size(); i++) {
if (avengers.get(i).equals("Doctor Strange")) {
avengers.remove(i);
}
System.out.println(avengers.get(i));
}
Seríamos recibidos con un desagradable IndexOutOfBoundsException
:
Simple loop example:
Ant-Man
Black Widow
Captain America
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 3, Size: 3
Esto tiene sentido ya que estamos modificando el tamaño de la colección a medida que la recorremos. Lo mismo ocurre con los avanzados for
lazo:
System.out.println("Simple loop example:n");
for (String avenger : avengers) {
if (avenger.equals("Doctor Strange")) {
avengers.remove(avenger);
}
System.out.println(avenger);
}
Nuevamente nos saludan con otra excepción:
Simple loop example:
Ant-Man
Black Widow
Captain America
Doctor Strange
Exception in thread "main" java.util.ConcurrentModificationException
Aquí es donde los iteradores son útiles, actuando como intermediarios para eliminar el elemento de la colección, pero también para garantizar que el recorrido continúe según lo planeado:
Iterator<String> avengersIterator = avengers.iterator();
while (avengersIterator.hasNext()) {
String avenger = avengersIterator.next();
// First we must find the element we wish to remove
if (avenger.equals("Ant-Man")) {
// This will remove "Ant-Man" from the original
// collection, in this case a List
avengersIterator.remove();
}
}
Este es un método seguro garantizado para eliminar elementos mientras atraviesa colecciones.
Y para validar si el artículo ha sido eliminado:
// We can also use the helper method .forEachRemaining()
System.out.println("For Each Remaining Example:n");
Iterator<String> avengersIteratorForEach = avengers.iterator();
// This will apply System.out::println to all elements in the collection
avengersIteratorForEach.forEachRemaining(System.out::println);
Y la salida es:
Te puede interesar:Control de flujo de Java: la declaración del cambioFor Each Remaining Example:
Black Widow
Captain America
Doctor Strange
Como puede ver, «Ant-Man» se ha eliminado del avengers
lista.
ListIterator ()
ListIterator
extiende el Iterator
interfaz. Solo se usa en List
sy puede iterar bidireccionalmente, lo que significa que puede iterar de adelante hacia atrás o de atrás hacia adelante. Tampoco tiene un elemento actual porque el cursor siempre se coloca entre 2 elementos en un List
, entonces debemos usar .previous()
o .next()
para acceder a un elemento.
¿Cuál es la diferencia entre un Iterator
y un ListIterator
?
Primero el Iterator
se puede aplicar a cualquier colección – List
s, Map
s, Queue
s, Set
s, etc.
los ListIterator
solo se puede aplicar a listas. Al agregar esta restricción, el ListIterator
puede ser mucho más específico cuando se trata de métodos, por lo que nos presentan muchos métodos nuevos que nos ayudan a modificar listas mientras recorremos.
Si se trata de un List
implementación (ArrayList
, LinkedList
, etc.), siempre es preferible utilizar el ListIterator
.
Estos son los métodos que probablemente utilizará:
.add(E e)
: Inserta el elemento en la lista..remove()
: Elimina el último elemento devuelto por.next()
o.previous()
de la lista..set(E e)
: Reemplaza el último elemento devuelto por.next()
o.previous()
con el elemento especificado.hasNext()
: Devolucionestrue
si no hemos llegado al final de una lista, devuelvefalse
de otra manera..next()
: Devuelve el siguiente elemento de una lista..nextIndex()
: Devuelve el índice del siguiente elemento..hasPrevious()
: Devolucionestrue
si no hemos llegado al principio de una lista, devuelvefalse
de otra manera..previous()
: Devuelve el elemento anterior en una lista..previousIndex()
: Devuelve el índice del elemento anterior.
Nuevamente, poblaremos un ArrayList
con algunos elementos:
ArrayList<String> defenders = new ArrayList<>();
defenders.add("Daredevil");
defenders.add("Luke Cage");
defenders.add("Jessica Jones");
defenders.add("Iron Fist");
Usemos un ListIterator
para recorrer una lista e imprimir los elementos:
ListIterator listIterator = defenders.listIterator();
System.out.println("Original contents of our List:n");
while (listIterator.hasNext())
System.out.print(listIterator.next() + System.lineSeparator());
Obviamente, funciona de la misma manera que el clásico. Iterator
. La salida es:
Original contents of our List:
Daredevil
Luke Cage
Jessica Jones
Iron Fist
Ahora, intentemos modificar algunos elementos:
System.out.println("Modified contents of our List:n");
// Now let's make a ListIterator and modify the elements
ListIterator defendersListIterator = defenders.listIterator();
while (defendersListIterator.hasNext()) {
Object element = defendersListIterator.next();
defendersListIterator.set("The Mighty Defender: " + element);
}
Imprimir la lista ahora produciría:
Modified contents of our List:
The Mighty Defender: Daredevil
The Mighty Defender: Luke Cage
The Mighty Defender: Jessica Jones
The Mighty Defender: Iron Fist
Ahora, sigamos adelante y recorramos la lista hacia atrás, como algo que podemos hacer con el ListIterator
:
System.out.println("Modified List backwards:n");
while (defendersListIterator.hasPrevious()) {
System.out.println(defendersListIterator.previous());
}
Y la salida es:
Modified List backwards:
The Mighty Defender: Iron Fist
The Mighty Defender: Jessica Jones
The Mighty Defender: Luke Cage
The Mighty Defender: Daredevil
Separador ()
los Spliterator
La interfaz es funcionalmente igual que una Iterator
. Puede que nunca necesite usar Spliterator
directamente, pero repasemos algunos casos de uso.
Sin embargo, primero debe familiarizarse un poco con Java Streams y Lambda Expressions en Java.
Te puede interesar:Control de flujo de Java: declaraciones if y if-elseSi bien enumeraremos todos los métodos Spliterator
tiene, el funcionamiento completo de la Spliterator
interfaz están fuera del alcance de este artículo. Una cosa que cubriremos con un ejemplo es cómo Spliterator
puede utilizar la paralelización para atravesar un Stream
que podemos romper.
Los métodos que usaremos al tratar con Spliterator
son:
.characteristics()
: Devuelve las características que tiene este Spliterator comoint
valor. Éstas incluyen:ORDERED
DISTINCT
SORTED
SIZED
CONCURRENT
IMMUTABLE
NONNULL
SUBSIZED
.estimateSize()
: Devuelve una estimación del número de elementos que se encontrarían con un recorrido como unlong
valor o devolucioneslong.MAX_VALUE
si no puede calcular..forEachRemaining(E e)
: Realiza la acción dada para cada elemento restante en una colección, en orden secuencial..getComparator()
: Si estoSpliterator
La fuente está ordenada porComparator
, devuelve esoComparator
..getExactSizeIfKnown()
: Devoluciones.estimateSize()
si se conoce el tamaño, de lo contrario devuelve-1
.hasCharacteristics(int characteristics)
: Devolucionestrue
si estoSpliterator
es.characteristics()
contienen todas las características dadas..tryAdvance(E e)
: Si existe un elemento restante, realiza la acción dada en él, devolviendotrue
, si no vuelvefalse
..trySplit()
: Si estoSpliterator
se puede particionar, devuelve unSpliterator
elementos de cobertura, que, al regresar de este método, no estarán cubiertos por esteSpliterator
.
Como de costumbre, comencemos con un simple ArrayList
:
List<String> mutants = new ArrayList<>();
mutants.add("Professor X");
mutants.add("Magneto");
mutants.add("Storm");
mutants.add("Jean Grey");
mutants.add("Wolverine");
mutants.add("Mystique");
Ahora, necesitamos aplicar el Spliterator
a un Stream
. Afortunadamente, es fácil convertir entre un ArrayList
y un Stream
debido al marco de Cobranzas:
// Obtain a Stream to the mutants List.
Stream<String> mutantStream = mutants.stream();
// Getting Spliterator object on mutantStream.
Spliterator<String> mutantList = mutantStream.spliterator();
Y para mostrar algunos de estos métodos, ejecutemos cada uno:
// .estimateSize() method
System.out.println("Estimate size: " + mutantList.estimateSize());
// .getExactSizeIfKnown() method
System.out.println("nExact size: " + mutantList.getExactSizeIfKnown());
System.out.println("nContent of List:");
// .forEachRemaining() method
mutantList.forEachRemaining((n) -> System.out.println(n));
// Obtaining another Stream to the mutant List.
Spliterator<String> splitList1 = mutantStream.spliterator();
// .trySplit() method
Spliterator<String> splitList2 = splitList1.trySplit();
// If splitList1 could be split, use splitList2 first.
if (splitList2 != null) {
System.out.println("nOutput from splitList2:");
splitList2.forEachRemaining((n) -> System.out.println(n));
}
// Now, use the splitList1
System.out.println("nOutput from splitList1:");
splitList1.forEachRemaining((n) -> System.out.println(n));
Y obtenemos esto como salida:
Estimate size: 6
Exact size: 6
Content of List:
Professor X
Magneto
Storm
Jean Grey
Wolverine
Mystique
Output from splitList2:
Professor X
Magneto
Storm
Output from splitList1:
Jean Grey
Wolverine
Mystique
Iterable ()
¿Y si por alguna razón quisiéramos hacer un personalizado? Iterator
interfaz. Lo primero que debe conocer es este gráfico:
Para hacer nuestra costumbre Iterator
necesitaríamos escribir métodos personalizados para .hasNext()
, .next()
y .remove()
.
Dentro de Iterable
interfaz, tenemos un método que devuelve un iterador para elementos en una colección, que es el .iterator()
método, y un método que realiza una acción para cada elemento en un iterador, el .forEach()
método.
Por ejemplo, imaginemos que somos Tony Stark, y necesitamos escribir un iterador personalizado para enumerar todos los trajes de Iron Man que tienes actualmente en tu arsenal.
Primero, creemos una clase para obtener y configurar los datos del traje:
public class Suit {
private String codename;
private int mark;
public Suit(String codename, int mark) {
this.codename = codename;
this.mark = mark;
}
public String getCodename() { return codename; }
public int getMark() { return mark; }
public void setCodename (String codename) {this.codename=codename;}
public void setMark (int mark) {this.mark=mark;}
public String toString() {
return "mark: " + mark + ", codename: " + codename;
}
}
A continuación, escribamos nuestro iterador personalizado:
// Our custom Iterator must implement the Iterable interface
public class Armoury implements Iterable<Suit> {
// Notice that we are using our own class as a data type
private List<Suit> list = null;
public Armoury() {
// Fill the List with data
list = new LinkedList<Suit>();
list.add(new Suit("HOTROD", 22));
list.add(new Suit("SILVER CENTURION", 33));
list.add(new Suit("SOUTHPAW", 34));
list.add(new Suit("HULKBUSTER 2.0", 48));
}
public Iterator<Suit> iterator() {
return new CustomIterator<Suit>(list);
}
// Here we are writing our custom Iterator
// Notice the generic class E since we do not need to specify an exact class
public class CustomIterator<E> implements Iterator<E> {
// We need an index to know if we have reached the end of the collection
int indexPosition = 0;
// We will iterate through the collection as a List
List<E> internalList;
public CustomIterator(List<E> internalList) {
this.internalList = internalList;
}
// Since java indexes elements from 0, we need to check against indexPosition +1
// to see if we have reached the end of the collection
public boolean hasNext() {
if (internalList.size() >= indexPosition +1) {
return true;
}
return false;
}
// This is our custom .next() method
public E next() {
E val = internalList.get(indexPosition);
// If for example, we were to put here "indexPosition +=2" we would skip every
// second element in a collection. This is a simple example but we could
// write very complex code here to filter precisely which elements are
// returned.
// Something which would be much more tedious to do with a for or while loop
indexPosition += 1;
return val;
}
// In this example we do not need a .remove() method, but it can also be
// written if required
}
}
Y finalmente la clase principal:
public class IronMan {
public static void main(String[] args) {
Armoury armoury = new Armoury();
// Instead of manually writing .hasNext() and .next() methods to iterate through
// our collection we can simply use the advanced forloop
for (Suit s : armoury) {
System.out.println(s);
}
}
}
La salida es:
mark: 22, codename: HOTROD
mark: 33, codename: SILVER CENTURION
mark: 34, codename: SOUTHPAW
mark: 48, codename: HULKBUSTER 2.0
Conclusión
En este artículo, cubrimos en detalle cómo trabajar con iteradores en Java e incluso escribimos uno personalizado para explorar todas las nuevas posibilidades del Iterable
interfaz.
También mencionamos cómo Java aprovecha la paralelización de flujos para optimizar internamente el recorrido a través de una colección utilizando el Spliterator
interfaz.