Stream은 언제, 어떻게 사용되는걸까?

LeeKyoungChang·2023년 9월 26일
0
post-thumbnail

🧐 왜 이 글을 작성하게 됐을까?
반복문, 조건문이 필요할 때마다 기초 교과서처럼 하면, 가독성이 떨어지는 점을 개선하기 위해 작성하게 되었다.

 

📚 1. Stream 기본 개념

✔️ Stream이란?

  • Java8부터 추가된 기능
  • 수많은 데이터의 흐름 속에서 각각의 원하는 값을 가공하여 최종 소비자에게 제공하는 역할
  • 컬렉션, 배열 등의 저장 요소를 함수형 인터페이스 람다식을 통해 구현하며 해당되는 결과 값을 반환

 

✔️ Stream을 왜 사용하는걸까?

불필요한 코딩(for, if) 없이 구현할 수 있으며, 직관적이기 때문에 가독성이 좋아진다.

 

💡 참고
가독성 좋은 코드 : 코드를 읽었을 때 이해하기 쉬운 코드

 

✔️ Stream은 어떤 것에 적용가능할까?

Stream은 주로 Collection, Arrays에서 쓰인다.

List<String> names = Arrays.asList("kyoung", "chang", "java");
names.stream(); // Collection에서 스트림 생성

Double[] dArray = {3.1, 3.2, 3.3};
Arrays.stream(dArray); // 배열로 스트림 생성

Stream<Integer> str = Stream.of(1, 2); // 스트림 직접 생성

 

✔️ Stream의 구조

  • Stream 생성
  • 중개 연산
  • 최종 연산

 

 

📚 2. Stream 사용방법과 주의사항?

🤔 Stream의 실제 사용법
Collections 객체 집합.스트림생성().중개연산().최종연산()

  • 파이프라인 : 계속 .으로 연계할 수 있게 하는 방법

 

Stream 생성

✔️ 배열과 객체를 입력받을 때

  • 배열을 stream형식으로 입력받을 때 : Arrays.stream(배열)
  • 객체를 stream형식으로 입력받을 때 : 객체.stream()

 

List<String> names = Arrays.asList("kyoung", "chang", "java");
names.stream(); // Collection에서 스트림 생성

Double[] dArray = {3.1, 3.2, 3.3};
Arrays.stream(dArray); // 배열로 스트림 생성 : IntStream이 반환

Stream<Integer> str = Stream.of(1, 2); // 스트림 직접 생성
  • Stream은 주로 Collection, Arrays에서 쓰인다.

 

중개 연산

✔️ Filter

조건에 맞는 것만 찾아서 Stream을 반환한다.

List<String> names = Arrays.asList("kyoung", "chang", "java");
Stream<String> s = names.stream().filter(x -> x.contains("o"));

// 결과 : kyoung

 

✔️ Map

Stream의 각 요소들에 대해 함수가 적용된 결과의 새로운 요소로 매핑해준다.

List<String> names = Arrays.asList("chang", "lee");  
names.stream().map(x -> x.concat("abc"))  
        .forEach(x -> System.out.println(x));

// 결과
// changabc
// leeabc

 

✔️ 이외

  • sorted() : Stream의 요소들을 정렬
  • limit(a) : Stream의 개수를 a개로 제한한다.
  • distinct() : Stream 요소에서 중복을 제거한다.
  • skip(a) : 첫 a개의 요소는 제외하고 나머지 요소들로 새로운 Stream 반환
  • mapToInt(Integer::intValue), mapToLong(Long::longValue), mapToDouble(Double::doubleValue), mapToObj(String::valueOf) : 해당 타입의 Stream으로 바꿔준다.
  • boxed() : IntStreamStream<Integer>로 변환
  • mapToInt() : Stream<Integer>IntStream으로 변환
  • flatMap<List::stream>: 변환된 Stream을 하나의 단일 Stream으로 병합한다.
  • chars() : IntStream으로 변환해준다. (문자열을 IntStream으로 변환) H e l l o ⇒ 72 101 108 108 111

 

📑 Stream에서 자주 사용되는 메서드

자주 사용되는 메서드

 

 

최종 연산

✔️ 갯수, 최솟값, 최댓값, 합계, 평균

count(), min(), max(), sum(), average()를 함수를 활용할 시, Stream에 있는 요소들에서 해당 값을 반환해준다.

 

✔️ reduce()

누적된 값을 계산하는 함수

List<Integer> ages = new ArrayList<Integer>();
ages.add(1);ages.add(2);ages.add(3);//1,2,3
System.out.println(ages.stream().reduce((b,c) -> b+c).get());//1+2+3=6

 

✔️ forEach()

List<Integer> ages = new ArrayList<Integer>();
ages.add(1);ages.add(2);ages.add(3);//1,2,3
Set<Integer> set = ages.stream().collect(Collectors.toSet());
set.forEach(x-> System.out.println(x));//1,2,3

 

✔️ collect()

Stream의 결과를 Collection으로 바꿔준다.
toMap(), toSet(), toList() 함수를 활용한다.
문자열로 반환하고 싶을 때는 Collectors의 joining()으로 가능하다.

String s = set.stream().map(String::valueOf).collect(Collectors.joining());

 

✔️ iterator

Stream의 값을 얻고 싶을 때 주로 사용한다.

List<String> names = Arrays.asList("jeong", "pro", "jdk", "java");
Iterator<String> iter = names.stream().iterator();
while(iter.hasNext()) {
    System.out.println(iter.next());//jeong, pro, jdk, java
}

 

✔️ noneMatch, anyMatch, allMatch

noneMatch : 최종적으로 얻은 Stream 결과에서 모든 요소들이 조건을 만족하지 않는지 판단해서 boolean을 return
anyMatch : Stream의 요소들 중에서 하나라도 조건을 만족한다면 true를 return
allMatch : Stream의 모든 요소들이 조건을 만족한다면 true를 return

 

 

📚 3. 조심해야할 것

✔️ Stream은 재사용이 불가능하다.

재사용할 시 발생한 오류 : stream has already been operated upon or closed.

Stream<String> a = names.stream().filter(x -> x.contains("o"));
count = a.count();
List<String> lists = a.collect(Collectors.toList());

 

✔️ 병렬 스트림을 만들고 싶다면 parallelStream()을 사용하자

Stream을 생성하지 않고 병렬 스트림을 만들 수 있다.

names.parallelStream().filter(x -> x.contains("o")).count();

 

✔️ 중개 연산은 최종 연산이 호출될 때까지 대기한다.

데이터를 처리하기 위한 연산을 정의한 중개 연산은 지연 연산(Lazy Evaluation)의 특성을 가진다.
이는, 중개 연산은 다른 중개 연산을 호출할 때 바로 실행되지 않고 최종 연산이 호출될 때 실행된다.

  • forEach, collect, reduce 등 최종 연산이 호출되면, 중개 연산 체인이 실행되며 Stream의 요소를 처리한다. (지연 연산)
  • 지연 연산을 통해 데이터 처리를 최적화한다.

 

 

📚 4. Stream 사용 예제

✔️ 두 배열 교집합 구하기

	int[] lost = {2, 4, 3, 5};
   int[] reserve = {1, 3, 5};

   Arrays.sort(lost);
   Arrays.sort(reserve);

   Set<Integer> owns = Arrays.stream(lost)
         .boxed()
         .collect(Collectors.toSet());
   owns.retainAll(Arrays.stream(reserve)
         .boxed()
         .collect(Collectors.toSet()));

   for (Integer own : owns) {
      System.out.println(own);
   }


// 결과
// 3
// 5

 

✔️ 배열의 각 문자열을 문자 단위로 분할하고, 중복 없는 문자들을 포함하는 구조로 변환하기

["ABC", "BCD", "DEF"] -> [["A", "B", "C"], ["B", "C", "D"], ["D", "E", "F"]]
Arrays.stream(orders)
	.map(String::chars)
	.map(charStream -> charStream
		.mapToObj(menu -> String.valueOf((char) menu)) // char값을 String으로 변경한다.
    .collect(Collectors.toSet()))
	.collect(Collectors.toList());
  • map(String::chars) : map 메서드를 사용하여 각 문자열을 문자 스트림으로 변환
  • charStream.mapToObj(menu -> String.valueOf((char) menu)) : 각 문자의 코드 포인트를 문자로 변환하여, 새로운 문자열 스트림을 생성 → IntStream을 Stream<String>으로 변환

 

✔️ Set<Character>을 String으로 변환하기

Set<Character> set = key.chars().mapToObj(c -> (char)c).collect(Collectors.toSet());

String s = set.stream().map(String::valueOf).collect(Collectors.joining());

 

✔️ Set<Character>에 순서 유지해서 저장하기

BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String key = reader.readLine();

Set<Character> set = key.chars().mapToObj(c -> (char)c).collect(Collectors.toCollection(LinkedHashSet::new));

 

 


참고자료

profile
"야, (오류 만났어?) 너두 (해결) 할 수 있어"

0개의 댓글

관련 채용 정보