-
[Java 기초] Stream - 1개발언어/Java 2019. 5. 26. 22:04
0. 들어가기 전에.
Java뿐만 아니라 C언어로 개발하면서, 그리고 OS 수업 등을 들을 때, Stream이라는 용어를 꽤나 접했던 것 같다. 그런데 지금껏 stream에 왜 stream이라는 단어가 쓰였는지 생각도 안해봤다. 그냥 외웠지..
그런데 지금 생각해보니 stream은 영어 뜻 자체가 '흐름'이라는 말이고, 이것을 개발쪽으로 가져와서 생각해보면 '데이터가 흐르는 통로?'같은거라고 생각해볼 수 있을거같다. 그리고 이것을 염두해두고 Stream을 공부하면 좀 더 이해하기 쉬운거같다.
1. Stream?
"컬렉션(배열 포함)에 저장된 데이터들을 하나씩 참조해서 람다로 처리할 수 있도록 해주는 반복자"
(즉, 스트림은 컬렉션을 통로로 해서 데이터를 흘려보내고, 이것을 하나하나 처리하는 것을 의미한다)
1) 반복자 스트림
List<String> list = Arrays.asList("A","B","C"); // 배열 컬렉션 생성 Stream<String> stream = list.stream(); // 스트림 생성 stream.forEach( x -> System.out.println(x)); //람다식. 변수를 선언해서 출력함.
stream 뒤에는 forEach라는 녀석이 붙는데, forEach는 말 뜻 그대로 for문을 돌릴건데 데이터 각각(each)에 대해 어떤 처리를 하겠다는 것이고, 여기서는 하나씩 흘려보내는 각각의 데이터들을 x라는 변수로 선언해서 그 데이터가 무엇인지 println으로 확인해보겠다는 것을 의미한다.
2) 스트림의 특징
스트림의 장점은 '요소 처리', '병렬 처리', '중간 처리', '최종 처리' 작업을 수행한다는 점이다.
(1) 요소처리
'반복자 스트림' 예제 코드에서 stream.forEach가 있는데, forEach가 각각의 데이터를 처리하는 요소처리 코드이며, 이것은 개발자가 하나하나의 요소에 신경쓸 필요가 없이 자동으로 데이터 하나하나를 처리한다는 점이다.
(2) 병렬 처리
for문, while문 등의 반복문들은 하나의 CPU코어로 돌기 때문에 조건문을 하나씩 실행시킨 후 그 조건식이 true면 for문 안에 있는 코드를 실행시키는 외부 반복자를 사용한다. 하지만 stream은 멀티코어 CPU를 사용하기 때문에 동시에 여러 요소를 검사하여 실행속도를 높일 수 있다.
List<String> list = Arrays.asList("a","b","c","d","e"); // 순차처리 Stream<String> stream = list.stream(); stream.forEach(s-> system.out.println(s)); //a,b,c,d,e 순서로 나옴 //병렬처리 Stream<String> parallelStream = list.parallelStream(); parallelStream.forEach(s-> system.out.println(s)); // 병렬처리.결과예측불가
(3) 중간 처리와 최종 처리
중간처리 : Mapping, Filtering, Sort
최종처리 : Iterating, Counting, Average, Sum 등 집계 처리
즉, 중간처리에서는 원하는 데이터를 얻을 수 있도록 처리하는 작업을 하고, 최종처리에서는 얻은 데이터를 가공하는 역할을 한다.
2. 스트림의 종류
리턴 타입 메소드(매개 변수) 소스 설명 Stream<T> java.util.Collection.stream()
java.util.Collection.parallelStream()컬렉션 컬렉션으로부터 스트림을 얻음 Stream<T>
IntStream
LongStream
DoubleStreamArrays.stream(T[]), Stream.of(T[])
Arrays.stream(int[]), IntStream.of(int[])
Arrays.stream(long[]), LongStream.of(long[])
Arrays.stream(double[]), DoubleStream.of(double[])배열 배열로부터 스트림을 얻음
즉, 데이터를 흘리는 통로를 만들어냄
IntStream IntStream.range(int, int)
IntStream.rangeClosed(int, int)int 범위 첫번째 매개변수부터 두번째 매개변수까지의 값을 모두 더함.
단, range는 두번째 매개변수값은 포함하지않음.LongStream LongStream.range(long, long)
LongStream.rangeClosed(long, long)long 범위 IntStream과 마찬가지 Stream<Path> Files.find(Path, int, BiPredicate, FileVisitOption)
Files.list(Path)디렉토리 Stream<String> Files.lines(Path, Charset)
BufferedReader.lines()파일 파일 내부의 값을 읽어옴 DoubleStream
IntStream
LongStreamRandom.doubles(...)
Random.ints()
Random.longs()랜덤 수 랜덤값을 만들어냄.(추측) <Stream의 종류>
3. 스트림 파이프라인
1) 중간 처리
종류 리턴 타입 메소드(매개 변수) 설명 필터링 Stream
IntStream
LongStream
DoubleStreamdistinct() 중복제거 filter(...) 해당 조건에 맞는 것들로 새로운 스트림을 구성함 매핑 flatMap(...) 요소를 대체하는 복수개의 요소들로 구성된 새로운 스트링 리턴 flatMapToDouble(...) flatMapToInt(...) flatMapToLong(...) map(...) 요소를 대체하는 요소로 구성된 새로운 스트림을 리턴 mapToInt(...) mapToLong(...) mapToDouble(...) mapToObj(...) asDoubleStream() int 또는 long을 Double로 타입변환해서 stream 생성 asLongStream() int를 long으로 타입변환해서 새로운 스트림 생성 boxed() int->Integer, long->Long, double->Double로 변환한 스트림생성 정렬 sorted(...) 오름차순으로 정렬 / 또는 입력된 Comparator에 따라 정렬 루핑 peek(...) 루핑. forEach()는 최종처리 메서드이고 peek()은 중간처리 메서드임 참고)
스트림 파이프라인의 중간처리 메서드들은 복수개를 동시에 사용할 수 있다.(아래 코드 참고)
IntStream intStream = Arrays.stream(new int[] {5,3,2,1,4}); intStream .filter(n -> n%2==0) //filter로 짝수만 담는 새로운 스트림을 만들어서(중간 처리) .peek(n -> System.out.print(n + ",")) //새로 만든 스트림의 값들을 출력한다(중간 처리) .sum() //합을 구한다(최종 처리)
2) 최종처리
(1) 기본 메서드
종류 리턴 타입 메소드(매개 변수) 설명 매칭 boolean allMatch(...) 스트림의 모든 요소가 특정 조건을 만족하는지 확인(true or false 리턴) boolean anyMatch(...) 스트림에 특정 조건을 만족하는 요소가 있는지 확인(true or false 리턴) boolean noneMatch(...) 스트림에 특정 조건을 만족하는 요소가 없는지 확인(true or false 리턴) 집계 long count() OptionalXXX findFirst() Optional 클래스는 값을 저장하는 값 기반 클래스들이다. 이 객체에서 값을 얻기 위해서는 get(), getAsDouble(),getAsInt(),getAsLong()을 호출하면 된다. Optional 클래스는 단순히 집계 값만 저장하는게 아니라 집계 값이 존재하지 않을 경우 디폴트값을 설정할 수 있고, 집계 값을 처리하는 Consumer도 등록할 수 있다. OptionalXXX max(...) OptionalXXX min(...) OptionalDouble average() OptionalXXX reduce(...) int, long, double sum() 루핑 void forEach(...) 수집 R collect(...) 중요) Optaional 클래스의 메서드
리턴 타입 메소드(매개 변수) 설명 boolean isPresent() 값이 저장되어 있는지 여부 T orElse(T) 값이 저장되어 있지 않을 경우 디폴트 값 지정 double orElse(double) int orElse(int) long orElse(long) void ifPresent(Consumer) 값이 저장되어 있을 경우 Consumer에서 처리 ifPresent(DoubleConsumer) ifPresent(IntConsumer) ifPresent(LongConsumer) 참고) Optional 사용방법(실무에서 자주쓰니 익혀둘 것)
- Optional을 사용하는 이유는 최종집계에서 어떤 예외(Exception)가 발생할 경우 Option을 주어 해당 예외를 처리하는 용도로 쓴다. 그래서 주로 실무에서는 orElse나 ifPresent를 많이 쓴다.
[방법1]
OptionalDouble optional = list.stream() .mapToInt(Integer :: intValue) .average(); if(optional.isPresent()){ System.out.println("평균: " + optional.getAsDouble()); } else { system.out.println("평균: 0.0"); }
[방법2]
double avg = list.stream() .mapToInt(Integer :: intValue) .average() .orElse(0.0); System.out.println("평균: " + avg);
[방법3]
list.stream() .mapToInt(Integer :: intValue) .average() .ifPresent(a -> System.out.println("평균: " + a));
(2) 커스텀집계(reduce())
reduce는 고정된 연산 메서드를 사용하는게 아니라 내부에서 연산을 지정할 수 있다. (아래 코드 참고)
int sum = studentList.stream() .map(Student :: getScore) .reduce((a,b) -> a+b) .get(); // 값이 없는 경우 NoSuchElementException 발생
=> 위의 코드는 예외가 나올 수 있어서 불완전하다.
int sum = studentList.stream() .map(Student :: getScore) .reduce(0, (a,b) -> a+b); //default값을 0으로 지정
=> default 값을 지정해서 예외가 발생하지 않도록함.
(3) 수집(collect())
'개발언어 > Java' 카테고리의 다른 글
Java Exception (0) 2020.03.16 예외 처리 (0) 2020.03.15 [Java 기초] 멀티 스레드 - 1 (2) 2019.05.13 [번역] Java 8의 동시성(Concurrency) (0) 2019.05.01