[CS-JAVA] Stream API

지영·2023년 8월 29일
0

CS

목록 보기
66/77

Stream API란,

자바는 객체지향 언어이기 때문에 함수형 프로그래밍이 불가능하다. 그래서 배열이나 데이터를 정렬된 상태로 출력하고자 할 때, 원본의 데이터가 직접 정렬이 되어야만했다.

따라서 아래의 코드와 같이 처리해야만 했다.

String[] nameArr = {"A", "C", "B", "F"}
List<String> nameList = Arrays.asList(nameArr);

// 원본의 데이터가 직접 정렬됨
Arrays.sort(nameArr);
Collections.sort(nameList);

for (String str: nameArr) {
  System.out.println(str);
}

for (String str : nameList) {
  System.out.println(str);
}

하지만 Stream API와 람다식, 함수형 인터페이스를 사용하면 자바를 이용한 함수형 프로그래밍이 가능하다. 그 중 Stream API는 데이터를 추상화/처리하는 데에 자주 사용하는 함수를 정의한다. (*여기서 추상화란 데이터의 종류에 상관없이 같은 방식으로 데이터를 처리할 수 있도록 하는 것. 재사용성 증대 등의 이점이 존재함)

따라서 아래의 코드로 가독성을 높여서 처리할 수 있다.

String[] nameArr = {"A", "C", "B", "F"}
List<String> nameList = Arrays.asList(nameArr);

// 원본의 데이터가 아닌 별도의 Stream을 생성함
Stream<String> nameStream = nameList.stream();
Stream<String> arrayStream = Arrays.stream(nameArr);

// 복사된 데이터를 정렬하여 출력함
nameStream.sorted().forEach(System.out::println);
arrayStream.sorted().forEach(System.out::println);

Stream API와 Collection의 차이

✔ Stream & Collection 공통점

둘 다 연속된 요소 형식의 값을 저장하는 자료구조의 인터페이스를 제공하며 순서에 따라 순서대로 요소에 접근한다.

✔ Stream & Collection 차이점

1. Collcetion

  • 현재 자료구조가 포함하는 모든 값을 메모리에 저장하는 자료구조, 따라서 컬렉션 안의 모든 요소는 컬력션에 추가하기 전에 계산되어야 한다.
  • 외부선언 (for-each)를 통해 사용자가 직접 반복 작업을 거쳐서 요소를 가져온다. 즉, 작업을 위해서 Iterator로 모든 요소를 순환한다.

2. Stream

  • Stream은 요청할 때만 요소를 계산하는 고정된 자료구조이다. 따라서 스트림에 요소를 추가하거나 삭제를 할 수 없다. 사용자가 요청할 때만 스트림에서 추출한다.
  • 내부 반복을 사용하므로, 추출 요소만 선언하면 알아서 반복처리를 진행한다. 즉, 계산식을 미리 적어두고 람다식으로 JVM에 넘긴다.
  • 생산자(producer)-소비자(consumer) 관계를 형성한다.
  • 파이프-필터(연산(filter, sorted, map...)끼리 연결한 파이프라인) 기반, 무한 연속 데이터 흐름 API 성격을 띈다.

🤔 내부반복 VS 외부반복 ?

  • 내부반복 : 작업을 병렬처리하면서 최적화된 순서로 처리하는 방법.
  • 외부반복 : 반복문으로 요소를 직접 탐색하는 방법. 병렬처리를 위해서는 synchronized를 통해 관리가 가능함.

Stream 연산

3단계 : 생성 > 가공 > 결과

1. 생성하기

  • Stream 객체 생성하기
  • Stream은 재사용이 불가능함. 따라서 닫히면 다시 생성해야 함

2. 가공하기

  • 원본의 데이터를 별도의 데이터로 가공하기 위한 중간 연산
  • 연산 결과는 Stream으로 반환되기 때문에 연속적으로 중간 연산을 할 수 있음

3. 결과 만들기

  • 가공된 데이터로부터 원하는 결과를 만들기 위한 최종 연산
  • Stream의 요소들을 소모하면서 연산이 수행되므로 일회용임.

연산 예시 코드

List<String> myList = Arrays.asList("a1", "a2", "b1", "c2", "c1");

myList
    .stream()							// 생성하기
    .filter(s -> s.startsWith("c"))			// 가공하기
    .map(String::toUpperCase)			// 가공하기
    .sorted()							// 가공하기
    .count();

중간연산 종류 (가공단계)

1. Filter

특정 조건을 만족한느 데이터만 거르기. true를 반환하며 stream형태로 반환.

List<String> startsWithN = names.stream()
    .filter(name -> name.startsWith("S"))
    .collect(Collectors.toList());

2. Map

데이터를 변형하는데 사용함.

List<String> names = Arrays.asList("Sehoon", "Songwoo", "Chan", "Youngsuk", "Dajung");

names.stream()
    .map(name -> name.toUpperCase())
    .forEach(name -> System.out.println(name));
   

3. Sorted

데이터가 순서대로 정렬된 stream 리턴, 데이터의 종류에 따라 Comparator가 필요할 수 있다.

// 오름차순
var numbers = List.of(5, 2, 3, 9, 4);
numbers.stream()
       .sorted()
       .collect(Collectors.toList());


//내림차순 
var numbers = List.of(5, 2, 3, 9, 4);
numbers.stream()
       .sorted(Comparator.reverseOrder())
       .collect(Collectors.toList());
       

// 오름차순 - Comparator.comparing 사용
var StudentList = List.of(
            new Student(4, "tae", 100, 100, 100),
            new Student(5, "lob", 75, 75, 100),
            new Student(1, "hong", 75, 90, 80),
            new Student(2, "sujin", 50, 90, 100),
            new Student(3, "kate", 90, 75, 75));

StudentList.stream()
       .sorted(Comparator.comparing(Student::getNo))
       .collect(Collectors.toList()); 
       
// 내림차순 - Comparator.comparing 사용
StudentList.stream()
       .sorted(Comparator.comparing(Student::getNo).reversed())
       .collect(Collectors.toList());    

4. reduce

Stream 요소들을 하나의 데이터로 만드는 작업을 수행함.


// 결과값에 대한 초기값이 없는 경우
Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Optional<Integer> sum = numbers.reduce((x, y) -> x + y);
sum.ifPresent(s -> System.out.println("sum: " + s));

// 초기값이 있는 경우
Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = numbers.reduce(10, (total, n) -> total + n);
System.out.println("sum: " + sum);

5. flatMap

map과 달리 평면화된 단일 스트림 반환, Array나 Object로 감싸져 있는 모든 원소를 단일 원소 스트림으로 반환.

// flatMap 사용하지 않았을 때
String[][] sample = new String[][]{
  {"a", "b"}, {"c", "d"}, {"e", "a"}, {"a", "h"}, {"i", "j"}
};

Stream<String> stream = sample.stream()
  .filter(alpha -> "a".equals(alpha[0].toString() || "a".equals(alpha[1].toString())))
stream.forEach(alpha -> System.out.println("{"+alpha[0]+", "+alpha[1]+"}"));
> output
{a, b}
{e, a}
{a, h}

// flatMap 사용했을 때
String[][] sample = new String[][]{
  {"a", "b"}, {"c", "d"}, {"e", "a"}, {"a", "h"}, {"i", "j"}
};

Stream<String> stream = sample.stream()
  .flatMap(array -> Arrays.stream(array))
  .filter(x-> "a".equals(x));

stream.forEach(System.out::println);

> output
a
a
a

*참고 : https://mangkyu.tistory.com/112, https://gyoogle.dev/blog/computer-language/Java/Stream.html

profile
꾸준함의 힘을 아는 개발자📍

0개의 댓글