Interfaz iterable de Java: Iterator, ListIterator y Spliterator

    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, Seto 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(): Devoluciones true si no hemos llegado al final de una colecci贸n, devuelve false 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:

    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:

    For 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 Listsy 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 – Lists, Maps, Queues, Sets, 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(): Devoluciones true si no hemos llegado al final de una lista, devuelve false de otra manera.
    • .next(): Devuelve el siguiente elemento de una lista.
    • .nextIndex(): Devuelve el 铆ndice del siguiente elemento.
    • .hasPrevious(): Devoluciones true si no hemos llegado al principio de una lista, devuelve false 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.

    Si 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 como int 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 un long valor o devoluciones long.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 esto SpliteratorLa fuente est谩 ordenada por Comparator, devuelve eso Comparator.
    • .getExactSizeIfKnown(): Devoluciones .estimateSize() si se conoce el tama帽o, de lo contrario devuelve -1
    • .hasCharacteristics(int characteristics): Devoluciones true si esto Spliteratores .characteristics() contienen todas las caracter铆sticas dadas.
    • .tryAdvance(E e): Si existe un elemento restante, realiza la acci贸n dada en 茅l, devolviendo true, si no vuelve false.
    • .trySplit(): Si esto Spliterator se puede particionar, devuelve un Spliterator elementos de cobertura, que, al regresar de este m茅todo, no estar谩n cubiertos por este Spliterator.

    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.

     

    Etiquetas:

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *