Tại sao Iterator của Java không phải là Iterable?


178

Tại sao Iteratorgiao diện không mở rộng Iterable?

Các iterator()phương pháp đơn giản có thể trở lại this.

Đây là mục đích hay chỉ là sự giám sát của các nhà thiết kế Java?

Sẽ thuận tiện khi có thể sử dụng vòng lặp for-every với các vòng lặp như thế này:

for(Object o : someContainer.listSomeObjects()) {
    ....
}

trong đó listSomeObjects()trả về một iterator.


1
OK - Tôi thấy điểm của bạn. nó vẫn sẽ thuận tiện ngay cả khi nó phá vỡ một ngữ nghĩa một chút:] Cảm ơn bạn vì tất cả các câu trả lời:]
ukasz Bownik

Tôi nhận ra câu hỏi này đã được hỏi từ lâu, nhưng - ý bạn là bất kỳ Iterator nào, hay chỉ là một Iterator liên quan đến Bộ sưu tập nào đó?
einpoklum

Câu trả lời:


67

Bởi vì một trình vòng lặp thường trỏ đến một thể hiện duy nhất trong một bộ sưu tập. Iterable ngụ ý rằng người ta có thể có được một trình vòng lặp từ một đối tượng để duyệt qua các phần tử của nó - và không cần phải lặp lại qua một thể hiện duy nhất, đó là những gì một trình vòng lặp thể hiện.


25
+1: Một bộ sưu tập có thể lặp lại. Trình lặp không thể lặp được vì nó không phải là bộ sưu tập.
S.Lott

50
Trong khi tôi đồng ý với câu trả lời, tôi không biết liệu mình có đồng ý với tâm lý đó không. Giao diện Iterable trình bày một phương thức duy nhất: Iterator <?> Iterator (); Trong mọi trường hợp, tôi sẽ có thể chỉ định một trình vòng lặp cho mỗi. Tôi không mua nó.
Chris K

25
@ S.Lott lý luận tròn đẹp đó.
yihtserns

16
@ S.Lott Lần thử cuối: Bộ sưu tập ∈ Có thể lặp lại. Trình lặp Bộ sưu tập Trình lặp ∉
Lặp lại

27
@ S.Lott: Bộ sưu tập hoàn toàn không liên quan đến cuộc thảo luận này. Bộ sưu tập chỉ là một trong nhiều triển khai có thể có của Iterable. Thực tế là thứ gì đó không phải là Bộ sưu tập không liên quan đến việc nó có phải là Iterable hay không.
ColinD

218

Một iterator là trạng thái. Ý tưởng là nếu bạn gọi Iterable.iterator()hai lần, bạn sẽ nhận được các trình vòng lặp độc lập - dù sao đối với hầu hết các lần lặp. Đó rõ ràng sẽ không phải là trường hợp trong kịch bản của bạn.

Ví dụ, tôi thường có thể viết:

public void iterateOver(Iterable<String> strings)
{
    for (String x : strings)
    {
         System.out.println(x);
    }
    for (String x : strings)
    {
         System.out.println(x);
    }
}

Điều đó sẽ in bộ sưu tập hai lần - nhưng với sơ đồ của bạn, vòng lặp thứ hai sẽ luôn chấm dứt ngay lập tức.


17
@Chris: Nếu một triển khai trả về cùng một trình vòng lặp hai lần, làm thế nào nó có thể thực hiện hợp đồng của Iterator? Nếu bạn gọi iteratorvà sử dụng kết quả, nó phải lặp lại bộ sưu tập - điều đó sẽ không làm nếu cùng một đối tượng đã lặp lại trên bộ sưu tập. Bạn có thể đưa ra bất kỳ triển khai chính xác nào (ngoài một bộ sưu tập trống) trong đó cùng một trình vòng lặp được trả về hai lần không?
Jon Skeet

7
Đây là một phản hồi tuyệt vời Jon, bạn thực sự có mấu chốt của vấn đề ở đây. Thật xấu hổ vì đây không phải là câu trả lời được chấp nhận! Hợp đồng cho Iterable được xác định nghiêm ngặt, nhưng ở trên là một lời giải thích tuyệt vời về lý do tại sao cho phép Iterator thực hiện Iterable (foreach) sẽ phá vỡ tinh thần của giao diện.
joelittlejohn

3
@JonSkeet trong khi Iterator <T> có trạng thái, hợp đồng của Iterable <T> không nói gì về việc có thể được sử dụng hai lần để có được các trình lặp độc lập, thậm chí đó là kịch bản trong 99% trường hợp. Tất cả những gì tôi có thể lặp lại <T> đều nói rằng nó cho phép một đối tượng trở thành mục tiêu của mục đích. Đối với những bạn không hài lòng với Iterator <T> không phải là Iterable <T>, bạn có thể tự do tạo một Iterator như vậy <T>. Nó sẽ không phá vỡ hợp đồng một chút. Tuy nhiên - Bản thân Iterator không nên lặp đi lặp lại vì điều đó sẽ khiến nó phụ thuộc theo vòng tròn và điều đó đặt nền tảng cho thiết kế icky.
Centril

2
@Centril: Phải rồi. Đã chỉnh sửa để chỉ ra rằng thường gọi iterablehai lần sẽ cung cấp cho bạn các trình vòng lặp độc lập.
Jon Skeet

2
Điều này không đi vào trung tâm của nó. Gần như có thể thực hiện Iterator Iterable thực hiện .iterator () bằng cách đặt lại chính nó, nhưng thiết kế này vẫn sẽ bị hỏng trong một số trường hợp, ví dụ, nếu nó được truyền cho một phương thức có Iterable và lặp trên tất cả các cặp có thể của các phần tử bằng cách lồng cho mỗi vòng lặp.
Theodore Murdock

60

Với 0,02 đô la của tôi, tôi hoàn toàn đồng ý rằng Iterator không nên triển khai Iterable, nhưng tôi nghĩ rằng vòng lặp nâng cao cũng nên chấp nhận. Tôi nghĩ rằng toàn bộ lập luận "make iterators iterable" xuất hiện như một công việc xung quanh một khiếm khuyết trong ngôn ngữ.

Toàn bộ lý do cho việc giới thiệu vòng lặp tăng cường là vì nó "loại bỏ sự sai sót và lỗi phát âm của các trình vòng lặp và các biến chỉ số khi lặp qua các bộ sưu tập và mảng" [ 1 ].

Collection<Item> items...

for (Iterator<Item> iter = items.iterator(); iter.hasNext(); ) {
    Item item = iter.next();
    ...
}

for (Item item : items) {
    ...
}

Tại sao sau đó, đối số tương tự này không giữ cho các trình vòng lặp?

Iterator<Iter> iter...
..
while (iter.hasNext()) {
    Item item = iter.next();
    ...
}

for (Item item : iter) {
    ...
}

Trong cả hai trường hợp, các lệnh gọi tới hasNext () và next () đã bị xóa và không có tham chiếu nào đến iterator trong vòng lặp bên trong. Vâng, tôi hiểu rằng Iterables có thể được sử dụng lại để tạo nhiều iterator, nhưng tất cả điều đó xảy ra bên ngoài vòng lặp for: bên trong vòng lặp chỉ có một tiến trình chuyển tiếp một mục tại một thời điểm so với các mục được lặp lại bởi iterator.

Ngoài ra, cho phép điều này cũng sẽ giúp bạn dễ dàng sử dụng vòng lặp for cho Số liệt kê, như đã được chỉ ra ở nơi khác, tương tự như Iterators chứ không phải Iterables.

Vì vậy, đừng làm cho Iterator thực hiện Iterable, nhưng hãy cập nhật vòng lặp for để chấp nhận.

Chúc mừng


6
Tôi đồng ý. Về mặt lý thuyết có thể có sự nhầm lẫn khi lấy iterator, sử dụng một phần của nó và sau đó đưa nó vào một foreach (phá vỡ hợp đồng "mỗi" foreach), nhưng tôi không nghĩ đó là một lý do đủ tốt để không có tính năng này.
Bart van Heukelom

Chúng tôi đã cập nhật / nâng cao vòng lặp để chấp nhận các mảng thay vì tạo các bộ sưu tập lặp. Bạn có thể hợp lý hóa quyết định này?
Val

Tôi đã đưa ra câu trả lời này, và đó là một sai lầm. Iterator là trạng thái, nếu bạn thúc đẩy lặp qua một iterator với for(item : iter) {...}cú pháp, thì nó sẽ gây ra lỗi khi cùng một iterator lặp đi lặp lại hai lần. Hãy tưởng tượng rằng nó Iteratorđược truyền vào iterateOverphương thức thay vì Iterabletrong ví dụ này .
Ilya Silvestrov

2
Việc bạn sử dụng kiểu for (String x : strings) {...}hay không thực sự không quan trọng while (strings.hasNext()) {...}: nếu bạn cố gắng lặp qua vòng lặp hai lần lần thứ hai sẽ không mang lại kết quả, vì vậy tôi không xem đó là một đối số chống lại việc cho phép cú pháp nâng cao. Câu trả lời của Jon thì khác, bởi vì anh ta cho thấy việc gói một Iteratorcái Iterablesẽ gây ra vấn đề như thế nào, vì trong trường hợp đó bạn sẽ có thể sử dụng lại bao nhiêu lần tùy thích.
Barney

17

Như được chỉ ra bởi những người khác, IteratorIterablelà hai điều khác nhau.

Ngoài ra, Iteratorviệc triển khai trước tăng cường cho các vòng lặp.

Việc khắc phục hạn chế này cũng không quan trọng bằng một phương thức bộ điều hợp đơn giản trông như thế này khi được sử dụng với nhập phương thức tĩnh:

for (String line : in(lines)) {
  System.out.println(line);
}

Mẫu thực hiện:

  /**
   * Adapts an {@link Iterator} to an {@link Iterable} for use in enhanced for
   * loops. If {@link Iterable#iterator()} is invoked more than once, an
   * {@link IllegalStateException} is thrown.
   */
  public static <T> Iterable<T> in(final Iterator<T> iterator) {
    assert iterator != null;
    class SingleUseIterable implements Iterable<T> {
      private boolean used = false;

      @Override
      public Iterator<T> iterator() {
        if (used) {
          throw new IllegalStateException("SingleUseIterable already invoked");
        }
        used = true;
        return iterator;
      }
    }
    return new SingleUseIterable();
  }

Trong Java 8, việc điều chỉnh một Iteratorcách Iterableđơn giản hơn:

for (String s : (Iterable<String>) () -> iterator) {

bạn đã khai báo một lớp trong một hàm; tui bỏ lỡ điều gì vậy? Tôi nghĩ rằng điều này là bất hợp pháp.
activedecay

Một lớp có thể được định nghĩa trong một khối trong Java. Nó được gọi là một lớp học địa phương
Colin D Bennett

3
Cảm ơn vìfor (String s : (Iterable<String>) () -> iterator)
AlikElzin-kilaka

8

Như những người khác đã nói, một Iterable có thể được gọi nhiều lần, trả về một Iterator mới trên mỗi cuộc gọi; một Iterator được sử dụng chỉ một lần. Vì vậy, chúng có liên quan, nhưng phục vụ các mục đích khác nhau. Tuy nhiên, thật khó chịu, phương pháp "compact for" chỉ hoạt động với một lần lặp.

Những gì tôi sẽ mô tả dưới đây là một cách để có được cả hai thế giới tốt nhất - trả về một Iterable (cho cú pháp đẹp hơn) ngay cả khi chuỗi dữ liệu cơ bản là một lần.

Bí quyết là trả lại một triển khai ẩn danh của Iterable thực sự kích hoạt công việc. Vì vậy, thay vì thực hiện công việc tạo ra một chuỗi một lần và sau đó trả lại một Iterator qua đó, bạn trả lại một Iterable mà mỗi lần nó được truy cập sẽ làm lại công việc. Điều đó có vẻ lãng phí, nhưng thường thì bạn sẽ chỉ gọi Iterable một lần, và ngay cả khi bạn gọi nó nhiều lần, nó vẫn có ngữ nghĩa hợp lý (không giống như một trình bao bọc đơn giản làm cho Iterator "trông giống như" Iterable, điều này đã thắng ' t thất bại nếu sử dụng hai lần).

Ví dụ: giả sử tôi có một DAO cung cấp một loạt các đối tượng từ cơ sở dữ liệu và tôi muốn cung cấp quyền truy cập thông qua một trình vòng lặp (ví dụ: để tránh tạo tất cả các đối tượng trong bộ nhớ nếu không cần thiết). Bây giờ tôi chỉ có thể trả về một trình vòng lặp, nhưng điều đó làm cho việc sử dụng giá trị được trả về trong một vòng lặp trở nên xấu xí. Vì vậy, thay vào đó tôi bọc mọi thứ trong một vòng lặp lặp lại:

class MetricDao {
    ...
    /**
     * @return All known metrics.
     */
    public final Iterable<Metric> loadAll() {
        return new Iterable<Metric>() {
            @Override
            public Iterator<Metric> iterator() {
                return sessionFactory.getCurrentSession()
                        .createQuery("from Metric as metric")
                        .iterate();
            }
        };
    }
}

điều này sau đó có thể được sử dụng trong mã như thế này:

class DaoUser {
    private MetricDao dao;
    for (Metric existing : dao.loadAll()) {
        // do stuff here...
    }
}

cho phép tôi sử dụng vòng lặp compact cho vòng lặp mà vẫn giữ mức sử dụng bộ nhớ tăng dần.

Cách tiếp cận này là "lười biếng" - công việc không được thực hiện khi Iterable được yêu cầu, mà chỉ sau đó khi nội dung được lặp lại - và bạn cần nhận thức được hậu quả của điều đó. Trong ví dụ với DAO có nghĩa là lặp lại các kết quả trong giao dịch cơ sở dữ liệu.

Vì vậy, có nhiều cảnh báo khác nhau, nhưng đây vẫn có thể là một thành ngữ hữu ích trong nhiều trường hợp.


ans tốt nhưng bạn returning a fresh Iterator on each callnên đi kèm với lý do tại sao tức là để ngăn chặn vấn đề tương tranh ...
Anirudha

7

Thật đáng kinh ngạc, không ai khác đã đưa ra câu trả lời này. Đây là cách bạn có thể "dễ dàng" lặp đi lặp lại Iteratorbằng cách sử dụng Iterator.forEachRemaining()phương thức Java 8 mới :

Iterator<String> it = ...
it.forEachRemaining(System.out::println);

Tất nhiên, có một giải pháp "đơn giản hơn" hoạt động trực tiếp với vòng lặp foreach, gói Iteratortrong Iterablelambda:

for (String s : (Iterable<String>) () -> it)
    System.out.println(s);

5

Iteratorlà một giao diện cho phép bạn lặp đi lặp lại một cái gì đó. Nó là một thực hiện của việc di chuyển thông qua một bộ sưu tập của một số loại.

Iterable là một giao diện chức năng biểu thị rằng một cái gì đó có chứa một trình vòng lặp có thể truy cập.

Trong Java8, điều này làm cho cuộc sống trở nên khá dễ dàng ... Nếu bạn có Iteratornhưng cần một thứ Iterableđơn giản bạn có thể làm:

Iterator<T> someIterator;
Iterable<T> = ()->someIterator;

Điều này cũng hoạt động trong vòng lặp for:

for (T item : ()->someIterator){
    //doSomething with item
}

2

Tôi đồng ý với câu trả lời được chấp nhận, nhưng muốn thêm lời giải thích của riêng tôi.

  • Iterator đại diện cho trạng thái di chuyển ngang, ví dụ: bạn có thể lấy phần tử hiện tại từ một trình vòng lặp và chuyển sang phần tiếp theo.

  • Iterable đại diện cho một bộ sưu tập có thể đi qua, nó có thể trả về bao nhiêu lần lặp mà bạn muốn, mỗi đại diện cho trạng thái di chuyển ngang của chính nó, một iterator có thể trỏ đến phần tử đầu tiên, trong khi một phần tử khác có thể trỏ đến phần tử thứ 3.

Sẽ thật tuyệt nếu Java cho vòng lặp chấp nhận cả Iterator và Iterable.


2

Để tránh phụ thuộc vào java.util gói

Theo JSR ban đầu, Vòng lặp nâng cao cho Ngôn ngữ lập trình Java ™ , các giao diện được đề xuất:

  • java.lang.Iterable
  • java.lang.ReadOnlyIterator
    (đề xuất được trang bị thêm java.util.Iterator, nhưng dường như điều này không bao giờ xảy ra)

Khoan được thiết kế để sử dụng java.langkhông gian tên gói hơn làjava.util .

Để trích dẫn JSR:

Các giao diện mới này phục vụ để ngăn chặn sự phụ thuộc của ngôn ngữ vào java.util sẽ dẫn đến kết quả khác.


Nhân tiện, cái cũ java.util.Iterableđã đạt được một forEachphương thức mới trong Java 8+, để sử dụng với cú pháp lambda (truyền a Consumer).

Đây là một ví dụ. Các Listgiao diện mở rộng Iterablegiao diện, dạng danh sách bất kỳ mang một forEachphương pháp.

List
.of ( "dog" , "cat" , "bird" )
.forEach ( ( String animal ) -> System.out.println ( "animal = " + animal ) );

2

Tôi cũng thấy nhiều người làm điều này:

public Iterator iterator() {
    return this;
}

Nhưng điều đó không làm cho nó đúng! Phương pháp này sẽ không như bạn muốn!

Phương thức iterator()này được cho là trả về một trình vòng lặp mới bắt đầu từ đầu. Vì vậy, người ta cần phải làm một cái gì đó như thế này:

public class IterableIterator implements Iterator, Iterable {

  //Constructor
  IterableIterator(SomeType initdata)
  {
    this.initdata = iter.initdata;
  }
  // methods of Iterable

  public Iterator iterator() {
    return new IterableIterator(this.intidata);
  }

  // methods of Iterator

  public boolean hasNext() {
    // ...
  }

  public Object next() {
    // ...
  }

  public void remove() {
    // ...
  }
}

Câu hỏi là: liệu có cách nào để tạo ra một lớp trừu tượng thực hiện điều này không? Vì vậy, để có được một IterableIterator, người ta chỉ cần thực hiện hai phương thức next () và hasNext ()


1

Nếu bạn đến đây để tìm kiếm một cách giải quyết, bạn có thể sử dụng IteratorIterable . (có sẵn cho Java 1.6 trở lên)

Ví dụ sử dụng (đảo ngược một Vector).

import java.util.Vector;
import org.apache.commons.collections4.iterators.IteratorIterable;
import org.apache.commons.collections4.iterators.ReverseListIterator;
public class Test {
    public static void main(String ... args) {
        Vector<String> vs = new Vector<String>();
        vs.add("one");
        vs.add("two");
        for ( String s: vs ) {
            System.out.println(s);
        }
        Iterable<String> is
            = new IteratorIterable(new ReverseListIterator(vs));
        for ( String s: is ) {
            System.out.println(s);
        }
    }
}

in

one
two
two
one

0

Để đơn giản, Iterator và Iterable là hai khái niệm riêng biệt, Iterable chỉ đơn giản là một cách viết tắt cho "Tôi có thể trả lại một Iterator". Tôi nghĩ rằng mã của bạn nên là:

for(Object o : someContainer) {
}

với một số đối tượng SomeContainer extends Iterable<Object>




0

Các trình vòng lặp có trạng thái, chúng có phần tử "tiếp theo" và trở nên "cạn kiệt" sau khi lặp lại. Để xem vấn đề ở đâu, hãy chạy đoạn mã sau, có bao nhiêu số được in?

Iterator<Integer> iterator = Arrays.asList(1,2,3).iterator();
Iterable<Integer> myIterable = ()->iterator;
for(Integer i : myIterable) System.out.print(i);
System.out.println();
for(Integer i : myIterable) System.out.print(i);

-1

Bạn có thể thử ví dụ sau:

List ispresent=new ArrayList();
Iterator iterator=ispresent.iterator();
while(iterator.hasNext())
{
    System.out.println(iterator.next());
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.