เหตุใดสตรีม <T> จึงไม่ใช้ Iterable <T>


262

ใน Java 8 เรามีคลาส Stream <T>ซึ่งมีวิธีการอยากรู้อยากเห็น

Iterator<T> iterator()

ดังนั้นคุณคาดหวังว่าจะใช้อินเตอร์เฟสIterable <T>ซึ่งต้องการวิธีการนี้อย่างแน่นอน แต่นั่นไม่ใช่กรณี

เมื่อฉันต้องการวนซ้ำสตรีมโดยใช้ลูป foreach ฉันต้องทำอะไรเช่นนี้

public static Iterable<T> getIterable(Stream<T> s) {
    return new Iterable<T> {
        @Override
        public Iterator<T> iterator() {
            return s.iterator();
        }
    };
}

for (T element : getIterable(s)) { ... }

ฉันทำอะไรบางอย่างหายไปหรือเปล่า


7
ไม่พูดถึงว่าอีก 2 วิธีของ iterable (forEach และ spliterator) ก็มีอยู่ใน Stream
njzk2

1
สิ่งนี้จำเป็นสำหรับการส่งผ่านStreamไปยัง API แบบดั้งเดิมที่คาดหวังIterable
ZhongYu

12
IDE ที่ดี (เช่น IntelliJ) จะแจ้งให้คุณทำให้รหัสของคุณง่ายขึ้นในgetIterable()การreturn s::iterator;
ZhongYu

24
คุณไม่ต้องการวิธีเลย ที่ที่คุณมีสตรีมและต้องการ Iterable เพียงแค่ส่งสตรีม :: iterator (หรือหากคุณต้องการ () -> stream.iterator ()) และคุณก็ทำเสร็จแล้ว
Brian Goetz

7
แต่น่าเสียดายที่ฉันไม่สามารถเขียนfor (T element : stream::iterator)ดังนั้นฉันยังคงชอบถ้ากระแสก็จะดำเนินการหรือวิธีการIterable toIterable()
Thorsten

คำตอบ:


197

ผู้คนได้ถามกันในรายชื่อผู้รับจดหมาย ☺ เหตุผลหลักคือ Iterable ยังมีความหมายซ้ำได้ในขณะที่ Stream ไม่

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

หากStreamขยายIterableรหัสที่มีอยู่แล้วอาจจะประหลาดใจเมื่อมันได้รับIterableที่พ่นครั้งที่สองที่พวกเขาทำExceptionfor (element : iterable)


22
อยากรู้อยากเห็นมีพฤติกรรมแบบนี้ซ้ำแล้วซ้ำอีกใน Java 7 เช่น DirectoryStream: ในขณะที่ DirectoryStream ขยาย Iterable มันไม่ได้เป็นวัตถุประสงค์ทั่วไป Iterable เพราะมันสนับสนุนเพียง Iterator เดียว; เรียกใช้เมธอด iterator เพื่อรับตัววนซ้ำที่สองหรือหลังจากนั้นส่ง IllegalStateException ( openjdk.java.net/projects/nio/javadoc/java/nio/file/... )
roim

32
น่าเสียดายที่ไม่มีเอกสารใดIterableเกี่ยวกับว่าiteratorควรจะโทรออกได้หลายครั้งหรือไม่ นั่นคือสิ่งที่พวกเขาควรใส่เข้าไปในนั้น สิ่งนี้ดูเหมือนจะเป็นการปฏิบัติตามมาตรฐานมากกว่าข้อกำหนดที่เป็นทางการ
Lii

7
หากพวกเขากำลังจะใช้ข้อแก้ตัวคุณคิดว่าอย่างน้อยพวกเขาสามารถเพิ่ม asIterable () วิธีการ - หรือมากเกินไปสำหรับวิธีการทั้งหมดที่ใช้ Iterable
Trejkaz

26
บางทีทางออกที่ดีที่สุดอาจทำให้ Java ของ foreach ยอมรับ Iterable <T> และอาจเป็นกระแส <T>
กลืน elysium

3
@Lii ตามหลักปฏิบัติแล้วมันค่อนข้างแข็งแกร่งทีเดียว
biziclop

161

หากต้องการแปลง a เป็นStreaman Iterableคุณสามารถทำได้

Stream<X> stream = null;
Iterable<X> iterable = stream::iterator

ในการผ่าน a Streamไปยังวิธีการที่คาดIterableไว้

void foo(Iterable<X> iterable)

ง่ายดาย

foo(stream::iterator) 

อย่างไรก็ตามมันอาจดูตลก มันอาจจะดีกว่าถ้าจะให้ชัดเจนกว่านี้สักหน่อย

foo( (Iterable<X>)stream::iterator );

67
คุณยังสามารถใช้สิ่งนี้ในลูปfor(X x : (Iterable<X>)stream::iterator)แม้ว่ามันจะดูน่าเกลียด จริงๆแล้วสถานการณ์ทั้งหมดนั้นไร้สาระ
Aleksandr Dubinsky

24
@HRJIntStream.range(0,N).forEach(System.out::println)
MikeFHay

11
ฉันไม่เข้าใจไวยากรณ์ของลำไส้ใหญ่คู่ในบริบทนี้ ความแตกต่างระหว่างstream::iteratorและstream.iterator()ที่ทำให้อดีตเป็นที่ยอมรับสำหรับIterableแต่ไม่หลังคืออะไร?
Daniel C. Sobral

20
ตอบตัวเอง: Iterableเป็นอินเตอร์เฟซที่ใช้งานได้ดังนั้นผ่านฟังก์ชั่นที่ใช้งานได้เพียงพอ
Daniel C. Sobral

5
ฉันต้องเห็นด้วยกับ @AleksandrDubinsky ปัญหาที่แท้จริงคือมีวิธีแก้ปัญหาแบบ Symbian สำหรับ (X x: (Iterable <X>) stream :: iterator) ซึ่งอนุญาตให้ใช้การดำเนินการเดียวกันแม้ว่าจะเป็นรหัสที่ดี แต่นี่คือ Java และเรายอมรับรายการ List <String> = ArrayList ใหม่ (Arrays.asList (array)) เป็นเวลานาน :) ถึงแม้ว่าพลังที่จะสามารถนำเสนอ List <String> list = array ทั้งหมดให้เราได้ toArrayList () แต่ไร้สาระจริงก็คือว่าโดยการกระตุ้นให้ทุกคนใช้ forEach ในลำธารมีข้อยกเว้นที่จำเป็นจะต้องถูกตรวจสอบ [พูดจาโผงผางสิ้นสุดวันที่]
ผู้ประสานงาน

10

ผมอยากจะชี้ให้เห็นว่าStreamExไม่ใช้Iterable(และStream) เช่นเดียวกับโฮสต์ของการทำงานที่น่ากลัวอย่างมากอื่น ๆ Streamหายไปจาก


8

คุณสามารถใช้สตรีมในการforวนซ้ำดังนี้

Stream<T> stream = ...;

for (T x : (Iterable<T>) stream::iterator) {
    ...
}

(เรียกใช้ตัวอย่างนี้ที่นี่ )

(สิ่งนี้ใช้การโยนส่วนต่อประสานการทำงานของ Java 8)

(นี่คือบางส่วนของความคิดเห็นข้างต้น (เช่นAleksandr Dubinsky ) แต่ฉันต้องการดึงมันออกมาเป็นคำตอบเพื่อให้มองเห็นได้ชัดเจนขึ้น)


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

7

kennytm อธิบายว่าทำไมจึงไม่ปลอดภัยที่จะปฏิบัติต่อ a StreamในฐานะIterableและZhong Yu เสนอวิธีแก้ปัญหาที่อนุญาตให้ใช้ a Streamin in Iterableถึงแม้ว่าในลักษณะที่ไม่ปลอดภัย เป็นไปได้ที่จะได้รับสิ่งที่ดีที่สุดของทั้งสองโลก: การใช้ซ้ำได้Iterableจากสิ่งStreamที่ตรงตามการรับประกันทั้งหมดที่ทำโดยIterableข้อกำหนด

หมายเหตุ: SomeTypeไม่ใช่พารามิเตอร์ประเภทที่นี่ - คุณต้องแทนที่ด้วยประเภทที่เหมาะสม (เช่นString) หรือรีสอร์ตเพื่อสะท้อนภาพ

Stream<SomeType> stream = ...;
Iterable<SomeType> iterable = stream.collect(toList()):

มีข้อเสียอย่างหนึ่งที่สำคัญคือ:

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

แน่นอนว่าประโยชน์ที่ยิ่งใหญ่คือคุณสามารถนำกลับมาใช้ใหม่ได้ในIterableขณะที่(Iterable<SomeType>) stream::iteratorอนุญาตให้ใช้เพียงครั้งเดียวเท่านั้น หากรหัสการรับจะวนซ้ำในการรวบรวมหลาย ๆ ครั้งสิ่งนี้ไม่เพียง แต่จำเป็นเท่านั้น แต่น่าจะเป็นประโยชน์ต่อประสิทธิภาพการทำงาน


1
คุณได้พยายามรวบรวมรหัสของคุณก่อนตอบรับหรือไม่? มันไม่ทำงาน
Tagir Valeev

1
@TagirValeev ใช่ฉันทำ คุณต้องแทนที่ T ด้วยประเภทที่เหมาะสม ฉันคัดลอกตัวอย่างจากรหัสการทำงาน
Zenexer

2
@TagirValeev ฉันทดสอบอีกครั้งใน IntelliJ ดูเหมือนว่าบางครั้ง IntelliJ จะสับสนกับไวยากรณ์นี้ ฉันไม่ได้พบรูปแบบจริงๆ อย่างไรก็ตามรหัสรวบรวมได้ละเอียดและ IntelliJ จะลบประกาศแจ้งข้อผิดพลาดหลังจากรวบรวม ฉันเดาว่ามันเป็นข้อผิดพลาด
Zenexer

1
Stream.toArray()ส่งกลับอาร์เรย์ไม่ได้Iterableดังนั้นรหัสนี้ยังไม่ได้รวบรวม แต่อาจเป็นข้อผิดพลาดใน eclipse เนื่องจาก IntelliJ ดูเหมือนว่าจะรวบรวมมัน
benez

1
@Zenexer คุณสามารถกำหนดอาร์เรย์ให้กับ Iterable ได้อย่างไร
radiantRazor

3

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

วิธีแก้ปัญหาเดียวที่ฉันสามารถนึกได้ซึ่งการทำซ้ำตามสตรีมสามารถเล่นซ้ำได้เช่นกันคือการสร้างสตรีมขึ้นใหม่ ฉันใช้Supplierด้านล่างเพื่อสร้างอินสแตนซ์ใหม่ของสตรีมทุกครั้งที่มีการสร้างตัววนซ้ำใหม่

    Supplier<Stream<Integer>> streamSupplier = () -> Stream.of(10);
    Iterable<Integer> iterable = () -> streamSupplier.get().iterator();
    for(int i : iterable) {
        System.out.println(i);
    }
    // Can iterate again
    for(int i : iterable) {
        System.out.println(i);
    }

2

หากคุณไม่ทราบใช้ห้องสมุดของบุคคลที่สามไซคลอปส์ทำปฏิกิริยากำหนดกระแสว่าการดำเนินการทั้งกระแสและ Iterable และเป็น replayable เกินไป (แก้ปัญหาkennytm อธิบาย )

 Stream<String> stream = ReactiveSeq.of("hello","world")
                                    .map(s->"prefix-"+s);

หรือ :-

 Iterable<String> stream = ReactiveSeq.of("hello","world")
                                      .map(s->"prefix-"+s);

 stream.forEach(System.out::println);
 stream.forEach(System.out::println);

[การเปิดเผยข้อมูลฉันเป็นผู้นำการพัฒนาไซคลอปตอบสนอง]


0

ไม่สมบูรณ์ แต่จะได้ผล:

iterable = stream.collect(Collectors.toList());

ไม่สมบูรณ์เพราะมันจะดึงข้อมูลรายการทั้งหมดจากกระแสและใส่ลงในที่Listซึ่งไม่ตรงกับสิ่งที่IterableและStreamที่เกี่ยวกับ พวกเขาควรจะขี้เกียจ


-1

คุณสามารถวนซ้ำไฟล์ทั้งหมดในโฟลเดอร์โดยใช้Stream<Path>ดังนี้:

Path path = Paths.get("...");
Stream<Path> files = Files.list(path);

for (Iterator<Path> it = files.iterator(); it.hasNext(); )
{
    Object file = it.next();

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