스트림(Stream)을 알아보자!

JWbase·2023년 11월 30일
0

Java

목록 보기
3/3
post-thumbnail

Stream

  • Java8에서 추가 된 기능으로 연속 된 정보를 처리하는데 사용한다.
    자바에서 연속된 배열을 사용하는 경우는 보통 Array나 Collections에서 사용한다.

    다음과 같이 1, 3, 5라는 값이 정수 배열로 있을 때 asList() 메소드를 사용해서 List로 변환 한다.

Integer[] values = { 1, 3, 5 };
List<Integer> list = new ArraysList<>(Arrays.asList(values));

Arrays 클래스에 있는 stream()이라는 메소드를 사용해서 List로 변경하는 방법도 있다.

Integer[] values = { 1, 3, 5 };
List<Integer> list = Arrays.stream(values)
							.collect(Collectors.toList());

Stream의 구조

스트림은 다음과 같은 구조를 가진다.

	list.stream()
    .filter(x -> x > 10)
    .count();
  • 스트림 생성( stream() ) : 컬렉션의 목록을 스트림 객체로 변환한다. 여기서 스트림 객체는 java.util.stream 패키지의 Stream 인터페이스를 뜻함.

  • 중개 연산( filter(x -> x > 10 ) : 생성된 스트림 객체를 사용하여 중개 연산 부분에서 처리한다. 하지만 이 부분에서는 아무런 결과를 리턴하지 못함 그래서 중개 연산(intermediate operation)이라고 부른다.

  • 종단 연산( count() ) : 마지막으로 중개 연산에서 작업된 내용을 바탕으로 결과를 리턴

중개 연산은 반드시 있어야 하는 것은 아니고 0개 이상의 중개 연산이 존재할 수 있다고 생각하는 것이 좋다.

stream()은 순차적으로 데이터를 처리한다. 즉, 10개의 데이터가 있다면 0 ~ 9번재 인덱스를 하나씩 처음부터 처리한다.

Stream에서 제공하는 연산의 종류

스트림에서는 다양한 종류의 연산을 제공하지만 지금은 일반적으로 많이 사용되는 연산자(foreach(), map(), filter())를 알아 보자

foreach()

  • for 루프를 수행하는 것처럼 각각의 항목을 꺼냄
@Getter
@Setter
public class StudentDTO {
    private String name;
    private int age;
    private int scoreMath;
    private int scoreEnglish;

    public StudentDTO(String name, int age, int scoreMath, int scoreEnglish) {
        this.name = name;
        this.age = age;
        this.scoreMath = scoreMath;
        this.scoreEnglish = scoreEnglish;
    }
}
import java.util.ArrayList;
import java.util.List;

public class StudentForEachSample {
    public static void main(String[] args) {
        StudentForEachSample sample = new StudentForEachSample();

        List<StudentDTO> studentList = new ArrayList<>();
        studentList.add(new StudentDTO("베이스", 40, 100, 100));
        studentList.add(new StudentDTO("스트림", 30, 85, 55));
        studentList.add(new StudentDTO("포이치", 20, 95, 75));

        sample.printStudentName(studentList);
    }

    private void printStudentName(List<StudentDTO> students) {
        students.stream()
                .forEach(student -> System.out.println(student.getName()));
    }
}

printStudentName() 메소드를 보면 학생들의 이름을 출력하는 기능을 하고 있다.

students.stream()까지는 스트림을 생성하는 부분이여서 넘어가고 forEach()를 보자.
람다식을 이용해서 매개변수로 넘어온 students List에서 하나의 studentDTO 객체를 의미한다.

forEach(student -> System.out.println(student.getName());

// 해당 코드는 위 코드와 동일한 기능을 한다.
for(StudentDTO student : students) {
	System.out.println(student.getName());
}

map()

  • 데이터를 특정 데이터로 변환

위에서 확인한 스트림의 구조를 다시 살펴보면

	list.stream()
    .filter(x -> x > 10)
    .count();

앞 서 설명했던 forEach() 메소드 같은 경우에는 종단 연산에 속하고 지금 살펴 볼 map() 메소드의 경우는 중개 연산에 속한다.

List<Integer> intList = Arrays.asList(1,2,3,4,5,6);
  • 해당 리스트를 모두 * 3해서 출력하는 예제이다.
private void mulitplayWithFor(List<Integer> integerList) {
        for (Integer value : integerList) {
            int tempValue = value * 3;
            System.out.println(tempValue);
        }
    }
private void mulitplayWithFor(List<Integer> integerList) {
        for (Integer value : integerList) {
            System.out.println(value * 3);
        }
    }

스트림을 사용하지 않으면 보통 이런식으로 작성하는 경우가 대부분이라고 생각한다.
스트림을 사용하면 이런식으로 작성할 수 있다.

	private void mulitplayWithFor(List<Integer> integerList) {
       integerList.stream()
               .forEach(value -> System.out.println(value * 3));
   }

하지만 3배로 변환된 값의 합을 구한다고 하면 복잡해진다. 이럴때 map()을 이용해서 편하게 사용할 수 있다.

       integerList.stream()
               .map(x -> x * 3)
               .forEach(System.out::println);

map(x -> x * 3)을 거치고 나면 {1, 2, 3, 4, 5, 6}의 값이 {3, 6, 9, 12, 15, 18}로 변경된다.
또한, map()은 숫자 뿐만 아니라 객체도 변경이 가능하다. 위의 예제 인 StudentDTO 리스트에서 이름만 추출해서 List로 뽑아내보도록 하자.

List<StudentDTO> studentList = new ArrayList<>();
        studentList.add(new StudentDTO("베이스", 40, 100, 100));
        studentList.add(new StudentDTO("스트림", 30, 85, 55));
        studentList.add(new StudentDTO("포이치", 20, 95, 75));

        List<String> studentNames = studentList.stream()
                .map(studnet -> studnet.getName())
                .collect(Collectors.toList());

이런 식으로 StudentDTO 객체에 있는 name값만 추출해서 리스트로 반환할 수 있다.

filter()

  • 데이터를 조건으로 거를때 사용
    위의 예제에서 수학 점수가 90점 이상 인 사람의 이름만 출력하도록 해보자
    private void filterWithMathScoreForLoop(List<StudentDTO> studentList, int scoreCutLine) {
        for (StudentDTO studnet : studentList) {
            if (studnet.getScoreMath() > scoreCutLine) {
                System.out.println(studnet.getName());
            }
        }
    }

해당 코드를 스트림을 사용해 작성하면

    private void filterWithMathScoreForLoop(List<StudentDTO> studentList, int scoreCutLine) {
        studentList.stream()
                .filter(student -> student.getScoreMath() > scoreCutLine)
                .forEach(student -> System.out.println(student.getName()));
    }

이렇게 나타낼 수 있다. 즉, filter는 조건문 처럼 스트림 내에서 필요한 데이터를 걸러서 처리할 때 사용한다.

Stream 정리

  • 스트림은 Collction과 같이 목록을 처리할 때 유용하게 사용
    - 스트림 생성 - 중간 연산 - 종단 연산으로 구분
    • 중간 연산은 데이터를 가공할 때 사용되고 연산 결과로 Stream 타입을 리턴하기 때문에 여러개의 중간 연산을 연결할 수 있음
    • 종단 연산은 스트림 처리를 마무리 하기 위해 사용되며, 숫자나 List같은 데이터를 리턴
profile
기억 저장소!!

0개의 댓글