Post

자바의 stream 간단 정리

🤔 스트림(stream) 이란?

  • 자바 8부터 추가된 기술로 데이터의 추상화된 연속적인 흐름을 다루는 API다.
  • 람다를 활용해 배열과 컬렉션을 함수형으로 간단하게 처리할 수 있다.
  • 스트림은 데이터 소스(타입)를 추상화하고, 데이터를 다루는데 자주 사용되는 메서드를 정의해 놓아서 데이터 소스에 상관없이 모두 같은 방식으로 다룰 수 있어 코드의 가독성과 재사용성이 높아진다.


[기존의 for문과 Iterator 비교]

  • 기존의 for문Iterator 를 사용하면 코드가 길어져서 가독성과 재사용성이 떨어지고 데이터 타입마다 다른 방식으로 다뤄야한다.
  • 스트림을 사용하면 코드가 더 간결하고 명시적인 반복을 제거할 수 있다.

  • for 문 예시
    1
    2
    3
    4
    5
    6
    7
    8
    
      List<String> names = Arrays.asList("John", "Jane", "Michael", "Sarah", "James");
      List<String> filteredNames = new ArrayList<>();
      for (String name : names) {
          if (name.startsWith("J")) {
              filteredNames.add(name);
          }
      }
      System.out.println(filteredNames);  // John, Jane
    
  • stream 사용
    1
    2
    3
    4
    5
    
      List<String> names = Arrays.asList("John", "Jane", "Michael", "Sarah", "James");
      List<String> filteredNames = names.stream()
          .filter(name -> name.startsWith("J"))
          .collect(Collectors.toList());
      System.out.println(filteredNames);  // John, Jane
    


🥸 스트림의 특징

  • 원본 데이터 소스를 변경하지 않는다.
    • 읽기만 수행하여 데이터의 무결성을 보장한다.
  • 한번 사용하면 닫혀서 재사용이 불가능한 일회용이다.
  • 최종 연산 전까지 중간 연산을 수행하지 않는다.
    • 불필요한 계산을 최소화한다.
  • 작업을 내부 반복으로 처리한다.
    • 반복을 명시적으로 나타내지 않음으로써 간결하게 표현할 수 있다.
  • 병렬 처리가 쉽다.
    • 멀티쓰레드 환경을 지원한다.
  • 기본형 스트림을 제공한다.
    • Stream<Integer> 대신 IntStream이 제공됨으로써 int <-> Integer 의 오토박싱과 언박싱 등의 불필요한 과정이 생략되고 성능 향상으로 이어진다.

      그러나 Stream<Integer> 을 쓰면 오토박싱과 언박싱이 수행된다.

    • 숫자의 경우 유용한 메서드를 추가로 제공한다. (sum(), average() 등)


😄 스트림 생성

  • 기존의 배열이나 스트림에 .stream() 메서드를 사용하여 생성할 수도 있고 직접 stream을 만들 수도 있다.


[배열 스트림]

  • Arrays.stream(); 안에 인자로 넣어줘야 한다.
1
2
String[] arr = new String[]{"a", "b", "c"};
Stream<String> stream = Arrays.stream(arr);


[컬렉션 스트림]

  • .stream() 메서드로 바로 스트림으로 변환할 수 있다.
1
2
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();


[builder로 생성]

  • Stream.builder() 로 생성 후 add로 요소를 추가할 수 있다.
1
2
3
Stream<String> builderStream = Stream.<String>builder()
    .add("a").add("b").add("c")
    .build();


[람다식으로 생성]

  • generate() 또는 iterate() 메서드를 사용하여 람다식을 이용한 스트림 생성이 가능하다.
  • 생성할 때 스트림의 크기가 정해져있지 않기 때문에 최대 크기를 limit() 로 제한해야 한다.
1
2
3
Stream<String> generatedStream = Stream.generate(()->"a").limit(3); // a, a, a

Stream<Integer> iteratedStream = Stream.iterate(0, n->n+2).limit(5); // 0, 2, 4, 6, 8


[기본 타입형 스트림]

  • 타입이 지정된 스트림이다.
1
IntStream intStream = IntStream.range(1, 5); // 1, 2, 3, 4


[병렬 스트림]

1
Stream<String> parallelStream = list.parallelStream();


😤 중간 연산(가공)

[Filtering]

  • 스트림 내 요소들을 하나씩 평가해서 걸러내는 작업을 한다.
  • 람다식의 반환값이 true인 경우만 다음 연산을 진행한다.
1
2
3
4
5
List<String> list = Arrays.asList("a","b","c");
Stream<String> stream = list.stream()
	.filter(list -> list.contains("a"));
    // 'a'가 들어간 요소만 선택
    // a


[Mapping]

  • 스트림 내 요소들을 하나씩 특정 값으로 변환한다.
1
2
3
4
5
List<String> list = Arrays.asList("a","b","c");
Stream<String> stream = list.stream()
	.map(String::toUpperCase);
    // 대문자로 변경
    // A, B, C


[Sorting]

  • 스트림 내 요소들을 정렬하는 작업
  • Comparator를 사용한다.
1
2
3
4
5
List<String> list = Arrays.asList("a","b","c");
Stream<String> stream = list.stream()
	.sorted(Comparator.reverseOrder());
    // 내림차순 정렬
    // c, b, a


[기타 연산]

1
2
3
4
5
Stream<String> stream = list.stream()
	.distinct() // 중복 제거
    .limit(max) // 최대 크기 제한
    .skip(n)    // 앞에서부터 n개 skip하기
    .peek(System.out::println) // 중간 작업결과 확인

최종 연산

[Calculating]

  • 기본형 타입을 사용하는 경우 스트림 내 요소들로 최소, 최대, 합, 평균 등을 구하는 연산을 수행할 수 있다.
1
2
3
4
5
6
IntStream stream = list.stream()
	.count()   //스트림 요소 개수 반환
    .sum()     //스트림 요소의 합 반환
    .min()     //스트림의 최소값 반환
    .max()     //스트림의 최대값 반환
    .average() //스트림의 평균값 반환


[Reduction]

  • 스트림의 요소를 하나씩 줄여가며 누적연산을 수행한다.
1
2
3
4
5
6
7
IntStream stream = IntStream.range(1,5);

int result = stream.reduce(10, (total,num)->total+num);
//reduce(초기값, (누적 변수,요소)->수행문)
// 10 + 1 + 2 + 3 + 4 = 20

System.out.println(result); // 20


[Collecting]

  • 스트림의 요소들을 원하는 형태로 변환하고 수집한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Stream<String> stream = Stream.of("Apple", "Banana", "Cherry", "Avocado");

// toList() - 리스트로 반환
List<String> list = stream.collect(Collectors.toList());

// toSet() - 세트로 반환
Set<String> set = stream.collect(Collectors.toSet());

// joining() - 하나의 문자열로 결합
String result = stream.collect(Collectors.joining(", "));
System.out.println(result); // Apple, Banana, Cherry, Avocado

// groupingBy() - 조건에 맞게 그룹지어서 Map으로 반환
Map<Character, List<String>> map = stream
            .collect(Collectors.groupingBy(s -> s.charAt(0)));
System.out.println(map);
// {A=[Apple, Avocado], B=[Banana], C=[Cherry]}

// collectingAndThen() - collecting 이후 추가 작업 수행
List<String> unmodifiableList = stream
    .collect(Collectors.collectingAndThen(
        Collectors.toList(),
        Collections::unmodifiableList));    // collecting 후 값을 변경하지 못하도록 설정
System.out.println(unmodifiableList);
        
try {
    unmodifiableList.add("New Fruit");  // 수정불가능하도록 설정했으므로 예외 발생
} catch (UnsupportedOperationException e) {
    System.out.println("Cannot modify the list");
}


[Matching]

  • 특정 조건을 만족하는 요소가 있는지의 결과를 반환한다.
    • anyMatch : 하나라도 만족하는 요소가 있으면 true
    • allMatch : 모두 만족하면 true
    • noneMatch : 모두 만족하지 않으면 true
1
2
3
4
5
6
7
8
9
10
List<String> members = Arrays.asList("Lee", "Park", "Hwang");

boolean matchResult = members.stream()
						.anyMatch(members->members.contains("w")); //w를 포함하는 요소가 있는지, True

boolean matchResult = members.stream()
						.allMatch(members->members.length() >= 4); //모든 요소의 길이가 4 이상인지, False

boolean matchResult = members.stream()
						.noneMatch(members->members.endsWith("t")); //t로 끝나는 요소가 하나도 없는지, True


[Iterating]

  • forEach로 스트림을 돌면서 실행되는 작업이다.
1
2
3
4
5
6
7
8
List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry", "Avocado");
Stream<String> fruitStream = fruits.stream();

fruitStream.forEach(System.out::println);
// Apple
// Banana
// Cherry
// Avocado


[Finding]

  • 스트림에서 특정 요소를 찾는 작업이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry", "Avocado");

// findFirst() - 첫번째 요소 반환
Optional<String> firstFruit = fruits.stream()
            .findFirst();
        
firstFruit.ifPresent(System.out::println); // Apple

// findAny() - 제일 먼저 찾은 요소 반환, 병렬 스트림의 경우 첫번째 요소 반환이 보장되지 않는다.
Optional<String> anyFruit = fruits.stream()
            .findAny();
        
anyFruit.ifPresent(System.out::println); // 임의의 과일 출력 (대부분 "Apple")


출처

[Java] 스트림(Stream) 정리


누군가가 물어본다면

스트림은 데이터의 추상화된 연속적인 흐름을 다루는 API 입니다.

데이터 컬렉션을 함수형 스타일로 연속적이고 선언적으로 처리할 수 있게 되어 코드의 가독성과 유지보수성이 증가합니다.
This post is licensed under CC BY 4.0 by the author.