goal
java의 Stream class을 이해한다.
- Stream 왜 배워야 하는가??
- Stream을 사용하지 않은 코드
- Stream을 사용한 코드
- Stream을 사용하지 않은 코드의 단점
- Stream을 사용한 코드의 장점
Stream..너란 녀석 ^^;
난 분명히 java를 배우고 있다. 근데 Stream을 만나고 다른 언어를 배우는 느낌을 강하게 받았다.
이해하기가 힘들어 공부하는 시간이 매우 길어졌고, 지금은 어느정도 이해한 상태이다.(?)
완벽하게 이해하고 있는 것은 아니지만 Stream을 배우며 느낀 것과 사용법을 적어보려고한다.
이번 포스팅은 나에게 의미있는 포스팅이 될 것 같다. 그만큼 힘들었음
Stream, 매력적이면서 신세계를 만난 느낌이다.
처음 Stream을 공부했을 때, 이해가 하나도 되지 않아 매우 울적했다.
지금도 제대로 아는 것은 아니지만, Stream을 이해할수록 감탄이 나왔다.
Stream을 잘 활용하면 미래에 진행할 나의 프로젝트의 코드는 매우 간결해질 것이다.
레거시(?)를 벗어나 새로운 패러다임에 발을 담그고 싶다면 Stream을 공부하자.
1 ] Stream왜 배워야 하는가? *스트림 사용법은 2탄에*
Stream을 공부하기 전에
Collection 프레임워크, 배열, 람다
의 개념이 잘 안 잡혀 있다면 선행을 하고 오도록하자. 앞선 선행의 공부가 되어있지 않으면 이 글을 읽는 것 자체가 손해가 될 것이다.
서술식 글이지만 한 번 읽으면 도움이 될 글
java의 Stream은 하나의 객체로써, 메소드의 연결들로 필터링 및 가공을 통해 우리가 원하는 값을 도출할 수 있도록 도와준다.
java를 조금 공부해보았다면 메소드가 리턴값이 있을 때 값을 반환시킨다는 것은 알고 있을 것이다.. 이런것이 연속적인 메소드로 묶여있다고 생각해보자. 각각의 메소드는 기능에 해당하는 무언가를 반환(return)하게 될 것이며, 반환된 값을 다시 메소드로 연결 시킴으로써 또 다른 가공을 통해 다른 반환(return)값을 도출하게 된다. 이것이 stream의 핵심이다.
추후에 설명하겠지만 아래와 같은 데이터 흐름을 가지는 것이 일반적이다.
(일반적이라고 말하는 이유가 지연된 연산 <Lazy Evaluation>
이 존재하기 때문이다. 어려운 내용은 아니다.)
[1] Stream의 등장 배경
기존의 java 문법을 조금 더 쉽게 사용하기 위해 jdk 1.8부터 함수형 프로그래밍
을 지원하기 시작했다.
함수형 프로그래밍이란, 하나의 일련의 과정으로 처리하는 구조를 말한다.
일련의 과정은 메소드의 연결로 이루어져 있는데, 이것을파이프 구성
이라고도 한다.
[2] Stream을 사용하지 않고 정렬 기능을 하는 모듈 만들기
- Stream을 사용하지 않고 Array와 List를 반환하는 코드
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Stream;
class Why_Stream {
public List<Integer> getSort_list(List<Integer> list) {
Collections.sort(list);
return list;
}
public Integer[] getSort_arr(Integer[] arr) {
Arrays.sort(arr);
return arr;
}
}
public class Note {
public static void main(String[] args){
Integer[] arr = new Integer[]{50, 20,38, 8,12};
List<Integer> list = Arrays.asList(arr);
Why_Stream why_stream = new Why_Stream();
System.out.println("getSort_arr(arr) 넣은 경우 : " + Arrays.toString(why_stream.getSort_arr(arr))); // getSort_arr -> 매개변수에 array를 넣은 경우
System.out.println();
System.out.println("getSort_list(list) 넣은 경우 : " + why_stream.getSort_list(list)); // getSort_list -> 매개변수에 list를 넣은 경우
System.out.println();
}
}
- 출력값
<코드설명>
※ Why_Stream 클래스
가 핵심이다.
Why_Stream 클래스
를 보면 getSort_list 메소드
와 getSort_arr 메소드
가 선언되어있다.
메소드 기능적으로 봤을 때는 요소를 정렬해준다는 공통점이 존재한다.
같은 기능을 가지는 메소드임에도 왜 두 개로 나뉘었을까? 이유는 아래와 같다.
첫 번째, List
와 array
가 정렬을 하는 법이 다르기 때문이다.
배열의 경우 Arrays.sort(arr)
을 이용해야하고 List같은 경우는 Collection.sort(list)
를 이용해야한다. 문법적으로 차이가 있음을 볼 수 있다.
두 번째, 메소드의 반환 타입이 다르기 때문이다.
Integer[]
를 매개변수로 받는 경우 Integer[]
로 반환하고,List<Integer>
를 매개변수로 받는 경우는 List<Integer>
로 반환한다.
그러면 아래와 같은 의문이 들 수 있다.
❓ 생각해 볼 수 있는 의문, 그러면 매개변수는 달리 받돼, return을 똑같이 하면 되는거 아니야? (메소드 오버라이딩의 활용)
import java.util.*;
import java.util.stream.Stream;
class Why_Stream {
public List<Integer> getSort(List<Integer> list) {
Collections.sort(list);
return list;
}
public List<Integer> getSort(Integer[] arr) {
Arrays.sort(arr);
List<Integer> list = Arrays.asList(arr);
return list;
}
}
public class Note {
public static void main(String[] args){
Integer[] arr = new Integer[]{50, 20,38, 8,12};
List<Integer> list = Arrays.asList(arr);
Why_Stream why_stream = new Why_Stream();
System.out.println("getSort_arr(arr) 넣은 경우 : " + why_stream.getSort(arr)); // // getSort(arr) -> 매개변수에 arr을 넣은경우
System.out.println();
System.out.println("getSort_list(list) 넣은 경우 : " + why_stream.getSort(list)); // getSort(list) -> 매개변수에 list를 넣은 경우
System.out.println();
}
}
전에 봤던 코드와 달리 List<Integer>
를 반환하는 getSort 메소드
로 리팩토링하였다. (결과값은 같다는 말)
지금 작성한 코드가 틀리고 나쁘다를 따질 기준은 프로젝트 성격에 있으나, 뒤에 나올 Stream과 비교 및 단점을 적는다면 아래와 같다.
Why_Stream 클래스
내부에 두 개의 함수를 가진다.
Stream은 하나의 함수로 구성이 가능
- 오버라이딩 된 getSort 메소드를 봤을 때, array와 list는 sort의 방식이 다르다.
구체적으로
arr_sort = Array.sort(arr);
list_sort = Collections.sort();
- Stream은 Stream 매개변수로 받기 때문에 sort의 동작이 같다.
반환을 List<Integer>가 아닌 Integer[]로 하고싶다면?
두 가지 방법이 있겠다.
1. 정렬하여 Integer[]로 반환하는 메소드 기능을 추가하기
2. 반환된 List<Integer>를 다시 array로 변경하기
1번은 정말 응가먹어라고
2번의 경우는 단점이라기 보다는 일관성 측면에서 Stream이 조금 더 낫다고 생각이 든다.(단점은 아니다.)
array
나collection
을 순환하여 가공하거나 특정 값을 뽑아내는 경우
array는 일반적으로 for문이나 while문을, collection은 iterlator를 이용하여 순환해야하는데 이는 코드가 길어진다.
하지만 Stream은 순환하여 출력하거나 저장, 가공, 특정 값으로 만들어주고 싶은 경우 데이터 흐름을 따라 한 줄로 소스코드를 작성할 수 있다.
그리고 조금만 생각해보면 설계
에도 큰 어려움이 생긴다. 지금은 sort
기능을 하는 메소드를 오버라이딩하여 2개를 만들었지만,arr
와 collection
의 동일한 기능을 만든다고 가정했을 때 기능의 개수가 n개 일 때, 메소드의 구현은 n * 2
개를 구현해줘야한다.
이는 매우 유연성이 떨어지고, 뒤에 나올 Stream과 비교했을 때 상대적으로 유지보수 비용이 높을 것이다.
※ 모든 collection의 구현체는 stream으로 만들 수 있음을 기억한다.
[3] Stream을 사용하여 정렬 기능을 하는 모듈 만들기
- Stream을 사용하여 정렬 기능을 하는 모듈 만들기
import java.util.*;
import java.util.stream.Stream;
class Why_Stream {
public Stream<Integer> getSort_Stream(Stream<Integer> stream){
return stream.sorted();
}
}
public class Note {
public static void main(String[] args){
Integer[] arr = new Integer[]{50, 20,38, 8,12};
List<Integer> list = Arrays.asList(arr);
Why_Stream why_stream = new Why_Stream();
Stream<Integer> arrStream = Arrays.stream(arr); // arr을 Stream 객체로
Stream<Integer> listStream = list.stream(); // collection을 Stream 객체로
System.out.printf("getSort_Stream(arr) 넣은 경우 : ");
why_stream.getSort_Stream(arrStream).forEach(n -> System.out.print(n + " ")); //getSort_Stream -> 매개변수에 array를 넣은 경우
System.out.println(); System.out.println();
System.out.printf("getSort_Stream(list) 넣은 경우 : "); //getSort_Stream -> 매개변수에 list를 넣은 경우
why_stream.getSort_Stream(listStream).forEach(n -> System.out.print(n + " "));
}
}
- 출력값
<코드설명>
Why_Stream 클래스
에서 getSort_Stream 메소드
는 Stream 타입
의 객체를 매개변수로 받고Stream.sorted()
로 일관시켜 sorted() 메소드
기능 만으로도 array
와 collection
을 정렬시킬 수 있다.
🤓 야 ㅋㅋ 잠깐만 그러면 해당 메소드를 사용하려면 stream으로 객체 변환해야하는거 아니야? 번거로운데?
맞다. arr
와 collection
모두 stream으로 변환시켜야한다.
번거롭게 느껴질 수 있다. (main 메소드
내부도 stream으로 변환시켜주는 코드를 삽입했다.)
하지만 아래와 같은 경우들을 생각해봤으면 좋겠다.
1. 기능관점
stream으로 변환하지 않을 경우, 기능 n개를 구현할 때, 약 2 * n의 비용이 든다.
stream을 이용한다면 기능 n개를 구현할 때, 약 n의 비용이 든다. (또는 n - 2)
2. Stream의 함수적 프로그래밍
Stream은 함수적 프로그래밍을 차용 및 데이터를 수정/가공하기 위한 많은 메소드들을 제공한다.
이를 활용하면 사용자가 원하는 데이터를 한 줄로 뽑아낼 수 있어서 코드의 간결함을 더해준다.
또한, 각종 타입으로 변환 할 수 있는 메소드를 제공함으로써 일관성있게 타입의 변환을 할 수 있다.
사실 이런저런 이야기를 늘어놓았지만 결론은 Stream은 데이터의 흐름이며, 간결한 코드를 작성할 수 있다는 장점이 있다는 것이다.
이렇게까지 작성할 생각이 없었는데... Stream()사용법과 관련한 포스팅은 추후에 작성해야겠다.