ความแตกต่างระหว่าง Java 8 สตรีมและ RxJava ที่สังเกตได้


144

Java 8 สตรีมคล้ายกับ RxJava หรือไม่

การกำหนดสตรีม Java 8:

คลาสในjava.util.streamแพ็คเกจใหม่มี Stream API เพื่อรองรับการทำงานในรูปแบบการใช้งานในส่วนขององค์ประกอบ


8
FYI มีข้อเสนอที่จะแนะนำเพิ่มเติม RxJava เช่นคลาสใน JDK 9 jsr166-concurrency.10961.n7.nabble.com/…
จอห์น Vint

@JohnVint สถานะของข้อเสนอนี้คืออะไร มันจะบินจริงเหรอ?
IgorGanapolsky

2
@IgorGanapolsky โอ้ใช่แน่นอนว่ามันจะทำให้เป็น jdk9 cr.openjdk.java.net/~martin/webrevs/openjdk9/... มีแม้กระทั่งพอร์ตสำหรับ RxJava จะไหลเป็นgithub.com/akarnokd/RxJavaUtilConcurrentFlow
John Vint

ฉันรู้ว่านี้เป็นคำถามที่เก่าจริงๆ แต่ฉันเพิ่งเข้าร่วมนี้การพูดคุยที่ดีโดย Venkat Subramaniam ซึ่งมีการใช้เวลาที่ชาญฉลาดในเรื่องและมีการปรับปรุงเพื่อ Java9: youtube.com/watch?v=kfSSKM9y_0E อาจเป็นเรื่องที่น่าสนใจสำหรับคนที่เจาะเข้าไปใน RxJava
Pedro

คำตอบ:


152

TL; DR : libs การประมวลผลตามลำดับ / สตรีมทั้งหมดจะเสนอ API ที่คล้ายกันมากสำหรับการสร้างไปป์ไลน์ ความแตกต่างอยู่ใน API สำหรับการจัดการหลายเธรดและองค์ประกอบของท่อ

RxJava ค่อนข้างแตกต่างจากสตรีม จากสิ่งต่างๆทั้งหมดของ JDK สิ่งที่ใกล้เคียงกับ rx.Observable มากที่สุดคือjava.util.stream.Collector Stream + CompletableFuture คอมโบ (ซึ่งมีค่าใช้จ่ายในการจัดการกับเลเยอร์ monad พิเศษนั่นคือต้องจัดการกับการแปลงระหว่างStream<CompletableFuture<T>>และCompletableFuture<Stream<T>>)

มีความแตกต่างที่สำคัญระหว่าง Observable และ Stream:

  • ลำธารเป็นแบบดึงขึ้นมา Observables เป็นแบบผลัก สิ่งนี้อาจฟังดูเป็นนามธรรมเกินไป แต่ก็มีผลกระทบที่สำคัญที่เป็นรูปธรรมมาก
  • สตรีมสามารถใช้ได้เพียงครั้งเดียวสามารถสังเกตได้สามารถสมัครได้หลายครั้ง
  • Stream#parallel()แยกลำดับออกเป็นพาร์ติชันObservable#subscribeOn()และObservable#observeOn()อย่า; มันยากที่จะเลียนแบบStream#parallel()พฤติกรรมด้วย Observable ซึ่งครั้งหนึ่งเคยมี.parallel()วิธีการ แต่วิธีนี้ทำให้เกิดความสับสนมากจน.parallel()การสนับสนุนถูกย้ายไปยังที่เก็บแยกต่างหากบน github, RxJavaParallel รายละเอียดเพิ่มเติมอยู่ในคำตอบอื่น
  • Stream#parallel()ไม่อนุญาตให้ระบุเธรดพูลที่จะใช้ซึ่งแตกต่างจากวิธี RxJava ส่วนใหญ่ที่ยอมรับ Scheduler ที่เป็นตัวเลือก เนื่องจากสตรีมอินสแตนซ์ทั้งหมดใน JVM ใช้กลุ่ม fork-join เดียวกันการเพิ่ม.parallel()อาจส่งผลต่อพฤติกรรมในโมดูลอื่นของโปรแกรมของคุณโดยไม่ได้ตั้งใจ
  • ลำธารจะขาดการดำเนินงานเกี่ยวกับเวลาเช่นObservable#interval(), Observable#window()และอื่น ๆ อีกมากมาย; ส่วนใหญ่เป็นเพราะ Streams เป็นแบบ pull-based และ upstream ไม่มีการควบคุมเมื่อปล่อยอิลิเมนต์ถัดไป downstream
  • สตรีมมีชุดการทำงานที่ จำกัด เมื่อเปรียบเทียบกับ RxJava เช่นสตรีมขาดการดำเนินการที่ถูกตัดออก ( takeWhile(), takeUntil()); วิธีแก้ปัญหาการใช้Stream#anyMatch()มี จำกัด : เป็นการดำเนินการของเทอร์มินัลดังนั้นคุณไม่สามารถใช้งานได้มากกว่าหนึ่งครั้งต่อสตรีม
  • ตั้งแต่ JDK 8 ไม่มีการดำเนินการสตรีม # zip ซึ่งค่อนข้างมีประโยชน์ในบางครั้ง
  • ลำธารนั้นยากต่อการสร้างด้วยตัวเอง Observable สามารถสร้างได้หลายวิธี แก้ไข:ตามที่ระบุไว้ในความคิดเห็นมีวิธีสร้าง Stream อย่างไรก็ตามเนื่องจากไม่มีการลัดวงจรที่ไม่ใช่เทอร์มินัลคุณไม่สามารถสร้างกระแสของบรรทัดในไฟล์ได้อย่างง่ายดาย (JDK ให้ไฟล์ # บรรทัดและ BufferedReader # บรรทัดออกจากกล่องแม้ว่าและสถานการณ์อื่น ๆ ที่คล้ายคลึงกันสามารถจัดการได้โดยการสร้างกระแส จาก Iterator)
  • Observable มีสิ่งอำนวยความสะดวกการจัดการทรัพยากร ( Observable#using()); คุณสามารถห่อ IO สตรีมหรือ mutex กับมันและให้แน่ใจว่าผู้ใช้จะไม่ลืมที่จะปลดปล่อยทรัพยากร - มันจะถูกกำจัดโดยอัตโนมัติเมื่อมีการบอกเลิกสมาชิก สตรีมมีonClose(Runnable)เมธอด แต่คุณต้องโทรด้วยตนเองหรือผ่านการลองกับทรัพยากร เช่น. คุณต้องจำไว้ว่าไฟล์ # บรรทัด () จะต้องอยู่ใน block ลองกับทรัพยากร
  • สิ่งที่สังเกตได้นั้นจะถูกซิงโครไนซ์ตลอดทาง (ฉันไม่ได้ตรวจสอบจริงๆ สิ่งนี้ช่วยให้คุณไม่คิดว่าการดำเนินการขั้นพื้นฐานจะปลอดภัยต่อเธรดหรือไม่ (คำตอบคือ 'ใช่' เสมอเว้นแต่ว่ามีข้อผิดพลาด) แต่ค่าใช้จ่ายที่เกี่ยวข้องกับการทำงานพร้อมกันจะอยู่ที่นั่นไม่ว่ารหัสของคุณต้องการหรือไม่

Round-up: RxJava แตกต่างจาก Streams อย่างมาก ตัวเลือก RxJava ที่แท้จริงคือการนำไปใช้งานอื่น ๆ ของReactiveStreamsเช่นส่วนที่เกี่ยวข้องของ Akka

ปรับปรุง มีเคล็ดลับในการใช้พูลการเข้าร่วมที่ไม่ใช่ค่าเริ่มต้นสำหรับStream#parallelดูเธรดพูลที่กำหนดเองใน Java 8 กระแสขนาน

ปรับปรุง จากข้อมูลทั้งหมดที่กล่าวมานี้อ้างอิงจากประสบการณ์ของ RxJava 1.x ตอนนี้RxJava 2.x อยู่ที่นี่คำตอบนี้อาจจะล้าสมัย


2
ทำไมสตรีมจึงยากที่จะสร้าง ตามบทความนี้ดูเหมือนง่าย: oracle.com/technetwork/articles/java/ …
IgorGanapolsky

2
มีหลายคลาสที่มีวิธีการ 'สตรีม': คอลเลกชัน, สตรีมอินพุต, ไฟล์ไดเรกทอรี ฯลฯ แต่ถ้าคุณต้องการสร้างสตรีมจากลูปที่กำหนดเอง - พูดว่าทำซ้ำเคอร์เซอร์ฐานข้อมูล? วิธีที่ดีที่สุดที่ฉันเคยพบคือการสร้าง Iterator ห่อด้วย Spliterator และในที่สุดก็เรียก StreamSupport # จากSpliterator กาวมากเกินไปสำหรับกรณี IMHO อย่างง่าย นอกจากนี้ยังมี Stream.iterate แต่จะสร้างกระแสไม่สิ้นสุด วิธีเดียวที่จะตัด sream ในกรณีนี้คือ Stream # anyMatch แต่เป็นการทำงานของเทอร์มินัลดังนั้นคุณจึงไม่สามารถแยกผู้ผลิตและผู้บริโภคสตรีมได้
Kirill Gamazkov

2
RxJava มี Observable จาก Callable, Observable.create เป็นต้น หรือคุณสามารถสร้าง Observable ที่ไม่มีขีด จำกัด ได้อย่างปลอดภัยแล้วพูดว่า '. TakeWhile (condition)' และคุณตกลงกับการจัดส่งลำดับนี้ไปยังผู้บริโภค
Kirill Gamazkov

1
ลำธารนั้นไม่ยากที่จะสร้างขึ้นมาเอง คุณสามารถโทรStream.generate()และผ่านSupplier<U>การใช้งานของคุณเองได้เพียงวิธีการง่ายๆเพียงวิธีเดียวที่คุณให้รายการถัดไปในสตรีม มีวิธีอื่นมากมาย หากต้องการสร้างลำดับStreamที่ขึ้นอยู่กับค่าก่อนหน้านี้อย่างง่ายดายคุณสามารถใช้interate()วิธีCollectionได้ทุกstream()วิธีมีและStream.of()สร้าง a Streamจาก varargs หรืออาร์เรย์ ในที่สุดก็ StreamSupportมีการสนับสนุนสำหรับการสร้างกระแสขั้นสูงมากขึ้นโดยใช้ตัวแยกหรือสำหรับชนิดดั้งเดิมของกระแส
jbx

"สตรีมขาดการดำเนินการที่ถูกตัดออก ( takeWhile(), takeUntil());" - JDK9 มีสิ่งเหล่านี้ผมเชื่อว่าในtakeWhile ()และdropWhile ()
Abdul

50

Java 8 Stream และ RxJava ดูคล้ายกันมาก พวกเขามีตัวดำเนินการเหมือนกัน (ตัวกรองแผนที่ flatMap ... ) แต่ไม่ได้สร้างขึ้นสำหรับการใช้งานเดียวกัน

คุณสามารถทำงาน asynchonus โดยใช้ RxJava

ด้วยสตรีม Java 8 คุณจะสำรวจรายการในคอลเล็กชันของคุณ

คุณสามารถทำสิ่งเดียวกันได้ใน RxJava (รายการสำรวจของคอลเลกชัน) แต่เนื่องจาก RxJava เน้นงานที่เกิดขึ้นพร้อมกัน, ... , มันใช้การซิงก์, สลัก, ... ดังนั้นงานเดียวกันโดยใช้ RxJava อาจช้ากว่า ด้วย Java 8 สตรีม

RxJava สามารถนำมาเปรียบเทียบได้CompletableFutureแต่นั่นสามารถคำนวณได้มากกว่าหนึ่งค่า


12
เป็นเรื่องน่าสังเกตว่าคุณแถลงเกี่ยวกับการแวะผ่านสตรีมเป็นจริงสำหรับสตรีมที่ไม่ขนาน parallelStreamรองรับการซิงโครไนซ์ที่คล้ายกันของการสำรวจเส้นทาง / แผนที่ / การกรองอย่างง่าย ๆ
John Vint

2
ฉันไม่คิดว่า "ดังนั้นงานเดียวกันโดยใช้ RxJava อาจช้ากว่ากับสตรีม Java 8" ถือเป็นจริงในระดับสากลขึ้นอยู่กับงานในมือ
daschl

1
ฉันดีใจที่คุณกล่าวว่างานเดียวกันโดยใช้ RxJava อาจจะช้ากว่ากับ Java 8 กระแส นี่เป็นข้อแตกต่างที่สำคัญมากที่ผู้ใช้ RxJava หลายคนไม่ทราบ
IgorGanapolsky

RxJava ซิงโครนัสตามค่าเริ่มต้น คุณมีมาตรฐานเพื่อรองรับคำแถลงของคุณหรือไม่ว่ามันอาจช้ากว่านี้?
Marcin Koziński

6
@ marcin-kozińskiคุณสามารถตรวจสอบมาตรฐานนี้: twitter.com/akarnokd/status/752465265091309568
dwursteisen

37

มีความแตกต่างทางด้านเทคนิคและแนวความคิดเล็กน้อยตัวอย่างเช่น Java 8 สตรีมเป็นแบบใช้ครั้งเดียวลำดับแบบซิงโครนัสของค่าในขณะที่ RxJava Observables สามารถสังเกตได้อีกครั้ง RxJava มีวัตถุประสงค์เพื่อ Java 6+ และทำงานบน Android เช่นกัน


4
รหัสทั่วไปที่เกี่ยวข้องกับ RxJava ใช้ประโยชน์จาก lambdas ซึ่งมีอยู่ใน Java 8 เท่านั้น ดังนั้นคุณสามารถใช้ Rx กับ Java 6 ได้ แต่โค้ดจะมีเสียงดัง
Kirill Gamazkov

1
ความแตกต่างที่คล้ายกันคือ Obx ของ Rx สามารถคงอยู่ได้ตลอดไปจนกว่าจะยกเลิกการสมัคร Java 8 สตรีมจะถูกยกเลิกด้วยการดำเนินการโดยค่าเริ่มต้น
IgorGanapolsky

2
@KirillGamazkov คุณสามารถใช้retrolambdaเพื่อทำให้โค้ดของคุณสวยขึ้นเมื่อกำหนดเป้าหมายเป็น Java 6
Marcin Koziński

Kotlin ดูเซ็กซี่กว่าชุดติดตั้งเพิ่มเติม
Kirill Gamazkov

30

Java 8 Streams นั้นใช้การดึง คุณทำซ้ำมากกว่าสตรีม Java 8 ที่บริโภคแต่ละรายการ และมันอาจเป็นกระแสที่ไม่รู้จบ

RXJava มาObservableจากการพุชแบบเริ่มต้น คุณสมัครเป็นสมาชิก Observable และคุณจะได้รับการแจ้งเตือนเมื่อรายการถัดไปมาถึง ( onNext) หรือเมื่อสตรีมเสร็จสมบูรณ์ ( onCompleted) หรือเมื่อเกิดข้อผิดพลาด ( onError) เพราะObservableคุณได้รับonNext, onCompleted, onErrorเหตุการณ์ที่คุณสามารถทำได้ทำงานที่มีประสิทธิภาพบางอย่างเช่นการรวมที่แตกต่างกันObservableเพื่อหนึ่งใหม่ ( zip, merge, concat) สิ่งอื่นที่คุณสามารถทำได้คือแคชการควบคุมปริมาณ ... และใช้ API เดียวกันมากขึ้นหรือน้อยลงในภาษาต่าง ๆ (RxJava, RX ใน C #, RxJS, ... )

โดยค่าเริ่มต้น RxJava เป็นเธรดเดียว นอกจากว่าคุณจะเริ่มใช้ Schedulers ทุกอย่างจะเกิดขึ้นในเธรดเดียวกัน


ในสตรีมที่คุณมีอยู่แต่ละครั้งนั้นเหมือนกันกับ onNext
paul

ที่จริงแล้ว Streams มักจะเป็นปลายทาง "การดำเนินการที่ปิดไปป์ไลน์เรียกว่าการดำเนินการของเทอร์มินัลพวกเขาสร้างผลลัพธ์จากไปป์ไลน์เช่น List, Integer หรือแม้แต่โมฆะ (ประเภทที่ไม่ใช่สตรีม)" ~ oracle.com/technetwork/articles/java/…
IgorGanapolsky

26

คำตอบที่มีอยู่นั้นครอบคลุมและถูกต้อง แต่ไม่มีตัวอย่างที่ชัดเจนสำหรับผู้เริ่มต้น อนุญาตให้ฉันวางรูปธรรมหลังคำเช่น "push / pull-based" และ "re-observable" หมายเหตุ : ฉันเกลียดคำนี้Observable(เป็นสายน้ำเพื่อประโยชน์ของสวรรค์) ดังนั้นจะอ้างถึงกระแส J8 กับ RX

พิจารณารายการจำนวนเต็ม

digits = [1,2,3,4,5]

สตรีม J8 เป็นเครื่องมือในการปรับเปลี่ยนคอลเลกชัน ตัวอย่างเช่นตัวเลขที่สามารถแยกเป็น

evens = digits.stream().filter(x -> x%2).collect(Collectors.toList())

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

ลองนึกภาพกระบวนการเธรดที่แยกต่างหากกำลังส่งออกจำนวนเต็มแบบสุ่มในขณะที่แอปกำลังทำงาน ( ---หมายถึงเวลา)

digits = 12345---6------7--8--9-10--------11--12

ใน RX evenสามารถตอบสนองต่อแต่ละหลักใหม่และใช้ตัวกรองแบบเรียลไทม์

even = -2-4-----6---------8----10------------12

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

evens_stored = even.collect()  

นี่คือเหตุผลที่คำเช่น "ไร้สัญชาติ" และ "หน้าที่" เกี่ยวข้องกับ RX มากกว่า


แต่ 5 ไม่ใช่แม้แต่ ... และดูเหมือนว่า J8 Stream นั้นจะซิงโครนัสในขณะที่ Rx Stream นั้นไม่ตรงกัน?
Franklin Yu

1
@ FranklinYu ขอบคุณฉันแก้ไขข้อผิดพลาด 5 ข้อ ถ้าคิดน้อยกว่าในแง่ของการซิงโครนัส vs asyncrhouns แม้ว่ามันอาจจะถูกต้องและอื่น ๆ ในแง่ของความจำเป็น vs การทำงาน ใน J8 คุณรวบรวมรายการทั้งหมดของคุณก่อนจากนั้นใช้ตัวกรองที่สอง ใน RX คุณกำหนดฟังก์ชั่นตัวกรองที่เป็นอิสระของข้อมูลแล้วเชื่อมโยงกับแหล่งที่มา (สตรีมสดหรือคอลเลกชัน java) ... มันเป็นรูปแบบการเขียนโปรแกรมที่แตกต่างกันโดยสิ้นเชิง
Adam Hughes

ฉันประหลาดใจมากกับสิ่งนี้ ฉันค่อนข้างมั่นใจว่าสตรีม Java สามารถสร้างข้อมูลแบบสตรีมได้คุณคิดว่าอะไรตรงกันข้าม
Vic Seedoubleyew

4

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

ชวา 8 ลำธารจะสวยมากการดำเนินงานของคอลเลกชันมากมายที่สวยคล้ายกับScala สตรีมหรือหมายเลขขี้เกียจ Clojure


3

Java 8 Streams เปิดใช้งานการประมวลผลคอลเลกชันขนาดใหญ่อย่างมีประสิทธิภาพในขณะที่ใช้ประโยชน์จากสถาปัตยกรรมแบบมัลติคอร์ ในทางตรงกันข้าม RxJava ถูกเธรดเดี่ยวโดยค่าเริ่มต้น (ไม่มี Schedulers) ดังนั้น RxJava จะไม่ใช้ประโยชน์จากเครื่องมัลติคอร์เว้นแต่ว่าคุณใช้รหัสนั้นเอง


4
สตรีมเป็นเธรดเดี่ยวตามค่าเริ่มต้นเช่นกันเว้นแต่คุณจะเรียกใช้. Parallel () นอกจากนี้ Rx ยังให้การควบคุมภาวะพร้อมกันมากขึ้น
Kirill Gamazkov

@KirillGamazkov Kotlin Coroutines Flow (รองรับ Java8 Streams) ในขณะนี้รองรับการทำงานพร้อมกันอย่างเป็นระบบ: kotlinlang.org/docs/reference/coroutines/flow.html#flows
IgorGanapolsky

จริง แต่ฉันไม่ได้พูดอะไรเกี่ยวกับ Flow และการทำงานพร้อมกันอย่างเป็นระบบ จุดสองจุดของฉันคือ: 1) ทั้งสตรีมและ Rx เป็นเธรดเดี่ยวเว้นแต่ว่าคุณจะเปลี่ยนแปลงอย่างชัดเจน; 2) Rx ให้คุณควบคุมอย่างละเอียดในขั้นตอนที่จะแสดงว่าเธรดพูลในทางตรงกันข้ามกับ Streams อนุญาตให้คุณพูดว่า "ทำให้มันขนานกันอย่างใดอย่างหนึ่ง"
Kirill Gamazkov

ฉันไม่เข้าใจคำถาม "คุณต้องการเธรดพูลเพื่ออะไร" ดังที่คุณได้กล่าวไว้ "เพื่อให้สามารถประมวลผลคอลเลกชันขนาดใหญ่ได้อย่างมีประสิทธิภาพ หรือบางทีฉันต้องการให้ IO-bound ส่วนหนึ่งของภารกิจรันบนเธรดพูลแยก ฉันไม่คิดว่าฉันเข้าใจเจตนาของคำถามของคุณแล้ว ลองอีกครั้ง?
Kirill Gamazkov

1
วิธีการคงที่ในคลาส Schedulers อนุญาตให้รับเธรดพูลที่กำหนดไว้ล่วงหน้ารวมถึงสร้างหนึ่งจากผู้บริหาร ดูreactivex.io/RxJava/2.x/javadoc/io/reactivex/schedulers/ …
Kirill Gamazkov
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.