왜 Java의 Iterator가 Iterable이 아닌가?


178

Iterator인터페이스가 확장되지 Iterable않습니까?

iterator()메소드는 단순히을 리턴 할 수 this있습니다.

Java 디자이너의 의도적이거나 단순한 감독입니까?

다음과 같이 반복자와 함께 for-each 루프를 사용하는 것이 편리합니다.

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

여기서 listSomeObjects()반복자를 반환합니다.


1
확인-당신의 요점은 알 수 있습니다. 그것은 의미론이 약간
깨져

나는이 질문이 오래 전에 요청되었음을 알고 있지만-Iterator 또는 일부 Collection과 관련된 Iterator를 의미합니까?
einpoklum

답변:


67

반복자는 일반적으로 컬렉션의 단일 인스턴스를 가리 킵니다. Iterable은 객체에서 요소를 가로 지르기 위해 객체에서 반복자를 얻을 수 있음을 의미하며 단일 인스턴스를 반복 할 필요가 없습니다. 이터레이터가 나타내는 것입니다.


25
+1 : 컬렉션을 반복 할 수 있습니다. 반복자는 컬렉션이 아니기 때문에 반복 할 수 없습니다.
S.Lott

50
나는 그 대답에 동의하지만, 나는 마음가짐에 동의하는지 모른다. Iterable 인터페이스는 단일 메소드를 제공합니다. Iterator <?> iterator (); 어쨌든, 각각에 대해 반복자를 지정할 수 있어야합니다. 나는 그것을 사지 않습니다.
Chris K

25
@ S.Lott 멋진 순환 추론이 있습니다.
yihtserns

16
@ S.Lott 마지막 시도 : 수집 ∈ 반복 가능. Iterator ≠ Collection ∴ Iterator ∉
Iters

27
@ S.Lott : 컬렉션은이 토론과 전혀 관련이 없습니다. 컬렉션은 Iterable의 많은 구현 중 하나 일뿐입니다. 컬렉션이 아닌 것은 사실 Iterable인지 여부와 관계가 없습니다.
ColinD

218

반복자는 상태 저장입니다. 아이디어는 Iterable.iterator()두 번 호출 하면 독립적 인 반복자를 얻을 수 있다는 것 입니다. 귀하의 시나리오에서는 그렇지 않습니다.

예를 들어, 나는 보통 다음과 같이 쓸 수 있습니다.

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

컬렉션을 두 번 인쇄해야하지만 스키마를 사용하면 두 번째 루프가 항상 즉시 종료됩니다.


17
@Chris : 구현이 동일한 반복자를 두 번 반환하면 지구상에서 어떻게 반복기 계약을 이행 할 수 있습니까? iterator결과 를 호출 하고 사용하면 컬렉션을 반복해야합니다. 동일한 개체가 이미 컬렉션을 반복 한 경우에는 수행되지 않습니다. 당신이 줄 수 있는 같은 반복자 두 번 반환됩니다 (하늘의 콜렉션 이외의) 올바른 구현을?
Jon Skeet

7
이것은 훌륭한 답변입니다. Jon, 여기 문제의 핵심이 있습니다. 안타깝게도 이것이 정답이 아닙니다! Iterable에 대한 계약은 엄격하게 정의되어 있지만 위의 내용은 Iterator가 Iterable을 구현하도록 허용하는 이유 (foreach)가 인터페이스의 정신을 어기는 이유에 대한 훌륭한 설명입니다.
joelittlejohn

3
@JonSkeet Iterator <T>가 stateful 인 동안 Iterable <T>의 계약은 독립 iterator를 얻기 위해 두 번 사용할 수있는 것에 대해서는 아무 것도 말하지 않으며 99 %의 경우에도 시나리오입니다. 모든 Iterable <T>는 객체가 foreach의 대상이 될 수 있다고 말합니다. Iterator <T>가 Iterable <T>이 아닌 것에 대해 불만이있는 사용자는 그러한 Iterator <T>를 자유롭게 만들 수 있습니다. 그것은 계약을 조금이라도 깨뜨리지 않을 것입니다. 그러나 반복자 자체는 순환 의존적이어서 icky 디자인의 토대가되기 때문에 반복 가능해서는 안됩니다.
Centril

2
@Centril : 그렇습니다. 일반적으로iterable 두 번 호출 하면 독립 반복자가 제공됨을 나타내도록 편집했습니다 .
Jon Skeet

2
이것은 그것의 핵심에 도달합니다. 자체를 재설정하여 .iterator ()를 구현하는 Iterable Iterator를 구현하는 것이 거의 가능하지만,이 디자인은 Iterable을 사용하고 가능한 모든 쌍을 반복하는 메소드에 전달 된 경우와 같이 일부 상황에서 여전히 중단됩니다. for-each 루프를 중첩하여 요소를 정의합니다.
Theodore Murdock

60

내 $ 0.02의 경우, Iterator가 Iterable을 구현해서는 안된다는 것에 전적으로 동의하지만, 향상된 for 루프는 어느 쪽이든 받아 들여야한다고 생각합니다. 나는 "반복자를 반복 가능하게 만든다"는 주장이 언어의 결함에 대한 해결책으로 나온다고 생각한다.

향상된 for 루프가 도입 된 이유는 "컬렉션과 배열을 반복 할 때 반복자와 인덱스 변수의 혼란과 오류가 발생하지 않기 때문입니다"[ 1 ].

Collection<Item> items...

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

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

그렇다면 왜이 같은 주장이 반복자에게 적용되지 않습니까?

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

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

두 경우 모두 hasNext () 및 next ()에 대한 호출이 제거되었으며 내부 루프의 반복자에 대한 참조가 없습니다. 예, Iterables를 사용하여 여러 반복기를 만들 수 있지만 모든 루프 외부에서 발생한다는 것을 알고 있습니다.

또한 이것을 허용하면 다른 곳에서 지적했듯이 반복문이 아닌 반복자와 유사한 열거 형 for 루프를 쉽게 사용할 수 있습니다.

따라서 Iterator를 Iterable로 구현하지 말고 for 루프를 업데이트하여 둘 중 하나를 수락하십시오.

건배,


6
나는 동의한다. 이론적으로 반복자를 가져 와서 일부를 사용하여 foreach에 넣을 때 (foreach의 "각"계약을 위반할 때) 혼란이있을 수 있지만, 이것이이 기능을 갖지 않는 충분한 이유라고 생각하지 않습니다.
바트 반 Heukelom

배열을 반복 가능한 컬렉션으로 만드는 대신 배열을 허용하도록 루프를 이미 업데이트 / 강화했습니다. 이 결정을 합리화 할 수 있습니까?
Val

이 답장을 찬성했고 실수였습니다. 반복자는 스테이트 풀 상태입니다. for(item : iter) {...}구문 을 사용하여 반복자를 통해 반복을 승격 하면 동일한 반복자가 두 번 반복 될 때 오류가 발생합니다. 그 상상 Iterator에 전달 iterateOver방법 대신 Iterable .
Ilya Silvestrov

2
for (String x : strings) {...}또는 while (strings.hasNext()) {...}스타일 을 사용하는지 여부는 중요하지 않습니다 . 반복자를 두 번 반복하려고하면 결과가 나타나지 않으므로 자체적으로 향상된 구문을 허용하지 않는 것에 대한 논쟁으로 보지 않습니다. Jon의 대답은 다릅니다. 그가 Iteratorin을 감싸는 Iterable것이 문제를 일으키는 방법을 보여 주었기 때문에 원하는 경우 여러 번 재사용 할 수 있기를 기대합니다.
Barney

17

마찬가지로 다른 사람에 의해 지적, Iterator그리고 Iterable다른 두 가지입니다.

또한 Iterator구현은 향상된 for 루프보다 우선합니다.

정적 메소드 가져 오기와 함께 사용할 때 다음과 같은 간단한 어댑터 메소드로이 제한 사항을 극복하는 것은 사소한 일입니다.

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

샘플 구현 :

  /**
   * 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();
  }

Java 8에서는 an Iterator에 적응하는 것이 Iterable더 간단합니다.

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

함수에서 클래스를 선언했습니다. 내가 뭔가를 놓치고 있습니까? 나는 이것이 불법이라고 생각했다.
activedecay 2016 년

클래스는 Java에서 블록으로 정의 할 수 있습니다. 그것은라고 하는 로컬 클래스
콜린 D 베넷

3
for (String s : (Iterable<String>) () -> iterator)
AlikElzin-kilaka에

8

다른 사람들이 말했듯이, Iterable은 여러 번 호출 될 수 있으며 각 호출마다 새로운 Iterator를 반환합니다. 반복자는 한 번만 사용됩니다. 그래서 그들은 관련되어 있지만 다른 목적으로 사용됩니다. 그러나 "compact for"방법은 반복적으로 만 작동합니다.

아래에서 설명하는 것은 두 데이터를 모두 활용하는 한 가지 방법입니다. 기본 데이터 시퀀스가 ​​일 회일 때도 (더 나은 구문을 위해) Iterable을 반환합니다.

트릭은 실제로 작업을 트리거하는 Iterable의 익명 구현을 리턴하는 것입니다. 따라서 일회성 시퀀스를 생성 한 다음 반복자를 반환하는 작업을 수행하는 대신 액세스 할 때마다 작업을 다시 실행하는 Iterable을 반환합니다. 그것은 낭비 적 인 것처럼 보일 수도 있지만, 어쨌든 한 번만 Iterable을 호출 할 것입니다. 여러 번 호출하더라도 여전히 합리적인 의미를 갖습니다 (Iterator를 "Iterable처럼 보이게하는 간단한 래퍼와 달리" 두 번 사용하면 실패합니다).

예를 들어, 데이터베이스에서 일련의 객체를 제공하는 DAO가 있고 반복자를 통해 액세스 할 수 있도록하려고합니다 (예 : 필요하지 않은 경우 메모리에 모든 객체를 생성하지 않기 위해). 이제 반복자를 반환 할 수는 있지만 반환 된 값을 루프에서 못 생겼습니다. 따라서 대신 모든 것을 Anon Iterable에 래핑합니다.

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();
            }
        };
    }
}

그러면 다음과 같은 코드에서 사용될 수 있습니다.

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

그래도 증분 메모리 사용을 유지하면서 컴팩트 한 for 루프를 사용할 수 있습니다.

이 접근 방식은 "게으른"것입니다. Iterable이 요청 될 때 작업이 완료되지 않고 나중에 내용이 반복 될 때만 수행되며 그 결과를 알고 있어야합니다. DAO가있는 예에서 이는 데이터베이스 트랜잭션 내에서 결과를 반복한다는 의미입니다.

따라서 여러 가지 경고가 있지만 여전히 많은 경우에 유용한 관용구가 될 수 있습니다.


좋은 ans 그러나 동시성 문제를 방지하기 위해 이유returning a fresh Iterator on each call 와 함께해야합니다 ...
Anirudha

7

놀랍게도 아직 아무도이 대답을하지 않았습니다. Iterator새로운 Java 8 Iterator.forEachRemaining()메소드를 사용하여 "간편하게"반복하는 방법은 다음과 같습니다 .

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

물론 foreach 루프와 직접 작동 Iterator하여 Iterable람다로 감싸는 "더 간단한"솔루션이 있습니다 .

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

5

Iterator무언가를 반복 할 수있는 인터페이스입니다. 일종의 컬렉션을 통해 이동하는 구현입니다.

Iterable 액세스 가능한 반복자가 포함 된 것을 나타내는 기능 인터페이스입니다.

Java8에서 이것은 인생을 아주 쉽게 만듭니다 ... 당신이 Iterator있지만 필요하다면 Iterable간단하게 할 수 있습니다 :

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

for 루프에서도 작동합니다.

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

2

수락 된 답변에 동의하지만 내 설명을 추가하고 싶습니다.

  • 반복자는 순회 상태를 나타냅니다. 예를 들어 반복자에서 현재 요소를 가져 와서 다음 요소로 이동할 수 있습니다.

  • Iterable은 순회 할 수있는 컬렉션을 나타내며 원하는만큼 많은 반복자를 반환 할 수 있습니다. 각 반복은 자체 순회 상태를 나타내며, 하나의 반복자는 첫 번째 요소를 가리키고 다른 반복자는 3 번째 요소를 가리킬 수 있습니다.

Java for loop가 Iterator와 Iterable을 모두 허용하면 좋을 것입니다.


2

java.util패키지에 대한 의존을 피하려면

원래 JSR, Java ™ 프로그래밍 언어를위한 향상된 for 루프에 따르면 제안 된 인터페이스는 다음과 같습니다.

  • java.lang.Iterable
  • java.lang.ReadOnlyIterator
    (에 개장 할 것을 제안 java.util.Iterator했지만 분명히 이런 일은 일어나지 않았습니다)

java.lang보다 패키지 네임 스페이스 를 사용하도록 설계되었습니다 java.util.

JSR을 인용하려면 :

이 새로운 인터페이스는 java.util에 대한 언어의 의존성을 막아줍니다.


그건 그렇고, 이전 java.util.IterableforEach람다 구문과 함께 사용하기 위해 Java 8 이상에서 새로운 메소드를 얻었습니다 (a 전달 Consumer).

다음은 예입니다. 모든 목록은 메소드를 전달하므로 인터페이스 는 List인터페이스를 확장 합니다.IterableforEach

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

2

나는 또한 많은 사람들이 이것을하는 것을 봅니다.

public Iterator iterator() {
    return this;
}

그러나 그것이 올바른 것은 아닙니다! 이 방법은 당신이 원하는 것이 아닙니다!

이 메소드 iterator()는 처음부터 새 반복자를 리턴해야합니다. 따라서 다음과 같이해야합니다.

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() {
    // ...
  }
}

문제는 이것을 수행하는 추상 클래스를 만들 수있는 방법이 있습니까? IterableIterator를 얻으려면 next () 및 hasNext ()라는 두 가지 메소드 만 구현하면됩니다.


1

해결 방법을 찾기 위해 여기에 온 경우 IteratorIterable 을 사용할 수 있습니다 . (Java 1.6 이상에서 사용 가능)

사용법 예 (벡터 반전).

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);
        }
    }
}

인쇄물

one
two
two
one

0

간단하게하기 위해 Iterator와 Iterable은 서로 다른 두 가지 개념으로, Iterable은 "Ierator를 반환 할 수 있습니다"의 약어입니다. 귀하의 코드는 다음과 같아야한다고 생각합니다.

for(Object o : someContainer) {
}

someContainer instanceof로 SomeContainer extends Iterable<Object>




0

반복자는 상태가 양호하며 "다음"요소를 가지며 반복되면 "고갈"됩니다. 문제가있는 곳을 보려면 다음 코드를 실행하십시오. 몇 개의 숫자가 인쇄됩니까?

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

다음 예제를 시도해 볼 수 있습니다.

List ispresent=new ArrayList();
Iterator iterator=ispresent.iterator();
while(iterator.hasNext())
{
    System.out.println(iterator.next());
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.