Stream 인터페이스 1

Jan 30, 2024
Stream 인터페이스 1

1. Stream 이란?

컬렉션(Collection)을 다루고 처리하는 데 유용한 기능 데이터 처리를 위해 배열이나 컬렉션과 같은 데이터 소스에서 요소를 추출하고, 조건에 따라 필터링하거나 변환하는 작업을 할 수 있다. 일시적으로 타입이 없는 상태라고 생각하면 편할듯 (Stream은 OS 레벨에서 사용하는 데이터 타입이라, (os타입임) 자바에선 타입이 아닌 것)
💡
Stream 객체 생성 > 중간 연산(가공) (여러 번 가공도 가능) > 최종 연산
💡
컬렉션에서도 반복문을 사용하지 않고 SQL처럼 선언만으로 작업할 수 있으면 얼마나 좋을까? 하고 나온게 Stream
 

1-1. 그림으로 보는 Stream

notion image
[ 순서 ]
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를 사용해서 최종 연산 해야한다는 말이 아닐까?)
notion image
💡
내가 만약, 대방어로 바로 가고 싶어도 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()); } }
notion image
 

 
[ 코드 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()); } }
notion image
list1 값을 newList 값으로 복사 / 복사한 값을 가공 -> 원본을 지킴
 
코드 설명
notion image
Arrays.asList()로 생성된 리스트 -> 값의 추가, 변경 불가능. 따라서 값을 추가, 변경하기 위해선 ArrayList()로 만든 코드가 필요
notion image
[5, 5, 5, 5] 를 받는 리스트 생성 완료
notion image
원본인 list1의 값을 가공·복사해서 붙여넣는 코드
💡
get()은 List 인터페이스의 메서드로, 특정 인덱스에 위치한 요소를 반환 list1.get(i)에서 i는 인덱스를 나타내며, list1라는 리스트에서 i번째 위치에 있는 요소를 가져온다. 인덱스는 0부터 시작하므로, i가 0일 경우에는 리스트의 첫 번째 요소를 가져오고, i가 1일 경우에는 두 번째 요소를 가져오는 식으로 진행
 

 
notion image
[위의 코드와 차이점] 위의 코드는 list1 에 있는 요소들을 받아오기 때문에 1,2,3,4 를 추가하지만 밑의 코드는 i라는 숫자를 추가하기에 0,1,2,3 이 추가 됨.
 

 
notion image
 
💡
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()); } }
notion image
💡
인터페이스의 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()); } }
notion image
💡
해쉬값이 동일했으나 , 값을 가공하자 해쉬값이 달리진 것 확인. 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()); } }
notion image
💡
map(i -> i+"") 라고, “” 를 사용해서 String으로 변환하니 해쉬값 다른 것 확인!
 
 
스트림API를 먼저 보고와라
 

[ 스트림이다 ]

💡
* 파일 자체도 스트림이다. (파일은 그 시작과 끝이 있는 데이터의 스트림이다.) 파일하드디스크와 연결된 스트림으로, 데이터가 파일 내부로 흘러들어와 저장되고, 필요한 경우에는 파일에서 데이터가 읽혀 나옵니다.
 
* HTTP 응답 데이터도 스트림이다. (브라우저가 서버에 HTTP 요청을 보내고 서버는 그에 대한 응답으로 데이터를 스트림 형태로 전송)
* 키보드 입력도 스트림이다. 사용자가 키보드로 입력한 문자열은 메모리와 연결된 스트림을 통해 전달
💡
스트림은 데이터를 생성하고 소비하는 양 끝단에 타겟(대상)이 존재해야 데이터가 만들어지고 처리될 수 있다. (양 끝단에 타겟들이 다 있다!)
 

 
💡
원본 값을 유지하면서 값을 가공하고 싶어 : stream().파이프라인.toList() 배열을 리스트 타입으로 변환해서 넣고 싶어 : Arrays.asList()
통신으로 전송하고 싶은 클래스를 DTO (DATA Transfer Object) 라고 한다. 아직 통신 안 배웠으니....
 
Share article

codingb