Java 8 스트림과 RxJava 옵저버 블의 차이점


144

Java 8 스트림은 RxJava와 비슷합니까?

Java 8 스트림 정의 :

새 java.util.stream패키지의 클래스 는 요소 스트림에서 기능 스타일 작업을 지원하는 Stream API를 제공합니다.


8
참고로 JDK 9에는 더 많은 RxJava와 같은 클래스를 도입 할 제안이 있습니다. jsr166-concurrency.10961.n7.nabble.com/…
— John 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 : 모든 시퀀스 / 스트림 프로세싱 라이브러리는 파이프 라인 구축을 위해 매우 유사한 API를 제공합니다. 멀티 스레딩 및 파이프 라인 구성을 처리하기위한 API의 차이점이 있습니다.

RxJava는 Stream과 상당히 다릅니다. 모든 JDK 것들 중, rx.Observable에 가장 가까운 아마도 java.util.stream.Collector 추가 모나드 층 처리의 비용으로 제공 스트림 + CompletableFuture 콤보 (즉 핸들 변환 사이에 필요 Stream<CompletableFuture<T>>하고CompletableFuture<Stream<T>> ).

Observable과 Stream 사이에는 중요한 차이점이 있습니다.

  • 스트림은 풀 기반이며, Observable은 푸시 기반입니다. 이것은 너무 추상적으로 들릴지 모르지만 매우 구체적인 결과를 초래합니다.
  • 스트림은 한 번만 사용할 수 있으며 Observable은 여러 번 구독 할 수 있습니다
  • Stream#parallel()시퀀스를 파티션으로 분할 Observable#subscribeOn()하고 Observable#observeOn()그렇지 않습니다. Stream#parallel()Observable 을 사용하여 동작 을 에뮬레이트하는 것은 까다 롭고 한 번 .parallel()방법이 있었지만이 방법은 너무 혼란 .parallel()스러워서 지원이 github RxJavaParallel의 별도 저장소로 이동되었습니다. 자세한 내용은 다른 답변에 있습니다.
  • Stream#parallel()선택적 스케줄러를 허용하는 대부분의 RxJava 메소드와 달리 사용할 스레드 풀을 지정할 수 없습니다. 이후 모든 JVM을에서 스트림 인스턴스가 동일한 사용 추가, 수영장 포크 - 조인 .parallel()실수로 프로그램의 다른 모듈의 동작에 영향을 미칠 수
  • 스트림은 같은 시간 관련 작업 부족 Observable#interval(), Observable#window()및 많은 다른 사람을; 스트림은 풀 기반이며 업스트림은 다음 요소 다운 스트림 을 언제 방출해야하는지 제어 할 수 없기 때문입니다
  • 스트림은 RxJava와 비교하여 제한된 조작 세트를 제공합니다. 예를 들어 스트림에는 컷오프 작업이 없습니다 ( takeWhile(), takeUntil()). 해결 방법 Stream#anyMatch()은 제한적입니다. 터미널 작업이므로 스트림 당 두 번 이상 사용할 수 없습니다.
  • JDK 8부터는 Stream # zip 작업이 없으므로 때로는 유용합니다.
  • 스트림은 스스로 구성하기가 어렵습니다. Observable은 여러 가지 방법으로 구성 할 수 있습니다 . 편집 : 의견에서 언급했듯이 스트림을 구성하는 방법이 있습니다. 그러나 비단 말 단락이 없기 때문에 파일에서 쉽게 라인 스트림을 생성 할 수 없습니다 (JDK는 즉시 파일 # 라인 및 BufferedReader # 라인을 제공하지만 스트림을 구성하여 다른 유사한 시나리오를 관리 할 수 ​​있음) Iterator에서).
  • 관찰 가능한 자원 관리 시설 ( Observable#using()); IO 스트림 또는 뮤텍스를 랩핑하고 사용자가 리소스를 해제하는 것을 잊지 않도록해야합니다. 구독이 종료되면 자동으로 처리됩니다. 스트림에는 onClose(Runnable)메서드가 있지만 리소스를 사용하거나 리소스를 통해 직접 호출해야합니다. 예 : Files # lines () 는 try-with-resources 블록으로 묶어야 한다는 것을 명심 해야합니다 .
  • Observables는 끝까지 동기화됩니다 (실제로 Streams가 동일한 지 여부는 실제로 확인하지 않았습니다). 이렇게하면 기본 작업이 스레드 안전인지 여부를 생각할 필요가 없습니다 (버그가 없으면 대답은 항상 '예'입니다).하지만 코드의 필요 여부에 관계없이 동시성 관련 오버 헤드가 있습니다.

반올림 : RxJava는 Streams와 크게 다릅니다. Real RxJava 대안은 Akka의 관련 부분과 같은 ReactiveStreams의 다른 구현입니다 .

업데이트 . 에 대한 기본이 아닌 포크 조인 풀을 사용하는 트릭이 있습니다. Java 8 병렬 스트림의 사용자 정의 스레드 풀을Stream#parallel 참조하십시오.

업데이트 . 위의 모든 내용은 RxJava 1.x 경험을 기반으로합니다. 지금 RxJava 2.x에서이 여기 ,이 답변이 오래된 될 수있다.


2
스트림을 구성하기 어려운 이유는 무엇입니까? 이 기사에 따르면, 쉬운 것 같습니다 : oracle.com/technetwork/articles/java/…
— IgorGanapolsky

2
컬렉션, 입력 스트림, 디렉토리 파일 등 'stream'메소드를 가진 클래스가 많이 있습니다. 그러나 사용자 정의 루프에서 스트림을 생성하려면 데이터베이스 커서를 반복하는 방법은 무엇입니까? 지금까지 찾은 가장 좋은 방법은 Iterator를 만들고 Spliterator로 래핑 한 다음 StreamSupport # fromSpliterator를 호출하는 것입니다. 간단한 경우 IMHO에 대한 접착제가 너무 많습니다. Stream.iterate도 있지만 무한 스트림을 생성합니다. 따라서 당신은 스트림 생산자와 소비자의 분리 할 수없는, 그 경우 sream 차단하는 유일한 방법은 스트림 # anyMatch이지만, 터미널 운영의
— 키릴 Gamazkov

2
RxJava에는 Observable.fromCallable, Observable.create 등이 있습니다. 또는 당신은 안전하게 소비자에게이 순서를 운송 당신에게있는 거 확인 '을 .takeWhile (조건)'무한 관찰 가능한, 다음 말을 생산하고 있습니다
— 키릴 Gamazkov에게

1
스트림은 스스로 구성하기가 어렵지 않습니다. 스트림에서 다음 항목을 제공하는 간단한 방법 중 하나를 사용 Stream.generate()하여 Supplier<U>구현을 호출 하고 전달할 수 있습니다 . 다른 방법이 많이 있습니다. 쉽게 구성하는 시퀀스 Stream는 사용 이전 값에 따라 interate()방법을 때때로 Collection갖는 stream()방법 및 Stream.of()를 구성 StreamA는 가변 인자 또는 배열. 마지막으로 StreamSupport스플리터 또는 스트림 기본 유형을 사용하여 고급 스트림 작성을 지원합니다.
— jbx 2016

"스트림에는 컷오프 작업이 없습니다 ( takeWhile(), takeUntil());" JDK9는 takeWhile () 과 dropWhile ()을 가지고 있습니다
— Abdul

50

Java 8 Stream과 RxJava는 매우 비슷합니다. 그들은 비슷한 연산자 (필터, 맵, flatMap ...)를 가지고 있지만 동일한 사용법으로 만들어지지 않았습니다.

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와 관련된 일반적인 코드는 Java 8에서만 사용할 수있는 람다를 많이 사용합니다. 당신은 자바 6 수신을 사용할 수 있지만, 코드가 시끄러운 것 그래서
— 키릴 Gamazkov

1
Rx Observables는 구독을 취소 할 때까지 무기한으로 유지 될 수 있습니다. Java 8 스트림은 기본적으로 작업으로 종료됩니다.
— IgorGanapolsky

2
@KirillGamazkov Java 6을 대상으로 할 때 retrolambda 를 사용하여 코드를 더 예쁘게 만들 수 있습니다 .
— Marcin Koziński

코 틀린는 개조보다 더 섹시 보이는
— 키릴 Gamazkov에게

30

Java 8 스트림은 풀 기반입니다. 각 항목을 소비하는 Java 8 스트림을 반복합니다. 그리고 그것은 끝없는 흐름이 될 수 있습니다.

RXJava Observable는 기본적으로 푸시 기반입니다. Observable에 가입하면 다음 항목이 도착 onNext하거나 ( ), 스트림이 완료 onCompleted될 때 ( ), 오류가 발생했을 때 ( ) 알림을받습니다 onError. 함께하기 때문에 Observable당신이받는 onNext, onCompleted, onError이벤트, 당신은 다른 결합과 같은 몇 가지 강력한 기능을 할 수있는 Observable새에들 ( zip, merge, concat). 당신이 할 수있는 다른 일은 캐싱, 조절, ... 그리고 그것은 다른 언어 (RxJava, C #의 RX, RxJS 등)에서 거의 동일한 API를 사용합니다.

기본적으로 RxJava는 단일 스레드입니다. 스케줄러를 사용하지 않으면 모든 것이 동일한 스레드에서 발생합니다.


Stream에서 forEach는 onNext와 거의 동일합니다.
— paul

실제로 스트림은 일반적으로 터미널입니다. "스트림 파이프 라인을 닫는 작업을 터미널 작업이라고합니다. List, Integer 또는 void (비 스트림 유형)와 같은 파이프 라인의 결과를 생성합니다." ~ oracle.com/technetwork/articles/java/…
— IgorGanapolsky

26

기존 답변은 포괄적이고 정확하지만 초보자에게는 명확한 예가 부족합니다. "푸시 / 풀 기반"및 "재 관측 가능"과 같은 용어 뒤에 구체적인 내용을 설명하겠습니다. 참고 : 나는 Observable(하늘을위한 스트림) 이라는 용어를 싫어 하므로 J8 대 RX 스트림을 간단히 언급 할 것입니다.

정수 목록을 고려하십시오.

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

J8 스트림은 콜렉션을 수정하는 유틸리티입니다. 예를 들어 짝수는 다음과 같이 추출 할 수 있습니다.

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

이것은 기본적으로 Python의 map, filter, reduce 이며 Java에 매우 훌륭하고 오래 연체되었습니다. 그러나 미리 숫자가 수집되지 않은 경우-앱이 실행되는 동안 숫자가 스트리밍되는 경우-짝수를 실시간으로 필터링 할 수 있습니까?

앱이 실행되는 동안 별도의 스레드 프로세스가 임의의 시간에 정수를 출력한다고 상상해보십시오 ( ---시간을 나타냄)

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

RX에서는 각각의 새로운 숫자에 반응 하고 실시간으로 필터를 적용 even할 수 있습니다

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

입력 및 출력 목록을 저장할 필요가 없습니다. 출력 목록 을 원한다면 스트리밍 가능한 문제도 없습니다. 실제로 모든 것이 스트림입니다.

evens_stored = even.collect()  

이것이 "stateless"및 "functional"과 같은 용어가 RX와 더 관련이있는 이유입니다.


그러나 5는 짝수입니다 ... 그리고 그것은 J8 스트림이 동 기적 인 것처럼 보이지만 Rx 스트림은 비동기 적입니까?
— Franklin Yu

1
@ FranklinYu 5 오타를 수정 해 주셔서 감사합니다. 동기식 대 비동기식의 관점에서 생각하는 것이 적을지라도, 정확할 수도 있지만 명령형 대 기능적 관점에서 더 많이 생각하십시오. J8에서는 먼저 모든 항목을 수집 한 다음 필터를 두 번째로 적용하십시오. RX에서는 데이터와 독립적으로 필터 기능을 정의한 다음 짝수 소스 (라이브 스트림 또는 Java 컬렉션)와 연결합니다. 완전히 다른 프로그래밍 모델입니다
— Adam Hughes

나는 이것에 매우 놀랐다. Java 스트림을 데이터 스트리밍으로 만들 수 있다고 확신합니다. 그 반대 생각은 무엇입니까?
— 빅 Seedoubleyew

4

RxJava는 또한 리 액티브 스트림 이니셔티브 와 밀접한 관련이 있으며 리 액티브 스트림 API의 간단한 구현으로 간주됩니다 (예 : Akka 스트림 구현 과 비교 ). 주요 차이점은 반응 스트림이 역압을 처리 할 수 ​​있도록 설계되었다는 것입니다. 그러나 반응 스트림 페이지를 보면 아이디어를 얻을 수 있습니다. 그들은 그들의 목표를 아주 잘 묘사하고 있으며, 시냇물도 반응성 선언문 과 밀접한 관련이 있습니다 .

Java 8 스트림은 Scala Stream 또는 Clojure lazy seq 와 매우 유사한 무제한 컬렉션 구현입니다 .


3

Java 8 Streams는 멀티 코어 아키텍처를 활용하면서 매우 큰 컬렉션을 효율적으로 처리 할 수 ​​있습니다. 반대로 RxJava는 기본적으로 단일 스레드입니다 (스케줄러 없음). 따라서 RxJava는 해당 로직을 직접 코딩하지 않으면 멀티 코어 머신을 이용하지 않습니다.


4
.parallel ()을 호출하지 않는 한 스트림은 기본적으로 단일 스레드입니다. 또한 Rx는 동시성을보다 강력하게 제어합니다.
— Kirill Gamazkov

(Java8 스트림 기준) @KirillGamazkov 코 틀린 코 루틴 흐름은 이제 구조화 동시 지원 kotlinlang.org/docs/reference/coroutines/flow.html#flows을
— IgorGanapolsky

사실이지만 흐름과 구조적 동시성에 대해서는 언급하지 않았습니다. 내 두 가지 요점은 다음과 같습니다. 1) 명시 적으로 변경하지 않는 한 Stream과 Rx는 모두 단일 스레드입니다. 2) 수신은 당신에게 오직 당신이 말하는 "어떻게 든 평행하게"할 수 있도록 스트림 달리하는 스레드 풀에서 수행 할 수있는 단계에 세밀하게 제어 할 수 있습니다
— 키릴 Gamazkov

나는 "무엇을 위해 스레드 풀이 필요한가?"라는 질문의 요점을 실제로 얻지 못했습니다. 당신이 말했듯이, "실제로 큰 컬렉션을 효율적으로 처리 할 수 ​​있도록". 또는 작업의 IO 바인딩 부분이 별도의 스레드 풀에서 실행되기를 원할 수도 있습니다. 나는 당신의 질문의 의도를 이해하지 못했다고 생각합니다. 다시 시도하십시오?
— Kirill Gamazkov

1
Schedulers 클래스의 정적 메서드를 사용하면 미리 정의 된 스레드 풀을 가져오고 Executor에서 스레드 풀을 만들 수 있습니다. 참조 reactivex.io/RxJava/2.x/javadoc/io/reactivex/schedulers/...
— 키릴 Gamazkov
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.