เหตุใด Iterator ของ Java จึงไม่ใช่ Iterable


178

ทำไมIteratorอินเตอร์เฟซที่ไม่ขยายIterable?

วิธีการก็จะกลับมาiterator()this

มันเป็นไปตามวัตถุประสงค์หรือเป็นเพียงการกำกับดูแลของนักออกแบบของ Java หรือไม่?

มันจะสะดวกที่จะใช้ลูปสำหรับแต่ละลูปกับตัววนซ้ำเช่นนี้:

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

โดยที่listSomeObjects()ส่งคืนตัววนซ้ำ


1
ตกลง - ฉันเห็นจุดของคุณ buut มันจะยังคงสะดวกแม้ว่ามันจะหยุดความหมายเล็กน้อย:] ขอบคุณ U สำหรับคำตอบทั้งหมด:]
--ukasz Bownik

ฉันรู้ว่าคำถามนี้ถูกถามมานานแล้ว แต่ - คุณหมายถึง Iterator ใด ๆ หรือเพียงแค่เป็น Iterator ที่เกี่ยวข้องกับการสะสมบางส่วน?
einpoklum

คำตอบ:


67

เนื่องจากตัววนซ้ำชี้ไปที่อินสแตนซ์เดี่ยวในคอลเล็กชัน Iterable บอกเป็นนัยว่าอาจได้รับตัววนซ้ำจากวัตถุเพื่อสำรวจองค์ประกอบ - และไม่จำเป็นต้องวนซ้ำในอินสแตนซ์เดี่ยวซึ่งเป็นสิ่งที่ตัววนซ้ำแสดงถึง


25
+1: คอลเลกชันสามารถทำซ้ำได้ ตัววนซ้ำไม่สามารถทำซ้ำได้เนื่องจากไม่ใช่ชุดสะสม
S.Lott

50
ในขณะที่ฉันเห็นด้วยกับคำตอบฉันไม่รู้ว่าฉันเห็นด้วยกับความคิดหรือไม่ อินเตอร์เฟส Iterable นำเสนอวิธีการเดียว: Iterator <?> iterator (); ไม่ว่าในกรณีใดฉันควรจะระบุตัววนซ้ำสำหรับแต่ละตัว ฉันไม่ซื้อ
Chris K

25
@ S.Lott เหตุผลเวียนดี
yihtserns

16
@ S.Lott ความพยายามครั้งสุดท้าย: การรวบรวม∈ซ้ำได้ Iterator ≠ Collection ∴ Iterator ∉ Iterable
yihtserns

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 ได้อย่างไร หากคุณโทรiteratorและใช้ผลลัพธ์จะต้องวนซ้ำคอลเลกชัน - ซึ่งจะไม่ทำถ้าวัตถุเดียวกันนั้นมีการทำซ้ำมากกว่าคอลเลกชันแล้ว คุณสามารถให้การใด ๆการดำเนินงานที่ถูกต้อง (นอกเหนือจากคอลเลกชันที่ว่างเปล่า) ที่ iterator เดียวกันจะถูกส่งกลับสองครั้ง
Jon Skeet

7
นี่คือการตอบสนองที่ยอดเยี่ยมจอนคุณได้รับปัญหาที่แท้จริงที่นี่ อับอายนี่ไม่ใช่คำตอบที่ยอมรับ! สัญญาของ Iterable มีการกำหนดไว้อย่างเคร่งครัด แต่ข้างต้นเป็นคำอธิบายที่ดีว่าทำไมการอนุญาตให้ Iterator ใช้ Iterable (foreach) จะทำลายจิตวิญญาณของอินเทอร์เฟซ
joelittlejohn

3
@JonSkeet ในขณะที่ Iterator <T> เป็นของรัฐสัญญาของ Iterable <T> ไม่ได้พูดอะไรเลยเกี่ยวกับความสามารถในการใช้ซ้ำสองครั้งเพื่อรับตัววนซ้ำอิสระแม้จะเป็นกรณี 99% ก็ตาม Iterable <T> ทั้งหมดบอกว่ามันอนุญาตให้วัตถุเป็นเป้าหมายของ foreach สำหรับผู้ที่ไม่พอใจกับ Iterator <T> ที่ไม่ใช่ Iterable <T> คุณมีอิสระที่จะสร้าง Iterator <T> มันจะไม่ทำลายสัญญาหนึ่งบิต อย่างไรก็ตาม - Iterator นั้นไม่ควรทำซ้ำได้เนื่องจากมันจะทำให้มันขึ้นอยู่กับการวนเวียนและวางพื้นสำหรับการออกแบบที่น่ากลัว
Centril

2
@ Centril: ใช่ ได้แก้ไขเพื่อระบุว่าโดยปกติแล้วการโทรiterableสองครั้งจะให้ตัวทำซ้ำอิสระแก่คุณ
Jon Skeet

2
นี่คือหัวใจของมัน มันเกือบจะเป็นไปได้ที่จะใช้ Iterable Iterator ที่ใช้ .iterator () โดยการรีเซ็ตตัวเอง แต่การออกแบบนี้จะยังคงแตกในบางสถานการณ์ตัวอย่างเช่นถ้ามันถูกส่งผ่านไปยังวิธีที่ใช้ Iterable และวนซ้ำทุกคู่ที่เป็นไปได้ ขององค์ประกอบโดยการทำรังสำหรับแต่ละลูป
Theodore Murdock

60

สำหรับ $ 0.02 ของฉันฉันยอมรับอย่างสมบูรณ์ว่า Iterator ไม่ควรใช้ Iterable แต่ฉันคิดว่าการปรับปรุงสำหรับ Loop ควรยอมรับเช่นกัน ฉันคิดว่าข้อโต้แย้งทั้งหมด "ทำให้ผู้ทำซ้ำได้" ขึ้นมาเป็นข้อบกพร่องในภาษา

เหตุผลทั้งหมดสำหรับการแนะนำของขั้นสูงสำหรับการวนซ้ำก็คือว่า "กำจัดความน่าเบื่อหน่ายและความผิดพลาด - เด่นชัดของตัววนซ้ำและตัวแปรดัชนีเมื่อวนซ้ำคอลเลกชันและอาร์เรย์" [ 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 () และถัดไป () ถูกลบแล้วและไม่มีการอ้างอิงถึงตัววนซ้ำในลูปด้านใน ใช่ฉันเข้าใจว่า Iterables สามารถนำมาใช้ซ้ำเพื่อสร้างตัววนซ้ำหลายตัวได้ แต่ทั้งหมดนั้นเกิดขึ้นนอกวง for for: ภายในลูปจะมีความก้าวหน้าไปข้างหน้าเพียงหนึ่งไอเท็มในเวลาเดียวกับไอเท็มที่ส่งคืนโดย iterator

นอกจากนี้การอนุญาตให้ทำเช่นนี้จะทำให้ง่ายต่อการใช้ลูป for Enumerations ซึ่งตามที่ได้รับการชี้ไปที่อื่นจะคล้ายกับ Iterators ไม่ใช่ Iterables

ดังนั้นอย่าทำให้ Iterator ใช้ Iterable แต่ให้ปรับปรุงการวนซ้ำเพื่อยอมรับ

ไชโย


6
ฉันเห็นด้วย. ในทางทฤษฎีอาจมีความสับสนเมื่อได้รับ iterator ใช้ส่วนหนึ่งของมันแล้ววางไว้ใน foreach (ทำลายสัญญา "แต่ละ" ของ foreach) แต่ฉันไม่คิดว่ามันเป็นเหตุผลที่ดีพอที่จะไม่มีคุณลักษณะนี้
Bart van Heukelom

เราได้อัปเดต / ปรับปรุงลูปเพื่อยอมรับอาร์เรย์แทนการทำให้อาร์เรย์สามารถรวบรวมได้ คุณสามารถหาเหตุผลเข้าข้างตนเองในการตัดสินใจครั้งนี้?
วาล

ฉันตอบกลับการตอบกลับนี้และมันเป็นความผิดพลาด Iterator เป็น stateful หากคุณส่งเสริมการวนซ้ำผ่านตัววนซ้ำด้วยfor(item : iter) {...}ไวยากรณ์มันจะทำให้เกิดข้อผิดพลาดเมื่อตัววนซ้ำเดียวกันซ้ำซ้ำสองครั้ง ลองนึกภาพที่Iteratorส่งผ่านไปยังiterateOverวิธีการแทนที่จะเป็นIterableในตัวอย่างนี้
Ilya Silvestrov

2
ไม่สำคัญว่าคุณจะใช้for (String x : strings) {...}หรือwhile (strings.hasNext()) {...}สไตล์: หากคุณพยายามวนซ้ำตัววนซ้ำสองครั้งในครั้งที่สองจะไม่มีผลลัพธ์ดังนั้นฉันจึงไม่เห็นว่าในตัวมันเองเป็นอาร์กิวเมนต์สำหรับการอนุญาตให้ใช้ไวยากรณ์ที่ปรับปรุงแล้ว คำตอบของจอนนั้นแตกต่างกันเพราะที่นั่นเขาแสดงให้เห็นว่าการห่อหุ้มIteratorในIterableจะทำให้เกิดปัญหาได้อย่างไรในกรณีนี้คุณคาดว่าจะสามารถนำมาใช้ซ้ำได้หลายครั้งตามที่คุณต้องการ
บาร์นีย์

17

ตามที่คนอื่น ๆ ชี้IteratorและIterableเป็นสองสิ่งที่แตกต่างกัน

นอกจากนี้Iteratorการใช้งานมีการปรับปรุงก่อนวันที่สำหรับลูป

นอกจากนี้ยังเป็นเรื่องเล็กน้อยที่จะเอาชนะข้อ จำกัด นี้ด้วยวิธีอะแดปเตอร์อย่างง่ายที่มีลักษณะเช่นนี้เมื่อใช้กับวิธีการนำเข้าแบบคงที่

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 การปรับตัวเข้าIteratorกับการใช้Iterableง่ายขึ้น:

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

คุณประกาศคลาสในฟังก์ชัน ฉันพลาดอะไรไปรึเปล่า? ฉันคิดว่านี่ผิดกฎหมาย
activedecay

คลาสสามารถถูกกำหนดในบล็อกใน Java มันเรียกว่าคลาสท้องถิ่น
Colin D Bennett

3
ขอบคุณสำหรับfor (String s : (Iterable<String>) () -> iterator)
AlikElzin-kilaka

8

ดังที่คนอื่น ๆ พูดไว้ Iterable สามารถถูกเรียกได้หลายครั้งและส่งคืน Iterator ที่สดใหม่ในการโทรแต่ละครั้ง Iterator ใช้เพียงครั้งเดียว ดังนั้นพวกเขาจึงมีความเกี่ยวข้อง แต่ให้บริการตามวัตถุประสงค์ที่แตกต่างกัน อย่างไรก็ตามเฉื่อยชาวิธี "กะทัดรัดสำหรับ" ทำงานได้เฉพาะกับ iterable

สิ่งที่ฉันจะอธิบายด้านล่างนี้เป็นวิธีหนึ่งที่จะได้ผลดีที่สุดของทั้งสองโลก - คืนค่า Iterable (สำหรับไวยากรณ์ที่ดีกว่า) ถึงแม้ว่าลำดับของข้อมูลที่อยู่ข้างนอกจะเป็นแบบครั้งเดียวก็ตาม

เคล็ดลับคือการคืนการใช้งานแบบไม่ระบุชื่อของ Iterable ที่ทำให้เกิดการทำงานจริง ดังนั้นแทนที่จะทำงานที่สร้างลำดับหนึ่งครั้งแล้วคืนค่า Iterator กลับมาคุณจะส่งคืน Iterable ซึ่งทุกครั้งที่มีการเข้าถึงงานจะทำซ้ำงานนั้น ที่อาจดูเหมือนสิ้นเปลือง แต่บ่อยครั้งที่คุณจะเรียก Iterable เพียงครั้งเดียวและแม้ว่าคุณจะเรียกมันหลายครั้งก็ยังมีความหมายที่สมเหตุสมผล (ซึ่งแตกต่างจากเสื้อคลุมเรียบง่ายที่ทำให้ Iterator ล้มเหลวถ้าใช้สองครั้ง)

ตัวอย่างเช่นสมมติว่าฉันมี DAO ที่ให้ชุดของวัตถุจากฐานข้อมูลและฉันต้องการให้การเข้าถึงผ่านทาง iterator (เช่นเพื่อหลีกเลี่ยงการสร้างวัตถุทั้งหมดในหน่วยความจำหากไม่จำเป็น) ตอนนี้ฉันสามารถคืนค่าตัววนซ้ำได้ แต่นั่นทำให้การใช้ค่าที่ส่งคืนในลูปน่าเกลียด ดังนั้นแทนที่ฉันห่อทุกอย่างในอานนท์ 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...
    }
}

ซึ่งให้ฉันใช้คอมแพคสำหรับลูปในขณะที่ยังคงใช้หน่วยความจำเพิ่มเติม

วิธีนี้คือ "สันหลังยาว" - งานไม่ได้ทำเมื่อมีการร้องขอ 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-loop:

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

2

ฉันเห็นด้วยกับคำตอบที่ยอมรับ แต่ต้องการเพิ่มคำอธิบายของตัวเอง

  • Iterator แสดงสถานะของการแวะผ่านเช่นคุณสามารถรับองค์ประกอบปัจจุบันจากตัววนซ้ำและเลื่อนไปข้างหน้าไปยังองค์ประกอบถัดไป

  • Iterable หมายถึงคอลเล็กชันที่สามารถข้ามผ่านได้มันสามารถส่งคืนตัววนซ้ำได้มากเท่าที่คุณต้องการโดยแต่ละอันจะแสดงสถานะการข้ามผ่านของตัวเองตัววนซ้ำหนึ่งตัวอาจชี้ไปที่องค์ประกอบแรกในขณะที่อีกองค์ประกอบหนึ่งอาจชี้ไปที่องค์ประกอบ

มันจะดีถ้า Java สำหรับ loop ยอมรับทั้ง Iterator และ Iterable


2

เพื่อหลีกเลี่ยงการพึ่งพา java.utilแพคเกจ

ตาม JSR ดั้งเดิมการปรับปรุงสำหรับลูปสำหรับ Java ™ Programming Language , อินเตอร์เฟสที่เสนอ:

  • java.lang.Iterable
  • java.lang.ReadOnlyIterator
    (เสนอให้ได้รับการดัดแปลงเพิ่มเติมเข้าjava.util.Iteratorมา แต่เห็นได้ชัดว่าเรื่องนี้ไม่เคยเกิดขึ้น)

... ได้รับการออกแบบมาให้ใช้java.langแพ็คเกจเนมสเปซมากกว่าjava.utilแพคเกจมากกว่า

ในการอ้างอิง JSR:

อินเตอร์เฟสใหม่เหล่านี้ทำหน้าที่ป้องกันการพึ่งพาภาษาบน java.util ที่อาจส่งผล


โดยวิธีการเก่าที่java.util.Iterableได้รับforEachวิธีการใหม่ใน Java 8+ สำหรับใช้กับไวยากรณ์แลมบ์ดา (ผ่าน a Consumer)

นี่คือตัวอย่าง Listอินเตอร์เฟซที่ขยายIterableอินเตอร์เฟซที่เป็นรายการใด ๆ ดำเนินการforEachวิธีการ

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 เพียงแค่ใช้สองวิธีถัดไป () และ 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 เป็นเพียงการจดชวเลขสำหรับ "ฉันสามารถคืน Iterator" ฉันคิดว่ารหัสของคุณควรเป็น:

for(Object o : someContainer) {
}

กับ someContainer instanceof SomeContainer extends Iterable<Object>



0

ในบันทึกที่เกี่ยวข้องคุณอาจพบว่าอะแดปเตอร์ IteratorIterable ใน Apache Commons Collections4 มีประโยชน์ เพียงแค่สร้างอินสแตนซ์จากตัววนซ้ำและคุณมีตัววนซ้ำที่สอดคล้องกัน

https://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/iterators/IteratorIterable.html

ID: org.apache.commons: commons-collection4: 4.0


0

Iterators เป็น stateful พวกเขามีองค์ประกอบ "ถัดไป" และกลายเป็น "หมด" เมื่อซ้ำแล้วซ้ำอีก หากต้องการดูว่าปัญหาอยู่ที่ใดให้เรียกใช้รหัสต่อไปนี้มีการพิมพ์ตัวเลขกี่หมายเลข

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.