스트림(Stream)이란?
- List 를 정렬할 때는 Collections.sort(), 배열을 정렬할 때는 Arrays.sort() 를 사용해야한다. 이는 메서드의 기능 중복과 데이터 소스마다 다른 방식으로 다뤄야 한다는 점에서 재사용성과 중복성의 불편함이 있다.
이러한 문제를 다루기 위해 만든것이 '스트림(Stream)'
- 스트림(Stream) 은 데이터 소스를 추상화 하고 데이터를 다루는데 자주 사용되는 메서드를을 정의해 놓았다.
- 데이터 소스를 추상화 해 놓았다는 것은, 데이터가 무엇이던 간에 같은 방식으로 다룰수 있게 되었다는 것이다.
두 데이터 소스 (String[], List<String>)를 기반으로 하는 스트림 생성 방법은 다음과 같다
String[] strArr = { "aaa", "ddd" , "ccc" };
List<String> strList = Arrays.asList(strArr) ;
Stream<String> strStreaml = strList.stream(); // 스트림을 생성
Stream<String> strStream2 = Arrays.stream(strArr) ; // 스트림을 생성
생성 방법은 각각의 데이터 소스에 따라 다르지만 출력 하는 방법은 모두 같다.
strStreaml.sorted().forEach(System.out::println);
strStream2.sorted().forEach(System.out::println);
스트림으로 변환하지 않았다면 아래와 같이 코드를 작성해야 했을 것이다.
Arrays.sort(strArr) ;
Collections.sort(strList) ;
for(String str : strArr)
System.out.println(str);
for(String str : strLi st)
System.out.println(str) ;
스트림의 특징
- 스트림은 데이터 소스를 변경 하지 않는다
- 읽기만 한다. 필요하다면 정렬된 결과를 새로운 컬렉션이나 배열에 담아 반환한다.
// 정렬된 결과를새로운 List에 담아서 반환한다.
List<String> sortedList = strStream2.sorted().collect(Collectors.toList()) ;
- 스트림은 일회용이다.
- Iterator 로 컬렉션의 요소를 모두 읽고 나면 다시 사용할 수 없는 것처럼 스트림도 1회용이다.
- 필요하다면 다시 생성해야 한다.
-
strStreaml.sorted().forEach(System.out::println) ; int numOfStr = strStreaml.count() ; // 에러! 스트림이 이미 닫혔음 .
- 스트림은 작업을 내부 반복으로 처리한다.
- 스트림을 이용한 작업이 간결할 수 있는 비결중의 하나가 바로 '내부 반복'
- 내부 반복 : 반복문을 메서드 내부에 숨길 수 있다는 것을 의미한다.
- forEach() 는 스트림에 정의된 메서드중 하나로 매개변수에 대입된 람다식을 데이터 소스의 모든 요소에 적용한다.
for(String str : strList)
System.out.println(str);
Stream.forEach(System.out::println); // = (str)-> System.out.println(str)
즉, forEach()는 메서드 안으로 for문을 넣은 것이다. 수행할 작업은 매개변수로 받는다.
void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action); // 매개변수의 널 체크
for(T t : src) { // 내부반복
action.accept(T);
}
}
스트림의 연산
스트림에 정의된 메서드 중에서 데이터 소스를 다루는 작업을 수행하는 것을 연산(operation) 이라고 한다.
- 중간 연산 : 연산 결과가 스트림인 연산, 스트림에 연속해서 중간 연산 할 수 있음
- 최종 연산 : 연산 결과가 스트림이 아닌 연산. 스트림의 요소를 소모하므로 단 한번만 가능
2.2.0 중간연산의 지연된 연산(Lazy Evaluation)
스트림에서 중요한점은 중간연산시 지연 연산이 발생한다는 점이다.
목적은 불필요한 연산을 피하기 위한 것이다.
final List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
System.out.println(
list.stream()
.filter(i -> i<6)
.filter(i -> i%2==0)
.map(i -> i*10)
.collect(Collectors.toList())
);
위의 연산을 살펴 보았을때는 중간 연산이 3가지 있다.
이 연산에 데이터소스를 한번에 순차적으로 처리한다고 생각할 수 있지만
public class Lamda {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
list.stream()
.filter(i -> {
System.out.println( i + " < 6");
return i<6;
})
.filter(i -> {
System.out.println( i + "%2 == 0");
return i%2==0;
})
.map(i -> {
System.out.println( i + " = i*10");
return i*10;
})
.collect(Collectors.toList());
}
}
스트림의 중간 연산 목록
중간연산 | 설명 |
Stream distinct() | 중복을제거 |
Stream filter (Predicate predicate) | 조건에 안 맞는요소제외 |
Stream limit (long maxSi ze) | 스트림의 일부를 낸다. |
Stre n skip(long n) | 스트림의 일부를 건너띈다. |
Stre n skip(long n) | 스트림의 소에 작업수행 |
Stream sorted() Stream sorted(Comparator comparator) |
스트림의 요소를 정렬한다 |
Stream map (Function mapper) DoubleStream mapToDouble (ToDoubleFunction mapper) IntStream mapTolnt (TolntFunction mapper) LongStream mapToLong (ToLongFunction mapper) Stream flatMap (Function> mapper) DoubleStream flatMapToDouble (Function m) IntStream flatMapTolnt (Function m) LongStream flatMapToLong (Function m) |
스트림의 요소를 변환한다. |
스트림의 최종 연산 목록
최종 연산 | 설명 |
void forEach (Consume r action ) void forEachOrdered(Consumer action) |
각 소에 정된 작업 수행 |
long count ( ) | 스트림의 의 개수 반환 |
Optional max (Comparator comparator) Optional min (Comparator compa rator) |
스트림의 최대 /최소값을 반환 |
Optional findAny ( ) Optional findFirst ( ) |
스트림의 요소 하나를 반환 |
boolean allMatch (Predicate p) boolean anyMatch (Predicate p) boolean noneMatch (Predi cate<> p) |
주어진 조건을 모든 요소가 만족시키는지, 만족시키지 않는지 확인 |
Object[] toArray() A[] toArray (IntFunction generator) |
스트림의 모든 요소를 배열로 반환 |
Optional reduce (BinaryOperator accumulator) T reduce (T identity, BinaryOperator accumulator) U reduce (U identity, BiFunction accumulator, BinaryOperator combiner) |
스트림의 요소를 하나씩 줄여가면서(리듀싱)계산 한다. |
R collect (Collector collector) R collect (Supplier supplier, BiConsumer accumulator, BiConsumer combiner) |
스트림의 요소를 수집한다. 주로 요소를 그룹화하거나 분할한 결과를 컬렉션에 담아 반환하는데 사용한다. |
중간 연산은 map() , flatMap()
최종 연산은 reduce() 와 collect() 가 핵심이며 나머지는 이해하기 쉽고 사용법도 간단하다.
중간 연산
3.1 map()
스트림의 요소에 저장된 값 중에서 원하는 필드만 뽑아내거나 특정 형태로 변환해야 할 때가 있다.
이 때 사용하는 것이 바로 map()이다.
매개변수로 T 타입을 변환해서 반환하는 함수를 지정해야 한다.
Strearn<File> fileStrearn = Strearn. of (new File ("Ex1 . j ava" ), new File ("Ex1 ") ,
new File ("Exl.bak") , new File ("Ex2 . java") , new File ("Exl. txt " )) ;
// map()으로 Stream<File>을 Stream<String>으로 변환
Stream<String> filenameStream = fileStream .map(File : :getName ) ;
filenameStream . forEach(System .out : :println) ; // 스트림의 모든 파일이름을 출력
- map() 역시 중간 연산, 연산결과는 String 을 요소로 하는 스트림이다.
- map() 으로 Stream<File>을 Stream<String>으로 변환했다고 볼 수 있다.
- map() 도 Filter() 처럼 하나의 스트림에 여러번 적용 할 수 있다.
ex) File 의 스트림에서 파일의 확장자 만을 뽑은 다음 중복을 제거해서 출력한다.
fileStream.map(File::getName) / / Stream<File> - Stream<String>
.filter(s-> s.indexOf(’.’)! =-l) // 확장자가 없는 것은 제외
.뼈p(s-> s.substr 19(s.inc용xOf(’.’)+1)) //Strearn<String~ Strearn<String>
.map(String::toUpperCase) //모두 대문자로 변환
.distinct() //중복 제거
.forEach(System.out::print);//JAVABAKTXT
mapToInt(), mapToLong(), mapToDouble()
map()은 연산의 결과로 Stream<T> 타입의 스트림을 반환하는데, 스트림의 요소를 숫자로 반환하는 경우
IntStream 같은 기본형 스트림으로 변환하는 것이 더 유용할 수 있다.
아래 스트림에 포함된 모든 학생의 성적을 합산하는 코드이다.
이럴 경우 mapToInt()를 사용해서 Stream<Integer> 가 아닌 IntStream 타입의 스트림을 생성해서 사용하는 것이 더 효율적이다. 성적을 더할때 Integer 를 int로 변환할 필요가 없기 때문이다.
IntStream studentScoreStream = studentStream.mapToInto(Student::getTotalScore);
int allTotalScore = studentScoreStream.sum(); // int sum();
'java' 카테고리의 다른 글
디자인 패턴(Design Pattern) - 목적에 따른 분류 (0) | 2021.11.26 |
---|---|
[JAVA]java Serializable (0) | 2021.11.04 |
[JAVA] JVM 에 대하여 ( + Garbage Collection) (0) | 2021.10.29 |
[JAVA] 자바의 정석 - Execption 정리 (0) | 2021.10.21 |
[JAVA] Concurrent Collections - ConcurrentHashMap() (0) | 2021.10.11 |