본문 바로가기

java

[JAVA] 자바의 정석 - Stream

스트림(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();

 

반응형