
우아한 테크코스 프리코스를 진행할 때, 반복문과 조건문을 너무 많이 써서 어떤 방법이 좋을까 찾아보던 중에 Stream을 찾아서 공부하게 되었다.
Stream정의와 장점
JDK 8부터 지원되기 시작한 API 중 하나이다.
Stream은 Collection 내부에 저장된 원소들을 하나씩 꺼내서 처리할 수 있는 코드패턴이다.
또한, Lambda 식을 통해서 반복문을 간결하게 표현할 수 있다는 장점이 존재하고,
내부 구조에 선언된 반복자를 통해서 병렬처리가 쉽다는 장점이 있다.
기존의 반복과의 비교
for문을 통한 반복문
import java.util.ArrayList;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
// 리스트 내부의 원소 중 "c"만 출력하라.
ArrayList<String> example_list = new ArrayList<String>(Arrays.asList("a", "b", "c", "d", "e"));
for(int i =0; i<example_list.size(); i++){
if(example_list.get(i).equals("c")){
System.out.print(example_list.get(i));
break;
}
}
}
}
기존의 For문을 통한 반복문은 단순한 로직을 구현하는데도 많은 Indentation을 차지하는 것을 볼 수 있다.
만약 더 복잡한 조건과 반복을 사용하게 된다면 나중에는 왼쪽의 브래킷이 굉장히 많아질 것이다.
Stream 사용
import java.util.ArrayList;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
// 리스트 내부의 원소 중 "c"만 출력하라.
ArrayList<String> example_list = new ArrayList<String>(Arrays.asList("a", "b", "c", "d", "e"));
example_list.stream()
.filter("c"::equals)
.forEach(System.out::println);
}
}
기존의 반복문에 비해 굉장히 간결한 코드로 동일한 동작을 수행할 수 있다..
위에서는 기존의 반복문과의 차이를 알아봤으니까, 이번에는 사용법에 대해서 알아볼 것이다.
Stream 사용법
Stream을 어디에 적용하는지에 따라서 다른 형식으로 사용을 해야 한다. Stream을 사용하는 형식에는 다양한 형식이 존재하지만, 자주 사용하게 될 것 같은 Collection과 Array, Primitive만 살펴보고 넘어가겠다.
1. Collection
위에서 했던 예제가 Collection에서 Stream을 사용하는 방법이었다.
ArrayList<String> example_list = new ArrayList<String>(Arrays.asList("a", "b", "c", "d", "e"));
Stream<String> example_stream = example_list.stream();
2. Array
Collection뿐만 아니라, Array에서도 Stream을 자주 사용한다.
String[] array = "a,b,c,d,e".split(",");
Stream<String> stream1 = Arrays.stream(array);
Stream<String> stream2 = Arrays.stream(array, 1, 3);
3. Primitive
Primitive는 기본 타입에 대한 이야기이다. Java에서는 Primitive 타입에 대해서는 오토 박싱과 언박싱이 발생한다.
int형 변수를 처리할 때, 내부적으로 Integer 타입으로 오토 박싱하여 처리하는 경우가 있는데 이런 경우에 변환하는 과정 중 오버헤드가 발생해서 처리 속도가 떨어질 수 있다. 이로 인해 Stream에서도 int와 Integer 각각 Stream을 사용하는 방법이 존재한다.
// int -> 오토 박싱 수행 X
IntStream intStream = IntStream.range(1,90);
// Integer
Stream<Integer> stream = IntStream.range(1,90).boxed();
Generic을 사용하기 위해서 Stream <Integer> stream의 경우는 .boxed()가 필요하다.
데이터 가공
이제 스트림을 통해서 데이터를 추출했으면, 그 데이터에 어떠한 작업을 거쳐서 사용해야 하기 때문에 어떤 메서드들을 통해서 데이터를 가공할 수 있는지 알아보겠다.
Filter
Filter는 스트림에서 추출되는 데이터에서 특정 패턴을 파악하거나, 특정 데이터를 필터링하는 메서드다.
ArrayList<String> example_list = new ArrayList<String>(Arrays.asList("a", "b", "c", "d", "e"));
Stream<String> example_stream = example_list.stream();
example_stream.filter(s -> s.equals("c")).forEach(System.out::println);
// 출력 예시
c
filter 메서드는 람다 식을 입력으로 받아 Boolean 값을 리턴하게 된다. 이때, 리턴하는 값이 true라면 다음으로 넘어가고, 아니라면 무시된다.
Map
Map() 메서드는 스트림에서 추출되는 데이터에 변형하여 Stream을 반환한다.
Stream<Integer> stream = IntStream.range(1,100).boxed();
stream.filter(s -> s % 10 == 0)
.map(s -> s * 6)
.forEach(System.out::println);
// 출력 예시
60
120
180
240
300
360
420
480
540
Sorted
Sorted() 메서드는 기본적으로는 오름차순으로 내부의 원소를 정렬한다. 옵션으로 내림차순으로 설정할 수 있다.
Stream<Integer> stream = new Random().ints(1, 100).limit(10).boxed();
//오름차순
stream.sorted().forEach(System.out::println);
//내림차순
stream.sorted(Comparator.reverseOrder()).forEach(System.out::println);
peek
peek는 map과 유사하게 스트림 내부의 원소들에 대해서 람다 함수를 처리할 수 있다.
차이점은 map()은 스트림을 반환하지만 peek는 그냥 적용하기만 한다.
Stream<Integer> stream = new Random().ints(1, 100).limit(10).boxed();
stream.peek(System.out::println).sorted(Comparator.reverseOrder()).forEach(System.out::println);
// 출력
원본 : 29,57,45,45,17,16,65,61,58,6
정렬 : 65,61,58,57,45,45,29,17,16,6
내부의 원소들은 System.out의 네임 스페이스의 println을 사용해서 10개의 랜덤 Int 원소를 출력하고, 해당 원소들을 내림차순으로 정렬하고 다시 출력하는 예시다.
데이터 결과
Collect
Java에서 Stream을 사용하는 주된 이유는 기존의 배열 또는 데이터에 함수들을 적용하고 다시 원래의 타입으로 변환하기 위해서 사용하게 된다.
Collector 내부에 선언된 자료형으로 스트림을 추출할 수 있다. 예를 들면, toList, toSet, toString과 같은 것들이 존재한다.
Stream<Integer> stream = new Random().ints(1, 100).limit(10).boxed();
List<Integer> Int_List = stream.filter(s -> s % 2 == 0).collect(Collectors.toList());
System.out.println("Even Numbers: " + Int_List);
// 예시
Even Numbers: [32, 56, 32, 12, 50, 56]
forEach
스트림에서 추출되는 값에 대해서 어떤 함수를 적용하고 싶을 때 forEach를 사용한다. forEach 메서드는 특정 값을 리턴하지는 않는다.
Stream<Integer> stream = new Random().ints(1, 100).limit(10).boxed();
stream.filter(s -> s % 2 == 0).collect(Collectors.toList()).forEach(System.out::println);
//출력
24,68,34,78
'Lang' 카테고리의 다른 글
[Java] 객체 지향 언어과 Java (1) | 2023.11.24 |
---|---|
[Java] equals가 안돼요... 어떻게 해야하나요? (1) | 2023.10.25 |