스트림 stream
자바7까지는 for문이나 반복자 Iterator로 컬렉션이나 배열의 요소를 출력할 수 있었습니다. 하지만 이 방식은 코드가 너무 길고, 재사용성이 떨어집니다.
또한, 지금까지 List를 사용할 때는 Collections.sort()를 사용하고, 배열을 정렬할 때는 Arrays.sort()를 사용해왔습니다. 각 클래스에 같은 기능의 메서드가 중복해서 정의되어 있는 것이죠.
자바8부터 사용할 수 있는 stream은 이러한 문제를 해결해줍니다.
스트림에는 데이터 소스를 추상화하여, 데이터 소스에 상관없이 같은 방식으로 데이터를 다룰 수 있게 해줍니다. 이로 인해 코드의 재사용성이 높아지게 됩니다.
스트림을 사용해서 Integer배열과 List<Integer>를 정렬해서 출력해보겠습니다.
public class streamEx1 {
public static void main(String[] args) {
Integer[] intArr = {19, 3, 99, 75, 10};
List<Integer> intList = Arrays.asList(intArr);
//스트림 생성
Stream<Integer> stream1 = Arrays.stream(intArr);
Stream<Integer> stream2 = intList.stream();
//정렬 및 출력
stream1.sorted().forEach(System.out::println);
System.out.println();
stream2.sorted().forEach(System.out::println);
}
}
데이터 소스는 다르지만, 데이터를 읽고 정렬 후 출력하는 것까지 똑같은 코드로 작성할 수 있습니다. for문이나 Iterator를 사용하는 것보다 훨씬 간결하죠.
굳이 스트림 객체를 따로 생성하지 않고, 한 줄로 코드를 작성할 수 있습니다.
Arrays.stream(intArr).sorted().forEach(System.out::println);
System.out.println();
intList.stream().sorted().forEach(System.out::println);
람다식을 사용해서 한 줄에 출력해보겠습니다.
//정렬 및 출력
stream1.sorted().forEach(data -> System.out.print(data + " "));
System.out.println();
stream2.sorted().forEach(data -> System.out.print(data + " "));
스트림의 특징
1. 데이터 소스를 변경하지 않는다
원본 데이터 소스를 읽기만 할 뿐, 변경하지 않습니다. 필요하다면 컬렉션이나 배열에 담아서 반환이 가능합니다.
public class streamEx1 {
public static void main(String[] args) {
Integer[] intArr = {19, 3, 99, 75, 10};
//정렬 및 반환
Integer[] sortedArr = Arrays.stream(intArr)
.sorted()
.toArray(Integer[]::new); //메서드 참조
//데이터 출력
Arrays.stream(intArr).forEach(data -> System.out.print(data + " ")); //람다식
System.out.println();
Arrays.stream(sortedArr).forEach(streamEx1::print); //메서드 참조
//Arrays.stream(sortedArr).forEach(System.out::println); //메서드 참조
}
public static void print(Integer i) {
System.out.print(i + " ");
}
}
2. 람다식으로 요소 처리 코드를 제공한다
위의 코드에서 데이터를 한 줄에 출력한 것처럼 람다식, 또는 메서드 참조를 이용할 수 있습니다.
3. 일회용이다
스트림은 한 번 사용하면 닫혀서 다시 사용할 수 없습니다. 따라서 필요할 때마다 새로 생성해주어야 합니다.
Stream<Integer> stream1 = Arrays.stream(intArr);
stream1.forEach(System.out::println);
stream1.forEach(System.out::println); //ERROR stream1이 닫혀있음
4. 작업을 내부 반복으로 처리한다.
외부 반복이라는 것은 직접 켈렉션이나 배열의 요소를 for문으로 출력하는 것을 말합니다. 그렇다면 내부 반복이 뭘까요?
내부 반복이라는 것은 반복문을 메서드 내부에 숨길 수 있는 것을 말합니다.
개발자가 메서드의 매개변수로 요소마다 수행할 작업을 람다식으로 넘기면, 메서드 안에서 컬렉션을 반복하면서 각 요소에 람다식을 적용하는 것입니다.
스트림의 연산
연산: 스트림에 정의된 메서드 중에서 데이터 소스를 다루는 작업을 수행하는 것
중간 연산
- 연산 결과(반환 타입)가 스트림
- 스트림에 연속해서 중간 연산 수행 가능
최종 연산
- 연산 결과(반환 타입)이 스트림이 아닌 연산
- 스트림의 요소를 소모하므로 단 한 번만 가능 (스트림이 닫힘)
아래 코드를 수행하면 오류가 발생합니다.
최종 연산인 collect() 메서드를 수행한 후, 연산을 시도했기 때문입니다. 최종 연산을 이미 수행했다면, 중간 연산이나 최종 연산 둘 다 수행이 불가능 합니다.
public class streamEx2 {
public static void main(String[] args) {
Integer[] intArr = {5, 3, 1, 3, 7, 5};
List<Integer> intList = Arrays.stream(intArr)
.distinct() //중간 연산
.sorted() //중간 연산
.filter(i -> i > 1) //중간 연산
.collect(Collectors.toList()) //최종 연산
.count(); //ERROR 최종연산, 스트림 닫혀있음
intList.stream().forEach(i -> System.out.print(i + " "));
}
}
병렬 스트림
병렬 처리는 하나의 작업 여러 프로세스에 분산시키고 동시에 처리하는 것을 말합니다. 병렬 처리 기법 중 하나인 멀티쓰레드에서는 이런 하나 하나의 프로세스를 쓰레드라고 합니다.
스트림을 사용하면 이런 병렬 처리가 쉽습니다. parallel() 메서드를 사용해서 연산을 병렬적으로 수행하도록 합니다.
기본적으로 스트림은 병렬 스트림이 아니기 때문에 순차적으로 처리하도록 하는 sequential() 메서드를 호출할 필요가 없고, 병렬 스트림을 순차 처리하도록 변경할 때 사용합니다.
public class streamEx3 {
public static void main(String[] args) {
List<String> names = Arrays.asList("a", "b", "c", "d");
//순차 처리
Stream<String> sequentialStream = names.stream();
sequentialStream.forEach(streamEx3::print);
System.out.println();
//병렬 처리
Stream<String> parallelStream = names.stream().parallel();
parallelStream.forEach(streamEx3::print);
}
public static void print(String s) {
System.out.println(s + ": " + Thread.currentThread().getName());
}
}
'Language > Java' 카테고리의 다른 글
[백준/Java] 1149번. RGB 거리 (0) | 2023.03.15 |
---|---|
[Java] Dynamic Programming, DP에 대해 알아보자 (0) | 2023.03.11 |
[Java] String 배열 대소문자 구분 없이 오름차순/내림차순 정렬하기 (0) | 2023.03.10 |
[Java] 객체 배열/리스트 오름차순,내림차순 정렬과 다중 조건 정렬 (0) | 2023.03.09 |
[Java] int 배열, List 오름차순/내림차순 정렬 (0) | 2023.03.09 |