JavaRush /Blogue Java /Random-PT /Padrão de iterador

Padrão de iterador

Publicado no grupo Random-PT
Hoje falaremos sobre o que é Iterator em Java e por que ele é necessário.
Padrão de iterador - 1
Como você provavelmente já sabe, Java possui uma interface Collection maravilhosa que implementa a interface Iterator. Deixe-me fazer uma reserva imediatamente: a interface do iterador não deve ser confundida com o padrão do iterador em Java! E para esclarecer, vamos primeiro dar uma olhada na interface.
Literalmente, “Iterador” pode ser traduzido como “força bruta ” . Ou seja, é uma determinada entidade que pode iterar por todos os elementos da coleção. Além disso, permite fazer isso sem se aprofundar na estrutura interna e na disposição das coleções.
Vamos imaginar por um segundo que não exista iterador em Java. Nesse caso, cada um terá que mergulhar nas profundezas das coleções e entender verdadeiramente o que é diferente ArrayListde LinkedListe HashSetde TreeSet.

Métodos que o Iterador deve implementar

boolean hasNext()— se ainda houver valores no objeto iterável (atualmente uma Coleção), o método retornará true, se não houver mais valores false. E next()— retorna o próximo elemento da coleção (objeto). Se não houver mais elementos (não houve check hasNext()e chamamos next()quando chegamos ao final da coleção), o método lançará NoSuchElementException. void remove()- removerá o último elemento obtido pelo next(). O método pode lançar:
  • UnsupportedOperationException, se este iterador não suportar o método remove()(no caso de coleções somente leitura, por exemplo)
  • IllegalStateException, se o método next()ainda não foi chamado ou se remove()já foi chamado desde a última chamada next().
Portanto, o iterador para List é a implementação mais comum. O iterador vai do início ao fim da coleção: procura ver se o próximo elemento está presente e o retorna se houver. Um ciclo é construído com base neste algoritmo simples for-each. Sua extensão é ListIterator. Vejamos métodos iteradores de lista java adicionais. Você provavelmente os conhece:
  • void add(E e)— insere um elemento Eem ;List
  • boolean hasPrevious()— retornará truese Listhouver elementos durante a busca reversa;
  • int nextIndex()— retornará o índice do próximo elemento;
  • E previous()— retornará o elemento da planilha anterior;
  • int previousIndex()— retornará o índice do elemento anterior;
  • void set(E e)- substituirá o elemento retornado pela última chamada next()ou previous()pelo elemento e.
Vejamos um pequeno exemplo. Vamos criar um Listcontendo as linhas de saudações aos alunos:
List<String> list = new ArrayList<>();
list.add("Hello");
list.add("Обучающимся");
list.add("На");
list.add("JavaRush");
Agora obteremos um iterador para ele e imprimiremos todas as linhas contidas no console:
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}
Agora haverá um “gargalo”: as coleções Java, como você provavelmente sabe (e se não sabe, descubra), estendem a interface Iterable, mas isso não significa apenas List, Sete Queuesuportam o iterador. For java Map iteratortambém é suportado, mas deve ser chamado for Map.entrySet():
Map<String, Integer> map = new HashMap<>();
Iterator mapIterator = map.entrySet().iterator();
Então o método next() retornará um objeto Entrycontendo um par “chave”-“valor”. Então tudo é igual com List:
while (mapIterator.hasNext()) {
    Map.Entry<String, Integer> entry = mapIterator.next();
    System.out.println("Key: " + entry.getKey());
    System.out.println("Value: " + entry.getValue());
}
Você pensa: “Pare. Estamos falando da interface, e o título do artigo diz “Padrão”. Ou seja, o padrão do iterador é a interface do Iterador? Ou a interface é um padrão? Se esta palavra aparecer pela primeira vez, dou-lhe uma referência: um padrão é um padrão de design, um determinado comportamento que uma classe ou muitas classes interligadas devem aderir. Um iterador em java pode ser implementado para qualquer objeto cuja estrutura interna envolva iteração, e você pode alterar a assinatura dos métodos em discussão. O principal ao implementar um padrão é a lógica que a classe deve seguir. A interface do iterador é uma implementação privada do padrão de mesmo nome, aplicada tanto a estruturas prontas ( List, Set, Queue, Map), quanto a outras, a critério do programador. Ao estender a interface do Iterator, você implementa um padrão, mas não precisa estender a interface para implementar o padrão. Uma analogia simples: todos os peixes nadam, mas nem tudo que nada é peixe. Como exemplo, decidi pegar... a palavra. Mais especificamente, um substantivo. Consiste em partes: prefixo, raiz, sufixo e finalização. Para partes de uma palavra, criaremos uma interface WordParte classes que a estendem Prefix, Root, Suffix и Ending:
interface WordPart {
    String getWordPart();
}

static class Root implements WordPart {

    private String part;

    public Root(String part) {
        this.part = part;
    }

    @Override
    public String getWordPart() {
        return part;
    }
}

static class Prefix implements WordPart {

    private String part;

    public Prefix(String part) {
        this.part = part;
    }

    @Override
    public String getWordPart() {
        return part;
    }
}

static class Suffix implements WordPart {

    private String part;

    public Suffix(String part) {
        this.part = part;
    }

    @Override
    public String getWordPart() {
        return part;
    }
}

static class Ending implements WordPart {

    private String part;

    public Ending(String part) {
        this.part = part;
    }

    @Override
    public String getWordPart() {
        return part;
    }
}
Então a classe Word(palavra) conterá partes, e além delas adicionaremos um número inteiro refletindo o número de partes da palavra:
public class Word {

    private Root root;
    private Prefix prefix;
    private Suffix suffix;
    private Ending ending;
    private int partCount;

    public Word(Root root, Prefix prefix, Suffix suffix, Ending ending) {
        this.root = root;
        this.prefix = prefix;
        this.suffix = suffix;
        this.ending = ending;
        this.partCount = 4;
    }

    public Word(Root root, Prefix prefix, Suffix suffix) {
        this.root = root;
        this.prefix = prefix;
        this.suffix = suffix;
        this.partCount = 3;

    }

    public Word(Root root, Prefix prefix) {
        this.root = root;
        this.prefix = prefix;
        this.partCount = 2;
    }

    public Word(Root root) {
        this.root = root;
        this.partCount = 1;
    }

    public Root getRoot() {
        return root;
    }

    public Prefix getPrefix() {
        return prefix;
    }

    public Suffix getSuffix() {
        return suffix;
    }

    public Ending getEnding() {
        return ending;
    }

    public int getPartCount() {
        return partCount;
    }

    public boolean hasRoot() {
        return this.root != null;
    }

    public boolean hasPrefix() {
        return this.prefix != null;
    }

    public boolean hasSuffix() {
        return this.suffix != null;
    }

    public boolean hasEnding() {
        return this.ending != null;
    }
Ok, temos quatro construtores sobrecarregados (para simplificar, vamos supor que só podemos ter um sufixo). Um substantivo não pode consistir em um prefixo, portanto, para um construtor com um parâmetro, definiremos a raiz. Agora vamos escrever uma implementação do padrão iterador: WordIterator, substituindo 2 métodos: hasNext()e next():
public class WordIterator implements Iterator<Word.WordPart> {

    private Word word;
    private int wordPartsCount;

    public WordIterator(Word word) {
        this.word = word;
        this.wordPartsCount = word.getPartCount();
    }

    @Override
    public boolean hasNext() {
        if (wordPartsCount == 4) {
            return word.hasPrefix() || word.hasRoot() || word.hasSuffix() || word.hasEnding();
        } else if (wordPartsCount == 3) {
            return word.hasPrefix() || word.hasRoot() || word.hasSuffix();
        } else if (wordPartsCount == 2) {
            return word.hasPrefix() || word.hasRoot();
        } else if (wordPartsCount == 1) {
            return word.hasRoot();
        }
        return false;
    }

    @Override
    public Word.WordPart next() throws NoSuchElementException {
        if (wordPartsCount <= 0) {
            throw new NoSuchElementException("No more elements in this word!");
        }

        try {
            if (wordPartsCount == 4) {
                return word.getEnding();
            }
            if (wordPartsCount == 3) {
                return word.getSuffix();
            }
            if (wordPartsCount == 2) {
                return word.getPrefix();
            }
            return word.getRoot();
        } finally {
            wordPartsCount--;
        }
    }
}
Tudo o que resta é atribuir o iterador à classe Word:
public class Word implements Iterable<Word.WordPart> {@Override
	public Iterator<WordPart>iterator() {
    		return new WordIterator(this);
	}}
Agora vamos realizar uma análise morfêmica da palavra “rush”:
public class Main {
    public static void main(String[] args) {
        Word.Root root = new Word.Root("беж");
        Word.Prefix prefix = new Word.Prefix("пере");
        Word.Suffix suffix = new Word.Suffix("к");
        Word.Ending ending = new Word.Ending("a");

        Word word = new Word(root, prefix, suffix, ending);

        Iterator wordIterator = word.iterator();
        while (wordIterator.hasNext()) {
            Word.WordPart part = (Word.WordPart) wordIterator.next();
            System.out.println(part.getClass() + ": " + part.getWordPart());
        }
    }
}
Observe que em minha implementação do padrão iterador, escolhi a seguinte ordem de saída:
  1. final
  2. sufixo
  3. console
  4. raiz
Ao projetar seu próprio iterador, você pode especificar o algoritmo de iteração conforme desejar. Boa sorte em seus estudos!
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION