태그

2014년 7월 20일 일요일

[세미나 후기] 2014 SW 아키텍트 대회

지난 목요일(17) 오후에 2014 SW아키텍트대회에 참석한 세션 중 공유 할 만한 세션을 정리해 보았습니다.

전체적으로 봤을때, 예전에 비해 규모 및 내용이 축소된 느낌이 있었으며, 북적 북적 거리는 느낌은 상대적으로 적었었습니다.

결론부터 말씀드리자면, IoT, 빅데이터 관련된 내용이 점점 많아지고 있다는 느낌과 함께, 생각보다 클라우드 관련된 주제가 많았다는 점.. 그리고 플랜트 관련된 사례도 소개 될 만큼 중공업 분야에 대한 ICT 성숙도가 점점 높아지고 있다는 느낌입니다.

상세 세션 후기 보냅니다.

-      빅데이터 플랫폼 SW 아키텍처(김병곤, 클라우다인 대표)
n  전체 세션 중 유일하게 빅데이터 아키텍처 중심의 세션이였음.
n  너무 많은 오픈소스와 배포판별로 서로 다른 버전과 호환성 문제가 많음.
n  근래에 상용 빅데이터 SW(호트웍스, 클라우데라, 피보탈 등)등과 오픈소스 SW를 어떻게 조합할지 고민해야 함
n  플라밍고는 빅데이터 플랫폼을 제공하는 오픈소스 임.
n  복잡한 빅데이터 관리 및 시각화 등을 방화벽 밖에서 웹 혹은 이클립스 등에서 쉽게 하기 위해 시작된 오픈소스 (일반적으로 터미널 기반으로 작업을 함)
n  플라밍고 상세 사항
u  Apache 라이선스 2.0(서버) + GPL v3 (Web UI)
u  다수의 Hadoop 클러스터 지원
u  기 작성한 MapReduce 재사용 극대화를 위한 프레임워크 내장
u  워크플로우 구성을 위한 그래프 프레임워크 개발
u  Hadoop1/ Hadoop2 모니터링 및 MapReduce Job 추적
u  다양한 데이터 처리 플로우를 구성하는 워크 플로우 디자이너 및 다양한 데이터 처리/분석 프로그램 제공
u  HDFS 등의 파일 시스템 관리, Audit, 권한 관리
u  Hive Matastore 연계 및 데이터베이스/테이블 관리
u  Job 스케줄링 기능
n  특히, HDFS 내에 들어있는 데이터들을 윈도우 익스플로러와 같은 형태의 웹 상에서 조회가 가능하고, 핸들링이 가능한 것이 인상 적이였음.
n  사용자에 따라 권한 설정이 UI 상으로 가능하고, 사용자의 작업 내용이 로그로 남아 추적이 용이하며, Hive와 연계하여 테이블 생성 및 핸들링, 조회 등이 직관적으로 되어 있어, 우리가 구축하는 데이터 관리 시스템에 유용하게 써 먹을 수 있다는 생각이 들었음.
n  시각화 부분 검토시 검토 했으면 함. (오픈소스라 소스 핸들링까지 가능함)

-      사물인터넷을 위한 클라우드 서비스 구성과 개발(김영욱, 한국MS 개발자 및 플랫폼 사업본부 부장)
n  오전 세션이라 참석하지는 못 했지만, 발표자료의 흐름을 공유하고자 함.
n  전체 세션 중 IoT 관련 세션은 2, 하나는 MS에서 나머지 하나는 IBM에서.. 빅 벤더들이 IoT관련 플랫폼에 대한 세일즈가 이미 시작되었음.
n  IoT 기술로 인하여 수동적 IT 가 능동적 IT로 변하고 있다는 부분을 여러가지 사례로 설명
u  에스마폴리스(천식약 용도의 흡입기에 블루투스 내장), 구글 네스트(머신 러닝 온도 조절계 등)
n  IoT Platform = Device + Service + Software(Windows Embedded Device + Windows Azure/Bing + Windows SW/Visual Studio)
n  Software 작성 언어는 다양한 장치 및 웹 환경의 범용성을 지원하기 위한 언어로 선정 되어야 함 à JavaScript à Node.js
n  JavaScript 중에서도 Node.js 에 대한 소개 및 Azure 에서 PaaS 형태로 Node.js로 작성된 어플리케이션 탑재 및 프로비져닝 등의 강점을 소개 하였음
u  Node.js   JavaScript 기반의 서버(백엔드) 프로그래밍 언어 임.
u  VM은 크롬 웹브라우저에 이식되어 있는 JavaScript 엔진인 V8 이며, Standalone으로 JavaScript를 구동케 해줌
u  단일 쓰레드의 논 블로킹 비동기식 이벤트 기반 처리로 인해 적은 리소스로 높은 성능을 보여줌.
n  플랜트 운전 데이터 확보시, 플랜트에 설치되는 데이터 수집용 서버 혹은 본사쪽에 빅 데이터 스토리지에 저장 하기 전에 데이터를 수집하는 용도의 서버에 Node.js 가 하나의 좋은 방안이 될 것으로 생각 됨.

-      비즈니스가 꿈꾸는 아키텍처 vs 기술자들이 꿈구는 아키텍처 (신창섭, 투이컨설팅 이사)
n  아키텍처는 기술자 입장에서가 아니라 비즈니스 관점에서 정말로 바라는 것이 무엇인지 볼 필요가 있음.
n  한 사례로 무주 안성면 면사무소에는 주민들이 제일 원하던 목욕탕을 지하에 설치하여 운영 중 임
n  내부의 목소리 잘 듣기 라는 타이틀로 남동발전 사례의 장표가 3 장 나왔음.







n  위 사례는 우리가 매일 고민하고 있는 내용에 대한 전반적인 내용이 상세하게 소개되어 개인적으로 놀랐음.
n  플랜트의 ICT 관련 Needs가 공식 석상에서 발표 될 만큼, 관련 분야의 ICT 활성도가 점점 높이지고 있다고 생각 됨.

n  남동발전에서는 이렇게 도출된 Needs를 어떤 식으로 빅데이터를 적용해서 끌어 나갈지, 컨설팅을 받고 있다고 함.

[번역] JDK 8 3/3 - The Stream API

KSUG에서 번역 요청한 세 번재 포스트입니다.

본문 링크 : 
http://blog.hartveld.com/2013/03/jdk-8-33-stream-api.html

게시자 : 
게시일 : 2013/03/22
번역자 : 조인석(isi.cho@gmail.com)
번역일 : 2014/07/19

번역을 하다가 한글로 변경하기 애매 한 경우는 의역하거나 내버려 두었습니다. 참고하세요..
조언이나 교정은 언제든지 감사한 마음으로 받겠습니다. :)
Api 관련 링크가 깨져서 오라클 공식 링크로 다 변경하였습니다.


=====================================================================



Posted  by 

이 포스트는 JDK 8를 위한 짧은 시리즈 중에 마지막 포스트이다 - 나의 첫 번째 포스트인 default methods for interfaces (번역), 그리고 이전 포스트인 lambda expressions(번역)도 참고하기 바란다.

Introduction to the Stream API (Stream API 소개)

JDK 8 이전의 자바의 단점 중 하나는 컬랙션(collection)들을 가공하기 위해서는 (수작업으로 for 루프 혹은 while 루프의) 이터레이터(iterators)를 통해서만 할 수 있다는 것이다. 이러한 루핑의 명확한 폼은 전통적인 명령적 프로그래밍 언어에서 비롯되었으며, 알고리즘 단계를 실행하기 위해서 개발자가 명확하게 프로그램을 작성해야 한다는 아이디어에서 비롯되었다. 예제를 살펴보자 :

List<Block> blocks = /* ... */;

int sumOfWeights = 0;
for (Block block : blocks) {
  if (block.getColor() == Color.RED) {
    sumOfWeights += block.getWeight();
  }
}


위 예제는 "blocks 컬렉션에 있는 빨간 블록들의 무게의 합"을 구하는 상당히 거추장스러운 예제이다. 이는 알고리즘이 어떻게 실행되어야 하는지 명확하게 그리고 있다. 이러한 방식의 코딩은 간혹 발생하는 성능 과부하 상황에서 요구되어질 수도 있겠다. 하지만 이는 소스를 꽤 복잡하게 만들게 한다: 개발자는 컴퓨터가 알고리즘을 어떻게 실행해야 하는 지 표현하고 있다. 대부분의 상황에서 이러한 방식의 코딩은 그 다지 중요하지 않다. 개발자는 대개 컴퓨터가 무엇을 계산해 낼지에 대해서만 관심을 가진다. (본문을 시작하는) 하나의 영어 문장은 같은 알고리즘을 (코드의 장황성 측면에서) 더 효율적으로 작성할 수 있게 만드는 것에 초점을 맞추고 있다.

이것 때문에, 다른 언어들은 이미 컬렉션을 위한 파이프와 필터 기반의 API(pipes-and-filters-based API)를 제공하고 있다. 예를 들자면 닷넷을 위한 LINQ to objects 혹은 스칼라의 컬렉션 API(collections API)를 들 수 있다. 이러한 API는 역제어(inversion of control)의 모양새를 사용하고 있다: 프레임워크 함수들(가령, 필터와 맵)의 인자를 미리 정의해 놓은 이터레이션 함수의 커스마이징의 일환으로 사용되어지는 (람다) 표현식으로 구성한다. 이 것을 내부이터레이션(internal iteration)이라고 부른다(vs 외부 이터레이션(external iteration), 위 예제와 같이 클라이언트가 이터레이터의 제어권 안에 들어가는 경우). 내부 이터레이션의 이점은 프레임워크를 제어 할 수 있다는 것이다. 개발자는 이터레이션을 어떻게 생성해야 하는지 더 이상 고민할 필요가 없다. 이는 잠재적으로 사용이 가능한 전략들(strategies)과 반복 할 수 있게 한다: 연속적으로, 2개 혹은 그 이상의 다중 프로세스 유닛 기반의 병렬 수행 혹은 아마존 AWS(Amazon AWS)와 같이 외부에서 관리되는 서비스(역자주: 클라우드 기반 인프라)에 의해 실행될 수 있다.

Java 역시, 위 API들과 유사한 컬렉션을 위한 API를 통해 확장되어졌다. 위 예제의 for 루프는 가독성 향상을 위하여 JDK 8의 Stream API를 통해 다음과 같이 작성될 수 있으며, 람다 표현식을 포괄적으로 사용하고 있다 : 


List<Block> blocks = /* ... */;
int sumOfWeights = blocks.stream()
                      .filter(b -> b.getColor() == Color.RED)
                      .map(b -> b.getWeight())
                      .sum();


blocks 컬렉션(List<T>)은 Collection 인터페이스의 stream 함수에 의해 Stream 속으로 변환된다. 반환값은 Stream<T> 이다. 신규 컬렉션 가공 연산자들은 이 인터페이스를 위하여 정의되어졌다.

우선, 스트림 내용은 서술식을 인자값으로 취하는 filter 함수에 의해 필터링 된다. 서술식은 람다 표현식에 의해 표현되며, 이는 함수형 인터페이스라 할 수 있다. filter 함수의 반환값은 Stream<T>의 새 인스턴스이다. 기존 스트림의 각 요소들은 서술식에 의하여 테스트되어 진다. 만약, 테스트 결과가 참이면, 그 요소는 신규 스트림속으로 배치된다.

그 다음 단계는 우리가 처음 부터 관심있었던 빨간 블록들의 합을 구하기 위하여, 각 블록은 해당 블록의 무게로 치환된다. 
map 함수는 스트림의 각 요소들을 신규 요소로 변환되는 데 사용된다. 다시 말하자면, 람다 표현식이 (이번 경우에는 Function 타입) 사용된다. 왜냐하면 Function은 두 타입의 매개 변수(R과 T, and a method R apply(T t))를 가지고 있고, 이는 각 "앞으로 올" 요소를 다른 타입으로의 변환 방식으로 정의케 해준다 - 이번 예제는 Block 을 int 로 변환한다. map의 반환값은 Stream<R>이다. 이 Stream<R>은 기존 스트림의 전체 요소중에 람다 표현식에 의해 걸러진 결과값의 집합이다. 이번 예제에서는 타켓 타입이 int 이기 때문에 ToIntFunction 매개변수가 사용되었으며, IntStream 이 반환되었다(성능상 이유로 Stream을 위한 int, long, double과 같은 primitive 타입을 제공한다).

마지막으로, IntStream은 sum 함수로 스트림내의 모든 요소값들의 합계를 계산하며, 한개의  int 형으로 값을 반환한다. 한번 사용되어진 스트림은 더 이상 사용 할 수 없음을 기억하라. 만약, 다시 한번 이터레이션을 수행하려면 기존 컬렉션으로부터 신규 스트림을 생성해야만 한다.

API overview (API 개요)

JDK 8 Stream API 패키지인 java.util.stream 의 가장 중요한 타입은 Stream 인터페이스이다. 이 인터페이스에는 여러 카테고리로 나눌수 있는 연산자들의 리스트를 정의하고 있다. 각 연산자들은 인자값이 없거나, 대부분 함수형 인터페이스 형태의 인자값을 가지고 있으며, 람다 표현식으로 표현이 가능하다. 결론적으로, Stream API를 지원하는 신규 타입이 몇 개 존재한다. 이번 섹션에서는 이런 타입과 함수들을 살펴 보도록 하자.

Functional interfaces (함수형 인터페이스)

대부분의 Stream 연산자들은 함수형 인터페이스 타입(functional interface types)을 인자값으로 취한다. 함수형 인터페이스 타입은 클라이언트 코드내에서 람다 표현식에 의해 초기화될수 있다. (번역)
이러한 함수형 인터페이스의 대부분은 java.util.function 패키지안에 정의되어져 있다. 통상적으로 많이 쓰여지는 것들은 아래와 같다.
  • Function와 BiFunction은  한 개 혹은 두 개의 (아마도 서로 다른 타입의) 인자값을 제 3의 다른 타입으로 변환하는 경우에 사용한다. 하위 타입인 UnaryOperator와 BinaryOperator은 단 하나의 타입만을 사용하게 제한된다. 해서, 인풋 타입과 결과값의 타입이 항상 같다.
  • Predicate와 BiPredicate은 (가령 필터링 목적으로) 테스트 되어질 요소들에 대하여 서술하는 데 사용한다. 위 클래스에는 테스트 결과를 참/거짓으로 반환하는 함수를 포함한다. 추가로,   BiPredicate은 추가 인자값을 취한다.
  • ConsumerBiConsumer와 Supplier는 새 값을 소비하고 생성하기 위해 사용된다. forEach 연산자는 각 요소들에 대한 작업을 위하여, 혹은 통상적으로 Stream API로부터 개발자 코드로 데이터를 추출할 목적으로 Consumer를 사용한다. Suppliers는 사용자 코드로 부터 Stream API로 데이터를 삽입하는 목적으로 사용된다.
유틸리티 클래스인 Functions 와 Predicates 안에는 위 인터페이스들을 구현한 정적 함수가 여러개 존재한다.

또 하나의 주요한 함수형 인터페이스는 java.util.stream 에 정의되어 있는 FlatMapper다.  이 타입은 flatMap을 인자값으로 취하는 함수를 대표한다. (Stream을 위한 단항 결합, 아래 함수 명세로 상세한 사항을 알아보자).

Optional and Spliterator (Optional 과 Spliterator)

Stream API에 사용되는 한 가지 흥미로운 신규 클래스는 바로 java.util 안의 Optional 이다. 이는 기본적으로 null을 명확하게 표한하는 대안이다 - 이는 반환값이 있는지 없는지 명확하지 않을때 특정 스트림 연산자에 의해 반환된다(예: 감소하기). 값의 존재 유무를 파악하기 위해서는 isPresent 가 호출된다. 만약, Option이 값을 가지고 있다면, get 이 값을 반환한다.

파이프라인(pipeline)의 병렬 수행을 지원하기 위해, 기존 컬렉션의 데이터 요소들은 다중 쓰레드 위에 분배되어야 한다. java.util 안에 있는 Spliterator 인터페이스는 이러한 기능을 제공한다. trySplit 함수는 기존 Spliterator의 요소들의 서브셋을 관리하는 신규 Spliterator를 반환한다. 이후에 기존 Spliterator는  서브셋에 위임된 요소들은 넘어간다(skip). 이상적인 Spliterator는 신규 Spliterator로 요소들의 절반의 데한 제어권을 위임할 것이다(확실한 한계점에 의거하여),  이로 인해 개발자들은 (예를 들자면 병렬수행을 위해) 데이터의 셋을 쉽게 조각 낼 수 있다. 일반적인 구현체들의 팩토리 함수들은 Spliterators  클래스 안에서 찾아 볼 수 있다.

Creating streams (스트림 생성하기)

스트림은 Collection 인터페이스의 확장 혹은 구현체의 타입으로 생성이 가능하다. stream 함수는 순차적인 스트림 구현체를 반화하며, parallelStream 함수는 병렬 구조에서 수행 가능한 스트림을 반환한다. Streams 클래스는 여러개의 일반적인 팩토리 함수들을 포함하고 있으며, 이미 기 존재하는 Arrays 클래스는 여러개의 스트림 팩토리 메소드를 오버로딩하여 확장되어져 있다.

Stream operators (스트림 연산자들)

Stream 상의 연산자들(함수들)은 스트림을 통하여 흘러가는 데이터를 가공한다. map과 같이 스트림 상에 중간 연산자로 불리는 녀석들은 단지 파이프라인 형태로 세팅되지만, 그 즉시 변환 되어지지는 않는다 - 이 것을 게이른(lazy) 연산자라고 부른다. forEach와 같이 터미널 연산자로 불리는 것들은 전체 스트림 파이프라인을 실행시킨다: 그때 데이터는 가공된다.

Intermediate operators (중간 연산자들)

연산자들은 무상태(stateless) 연산자와 상태유지(stateful) 연산자들로 구성된다. map 혹은 filter와 같은 무상태 연산자들은 다음 연산자에게 연산 결과를 그저 넘기기만 한다. sorted와 같은 상태유지 연산자들은 연산 수행시의 데이터 일부를 가지고 있다가 상위스트림의 연산자로부터 모든 데이터가 도착할때까지 대기 할 수도 있다.

 Stream 인터페이스의 가장 중요한 무상태 중간 연산자는 다음과 같다:
  • <R> Stream<R> map(Function<? super T,? extends R> mapper)
    T 타입의 요소를 R 타입의 요소로 매핑한다. 각 요소는 mapper 함수에 의해 매핑된 스트림을 통하여 전달된다. mapper 함수의 반환값은 R 타입의 스트림이다. 
  • Stream<T> filter(Predicate<? super T> predicate)
    주어진 서술식에 의하여 성공적으로 테스트 되지 않은 모든 요소들을 걸러낸다. 필터링된 요소값들을 신규 Stream은 필터링된 요소값들을 포함혀여 반환된다. 
  • <R> Stream<R> flatMap(Function<T,Stream<? extends R>> mapper)
    flatMap 연산자는 단항 결합(monadic bind)과 같다. 첫 버전의 flatMap에서는 Stream<T>를 취하고 mapper 함수는 T 요소를 Stream<R>로 변환한다(그래서 단일 요소 T 가 다중 요소를 지닌 R 로 반환될 수 있다). 그리고 나서 결과값 스트림은 반환된 단일 Stream<R> 안으로 들어간다.
     
  • <R> Stream<R> flatMap(FlatMapper<? super T,R> mapper)
    두 번째 버전의 flatMap은 훨씬 개발자에게 친숙하며, 다음 단계를 위해 역제어(IoC)의 원칙을 취한다 : 컨테이너(스트림 프레임워크)는  R 타입의 모든 결과값들을 담을 수 있는 Consumer<R> 타입의 그릇을 제공한다.
상태유지 연산자들의 예제는 다음과 같다:
  • Stream<T> sorted()
    Comparable.compareTo 구현체에 의해 정렬된 스트림이 반환된다.
    Stream<T> sorted(Comparator<? super T> comparator)
    주어진 Comparator 구현체에 의해 정렬된 스트림이 반환된다.

Terminal operators (터미널 연산자들)

중간 연산자들은 기존 스트림과는 다른 스트림을 반환하는 데에 비해, 터미널 연산자는 종료된 스트림 파이프라인으로 부터 추출된 실제 결과값을 반환한다. 터미널 연산자들의 예를 살펴보자 : 
  • void forEach(Consumer<? super T> consumer)
    이 함수는 스트림내의 각 요소에 대하여 반드시 수행되어야 하는 코드를 작성하는 데 사용된다. - 기본적으로 프로그래밍 언어의 for-each 구문과 같은 개념이다.
  • <R> R collect(Collector<? super T,R> collector)
    수정이 가능한 결과값으로 모든 요소를 모은다. 예시로는 List에 담은 스트림의 모든 요소들의 컬렉션 혹은 모든 스트림 요소로부터의 긴 문자열의 생성을 들 수 있다. Collector<T, R>의 세 함수와 동일한 세 인자값을 가지는 함수형 인터페이스로 오버로딩 되는 것도 존재한다. (역자주: 해석이 매끄럽지 못하네요. An example is the collection of all elements of a stream into a List, or the creation of a long string from all stream elements. There is also an overload which takes as three arguments the functional interface equivalents of the three methods of Collector<T, R>). Collectoer의 일반적인 구현체는Collectors 에서 찾아 볼 수 있다.
  • Optional<T> reduce(BinaryOperator<T> reducer)
    스트림의 값들을 감소하여 단일 Optional 값으로 변환한다. 감소를 위한 초기 값 세팅을 위하여 추가되는 인자값을 취하는 두 개의 오버로딩된 함수가 있다.
  • Iterator<T> iterator()
    외부 이터레이션에서 사용하기 위해 스트림의 모든 요소들을 제어할수 있는 Iterator<T>를 반환한다.

Concurrency (동시성)

Stream 인스턴스는 stream 함수로 생성되어진다. 이 함수는 순차적으로 수행되는 혹은 단일 쓰레드 기반의 스트림을 생성한다. 반면에, 내부 이터레이션의 이점 중 하나는 추가적인 실행 전략들을 적용할 가능성이 있다는 것이다. 가장 명확한 예시는 멀티 프로세스 위에 수행되는 병렬 수행이다. Stream API는 이 시나리오를 위한 기능을 제공한다. 어떤 순차적 스트림은 parallel 연산자를 사용함으로써 병렬 수행이 가능해진다. parallel 연산자는 기존 스트림의 전체 요소들을 품고 있는 스트림을 반환하며, 이후의 연산자들도 병렬 적으로 호출된다. Spliterator를 사용한다면, 인풋 컬렉션은 여러개의 서브 컬렉션으로 나눠진다. 각 서브컬렉션들은 서로 다른 (병렬) 쓰레드에 의해 스트림 파이프라인 형태로 사용 되어진다.

Streams for primitive types (프리미티브 타입을 위한 스트림)

Stream 인터페이스는 제너릭 타입이다. 스트림이 프리미티브 타입과 함께 쓰여질 때, 무척 많은 (un)boxing(역자주: 예) int <-> Integer) 이 수행되어야 한다. 이는 큰 사이즈의 데이터를 가공할 때 비효율적이다. 반면에, int, long 그리고 double 을 위한 효율적인 구현 방식을 제공하기 위한 프리미티브-지향적인 인터페이스를 제공한다: 예를 들어 이 IntStreamLongStream DoubleStream 인터페이스들은  T를 int로 변환하여 반환하는 데 사용된다. 이 기능을 제공하기 위하여, 프리미티브 매개변수 와/혹은 반환값 타입을 위한 특화된 함수형 인터페이스가 있다: ToIntFunctionIntConsumerIntSupplier, 기타 등등.

    Limitations of the Stream API (Stream API의 제약사항)

    JDK 8의 Stream API는 Java 언어에게 수 많은 강력한 표현 방법을 제공한다. 하지만, 해당 API와 프레임워크를 사용하는 데 있어서 몇 개의 제약사항을 가진다. 또한, 다른 언어의 프레임워크와 비교하자면, 주요 기능 일부가 누락된 것을 알 수 있다.

    Stateless lambda expressions (무상태 람다 표현식)

    이 프로그래밍 모델이 내재하고 있는 제약사항으로는 람다 표현식이 반드시 무상태(stateless) 이여만 한다는 것이다. 상태유지 람다는 순차적으로 수행시에는 대개 문제가 되지 않지만, 병렬적으로 수행될 경우에는 깨지기 마련이다. 이번 예제는 map 함수를 사용한 상태유지 람다 표현식의 사용법이다. 이는 예상하는데로 동작되지 않는다:

    List<String> ss = ...;
    List<String> result = ...;
    
    Stream<String> stream = ss.stream();
    
    stream.map(s -> {
            synchronized (result) {
              if (result.size() < 10) {
                result.add(s);
              }
            }
        })
        .forEach(e -> { });
    
    // The contents of result will depend on the order of execution.
    // If this stream is run in parallel, result.add(s) is called
    // for some elements, depending on which thread/spliterator is
    // called first.
    // 결과값은 실행 순서에 의해 변경된다.
    // 스트림이 병력적으로 수행 된다면, 먼저 호출된 쓰레드/spliterator에 의해 
    // 요소들 중 일부가 result.add(s)에 의해 호출될 것이다.

    Streams are not reusable (스트림 재사용 불가)

    만들어진 스트림 트리는 다신 재사용 할 수 없다 - 스트림 트리는 사용 후 버려진다(혹은 익셉션이 발생한다). 조금 이상해보일수 있지만, 이는 중간 스트림 연산자들은 게이르기 때문(lazy, 역자주 : 미리 초기화 되는 것이 아니라 호출하는 시점에 초기화되는 것) 이다. 만약, 복잡한 단계를 수행한 후 스트림 재사용이 가능했더라면, 무척 흥미로운 주제였을 것이다.

    Plain streams, no query trees (순수 스트림, 쿼리 트리 없음)

    C#인 경우, LINQ를 지원하기 위해 쿼리 트리와 같은 패러다임이 구현되어져 있다. 이는 (부분) 쿼리의 재사용을 가능케 한다. JDK 8 은 이러한 기능은 제공하지 않는다: 전체 쿼리 트리 계층은 JDK 8 Stream API에는 (의도적으로) 생략되어졌다. 다른 한편으로는, public 인터페이스는 내부 사항들을 많이 노출하지 않고 있다. 이는  아마도 유사한 기능을 미래에 구현 할 수도 있다고 해석할 수 있겠다.

    No custom operators (커스텀 연산자 없음)

    개발자는 자신만의 연산자를 생성할 수 없다. 물론, map/flatMap 연산자들을 사용하면 대부분의 것들이 구현 가능하겠지만, 알다시피 많은 연산자들이 사용하기 편리하게 구현된 것 만은 아니다. C#의 LINQ에서 객체/상호적 확장을 위하여 제공하는 모든 연산자들을 살펴보면, 당신은 의문을 제기 하게 될 것이다 -C#의  extension methods 는 정말 끝내준다.

    Posted  by