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.