JavaRush /Java Blog /Random-JA /イテレータパターン
Алексей Дмитревский
レベル 31
Москва

イテレータパターン

Random-JA グループに公開済み
今日は、 Java における Iterator とは 何か、そしてそれが必要な理由について説明します。
反復子パターン - 1
おそらくすでにご存知かと思いますが、Java には Iterator インターフェイスを実装する素晴らしい Collection インターフェイスがあります。すぐに予約しておきます。イテレータ インターフェイスを Java のイテレータ パターンと混同しないでください。明確にするために、まずインターフェイスを見てみましょう。
文字通り、「イテレーター」は「ブルートフォース」と翻訳できます。つまり、コレクション内のすべての要素を反復処理できる特定のエンティティです。さらに、コレクションの内部構造や配置を詳しく調べることなく、これを行うことができます。
Java にはイテレータが存在しないと少し想像してみましょう。この場合、一人ひとりがコレクションの奥深くまで潜り込み、 と の違いを真に理解する必要 ArrayListLinkedListありHashSetますTreeSet

Iterator が実装する必要があるメソッド

boolean hasNext()— 反復可能なオブジェクト (現在は Collection) にまだ値が残っている場合、メソッドは を返しますtrue。値がもうない場合は、メソッドは を返しますfalseE next()— コレクションの次の要素 (オブジェクト) を返します。これ以上要素がない場合 ( check がなく、コレクションの最後に到達したときにhasNext()呼び出した場合)、メソッドは をスローします。 - によって最後に取得された要素が削除されます。このメソッドは以下をスローできます。 next()NoSuchElementExceptionvoid remove()next()
  • UnsupportedOperationException、このイテレータがメソッドをサポートしていない場合remove()(読み取り専用コレクションの場合など)
  • IllegalStateExceptionメソッドがnext()まだ呼び出されていないか、remove()最後の呼び出し以降にすでに呼び出されている場合next()
したがって、List のイテレータは最も一般的な実装です。イテレータはコレクションの先頭から末尾まで移動します。次の要素が存在するかどうかを確認し、存在する場合はそれを返します。サイクルは、この単純なアルゴリズムに基づいて構築されますfor-each。その拡張子は ListIterator です。追加の Java リスト反復子メソッドを見てみましょう。あなたもおそらく知っているでしょう:
  • void add(E e)E—に要素を挿入します。List
  • boolean hasPrevious()—逆検索中に要素があるtrue場合に返されます。List
  • int nextIndex()— 次の要素のインデックスを返します。
  • E previous()— 前のシート要素を返します。
  • int previousIndex()— 前の要素のインデックスを返します。
  • void set(E e)- 最後の呼び出しによって返された要素next()、またはprevious()要素で置き換えられますe
小さな例を見てみましょう。List学生への挨拶文を含む を 作成しましょう。
List<String> list = new ArrayList<>();
list.add("Hello");
list.add("Обучающимся");
list.add("На");
list.add("JavaRush");
次に、その反復子を取得し、含まれているすべての行をコンソールに出力します。
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}
ここで「ボトルネック」が発生します。おそらくご存知のとおり (知らない場合は調べてください)、Java コレクションはインターフェイスを拡張しますが、Iterableこれは単にイテレータをサポートするという意味ではありListませんSetQueueforjava Map iteratorもサポートされていますが、次のように呼び出す必要がありますMap.entrySet()
Map<String, Integer> map = new HashMap<>();
Iterator mapIterator = map.entrySet().iterator();
次に、メソッドは「キー」と「値」のペアを含むnext() オブジェクトを返します。Entryその後、すべてが同じになりますList:
while (mapIterator.hasNext()) {
    Map.Entry<String, Integer> entry = mapIterator.next();
    System.out.println("Key: " + entry.getKey());
    System.out.println("Value: " + entry.getValue());
}
あなたはこう思います。私たちはインターフェースについて話していますが、記事のタイトルには「パターン」とあります。つまり、反復パターンは Iterator インターフェイスですか? それともインターフェースがパターンなのでしょうか?この言葉が初めて出てきた場合は、参考にしてください。パターンとは設計パターンであり、クラスまたは相互接続された多数のクラスが従う必要がある特定の動作です。Java のイテレータは、内部構造に反復処理が含まれる任意のオブジェクトに対して実装でき、説明されているメソッドのシグネチャを変更できます。パターンを実装するときに重要なことは、クラスが従う必要があるロジックです。 イテレータ インターフェイスはList, Set, Queue, Map、同じ名前のパターンのプライベート実装であり、プログラマの裁量で、既製の構造体 ( ) と他の構造体の両方に適用されます。Iterator インターフェイスを拡張することでパターンを実装しますが、パターンを実装するためにインターフェイスを拡張する必要はありません。簡単なたとえ: すべての魚は泳ぎますが、泳ぐすべてが魚であるわけではありません。例として、私は...という言葉を取り上げることにしました。もっと具体的に言えば、名詞です。これは、プレフィックス、ルート、サフィックス、エンディングの部分で構成されます。単語の一部については、WordPartそれを拡張するインターフェイスとクラスを作成します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;
    }
}
次に、クラスWord(単語) にパーツが含まれ、それらに加えて、単語内のパーツの数を反映する整数を追加します。
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;
    }
オーバーロードされたコンストラクターが 4 つあります (簡単にするために、接尾辞は 1 つだけであると仮定します)。名詞は 1 つの接頭辞で構成することはできないため、1 つのパラメーターを持つコンストラクターの場合はルートを設定します。次に、反復子パターンの実装である WordIterator を作成して、2 つのメソッドをオーバーライドします:hasNext()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--;
        }
    }
}
残っているのは、反復子をクラスに割り当てることだけですWord
public class Word implements Iterable<Word.WordPart> {@Override
	public Iterator<WordPart>iterator() {
    		return new WordIterator(this);
	}}
ここで、「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());
        }
    }
}
イテレータ パターンの実装では、次の出力順序を選択したことに注意してください。
  1. エンディング
  2. サフィックス
  3. コンソール
独自の反復子を設計するときは、必要に応じて反復アルゴリズムを指定できます。勉強頑張ってください!
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION