ทำไม Iterable <T> ไม่ได้ให้วิธีการสตรีม () และ parallelStream ()


241

ฉันสงสัยว่าทำไมIterableอินเทอร์เฟซไม่ได้ให้stream()และparallelStream()วิธีการ พิจารณาคลาสต่อไปนี้:

public class Hand implements Iterable<Card> {
    private final List<Card> list = new ArrayList<>();
    private final int capacity;

    //...

    @Override
    public Iterator<Card> iterator() {
        return list.iterator();
    }
}

มันคือการนำมือมาใช้เพราะคุณสามารถมีไพ่ในมือขณะเล่นเกมการ์ดซื้อขาย

โดยพื้นฐานแล้วมันจะห่อหุ้มList<Card>ให้แน่ใจว่ามีความจุสูงสุดและมีคุณสมบัติที่มีประโยชน์อื่น ๆ List<Card>มันจะดีกว่าขณะที่การดำเนินการนั้นโดยตรงเป็น

ตอนนี้เพื่อความมั่นใจฉันคิดว่ามันจะเป็นการดีถ้าจะนำไปใช้Iterable<Card>เช่นคุณสามารถใช้ลูปเสริมสำหรับลูปถ้าคุณต้องการวนซ้ำ ( Handชั้นเรียนของฉันมีget(int index)วิธีการด้วยเหตุนี้สิ่งนี้จึงIterable<Card>เป็นเหตุผลในความคิดของฉัน)

Iterableอินเตอร์เฟซให้ดังต่อไปนี้ (ซ้ายออก Javadoc):

public interface Iterable<T> {
    Iterator<T> iterator();

    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

    default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
}

ตอนนี้คุณสามารถรับสตรีมด้วย:

Stream<Hand> stream = StreamSupport.stream(hand.spliterator(), false);

ดังนั้นคำถามที่แท้จริง:

  • ทำไมไม่มีIterable<T>วิธีการเริ่มต้นที่นำไปใช้stream()และparallelStream()ฉันไม่เห็นสิ่งใดที่ทำให้เป็นไปไม่ได้

คำถามที่เกี่ยวข้องที่ฉันพบมีดังต่อไปนี้: ทำไมสตรีม <T> ไม่ใช้ Iterable <T>
ซึ่งแปลกพอแนะนำให้ทำค่อนข้างวิธีอื่น ๆ


1
ผมคิดว่านี่เป็นคำถามที่ดีสำหรับรายชื่อผู้รับจดหมายแลมบ์ดา
Edwin Dalorzo

ทำไมมันแปลกที่ต้องการย้ำผ่านกระแส? คุณจะbreak;ทำซ้ำอีกได้ยังไง? (ตกลงStream.findFirst()อาจจะมีวิธีแก้ปัญหา แต่ที่อาจจะไม่ตอบสนองความต้องการทั้งหมด ... )
glglgl

ดูเพิ่มเติมแปลง Iterable เป็น Stream โดยใช้ Java 8 JDKสำหรับการแก้ไขปัญหาจริง
Vadzim

คำตอบ:


298

นี่ไม่ใช่การละเลย มีการอภิปรายรายละเอียดเกี่ยวกับรายชื่อ EG ในเดือนมิถุนายน 2556

การอภิปรายที่ชัดเจนของกลุ่มผู้เชี่ยวชาญมีรากฐานมาจากหัวข้อนี้

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

Stream<T> stream()

ไม่ได้เป็นอย่างที่คุณต้องการ บางสิ่งบางอย่างที่Iterable<Integer>ค่อนข้างจะมีวิธีการส่งคืนของพวกเขาIntStreamตัวอย่างเช่น แต่การวางstream()วิธีนี้ให้สูงขึ้นในลำดับชั้นจะทำให้เป็นไปไม่ได้ ดังนั้นเราจึงทำให้ง่ายต่อการสร้างStreamจากIterableโดยการให้spliterator()วิธีการแทน การใช้งานของstream()in Collectionเป็นเพียง:

default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}

ลูกค้าทุกคนสามารถรับสตรีมที่ต้องการได้Iterableด้วย:

Stream s = StreamSupport.stream(iter.spliterator(), false);

ในท้ายที่สุดเราสรุปได้ว่าการเพิ่มstream()เข้าไปIterableจะเป็นความผิดพลาด


8
ฉันเห็นก่อนอื่นขอบคุณมากสำหรับการตอบคำถาม ฉันยังคงอยากรู้ว่าทำไมIterable<Integer>(ฉันคิดว่าคุณกำลังพูดถึง?) IntStreamต้องการที่จะกลับ การกระทำนั้นจะPrimitiveIterator.OfIntไม่เป็นเช่นนั้นหรือ หรือคุณอาจหมายถึง usecase อื่นหรือไม่
skiwi

139
ฉันคิดว่ามันแปลกที่ตรรกะข้างต้นถูกนำไปใช้กับ Iterable (ฉันไม่สามารถสตรีมได้) เพราะบางคนอาจต้องการให้ส่งคืน IntStream) ในขณะที่จำนวนความคิดที่เท่ากันไม่ได้ถูกให้เพิ่มวิธีเดียวกันในคอลเลกชัน ( ฉันอาจต้องการให้กระแสของคอลเลกชัน <Integer> ของฉัน () เพื่อส่งคืน IntStream ด้วย) ไม่ว่าจะมีอยู่ทั้งสองอย่างหรือขาดทั้งสองคนผู้คนอาจจะเพิ่งได้รับกับชีวิตของพวกเขา แต่เพราะมันอยู่ในที่เดียว อื่น ๆ ก็จะกลายเป็นค่อนข้างละเลยจ้องมอง ...
Trejkaz

6
Brian McCutchon: นั่นสมเหตุสมผลสำหรับฉันมากขึ้น ดูเหมือนว่าผู้คนเริ่มเบื่อการโต้เถียงและตัดสินใจเล่นอย่างปลอดภัย
Jonathan Locke

44
ในขณะนี้ทำให้รู้สึกมีเหตุผลว่าทำไมไม่มีทางเลือกคงStream.of(Iterable)ที่อย่างน้อยจะทำให้วิธีการค้นพบอย่างสมเหตุสมผลพอสมควรโดยการอ่านเอกสาร API - เป็นคนที่ไม่เคยทำงานกับ internals ของ Streams ฉันไม่เคย แม้มองที่StreamSupportซึ่งอธิบายไว้ในเอกสารการให้ "การดำเนินงานในระดับต่ำ" ที่มี "ส่วนใหญ่สำหรับนักเขียนห้องสมุด"
จูลส์

9
ฉันเห็นด้วยกับจูลส์ทั้งหมด ควรเพิ่มเมธอดสแตติก Stream.of (Iteratable iter) หรือ Stream.of (Iterator iter) แทน StreamSupport.stream (iter.spliterator (), false);
user_3380739

23

ฉันทำการสอบสวนในรายชื่อผู้รับจดหมายแลมด้าหลายโครงการและฉันคิดว่าฉันพบการสนทนาที่น่าสนใจสองสามข้อ

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

ผู้เชี่ยวชาญด้านข้อมูลจำเพาะของแลมบ์ดา Libs

ฉันพบการสนทนาเกี่ยวกับเรื่องนี้ในรายการส่งเมลผู้เชี่ยวชาญของ Lambda Libs :

ภายใต้Iterable / Iterator.stream () Sam Pullara กล่าวว่า:

ฉันทำงานกับ Brian เพื่อดูว่าขีด จำกัด / ฟังก์ชันการทำงานย่อย [1] อาจถูกนำไปใช้และเขาแนะนำให้เปลี่ยนเป็น Iterator เป็นวิธีที่เหมาะสมในการดำเนินการ ฉันคิดเกี่ยวกับวิธีแก้ปัญหานั้น แต่ไม่พบวิธีที่ชัดเจนในการใช้ตัววนซ้ำและเปลี่ยนเป็นกระแส ปรากฎว่ามันอยู่ในนั้นคุณเพียงแค่ต้องแปลง iterator เป็น spliterator ก่อนจากนั้นแปลง spliterator เป็น stream ดังนั้นสิ่งนี้ทำให้ฉันทบทวนว่าเราควรจะแขวนสิ่งเหล่านี้จาก Iterable / Iterator โดยตรงหรือทั้งสองอย่าง

คำแนะนำของฉันคืออย่างน้อยก็มีไว้ใน Iterator เพื่อให้คุณสามารถเคลื่อนย้ายระหว่างโลกทั้งสองได้อย่างหมดจดและจะสามารถค้นพบได้ง่ายแทนที่จะต้องทำ:

Streams.stream (Spliterators.spliteratorUnknownSize (iterator, Spliterator.ORDERED))

แล้วBrian Goetz ตอบกลับ :

ฉันคิดว่าประเด็นของแซมคือมีห้องสมุดมากมายที่ให้ Iterator แก่คุณ แต่อย่าปล่อยให้คุณเขียน spliterator ของคุณเอง ดังนั้นสิ่งที่คุณสามารถทำได้คือการโทรกระแส (spliteratorUnknownSize (iterator)) แซมแนะนำให้เรากำหนด Iterator.stream () ให้ทำเพื่อคุณ

ฉันต้องการเก็บเมธอดสตรีม () และ spliterator () ไว้สำหรับผู้เขียนห้องสมุด / ผู้ใช้ขั้นสูง

และหลังจากนั้น

"เนื่องจากการเขียน Spliterator นั้นง่ายกว่าการเขียน Iterator ฉันต้องการเขียน Spliterator แทนที่จะเป็น Iterator (Iterator นั้น 90s :)

แม้ว่าคุณจะพลาดจุดนี้ มี zillions ของการเรียนออกมีที่มีอยู่แล้วในมือคุณ Iterator และหลายคนยังไม่พร้อมจำหน่าย

การสนทนาก่อนหน้าในรายชื่อผู้รับจดหมายแลมบ์ดา

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

ในคำพูดของ Brian Goetz ภายใต้Streams จาก Iterable :

ก้าวถอยหลัง ...

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

iterator

ขนาดตัววนซ้ำ +

Spliterator

Spliterator ที่รู้ขนาดของมัน

Spliterator ที่รู้ขนาดของมันและรู้เพิ่มเติมว่าการแบ่งย่อยทั้งหมดรู้ขนาดของมัน

(บางคนอาจประหลาดใจที่พบว่าเราสามารถแยกคู่ขนานได้จากตัววนซ้ำในกรณีที่ Q (ทำงานต่อองค์ประกอบ) เป็นสิ่งที่ไม่สำคัญ)

หาก Iterable มีเมธอดสตรีม () ก็จะหุ้ม Iterator ด้วย Spliterator โดยไม่มีข้อมูลขนาด แต่สิ่งที่ Iterable ส่วนใหญ่มีข้อมูลขนาด ซึ่งหมายความว่าเราให้บริการสตรีมที่ไม่เพียงพอ นั่นไม่ดีเลย

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

ค่าเริ่มต้นที่จัดทำโดย Iterable จะเป็นเส็งเคร็งแน่นอน - มันจะยกเลิกขนาดแม้ว่า Iterables ส่วนใหญ่จะรู้ข้อมูลนั้น

ความขัดแย้ง?

ถึงแม้ว่าดูเหมือนว่าการสนทนาจะขึ้นอยู่กับการเปลี่ยนแปลงที่กลุ่มผู้เชี่ยวชาญทำกับการออกแบบเริ่มต้นของสตรีมซึ่งเริ่มต้นจากการทำซ้ำ

ถึงกระนั้นก็เป็นที่น่าสนใจที่จะสังเกตเห็นว่าในอินเตอร์เฟซเช่นคอลเลกชัน, วิธีการสตรีมถูกกำหนดเป็น:

default Stream<E> stream() {
   return StreamSupport.stream(spliterator(), false);
}

ซึ่งอาจเป็นรหัสเดียวกันที่ถูกใช้ในส่วนต่อประสาน Iterable

ดังนั้นนี่คือเหตุผลที่ฉันพูดว่าคำตอบนี้อาจไม่เป็นที่น่าพอใจ แต่ก็ยังน่าสนใจสำหรับการอภิปราย

หลักฐานการเปลี่ยนสถานะ

ดำเนินการต่อกับการวิเคราะห์ในรายชื่อผู้รับจดหมายดูเหมือนว่าวิธี splitIterator เดิมในอินเตอร์เฟซการเก็บรวบรวมและในบางจุดในปี 2013 พวกเขาย้ายมันขึ้นเพื่อ Iterable

ดึง splitIterator เพิ่มขึ้นจากการเก็บเพื่อ Iterable

สรุป / ทฤษฎี?

โอกาสที่ว่าการขาดวิธีใน Iterable เป็นเพียงการละเว้นเนื่องจากดูเหมือนว่าพวกเขาควรจะย้ายวิธีการสตรีมด้วยเช่นกันเมื่อพวกเขาย้ายตัวแยกสัญญาณขึ้นจากคอลเลกชันไปยัง Iterable

หากมีเหตุผลอื่นที่ไม่ชัดเจน มีใครอีกบ้างที่มีทฤษฎีอื่น ๆ


ฉันขอขอบคุณคำตอบของคุณ แต่ฉันไม่เห็นด้วยกับเหตุผลที่นั่น ในขณะที่คุณแทนที่spliterator()ของIterableแล้วปัญหาทั้งหมดที่มีได้รับการแก้ไขและคุณนิด ๆ สามารถดำเนินการstream()และparallelStream()..
skiwi

@skiwi นั่นเป็นเหตุผลที่ฉันพูดแบบนี้อาจไม่ใช่คำตอบ ฉันแค่พยายามที่จะเพิ่มการอภิปรายเพราะมันยากที่จะรู้ว่าทำไมกลุ่มผู้เชี่ยวชาญทำการตัดสินใจที่พวกเขาทำ ฉันเดาว่าสิ่งที่เราทำได้คือพยายามพิสูจน์หลักฐานทางนิติวิทยาศาสตร์ในรายชื่อผู้รับจดหมายและดูว่าเราสามารถทำอะไรได้บ้าง
Edwin Dalorzo

1
@skiwi ฉันตรวจสอบรายชื่อผู้รับจดหมายอื่น ๆ และพบหลักฐานเพิ่มเติมสำหรับการอภิปรายและอาจมีความคิดบางอย่างที่ช่วยในการสร้างทฤษฎีการวินิจฉัยบางอย่าง
Edwin Dalorzo

ขอบคุณสำหรับความพยายามของคุณฉันควรเรียนรู้วิธีแยกรายการจดหมายเหล่านั้นอย่างมีประสิทธิภาพ มันจะช่วยถ้าพวกเขาสามารถมองเห็นได้ในบางวิธี ... ที่ทันสมัยเช่นฟอรั่มหรือบางสิ่งบางอย่างเพราะการอ่านอีเมลข้อความธรรมดาที่มีคำพูดในนั้นไม่ได้มีประสิทธิภาพตรง
skiwi

6

หากคุณรู้ขนาดที่คุณสามารถใช้java.util.Collectionซึ่งให้stream()วิธีการ:

public class Hand extends AbstractCollection<Card> {
   private final List<Card> list = new ArrayList<>();
   private final int capacity;

   //...

   @Override
   public Iterator<Card> iterator() {
       return list.iterator();
   }

   @Override
   public int size() {
      return list.size();
   }
}

แล้ว:

new Hand().stream().map(...)

ฉันประสบปัญหาเดียวกันและรู้สึกประหลาดใจที่Iterableการใช้งานของฉันสามารถขยายไปสู่การAbstractCollectionใช้งานได้ง่ายมากโดยเพียงเพิ่มsize()วิธีการ (โชคดีที่ฉันมีขนาดของคอลเลกชัน :-)

คุณควรพิจารณาแทนที่Spliterator<E> spliterator()ด้วย

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