반응형
250x250
Notice
Recent Posts
Recent Comments
Link
관리 메뉴

Yeonee's Story

[JAVA] 스트림(stream)이란 무엇인가? (코딩테스트 활용 내용 부분 정리) 본문

。*:・゚☆・゚schedule・゚*:・゚★・:*:・☆ *:・゚★/활용해본 코드 적용 예시 (◍•ᴗ•◍)❤

[JAVA] 스트림(stream)이란 무엇인가? (코딩테스트 활용 내용 부분 정리)

yeonee 여니 2023. 10. 4. 17:10
728x90
반응형
SMALL

안녕하세요.
https://blog.naver.com/sysysy0302 여니입니다 :)

 

스트림(Stream)의 개념

자바에서는 파일이나 콘솔의 입출력을 직접 다루지 않고, 스트림(stream)이라는 흐름을 통해 다룹니다. 
스트림이란 실제의 입력이나 출력이 표현된 데이터의 이상화된 흐름을 의미합니다. 
즉, 스트림은 운영체제에 의해 생성되는 가상의 연결 고리를 의미하며, 중간 매개자 역활을 합니다.

위의 내용은 Java SE 8부터 추가된 스트림 API라는 개념과 별개의 개념입니다.

8버전부터 등장하는 스트림 API는 데이터를 추상화하여 다루므로, 다양한 방식으로 저장된 데이터를 읽고 쓰기 위한 공통된 방법을 제공합니다. 자바에서는 많은 양의 데이터를 저장하기 위해서 배열이나 컬렉션을 이용합니다. 
이렇게 저장된 데이터에 접근하기 위해서는 반복문이나 반복자(iterator)를 사용하여 매번 새로운 코드를 작성해야 합니다.  하지만 이렇게 작성된 코드는 길이가 너무 길고 가독성도 떨어지며, 코드의 재사용이 거의 불가능합니다. 
즉, 데이터베이스의 쿼리와 같이 정형화된 처리 패턴을 가지지 못했기에 데이터마다 다른 방법으로 접근해야만 했습니다.이러한 문제점을 극복하기 위해서 JAVA SE 8부터 스트림(stream) API를 도입했다고 합니다.

+ 실제로 코딩테스트를 진행하면서 람다식과 메서드참조 그리고 중간연산과 최종연산을 거치면서 데이터에 맞는 방식(Map, list 등등)으로 길게 써야 했던 코드를 짧게 이용할 수 있음을 경험했습니다.

 

스트림(Stream)의 특징
  1. 람다식과 함께 사용되어 컬렉션에 들어있는 데이터에 대한 처리를 매우 간결한 표현으로 작성할 수 있습니다.
  2. 스트림은 외부 반복을 통해 작업하는 컬렉션과는 달리 내부 반복(Internal iteration)을 통해 작업을 수행합니다.
  3. 스트림은 재사용이 가능한 컬렉션과는 달리 단 한번만 사용할 수 있습니다.
  4. 스트림은 원본 데이터를 변경하지 않습니다.
  5. 스트림의 연산은 필터-맵(filter-map) 기반의 API를 사용하여 지연(lazy) 연산을 통해 성능을 최적화합니다.
  6. 스트림은 내부반복자를 사용하기 때문에 병렬 처리가 쉽습니다.

 

'Java 6'이전까지는 컬렉션의 엘리먼트들을 순회하기 위해서 Iterator객체를 이용했습니다.

ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c"));

Iterator<String> iterator = list.iterator();

while(iterator.hasNext()) {
    String value = iterator.next();

        if (StringUtils.equals(value, "b") {
        System.out.println("값 : " + value);
    }
}


"컬렉션을 순회하면서 값들을 출력"이라는 단순한 동작을 위해서 보기에 지저분한 코드들이 많이 생성됩니다. 가독성도 떨어지게 됩니다. for each 구문을 이용하면 좀 더 깔끔해지긴하지만....

ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c"));

for (String value : list) {

        if (StringUtils.equals(value, "b") {
        System.out.println("값 : " + value);
    }
}

 

'Java 8'부터 추가된 스트림을 사용하면 조금 더 단순하고 깔끔하게 코드를 작성할 수 있습니다.

ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c"));
list.stream()
    .filter("b"::equals)    
    .forEach(System.out::println);

 

스트림(Stream) 사용법

1. 스트림 생성

컬렉션

자바의 스트림을 사용하려면 우선 스트림 객체를 생성해야합니다.

List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();

자바 코드에서 자주 사용하는 컬렉션 객체들은 stream()메소드를 지원합니다.
컬렉션 객체에서 stream()메소드를 호출하면 스트림 객체를 만들 수 있습니다.

 

배열

배열의 경우 정적 메소드를 이용합니다.

String[] array = new String[]{"a", "b", "c"};
Stream<String> stream1 = Arrays.stream(array);
Stream<String> stream2 = Arrays.stream(array, 1, 3); // 인덱스 1포함, 3제외 ("b", "c")

정적 메소드 'Arrays.stream()'에 인자로 배열을 입력하면 배열을 순회하는 스트림객체를 만들 수 있습니다.
Arrays.stream()메소드에 배열과 시작, 종료 인덱스를 인자로 주면 배열의 일부를 순회하는 스트림 객체를 만들 수도 있습니다. (이때, 종료 인덱스는 명시한 숫자를 포함하지 않습니다.)

 

스트림 연결

두 개의 스트림을 연결해서 하나의 새로운 스트림으로 만들어 낼 수 있습니다.

Stream<String> stream1 = Stream.of("Apple", "Banana", "Melon");
Stream<String> stream2 = Stream.of("Kim", "Lee", "Park");

Stream<String> stream3 = Stream.concat(stream1, stream2);
// "Apple", "Banana", "Melon", "Kim", "Lee", "Park"

Stream.concat() 메소드를 이용해서 두 개의 스트림을 붙여 새로운 스트림을 만들 수 있습니다.

 

2. 스트림 데이터 가공

Filter

필터(filter)는 스트림에서 뽑아져 나오는 데이터에서 특정 데이터들만 골라내는 역할을 합니다.

Stream<T> filter(Predicate<? super T> predicate);

filter()메소드는 boolean값을 리턴하는 람다식을 넘겨주게 됩니다. 그러면 뽑아져 나오는 데이터에 의해 람다식을 적용해서 true가 리턴되는 데이터만 선별합니다.

간단한 예를 들어보겠습니다.

Stream<Integer> stream = IntStream.range(1, 10).boxed();
stream.filter(v -> ((v % 2) == 0))
            .forEach(System.out::println);
// 2, 4, 6, 8

1~9까지 데이터를 뽑아내는 스트림을 만들어줍니다. filter메소드에 짝수를 선별( (v%2) == 0 )해주는 람다식을 넣어줍니다. 이러면 1~9까지의 데이터 중 짝수 데이터만 뽑아내주는 스트림 객체가 리턴됩니다.

 

Map

map()은 스트림에서 뽑아져 나오는 데이터에 변경을 가해줍니다.

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

mpa()메소드는값을 변환해주는 람다식을 인자로 받습니다. 스트림에서 생성된 데이터에 map()메소드의 인자로 받은 람다식을 적용해 새로운 데이터를 만들어줍니다.

간단한 예를 들어보겠습니다.

Stream<Integer> stream = IntStream.range(1, 10).boxed();
stream.filter(v -> ((v % 2) == 0))
            .map(v -> v * 10)
            .forEach(System.out::println);
// 20, 40, 60, 80

해당 코드는 1~9까지의 숫자 중에 filter()를 이용해서 짝수만 뽑아낸 다음, 곱하기 10을 해서 10배에 해당하는 숫자를 생성하는 스트림입니다.

 

Sorted

스트림 데이터들을 정렬하고자 할 때, sorted()메소드를 사용합니다.

Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);

인자 없이 sorted()메소드를 호출할 때에는 오름차순으로 정렬됩니다. 만약 정렬할 때 두 값을 비교하는 별도의 로직이 있다면, comparator를 sorted()메소드의 인자로 넘겨줄 수 있습니다.

 

3. 스트림 결과 생성

지금까지 본 데이터 수정 연산, 데이터에 수정을 가한 결과 데이터들을 만들어내는 또 다른 스트림 객체를 리턴하는 작업을 하였습니다. 즉, 중간 작업들이며 이들만으로는 의미있는 스트림을 만들 수 없습니다. 데이터를 가공하고 필터링한 다음 그 값을 출력하거나 또 다른 컬렉션으로 모아두는 등의 마무리 작업이 있어야 원하는 값을 도출해낼 수 있습니다.

통계 값

정수 값을 받는 스트림의 마무리는 '총합'을 구하거나 '최대값', '최소값', '숫자의 개수', '평균값' 등에 대한 계산입니다.
코딩테스트의 주요된 문제에 포함되기도 하고 실제 맞춘 정답들 사례를 보면 stream으로 간단하게 풀어놓은 답들을 볼 수 있습니다.

간단한 예를 들어보겠습니다.

int sum = IntStream.range(1, 10).sum();
int count = IntStream.range(1, 10).count();

int max = IntStream.range(1, 10).max();
int min = IntStream.range(1, 10).min();
int avg = IntStream.range(1, 10).average();

// 짝수 숫자의 총합
int evenSum = IntStream.range(1, 10)
                       .filter(v -> ((v % 2) == 0))
                       .sum();

 

Collect

자바 스트림을 이용하는 가장 많은 패턴 중 하나로 컬렉션의 엘리먼트 중 일부를 필터링하고, 그 값을 변형해서 또 다른 컬렉션으로 만드는 것입니다. 

Set<Integer> evenNumber = IntStream.range(1, 1000).boxed()
                                    .filter(n -> (n%2 == 0))
                                    .collect(Collectors.toSet());

collect()메소드를 이용해 뽑아져 나오는 데이터들을 컬렉션으로 모아 둘 수 있습니다. 위 예제는 1~999까지의 숫자 중 짝수만 filter링하여 Set컬렉션에 모아두는 예제입니다.

collect()메소드에는 Collector메소드를 사용할 수 있습니다. Collector클래스에 있는 정적 메소드를 이용해서 뽑아져나오는 객체들을 원하는 컬렉션으로 만들 수 있습니다. Collector.toList()를 호출하면 리스트로 만들고, Collector.toSet()을 호출하면 Set으로 만들어줍니다.

간단한 예를 들어보겠습니다.
Collector.joining()을 사용하면 작업한 결과를 하나의 문자열로 이어 붙일 수 있습니다.

List<String> fruit = Arrays.asList("Banana", "Apple", "Melon");
String returnValue = fruit.stream()
            .collect(Collectors.joining());

System.out.println(returnValue);
// BananaAppleMelon

 

Collector.joining()메소드에 추가로 인자를 주면 문자열을 좀 더 멋지게 붙일 수 있습니다.

List<String> fruit = Arrays.asList("Banana", "Apple", "Melon");
String returnValue = fruit.stream()
            .collect(Collectors.joining(",", "<", ">"));

System.out.println(returnValue);
// <Banana,Apple,Melon>

첫번째 인자는 구분자이고, 두번째와 세번째 인자는 맨 처음(prefix), 마지막(suffix)에 올 문자열 입니다.

 

foreach

스트림에서 뽑아져 나오는 값에 대해서 어떤 작업을 하고 싶을 때, foreach메소드를 사용합니다.
이 메소드는 앞의 다른 메소드들과 다르게 어떤 값을 리턴하지 않습니다.

간단한 예를 들어보겠습니다.

Set<Integer> evenNumber = IntStream.range(1, 1000).boxed()
                                    .filter(n -> (n%2 == 0))
                                    .forEach(System.out::println);

1~999까지의 숫자 중 짝수만 뽑아내서 출력하는 코드입니다.

 

이외에도 더 다양한 내용이 많은데 코딩테스트를 하면서 궁금한 점을 좀 더 자세히 분석해보고 싶어서 작성하였으며,
추후 스트림에 대한 전체적인 내용 정리를 할 예정입니다.
자세한 내용은 아래 참고 사이트를 방문하여 확인해보면 좋을 것 같습니다.

 

+ 참조 사이트

728x90
반응형
LIST