1. Stream 이란?
컬렉션(Collection)을 다루고 처리하는 데 유용한 기능 데이터 처리를 위해 배열이나 컬렉션과 같은 데이터 소스에서 요소를 추출하고, 조건에 따라 필터링하거나 변환하는 작업을 할 수 있다. 일시적으로 타입이 없는 상태라고 생각하면 편할듯 (Stream은 OS 레벨에서 사용하는 데이터 타입이라, (os타입임) 자바에선 타입이 아닌 것)
Stream 객체 생성 > 중간 연산(가공) (여러 번 가공도 가능) > 최종 연산
컬렉션에서도 반복문을 사용하지 않고
SQL처럼 선언만으로 작업할 수 있으면 얼마나 좋을까? 하고 나온게 Stream
1-1. 그림으로 보는 Stream
[ 순서 ]
1. 물길에다가 광어, 숭어, 대방어들을 던지는 것 (Stream) - 타입이 없는 상태. (바이트 스트림에다가 다 던졌다는 말) 2. 가공 = 보통 stream은 가공해서 복사하려고 쓰기 때문에 2번은 **가공**이 필요 = Map = 물길에 던진걸 끄집어 올리면서 이 물고기들에게 초장을 묻힘. 그럼 이 물고기들은 초장이 묻은 물고기다 되잖아? 이게 바로 맵! (ex. 모두 x100을 하고 싶은 경우, 그럴때 쓰는 것) = Filter boolean 타입을 리턴. 걸러내는 것. 광어이면 true, 광어가 아니면 false 즉, 광어만 낚아채겠다! 그럼 수집했을때, 광어만 수집이 됨. = 여러번 가공할 수도 있음 -> '파이프라인'이라고 함 3. collect 물길에 있는 물고기들의 타입을 만들어낸다.
1. Stream은 데이터 요소의 흐름 - 일시적으로 타입이 없는 상태라고 이해하면 편할듯? 데이터 소스로부터 요소를 추출하고, 그 요소들이 연속적으로 처리되는 과정 2. 중간 연산은 데이터를 가공하거나 조건에 따라 필터링하는 작업을 수행 = Map은 요소를 변환하거나 가공하는데 사용 = Filter는 조건에 맞는 요소만을 선택하는데 사용 (boolean 타입을 리턴) = 중간 연산은 체인으로 연결하여 여러 번 가공할 수 있다. 이러한 체인이 바로 '파이프라인' 3. 최종 연산은 파이프라인의 결과를 반환하거나 요소를 집계하는 작업을 수행 = collect() 메서드는 스트림의 요소들을 수집(collect)하여 원하는 자료구조에 저장 = .collect() 메서드는 스트림의 요소들을 리스트(List), 세트(Set), 맵(Map) 등의 자료구조로 수집하는 데 사용 = 스트림의 요소들을 리스트로 수집하려면 .collect(Collectors.toList())를 사용 (스트림이 일회성이니까... 변수값에 저장하거나 사용하려면 .collect를 사용해서 최종 연산 해야한다는 말이 아닐까?)
내가 만약, 대방어로 바로 가고 싶어도 Stream에서는 못간다!
광어부터 거쳐야…
[ 파이프 라인 ]
처음에 map을 써서 가공 > 결과 값을 받은 후 > 필터로 한 번 더 가공
이런 식으로 요소를 여러 번 가공하는 것
List<Integer> list = new ArrayList<>(1,2,3,4);
arr.stream().map(i → i*2).filter(i → i > 3).toList();
해당 부분을 ‘파이프라인’이라고 한다.
만약 stream을 안 쓰면 for문을 돌리면서 if로 걸러내야 한다.
2. Stream 없이/있이 값 가공
[ 레퍼런스 타입의 값 가공·복사 - Stream XXX ]
코드 보기
package ex08; import java.util.ArrayList; import java.util.Arrays; public class Test02 { public static void main(String[] args) { var list1 = Arrays.asList(1, 2, 3, 4); var newList = new ArrayList<>(); newList.add(5); newList.add(5); newList.add(5); newList.add(5); System.out.println(newList); for (int i = 0; i < list1.size(); i++) { //값 가공 newList.add((list1.get(i))); } System.out.println(newList); System.out.println(list1.hashCode()); System.out.println(newList.hashCode()); } }
[ 코드 2 ]
package ex08; import java.util.ArrayList; import java.util.Arrays; public class Test02 { public static void main(String[] args) { var list1 = Arrays.asList(1, 2, 3, 4); var newList = new ArrayList<>(list1); //list1의 값 복사 newList.add(5); for (int i = 0; i < list1.size(); i++) { //값 가공 newList.add((list1.get(i))); } System.out.println(list1); System.out.println(newList); System.out.println(list1.hashCode()); System.out.println(newList.hashCode()); } }
list1 값을 newList 값으로 복사 / 복사한 값을 가공 -> 원본을 지킴
코드 설명
Arrays.asList()로 생성된 리스트 -> 값의 추가, 변경 불가능. 따라서 값을 추가, 변경하기 위해선 ArrayList()로 만든 코드가 필요
[5, 5, 5, 5] 를 받는 리스트 생성 완료
원본인 list1의 값을 가공·복사해서 붙여넣는 코드
get()은 List 인터페이스의 메서드로, 특정 인덱스에 위치한 요소를 반환
list1.get(i)에서 i는 인덱스를 나타내며, list1라는 리스트에서 i번째 위치에 있는 요소를 가져온다. 인덱스는 0부터 시작하므로, i가 0일 경우에는 리스트의 첫 번째 요소를 가져오고, i가 1일 경우에는 두 번째 요소를 가져오는 식으로 진행
[위의 코드와 차이점] 위의 코드는 list1 에 있는 요소들을 받아오기 때문에 1,2,3,4 를 추가하지만 밑의 코드는 i라는 숫자를 추가하기에 0,1,2,3 이 추가 됨.
list1
의 값을 가공하고 newList
에 복사하는 과정이 꽤 번거롭다.
그래서 Stream이 등장[ 레퍼런스 타입의 값 복사·가공 - Stream O ]
코드 보기
package ex08; import java.util.Arrays; public class Test03 { public static void main(String[] args) { var list2 = Arrays.asList(1, 2, 3, 4); //배열을 리스트로 변환 //즉, 리스트 타입 var newList = list2.stream().toList(); //값 복사 //스트림을 리스트로 변환 //원본값을 유지할 수 있음 var newList2 = list2.stream().map(integer -> integer + 5).toList(); //가공 System.out.println(list); System.out.println(newList); System.out.println(newList2); System.out.println(list); System.out.println(list.hashCode()); System.out.println(newList.hashCode()); System.out.println(newList2.hashCode()); } }
인터페이스의
toList()
메서드는 스트림을 리스트로 변환하는 역할을 수행하며, 변환된 리스트는 고정 크기이다.
따라서 newList.add(5)
와 같이 값을 추가할 수 없다.
map()
메서드를 사용하여 스트림의 요소를 가공하면 해시 값이 달라질 수 있다.원본 값을 유지해서 사용하려고 하는 듯 하다.
해쉬값이 같은 이유 (원래는 달라야 하는 듯?)
list에 1,2,3,4가 들어와있는데, 객체 복사를 한다한들, 자바인 나는 값이 동일하다고 판단되니까 값 복사를 하지 않겠다!!! 이러는 거임. 근데 니가 만약, 둘중의 하나의 값을 바꾸면 내가 해쉬값 바꿔줄게 하는 것. (아래에서 설명)
값을 가공하면 해쉬값이 달라지며, 원본 값은 그대로
package ex08; import java.util.Arrays; public class Test03 { public static void main(String[] args) { var list2 = Arrays.asList(1, 2, 3, 4); var newList = list2.stream().map(integer -> integer + 5).toList(); System.out.println(list2); System.out.println(newList); System.out.println(list2.hashCode()); System.out.println(newList.hashCode()); } }
해쉬값이 동일했으나 , 값을 가공하자 해쉬값이 달리진 것 확인.
newList의 값이 변해도 원본 데이터가 변하지 않은 것 확인.
package ex14.example1; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class CopyEx01 { public static void main(String[] args) { var list = Arrays.asList(1,2,3,4); //컬렉션 복사 List<String> newList = list.stream().map(i -> i+"").toList(); System.out.println(list.hashCode()); System.out.println(newList.hashCode()); } }
map(i -> i+"") 라고, “” 를 사용해서 String으로 변환하니 해쉬값 다른 것 확인!
스트림API를 먼저 보고와라
[ 스트림이다 ]
* 파일 자체도 스트림이다. (파일은 그 시작과 끝이 있는 데이터의 스트림이다.)
파일은 하드디스크와 연결된 스트림으로, 데이터가 파일 내부로 흘러들어와 저장되고,
필요한 경우에는 파일에서 데이터가 읽혀 나옵니다.
* HTTP 응답 데이터도 스트림이다.
(브라우저가 서버에 HTTP 요청을 보내고
서버는 그에 대한 응답으로 데이터를 스트림 형태로 전송)
* 키보드 입력도 스트림이다.
사용자가 키보드로 입력한 문자열은 메모리와 연결된 스트림을 통해 전달
스트림은 데이터를 생성하고 소비하는 양 끝단에 타겟(대상)이 존재해야 데이터가 만들어지고 처리될 수 있다. (양 끝단에 타겟들이 다 있다!)
원본 값을 유지하면서 값을 가공하고 싶어 : stream().파이프라인.toList()
배열을 리스트 타입으로 변환해서 넣고 싶어 : Arrays.asList()
통신으로 전송하고 싶은 클래스를 DTO (DATA Transfer Object) 라고 한다. 아직 통신 안 배웠으니....
Share article