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());
스트림은 다음과 같은 구조를 가진다.
list.stream()
.filter(x -> x > 10)
.count();
스트림 생성( stream() ) : 컬렉션의 목록을 스트림 객체로 변환한다. 여기서 스트림 객체는 java.util.stream 패키지의 Stream 인터페이스를 뜻함.
중개 연산( filter(x -> x > 10 ) : 생성된 스트림 객체를 사용하여 중개 연산 부분에서 처리한다. 하지만 이 부분에서는 아무런 결과를 리턴하지 못함 그래서 중개 연산(intermediate operation)이라고 부른다.
종단 연산( count() ) : 마지막으로 중개 연산에서 작업된 내용을 바탕으로 결과를 리턴
중개 연산은 반드시 있어야 하는 것은 아니고 0개 이상의 중개 연산이 존재할 수 있다고 생각하는 것이 좋다.
stream()은 순차적으로 데이터를 처리한다. 즉, 10개의 데이터가 있다면 0 ~ 9번재 인덱스를 하나씩 처음부터 처리한다.
스트림에서는 다양한 종류의 연산을 제공하지만 지금은 일반적으로 많이 사용되는 연산자(foreach(), map(), filter())를 알아 보자
@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());
}
위에서 확인한 스트림의 구조를 다시 살펴보면
list.stream()
.filter(x -> x > 10)
.count();
앞 서 설명했던 forEach() 메소드 같은 경우에는 종단 연산에 속하고 지금 살펴 볼 map() 메소드의 경우는 중개 연산에 속한다.
List<Integer> intList = Arrays.asList(1,2,3,4,5,6);
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값만 추출해서 리스트로 반환할 수 있다.
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는 조건문 처럼 스트림 내에서 필요한 데이터를 걸러서 처리할 때 사용한다.