JavaRush /Blog Java /Random-VI /Mẫu lặp

Mẫu lặp

Xuất bản trong nhóm
Hôm nay chúng ta sẽ nói về Iterator là gì trong Java và tại sao nó lại cần thiết.
Mẫu vòng lặp - 1
Như bạn có thể đã biết, Java có giao diện Bộ sưu tập tuyệt vời triển khai giao diện Iterator. Hãy để tôi đặt chỗ ngay: giao diện iterator không nên nhầm lẫn với mẫu iterator trong Java! Và để làm rõ, trước tiên chúng ta hãy nhìn vào giao diện.
Theo nghĩa đen, “Iterator” có thể được dịch là “vũ lực ” . Nghĩa là, nó là một thực thể nhất định có thể lặp qua tất cả các phần tử trong bộ sưu tập. Hơn nữa, nó cho phép bạn làm điều này mà không cần đi sâu vào cấu trúc bên trong và cách sắp xếp các bộ sưu tập.
Hãy tưởng tượng một chút rằng không có trình vòng lặp trong Java. Trong trường hợp này, mỗi người sẽ phải đi sâu vào các bộ sưu tập và thực sự hiểu những gì khác biệt ArrayListLinkedListkhác HashSetbiệt với TreeSet.

Các phương thức mà Iterator phải triển khai

boolean hasNext()— nếu vẫn còn giá trị trong đối tượng có thể lặp lại (hiện tại là Bộ sưu tập), phương thức sẽ trả về true, nếu không còn giá trị nào nữa false. E next()— trả về phần tử tiếp theo của bộ sưu tập (đối tượng). Nếu không còn phần tử nào nữa (không có kiểm tra hasNext()và chúng tôi đã gọi next()khi đến cuối bộ sưu tập), phương thức sẽ ném NoSuchElementException. void remove()- sẽ xóa phần tử được next(). Phương thức này có thể ném:
  • UnsupportedOperationException, nếu trình vòng lặp này không hỗ trợ phương thức remove()(ví dụ: trong trường hợp bộ sưu tập chỉ đọc)
  • IllegalStateException, nếu phương thức này next()chưa được gọi hoặc nếu nó remove()đã được gọi kể từ lần gọi cuối cùng next().
Vì vậy, iterator cho List là cách triển khai phổ biến nhất. Trình vòng lặp đi từ đầu bộ sưu tập đến cuối bộ sưu tập: nó xem liệu phần tử tiếp theo có hiện diện hay không và trả về nó nếu có. Một chu trình được xây dựng trên cơ sở thuật toán đơn giản này for-each. Phần mở rộng của nó là ListIterator. Chúng ta hãy xem xét các phương thức lặp danh sách java bổ sung. Rất có thể bạn biết họ:
  • void add(E e)— chèn một phần tử Evào ;List
  • boolean hasPrevious()— sẽ trả về truenếu Listcó các phần tử trong quá trình tìm kiếm ngược lại;
  • int nextIndex()— sẽ trả về chỉ mục của phần tử tiếp theo;
  • E previous()— sẽ trả về phần tử trang tính trước đó;
  • int previousIndex()— sẽ trả về chỉ mục của phần tử trước đó;
  • void set(E e)- sẽ thay thế phần tử được trả về bởi lệnh gọi cuối cùng next()hoặc previous()bằng phần tử e.
Hãy xem xét một ví dụ nhỏ. Hãy tạo một Listdòng chứa lời chào tới học sinh:
List<String> list = new ArrayList<>();
list.add("Hello");
list.add("Обучающимся");
list.add("На");
list.add("JavaRush");
Bây giờ chúng ta sẽ lấy một trình vòng lặp cho nó và in tất cả các dòng có trong bảng điều khiển:
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}
Bây giờ sẽ có một nút thắt cổ chai: Java Collections, như bạn có thể biết (và nếu bạn không biết, hãy tìm hiểu), mở rộng giao diện Iterable, nhưng điều này không có nghĩa là chỉ có List, SetQueuehỗ trợ iterator. For java Map iteratorcũng được hỗ trợ, nhưng phải được gọi cho Map.entrySet():
Map<String, Integer> map = new HashMap<>();
Iterator mapIterator = map.entrySet().iterator();
Sau đó, phương thức next() sẽ trả về một đối tượng Entrychứa cặp “key”-“value”. Sau đó, mọi thứ đều giống nhau với List:
while (mapIterator.hasNext()) {
    Map.Entry<String, Integer> entry = mapIterator.next();
    System.out.println("Key: " + entry.getKey());
    System.out.println("Value: " + entry.getValue());
}
Bạn nghĩ: “Dừng lại. Chúng ta đang nói về giao diện và tiêu đề của bài viết là “Mẫu”. Tức là mẫu iterator là giao diện Iterator? Hay giao diện là một mẫu? Nếu từ này xuất hiện lần đầu tiên, tôi sẽ cho bạn tham khảo: mẫu là một mẫu thiết kế, một hành vi nhất định mà một lớp hoặc nhiều lớp liên kết với nhau phải tuân thủ. Một trình vòng lặp trong java có thể được triển khai cho bất kỳ đối tượng nào có cấu trúc bên trong liên quan đến việc lặp lại và bạn có thể thay đổi chữ ký của các phương thức đang được thảo luận. Điều chính khi triển khai một mẫu là logic mà lớp phải tuân theo. Giao diện iterator là một triển khai riêng của mẫu cùng tên, được áp dụng cho cả các cấu trúc tạo sẵn ( List, Set, Queue, Map) và các cấu trúc khác, theo quyết định của người lập trình. Bằng cách mở rộng giao diện Iterator, bạn triển khai một mẫu nhưng bạn không cần mở rộng giao diện để triển khai mẫu đó. Một sự tương tự đơn giản: tất cả các loài cá đều bơi, nhưng không phải mọi thứ bơi được đều là cá. Để làm ví dụ, tôi quyết định lấy... từ này. Cụ thể hơn là một danh từ. Nó bao gồm các phần: tiền tố, gốc, hậu tố và kết thúc. Đối với các phần của một từ, chúng ta sẽ tạo một giao diện WordPartvà các lớp mở rộng nó: 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;
    }
}
Khi đó lớp Word(từ) sẽ chứa các phần và ngoài chúng, chúng ta sẽ thêm một số nguyên phản ánh số phần trong từ:
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;
    }
Được rồi, chúng ta có bốn hàm tạo quá tải (để đơn giản, giả sử chúng ta chỉ có thể có một hậu tố). Một danh từ không thể bao gồm một tiền tố, vì vậy đối với hàm tạo có một tham số, chúng ta sẽ đặt gốc. Bây giờ hãy viết một triển khai mẫu lặp: WordIterator, ghi đè 2 phương thức: 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--;
        }
    }
}
Tất cả những gì còn lại là gán iterator cho lớp Word:
public class Word implements Iterable<Word.WordPart> {@Override
	public Iterator<WordPart>iterator() {
    		return new WordIterator(this);
	}}
Bây giờ chúng ta hãy tiến hành phân tích hình thái của từ “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());
        }
    }
}
Xin lưu ý rằng khi triển khai mẫu vòng lặp, tôi đã chọn thứ tự đầu ra sau:
  1. kết thúc
  2. hậu tố
  3. bảng điều khiển
  4. nguồn gốc
Khi thiết kế trình lặp của riêng mình, bạn có thể chỉ định thuật toán lặp theo ý muốn. Chúc may mắn trong các nghiên cứu của bạn!
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION